| @@ -1933,6 +1933,7 @@ func CreateCloudbrain(cloudbrain *Cloudbrain) (err error) { | |||
| session.Commit() | |||
| go IncreaseDatasetUseCount(cloudbrain.Uuid) | |||
| go OperateRepoAITaskNum(cloudbrain.RepoID, 1) | |||
| return nil | |||
| } | |||
| @@ -2088,9 +2089,29 @@ func DeleteJob(job *Cloudbrain) error { | |||
| func deleteJob(e Engine, job *Cloudbrain) error { | |||
| _, err := e.ID(job.ID).Delete(job) | |||
| if err == nil { | |||
| go updateAITaskNumWhenDeleteJob(job) | |||
| } | |||
| return err | |||
| } | |||
| func updateAITaskNumWhenDeleteJob(job *Cloudbrain) { | |||
| repoId := job.RepoID | |||
| if repoId == 0 { | |||
| t := &Cloudbrain{} | |||
| _, tempErr := x.ID(job.ID).Unscoped().Get(t) | |||
| if tempErr != nil { | |||
| log.Error("updateAITaskNumWhenDeleteJob error.%v", tempErr) | |||
| return | |||
| } | |||
| repoId = t.RepoID | |||
| } | |||
| if repoId > 0 { | |||
| go OperateRepoAITaskNum(repoId, -1) | |||
| } | |||
| } | |||
| func GetCloudbrainByName(jobName string) (*Cloudbrain, error) { | |||
| cb := &Cloudbrain{JobName: jobName} | |||
| return getRepoCloudBrain(cb) | |||
| @@ -2293,7 +2314,6 @@ func RestartCloudbrain(old *Cloudbrain, new *Cloudbrain) (err error) { | |||
| } | |||
| go IncreaseDatasetUseCount(new.Uuid) | |||
| return nil | |||
| } | |||
| func CloudbrainAll(opts *CloudbrainsOptions) ([]*CloudbrainInfo, int64, error) { | |||
| @@ -10,6 +10,26 @@ import ( | |||
| "xorm.io/xorm" | |||
| ) | |||
| type AvailablePageSize int | |||
| const ( | |||
| PageSize15 AvailablePageSize = 15 | |||
| PageSize30 AvailablePageSize = 30 | |||
| PageSize50 AvailablePageSize = 50 | |||
| ) | |||
| func (s AvailablePageSize) IsLegal() bool { | |||
| switch s { | |||
| case PageSize30, PageSize50, PageSize15: | |||
| return true | |||
| } | |||
| return false | |||
| } | |||
| func (s AvailablePageSize) Int() int { | |||
| return int(s) | |||
| } | |||
| // ListOptions options to paginate results | |||
| type ListOptions struct { | |||
| PageSize int | |||
| @@ -231,10 +231,43 @@ type Repository struct { | |||
| CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | |||
| UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` | |||
| Hot int64 `xorm:"-"` | |||
| Active int64 `xorm:"-"` | |||
| Alias string `xorm:"INDEX"` | |||
| LowerAlias string `xorm:"INDEX"` | |||
| Hot int64 `xorm:"-"` | |||
| Active int64 `xorm:"-"` | |||
| Alias string `xorm:"INDEX"` | |||
| LowerAlias string `xorm:"INDEX"` | |||
| AiTaskCnt int64 `xorm:"NOT NULL DEFAULT 0"` | |||
| ModelCnt int64 `xorm:"NOT NULL DEFAULT 0"` | |||
| DatasetCnt int64 `xorm:"NOT NULL DEFAULT 0"` | |||
| LastMonthVisits int64 `xorm:"NOT NULL DEFAULT 0"` | |||
| LastFourMonthCommits int64 `xorm:"NOT NULL DEFAULT 0"` | |||
| } | |||
| // Repository4Card format for front display | |||
| type Repository4Card struct { | |||
| ID int64 | |||
| OwnerID int64 | |||
| OwnerName string | |||
| LowerName string | |||
| Name string | |||
| Alias string | |||
| NumWatches int | |||
| NumStars int | |||
| NumForks int | |||
| Description string | |||
| Topics []string | |||
| AiTaskCnt int64 | |||
| ModelCnt int64 | |||
| DatasetCnt int64 | |||
| CreatedUnix timeutil.TimeStamp | |||
| UpdatedUnix timeutil.TimeStamp | |||
| PrimaryLanguage *LanguageStat | |||
| RelAvatarLink string | |||
| Contributors []*ContributorInfo | |||
| IsPrivate bool | |||
| IsFork bool | |||
| IsMirror bool | |||
| IsOwnerPrivate bool | |||
| IsArchived bool | |||
| } | |||
| type RepositoryShow struct { | |||
| @@ -243,6 +276,47 @@ type RepositoryShow struct { | |||
| Alias string | |||
| } | |||
| func (repo *Repository) ToCardFormat() *Repository4Card { | |||
| link := repo.RelAvatarLink() | |||
| var isOwnerPrivate bool | |||
| if repo.Owner != nil && repo.Owner.Visibility.IsPrivate() { | |||
| isOwnerPrivate = true | |||
| } | |||
| result := &Repository4Card{ | |||
| ID: repo.ID, | |||
| OwnerID: repo.OwnerID, | |||
| OwnerName: repo.OwnerName, | |||
| LowerName: repo.LowerName, | |||
| Name: repo.Name, | |||
| NumWatches: repo.NumWatches, | |||
| NumStars: repo.NumStars, | |||
| NumForks: repo.NumForks, | |||
| Description: repo.Description, | |||
| Topics: repo.Topics, | |||
| AiTaskCnt: repo.AiTaskCnt, | |||
| ModelCnt: repo.ModelCnt, | |||
| DatasetCnt: repo.DatasetCnt, | |||
| CreatedUnix: repo.CreatedUnix, | |||
| UpdatedUnix: repo.UpdatedUnix, | |||
| PrimaryLanguage: repo.PrimaryLanguage, | |||
| RelAvatarLink: link, | |||
| Alias: repo.Alias, | |||
| IsPrivate: repo.IsPrivate, | |||
| IsFork: repo.IsFork, | |||
| IsMirror: repo.IsMirror, | |||
| IsOwnerPrivate: isOwnerPrivate, | |||
| IsArchived: repo.IsArchived, | |||
| } | |||
| return result | |||
| } | |||
| type ContributorInfo struct { | |||
| RelAvatarLink string | |||
| UserName string | |||
| Email string | |||
| CommitCnt int | |||
| } | |||
| // SanitizedOriginalURL returns a sanitized OriginalURL | |||
| func (repo *Repository) SanitizedOriginalURL() string { | |||
| if repo.OriginalURL == "" { | |||
| @@ -2379,6 +2453,75 @@ func CheckRepoStats(ctx context.Context) error { | |||
| } | |||
| } | |||
| // ***** END: Repository.NumForks ***** | |||
| // ***** START: Repository.DatasetCnt ***** | |||
| desc = "repository count 'dataset_cnt'" | |||
| results, err = x.Query("SELECT repository.id FROM `repository` WHERE repository.dataset_cnt!=(select count(1) from attachment inner join dataset on attachment.dataset_id = dataset.id where dataset.repo_id = repository.id)") | |||
| if err != nil { | |||
| log.Error("Select %s: %v", desc, err) | |||
| } else { | |||
| for _, result := range results { | |||
| id := com.StrTo(result["id"]).MustInt64() | |||
| select { | |||
| case <-ctx.Done(): | |||
| log.Warn("CheckRepoStats: Cancelled") | |||
| return ErrCancelledf("during %s for repo ID %d", desc, id) | |||
| default: | |||
| } | |||
| log.Trace("Updating %s: %d", desc, id) | |||
| err = ResetRepoDatasetNum(id) | |||
| if err != nil { | |||
| log.Error("Update %s[%d]: %v", desc, id, err) | |||
| } | |||
| } | |||
| } | |||
| // ***** END: Repository.DatasetCnt ***** | |||
| // ***** START: Repository.ModelCnt ***** | |||
| desc = "repository count 'model_cnt'" | |||
| results, err = x.Query("SELECT repository.id FROM `repository` WHERE repository.model_cnt!=(select count(1) from ai_model_manage where repository.id = ai_model_manage.repo_id and ai_model_manage.size > 0 )") | |||
| if err != nil { | |||
| log.Error("Select %s: %v", desc, err) | |||
| } else { | |||
| for _, result := range results { | |||
| id := com.StrTo(result["id"]).MustInt64() | |||
| select { | |||
| case <-ctx.Done(): | |||
| log.Warn("CheckRepoStats: Cancelled") | |||
| return ErrCancelledf("during %s for repo ID %d", desc, id) | |||
| default: | |||
| } | |||
| log.Trace("Updating %s: %d", desc, id) | |||
| err = ResetRepoModelNum(id) | |||
| if err != nil { | |||
| log.Error("Update %s[%d]: %v", desc, id, err) | |||
| } | |||
| } | |||
| } | |||
| // ***** END: Repository.ModelCnt ***** | |||
| // ***** START: Repository.AiTaskCnt ***** | |||
| desc = "repository count 'ai_task_cnt'" | |||
| results, err = x.Query("SELECT repository.id FROM `repository` WHERE repository.ai_task_cnt!=(select count(1) from cloudbrain where repository.id = cloudbrain.repo_id and (cloudbrain.deleted_at is null or cloudbrain.deleted_at = '0001-01-01 00:00:00') )") | |||
| if err != nil { | |||
| log.Error("Select %s: %v", desc, err) | |||
| } else { | |||
| for _, result := range results { | |||
| id := com.StrTo(result["id"]).MustInt64() | |||
| select { | |||
| case <-ctx.Done(): | |||
| log.Warn("CheckRepoStats: Cancelled") | |||
| return ErrCancelledf("during %s for repo ID %d", desc, id) | |||
| default: | |||
| } | |||
| log.Trace("Updating %s: %d", desc, id) | |||
| err = ResetRepoAITaskNum(id) | |||
| if err != nil { | |||
| log.Error("Update %s[%d]: %v", desc, id, err) | |||
| } | |||
| } | |||
| } | |||
| // ***** END: Repository.AiTaskCnt ***** | |||
| return nil | |||
| } | |||
| @@ -2775,3 +2918,85 @@ func ReadLatestFileInRepo(userName, repoName, refName, treePath string) (*RepoFi | |||
| } | |||
| return &RepoFile{CommitId: commitId, Content: d}, nil | |||
| } | |||
| func ResetRepoAITaskNum(repoId int64) error { | |||
| n, err := x.Where("repo_id = ? ", repoId).Count(&Cloudbrain{}) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| r := Repository{ | |||
| AiTaskCnt: n, | |||
| } | |||
| _, err = x.Cols("ai_task_cnt").Where("id = ?", repoId).Update(&r) | |||
| return err | |||
| } | |||
| func ResetRepoDatasetNum(repoId int64) error { | |||
| n, err := x.Table("attachment").Join("inner", "dataset", "attachment.dataset_id = dataset.id").Where("dataset.repo_id = ?", repoId).Count() | |||
| if err != nil { | |||
| return err | |||
| } | |||
| r := Repository{ | |||
| DatasetCnt: n, | |||
| } | |||
| _, err = x.Cols("dataset_cnt").Where("id = ?", repoId).Update(&r) | |||
| return err | |||
| } | |||
| func ResetRepoModelNum(repoId int64) error { | |||
| _, err := x.Exec("update repository set model_cnt = (select count(1) from ai_model_manage where ai_model_manage.repo_id = ? and size > 0) where id = ?", repoId, repoId) | |||
| return err | |||
| } | |||
| func operateRepoCol(repoId int64, colName string, amount int64, engines ...*xorm.Engine) error { | |||
| var err error | |||
| if amount == 0 { | |||
| return nil | |||
| } | |||
| var ee *xorm.Engine | |||
| if len(engines) == 0 { | |||
| ee = x | |||
| } else { | |||
| ee = engines[0] | |||
| } | |||
| if amount > 0 { | |||
| _, err = ee.Exec(fmt.Sprintf("update repository set %s = %s + ? where id = ?", colName, colName), amount, repoId) | |||
| } else { | |||
| _, err = ee.Exec(fmt.Sprintf("update repository set %s = %s - ? where id = ?", colName, colName), -1*amount, repoId) | |||
| } | |||
| return err | |||
| } | |||
| func OperateRepoDatasetNum(repoId int64, amount int64, engines ...*xorm.Engine) error { | |||
| return operateRepoCol(repoId, "dataset_cnt", amount, engines...) | |||
| } | |||
| func OperateRepoModelNum(repoId int64, amount int64, engines ...*xorm.Engine) error { | |||
| return operateRepoCol(repoId, "model_cnt", amount, engines...) | |||
| } | |||
| func OperateRepoAITaskNum(repoId int64, amount int64, engines ...*xorm.Engine) error { | |||
| return operateRepoCol(repoId, "ai_task_cnt", amount, engines...) | |||
| } | |||
| func UpdateRepositoryLastFourMonthCommits(repoID int64, amount int64) error { | |||
| _, err := x.Exec("update repository set last_four_month_commits = ? where id = ?", amount, repoID) | |||
| return err | |||
| } | |||
| func UpdateRepositoryLastMonthVisits(repoID int64, amount int64) error { | |||
| _, err := x.Exec("update repository set last_month_visits = ? where id = ?", amount, repoID) | |||
| return err | |||
| } | |||
| func SyncStatDataToRepo(repo *Repository) { | |||
| //Save the visit number of repository in the last month | |||
| if lv, err := SumLastMonthNumVisits(repo.ID); err == nil { | |||
| UpdateRepositoryLastMonthVisits(repo.ID, lv) | |||
| } | |||
| //Save the commits number of repository in the last four month | |||
| if lc, err := SumLastFourMonthNumCommits(repo.ID); err == nil { | |||
| UpdateRepositoryLastFourMonthCommits(repo.ID, lc) | |||
| } | |||
| } | |||
| @@ -201,29 +201,41 @@ func (s SearchOrderBy) String() string { | |||
| return string(s) | |||
| } | |||
| type FindReposResponse struct { | |||
| Repos []*Repository4Card | |||
| Page int | |||
| PageSize int | |||
| Total int64 | |||
| } | |||
| // Strings for sorting result | |||
| const ( | |||
| SearchOrderByAlphabetically SearchOrderBy = "name ASC" | |||
| SearchOrderByAlphabeticallyReverse SearchOrderBy = "name DESC" | |||
| SearchOrderByLeastUpdated SearchOrderBy = "updated_unix ASC" | |||
| SearchOrderByRecentUpdated SearchOrderBy = "updated_unix DESC" | |||
| SearchOrderByOldest SearchOrderBy = "created_unix ASC" | |||
| SearchOrderByNewest SearchOrderBy = "created_unix DESC" | |||
| SearchOrderBySize SearchOrderBy = "size ASC" | |||
| SearchOrderBySizeReverse SearchOrderBy = "size DESC" | |||
| SearchOrderByID SearchOrderBy = "id ASC" | |||
| SearchOrderByIDReverse SearchOrderBy = "id DESC" | |||
| SearchOrderByStars SearchOrderBy = "num_stars ASC" | |||
| SearchOrderByStarsReverse SearchOrderBy = "num_stars DESC" | |||
| SearchOrderByForks SearchOrderBy = "num_forks ASC" | |||
| SearchOrderByForksReverse SearchOrderBy = "num_forks DESC" | |||
| SearchOrderByDownloadTimes SearchOrderBy = "download_times DESC" | |||
| SearchOrderByUseCount SearchOrderBy = "use_count ASC" | |||
| SearchOrderByUseCountReverse SearchOrderBy = "use_count DESC" | |||
| SearchOrderByHot SearchOrderBy = "(num_watches + num_stars + num_forks + clone_cnt) DESC" | |||
| SearchOrderByActive SearchOrderBy = "(num_issues + num_pulls + num_commit) DESC" | |||
| SearchOrderByWatches SearchOrderBy = "num_watches DESC" | |||
| SearchOrderByDefault SearchOrderBy = "recommend desc,num_stars DESC,updated_unix DESC" | |||
| SearchOrderByAlphabetically SearchOrderBy = "name ASC" | |||
| SearchOrderByAlphabeticallyReverse SearchOrderBy = "name DESC" | |||
| SearchOrderByLeastUpdated SearchOrderBy = "updated_unix ASC" | |||
| SearchOrderByRecentUpdated SearchOrderBy = "updated_unix DESC" | |||
| SearchOrderByOldest SearchOrderBy = "created_unix ASC" | |||
| SearchOrderByNewest SearchOrderBy = "created_unix DESC" | |||
| SearchOrderBySize SearchOrderBy = "size ASC" | |||
| SearchOrderBySizeReverse SearchOrderBy = "size DESC" | |||
| SearchOrderByID SearchOrderBy = "id ASC" | |||
| SearchOrderByIDReverse SearchOrderBy = "id DESC" | |||
| SearchOrderByStars SearchOrderBy = "num_stars ASC" | |||
| SearchOrderByStarsReverse SearchOrderBy = "num_stars DESC" | |||
| SearchOrderByForks SearchOrderBy = "num_forks ASC" | |||
| SearchOrderByForksReverse SearchOrderBy = "num_forks DESC" | |||
| SearchOrderByDownloadTimes SearchOrderBy = "download_times DESC" | |||
| SearchOrderByUseCount SearchOrderBy = "use_count ASC" | |||
| SearchOrderByUseCountReverse SearchOrderBy = "use_count DESC" | |||
| SearchOrderByHot SearchOrderBy = "(num_watches + num_stars + num_forks + clone_cnt) DESC" | |||
| SearchOrderByActive SearchOrderBy = "(num_issues + num_pulls + num_commit) DESC" | |||
| SearchOrderByWatches SearchOrderBy = "num_watches DESC" | |||
| SearchOrderByDefault SearchOrderBy = "recommend desc,num_stars DESC,updated_unix DESC" | |||
| SearchOrderByAiTaskCntReverse SearchOrderBy = "ai_task_cnt desc" | |||
| SearchOrderByModelCntReverse SearchOrderBy = "model_cnt desc" | |||
| SearchOrderByDatasetCntReverse SearchOrderBy = "dataset_cnt desc" | |||
| SearchOrderByLastMonthVisitsReverse SearchOrderBy = "last_month_visits desc" | |||
| SearchOrderByLastFourMonthCommitsReverse SearchOrderBy = "last_four_month_commits desc" | |||
| ) | |||
| // SearchRepositoryCondition creates a query condition according search repository options | |||
| @@ -200,3 +200,23 @@ func UpdateRepoStatVisits(repoStat *RepoStatistic) error { | |||
| _, err := xStatistic.Exec(sql, repoStat.NumVisits, repoStat.RepoID, repoStat.Date) | |||
| return err | |||
| } | |||
| func SumRepoStatColumn(begin, end time.Time, repoId int64, columnName string) (int64, error) { | |||
| res, err := xStatistic.Where("created_unix <= ? and created_unix >= ? and repo_id = ? ", end.Unix(), begin.Unix(), repoId).Sum(&RepoStatistic{}, columnName) | |||
| if err != nil { | |||
| return 0, err | |||
| } | |||
| return int64(res), nil | |||
| } | |||
| func SumLastMonthNumVisits(repoId int64) (int64, error) { | |||
| end := time.Now() | |||
| begin := end.AddDate(0, 0, -30) | |||
| return SumRepoStatColumn(begin, end, repoId, "num_visits") | |||
| } | |||
| func SumLastFourMonthNumCommits(repoId int64) (int64, error) { | |||
| end := time.Now() | |||
| begin := end.AddDate(0, 0, -120) | |||
| return SumRepoStatColumn(begin, end, repoId, "num_commits_added") | |||
| } | |||
| @@ -4,6 +4,7 @@ import ( | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/modules/timeutil" | |||
| "fmt" | |||
| "xorm.io/builder" | |||
| ) | |||
| type OfficialTag struct { | |||
| @@ -166,3 +167,33 @@ func GetAllOfficialTags() ([]OfficialTag, error) { | |||
| } | |||
| return o, nil | |||
| } | |||
| type FindSelectedReposOpts struct { | |||
| ListOptions | |||
| OrgId int64 | |||
| OnlyPublic bool | |||
| } | |||
| func GetSelectedRepos(opts FindSelectedReposOpts) ([]*Repository, error) { | |||
| if opts.Page < 1 { | |||
| opts.Page = 1 | |||
| } | |||
| var cond = builder.NewCond() | |||
| cond = cond.And(builder.Eq{"official_tag.code": "selected"}) | |||
| if opts.OrgId > 0 { | |||
| cond = cond.And(builder.Eq{"official_tag_repos.org_id": opts.OrgId}) | |||
| } | |||
| if opts.OnlyPublic { | |||
| cond = cond.And(builder.Eq{"repository.is_private": false}) | |||
| } | |||
| t := make([]*Repository, 0) | |||
| err := x.Join("inner", "official_tag_repos", "repository.id = official_tag_repos.repo_id"). | |||
| Join("inner", "official_tag", "official_tag.id = official_tag_repos.tag_id"). | |||
| Where(cond).OrderBy("repository.updated_unix desc").Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).Find(&t) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| return t, nil | |||
| } | |||
| @@ -9,6 +9,7 @@ import ( | |||
| "regexp" | |||
| "strings" | |||
| "unicode/utf8" | |||
| "xorm.io/xorm" | |||
| "code.gitea.io/gitea/modules/timeutil" | |||
| @@ -337,3 +338,16 @@ func GetOrgTopics(orgId int64) ([]Topic, error) { | |||
| return result, nil | |||
| } | |||
| func UpdateRepoTopics(repoID int64, topicNames []string, sess ...*xorm.Engine) error { | |||
| e := x | |||
| if len(sess) > 0 { | |||
| e = sess[0] | |||
| } | |||
| if _, err := e.ID(repoID).Cols("topics").Update(&Repository{ | |||
| Topics: topicNames, | |||
| }); err != nil { | |||
| return err | |||
| } | |||
| return nil | |||
| } | |||
| @@ -198,6 +198,40 @@ type SearchOrganizationsOptions struct { | |||
| All bool | |||
| } | |||
| type User4Front struct { | |||
| ID int64 | |||
| LowerName string `xorm:"UNIQUE NOT NULL"` | |||
| Name string `xorm:"UNIQUE NOT NULL"` | |||
| FullName string | |||
| Email string `xorm:"NOT NULL"` | |||
| Language string `xorm:"VARCHAR(5)"` | |||
| Description string | |||
| RelAvatarLink string | |||
| NumMembers int | |||
| CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | |||
| UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` | |||
| } | |||
| func (u *User) ToFrontFormat() *User4Front { | |||
| uf := &User4Front{ | |||
| ID: u.ID, | |||
| LowerName: u.LowerName, | |||
| Name: u.Name, | |||
| FullName: u.FullName, | |||
| Email: u.Email, | |||
| Language: u.Language, | |||
| Description: u.Description, | |||
| CreatedUnix: u.CreatedUnix, | |||
| UpdatedUnix: u.UpdatedUnix, | |||
| NumMembers: u.NumMembers, | |||
| } | |||
| if !u.KeepEmailPrivate { | |||
| uf.Email = u.Email | |||
| } | |||
| uf.RelAvatarLink = u.RelAvatarLink() | |||
| return uf | |||
| } | |||
| // GenerateRandomAvatar generates a random avatar for user. | |||
| func (u *User) IsBindWechat() bool { | |||
| return u.WechatOpenId != "" | |||
| @@ -2443,3 +2443,9 @@ func GetContentFromPromote(url string) (string, error) { | |||
| allLineStr := string(bytes) | |||
| return allLineStr, nil | |||
| } | |||
| func QueryLast30DaysHighestIndexUsers(size int) ([]int64, error) { | |||
| userIds := make([]int64, 0) | |||
| err := xStatistic.Table("user_business_analysis_last30_day").Cols("id").OrderBy("user_index desc").Limit(size).Find(&userIds) | |||
| return userIds, err | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| package redis_key | |||
| import "fmt" | |||
| const REPO_PREFIX = "repo" | |||
| func RepoTopNContributors(repoId int64, N int) string { | |||
| return KeyJoin(REPO_PREFIX, fmt.Sprint(repoId), fmt.Sprint(N), "top_n_contributor") | |||
| } | |||
| @@ -677,6 +677,10 @@ var ( | |||
| CloudbrainStoppedTitle string | |||
| CloudbrainStoppedRemark string | |||
| //repo square config | |||
| IncubationSourceOrgName string | |||
| PaperRepoTopicName string | |||
| //nginx proxy | |||
| PROXYURL string | |||
| RadarMap = struct { | |||
| @@ -1585,6 +1589,10 @@ func NewContext() { | |||
| CloudbrainStoppedTitle = sec.Key("CLOUDBRAIN_STOPPED_TITLE").MustString("您好,您申请的算力资源已结束使用,任务已完成运行,状态为%s,请您关注运行结果") | |||
| CloudbrainStoppedRemark = sec.Key("CLOUDBRAIN_STOPPED_REMARK").MustString("感谢您的耐心等待。") | |||
| sec = Cfg.Section("repo-square") | |||
| IncubationSourceOrgName = sec.Key("INCUBATION_ORG_NAME").MustString("OpenI") | |||
| PaperRepoTopicName = sec.Key("PAPER_REPO_TOPIC_NAME").MustString("openi-paper") | |||
| sec = Cfg.Section("point") | |||
| CloudBrainPaySwitch = sec.Key("CLOUDBRAIN_PAY_SWITCH").MustBool(false) | |||
| CloudBrainPayDelay = sec.Key("CLOUDBRAIN_PAY_DELAY").MustDuration(30 * time.Minute) | |||
| @@ -17,11 +17,12 @@ | |||
| "core-js": "3.6.5", | |||
| "css-loader": "3.5.3", | |||
| "cssnano": "4.1.10", | |||
| "dayjs": "1.10.7", | |||
| "domino": "2.1.5", | |||
| "dropzone": "5.7.2", | |||
| "echarts": "3.8.5", | |||
| "element-ui": "2.15.5", | |||
| "esdk-obs-browserjs": "3.20.7", | |||
| "esdk-obs-browserjs": "3.22.3", | |||
| "esdk-obs-nodejs": "3.20.11", | |||
| "fast-glob": "3.2.2", | |||
| "file-loader": "6.0.0", | |||
| @@ -631,20 +631,12 @@ function displayRepo(json){ | |||
| for (var i = 0, iLen = repos.length; i < iLen; i++) { | |||
| if (i >= 4) break; | |||
| var repo = repos[i]; | |||
| // <i class="ri-star-line"></i>${repo["NumStars"]}<i class="ri-git-branch-line am-ml-10"></i>${repo["NumForks"]}</span> <div class="ui tags nowrap am-mt-10"></div> | |||
| html += `<div class="ui fluid card" style="border-radius:6px;"> | |||
| <div class="content"> | |||
| ${repo["Avatar"] ? `<img class="left floated mini ui image" src="${repo["Avatar"]}">` : `<img class="left floated mini ui image" avatar="${repo["OwnerName"]}">`} | |||
| <div class="content" style="position:relative;"> | |||
| ${repo["Avatar"] ? `<img style="border-radius:100%;" class="left floated mini ui image" src="${repo["Avatar"]}">` : `<img style="border-radius:100%;" class="left floated mini ui image" avatar="${repo["OwnerName"]}">`} | |||
| <a class="header nowrap" style="color:rgb(50, 145, 248);font-size:14px;" href="/${repo["OwnerName"]}/${repo["Name"]}" title="${repo["Alias"]}">${repo["Alias"]}</a> | |||
| <div class="description nowrap-2" style="rgba(136,136,136,1);;font-size:12px;" title="${repo["Description"]}">${repo["Description"]}</div> | |||
| `; | |||
| // if (repo["Topics"] != null) { | |||
| // for(var j = 0; j < repo["Topics"].length; j++){ | |||
| // var topic = repo["Topics"][j]; | |||
| // var url = "/explore/repos?q=" + (topic) + "&topic=" | |||
| // html += `<a class="ui small label topic" href=" ${url}">${topic}</a>`; | |||
| // } | |||
| // } | |||
| <a href="/${repo["OwnerName"]}/${repo["Name"]}" style="height:100%;width:100%;position:absolute;left:0;top:0"></a>`; | |||
| html += ` | |||
| </div> | |||
| </div>`; | |||
| @@ -307,3 +307,37 @@ func RefreshHistorySpec(ctx *context.Context) { | |||
| r["total"] = total | |||
| ctx.JSON(http.StatusOK, response.SuccessWithData(r)) | |||
| } | |||
| func RefreshReposHistoryCnt(ctx *context.Context) { | |||
| scope := ctx.Query("scope") | |||
| list := ctx.Query("list") | |||
| var scopeAll = false | |||
| if scope == "all" { | |||
| scopeAll = true | |||
| } | |||
| var ids = make([]int64, 0) | |||
| if list != "" { | |||
| strs := strings.Split(list, "|") | |||
| for _, s := range strs { | |||
| i, err := strconv.ParseInt(s, 10, 64) | |||
| if err != nil { | |||
| ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||
| return | |||
| } | |||
| ids = append(ids, i) | |||
| } | |||
| } | |||
| total, success, err := resource.RefreshHistorySpec(scopeAll, ids) | |||
| if err != nil { | |||
| log.Error("RefreshHistorySpec error. %v", err) | |||
| ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||
| return | |||
| } | |||
| r := make(map[string]interface{}, 0) | |||
| r["success"] = success | |||
| r["total"] = total | |||
| ctx.JSON(http.StatusOK, response.SuccessWithData(r)) | |||
| } | |||
| @@ -177,13 +177,25 @@ func AddTopic(ctx *context.APIContext) { | |||
| return | |||
| } | |||
| _, err = models.AddTopic(ctx.Repo.Repository.ID, topicName) | |||
| topic, err := models.AddTopic(ctx.Repo.Repository.ID, topicName) | |||
| if err != nil { | |||
| log.Error("AddTopic failed: %v", err) | |||
| ctx.InternalServerError(err) | |||
| return | |||
| } | |||
| found := false | |||
| topicNames := make([]string, len(topics)) | |||
| for i, t := range topics { | |||
| topicNames[i] = t.Name | |||
| if strings.EqualFold(topic.Name, t.Name) { | |||
| found = true | |||
| break | |||
| } | |||
| } | |||
| if !found && topic.Name != "" { | |||
| topicNames = append(topicNames, topic.Name) | |||
| } | |||
| models.UpdateRepoTopics(ctx.Repo.Repository.ID, topicNames) | |||
| ctx.Status(http.StatusNoContent) | |||
| } | |||
| @@ -7,6 +7,7 @@ package routers | |||
| import ( | |||
| "bytes" | |||
| "code.gitea.io/gitea/routers/response" | |||
| "encoding/json" | |||
| "net/http" | |||
| "strconv" | |||
| @@ -43,6 +44,8 @@ const ( | |||
| tplHomeTerm base.TplName = "terms" | |||
| tplHomePrivacy base.TplName = "privacy" | |||
| tplResoruceDesc base.TplName = "resource_desc" | |||
| tplRepoSquare base.TplName = "explore/repos/square" | |||
| tplRepoSearch base.TplName = "explore/repos/search" | |||
| ) | |||
| // Home render home page | |||
| @@ -296,6 +299,109 @@ func ExploreRepos(ctx *context.Context) { | |||
| }) | |||
| } | |||
| func GetRepoSquarePage(ctx *context.Context) { | |||
| ctx.Data["SquareBanners"] = repository.GetBanners() | |||
| ctx.Data["SquareTopics"] = repository.GetTopics() | |||
| ctx.Data["SquareRecommendRepos"] = repository.GetRecommendRepos() | |||
| repos, _ := repository.GetPreferredRepos() | |||
| ctx.Data["SquarePreferredRepos"] = repos | |||
| ctx.HTML(200, tplRepoSquare) | |||
| } | |||
| func GetRepoSearchPage(ctx *context.Context) { | |||
| ctx.Data["SquareTopics"] = repository.GetTopics() | |||
| ctx.HTML(200, tplRepoSearch) | |||
| } | |||
| func RepoSquare(ctx *context.Context) { | |||
| var result []*models.Repository4Card | |||
| var err error | |||
| switch ctx.Query("type") { | |||
| case "preferred": | |||
| result, err = repository.GetPreferredRepos() | |||
| case "incubation": | |||
| result, err = repository.GetIncubationRepos() | |||
| case "hot-paper": | |||
| result, err = repository.GetHotPaperRepos() | |||
| default: | |||
| result, err = repository.GetPreferredRepos() | |||
| } | |||
| if err != nil { | |||
| ctx.JSON(http.StatusOK, response.ResponseError(err)) | |||
| return | |||
| } | |||
| resultMap := make(map[string]interface{}, 0) | |||
| resultMap["Repos"] = result | |||
| ctx.JSON(http.StatusOK, response.SuccessWithData(resultMap)) | |||
| } | |||
| func ActiveUser(ctx *context.Context) { | |||
| var err error | |||
| var currentUserId int64 | |||
| if ctx.User != nil { | |||
| currentUserId = ctx.User.ID | |||
| } | |||
| result, err := repository.GetActiveUser4Square(currentUserId) | |||
| if err != nil { | |||
| log.Error("ActiveUser err. %v", err) | |||
| ctx.JSON(http.StatusOK, response.Success()) | |||
| return | |||
| } | |||
| resultMap := make(map[string]interface{}, 0) | |||
| resultMap["Users"] = result | |||
| ctx.JSON(http.StatusOK, response.SuccessWithData(resultMap)) | |||
| } | |||
| func ActiveOrg(ctx *context.Context) { | |||
| result, err := repository.GetActiveOrgs() | |||
| if err != nil { | |||
| log.Error("ActiveOrg err. %v", err) | |||
| ctx.JSON(http.StatusOK, response.Success()) | |||
| return | |||
| } | |||
| resultMap := make(map[string]interface{}, 0) | |||
| resultMap["Orgs"] = result | |||
| ctx.JSON(http.StatusOK, response.SuccessWithData(resultMap)) | |||
| } | |||
| func RepoFind(ctx *context.Context) { | |||
| keyword := strings.Trim(ctx.Query("q"), " ") | |||
| topic := strings.Trim(ctx.Query("topic"), " ") | |||
| sort := strings.Trim(ctx.Query("sort"), " ") | |||
| page := ctx.QueryInt("page") | |||
| pageSize := ctx.QueryInt("pageSize") | |||
| if pageSize == 0 { | |||
| pageSize = 15 | |||
| } | |||
| if pageSize > 100 { | |||
| ctx.JSON(http.StatusOK, response.ServerError("pageSize illegal")) | |||
| return | |||
| } | |||
| if page <= 0 { | |||
| page = 1 | |||
| } | |||
| var ownerID int64 | |||
| if ctx.User != nil && !ctx.User.IsAdmin { | |||
| ownerID = ctx.User.ID | |||
| } | |||
| result, err := repository.FindRepos(repository.FindReposOptions{ | |||
| ListOptions: models.ListOptions{Page: page, PageSize: pageSize}, | |||
| Actor: ctx.User, | |||
| Sort: sort, | |||
| Keyword: keyword, | |||
| Topic: topic, | |||
| Private: ctx.User != nil, | |||
| OwnerID: ownerID, | |||
| }) | |||
| if err != nil { | |||
| log.Error("RepoFind error. %v", err) | |||
| ctx.JSON(http.StatusOK, response.ResponseError(err)) | |||
| return | |||
| } | |||
| ctx.JSON(http.StatusOK, response.SuccessWithData(result)) | |||
| } | |||
| func ExploreDatasets(ctx *context.Context) { | |||
| ctx.Data["Title"] = ctx.Tr("explore") | |||
| ctx.Data["PageIsExplore"] = true | |||
| @@ -6,6 +6,7 @@ | |||
| package private | |||
| import ( | |||
| "code.gitea.io/gitea/services/repository" | |||
| "strings" | |||
| "code.gitea.io/gitea/routers/admin" | |||
| @@ -55,7 +56,9 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| m.Post("/task/history_handle/duration", repo.HandleTaskWithNoDuration) | |||
| m.Post("/task/history_handle/aicenter", repo.HandleTaskWithAiCenter) | |||
| m.Post("/resources/specification/handle_historical_task", admin.RefreshHistorySpec) | |||
| m.Post("/repos/cnt_stat/handle_historical_task", admin.RefreshHistorySpec) | |||
| m.Post("/duration_statisctic/history_handle", repo.CloudbrainUpdateHistoryData) | |||
| m.Post("/square/repo/stat/refresh", repository.RefreshRepoStatData) | |||
| }, CheckInternalToken) | |||
| } | |||
| @@ -2,6 +2,7 @@ package repo | |||
| import ( | |||
| "archive/zip" | |||
| "code.gitea.io/gitea/services/repository" | |||
| "encoding/json" | |||
| "errors" | |||
| "fmt" | |||
| @@ -170,10 +171,17 @@ func updateStatus(id string, modelSize int64, status int, modelPath string, stat | |||
| if len(statusDesc) > 400 { | |||
| statusDesc = statusDesc[0:400] | |||
| } | |||
| m, _ := models.QueryModelById(id) | |||
| err := models.ModifyModelStatus(id, modelSize, status, modelPath, statusDesc) | |||
| if err != nil { | |||
| log.Info("update status error." + err.Error()) | |||
| } | |||
| if m != nil { | |||
| if modelSize > 0 && m.Size == 0 { | |||
| go repository.ResetRepoModelNum(m.RepoId) | |||
| } | |||
| } | |||
| } | |||
| func SaveNewNameModel(ctx *context.Context) { | |||
| @@ -308,13 +316,14 @@ func getSize(files []storage.FileInfo) int64 { | |||
| func UpdateModelSize(modeluuid string) { | |||
| model, err := models.QueryModelById(modeluuid) | |||
| if err == nil { | |||
| var size int64 | |||
| if model.Type == models.TypeCloudBrainOne { | |||
| if strings.HasPrefix(model.Path, setting.Attachment.Minio.Bucket+"/"+Model_prefix) { | |||
| files, err := storage.GetAllObjectByBucketAndPrefixMinio(setting.Attachment.Minio.Bucket, model.Path[len(setting.Attachment.Minio.Bucket)+1:]) | |||
| if err != nil { | |||
| log.Info("Failed to query model size from minio. id=" + modeluuid) | |||
| } | |||
| size := getSize(files) | |||
| size = getSize(files) | |||
| models.ModifyModelSize(modeluuid, size) | |||
| } | |||
| } else if model.Type == models.TypeCloudBrainTwo { | |||
| @@ -323,10 +332,13 @@ func UpdateModelSize(modeluuid string) { | |||
| if err != nil { | |||
| log.Info("Failed to query model size from obs. id=" + modeluuid) | |||
| } | |||
| size := getSize(files) | |||
| size = getSize(files) | |||
| models.ModifyModelSize(modeluuid, size) | |||
| } | |||
| } | |||
| if model.Size == 0 && size > 0 { | |||
| go repository.ResetRepoModelNum(model.RepoId) | |||
| } | |||
| } else { | |||
| log.Info("not found model,uuid=" + modeluuid) | |||
| } | |||
| @@ -441,13 +453,14 @@ func DeleteModelFile(ctx *context.Context) { | |||
| fileName := ctx.Query("fileName") | |||
| model, err := models.QueryModelById(id) | |||
| if err == nil { | |||
| var totalSize int64 | |||
| if model.ModelType == MODEL_LOCAL_TYPE { | |||
| if model.Type == models.TypeCloudBrainOne { | |||
| bucketName := setting.Attachment.Minio.Bucket | |||
| objectName := model.Path[len(bucketName)+1:] + fileName | |||
| log.Info("delete bucket=" + bucketName + " path=" + objectName) | |||
| if strings.HasPrefix(model.Path, bucketName+"/"+Model_prefix) { | |||
| totalSize := storage.MinioGetFilesSize(bucketName, []string{objectName}) | |||
| totalSize = storage.MinioGetFilesSize(bucketName, []string{objectName}) | |||
| err := storage.Attachments.DeleteDir(objectName) | |||
| if err != nil { | |||
| log.Info("Failed to delete model. id=" + id) | |||
| @@ -467,7 +480,7 @@ func DeleteModelFile(ctx *context.Context) { | |||
| objectName := model.Path[len(setting.Bucket)+1:] + fileName | |||
| log.Info("delete bucket=" + setting.Bucket + " path=" + objectName) | |||
| if strings.HasPrefix(model.Path, bucketName+"/"+Model_prefix) { | |||
| totalSize := storage.ObsGetFilesSize(bucketName, []string{objectName}) | |||
| totalSize = storage.ObsGetFilesSize(bucketName, []string{objectName}) | |||
| err := storage.ObsRemoveObject(bucketName, objectName) | |||
| if err != nil { | |||
| log.Info("Failed to delete model. id=" + id) | |||
| @@ -484,6 +497,9 @@ func DeleteModelFile(ctx *context.Context) { | |||
| } | |||
| } | |||
| } | |||
| if (model.Size - totalSize) <= 0 { | |||
| go repository.ResetRepoModelNum(model.RepoId) | |||
| } | |||
| } | |||
| ctx.JSON(200, map[string]string{ | |||
| "code": "0", | |||
| @@ -552,6 +568,9 @@ func deleteModelByID(ctx *context.Context, id string) error { | |||
| } | |||
| } | |||
| } | |||
| if model.Size > 0 { | |||
| go repository.ResetRepoModelNum(model.RepoId) | |||
| } | |||
| } | |||
| } | |||
| return err | |||
| @@ -29,6 +29,7 @@ import ( | |||
| "code.gitea.io/gitea/modules/storage" | |||
| "code.gitea.io/gitea/modules/upload" | |||
| "code.gitea.io/gitea/modules/worker" | |||
| repo_service "code.gitea.io/gitea/services/repository" | |||
| gouuid "github.com/satori/go.uuid" | |||
| ) | |||
| @@ -180,6 +181,7 @@ func DeleteAttachment(ctx *context.Context) { | |||
| ctx.Error(500, fmt.Sprintf("DeleteAttachment: %v", err)) | |||
| return | |||
| } | |||
| go repo_service.DecreaseRepoDatasetNum(attach.DatasetID) | |||
| attachjson, _ := json.Marshal(attach) | |||
| labelmsg.SendDeleteAttachToLabelSys(string(attachjson)) | |||
| @@ -894,6 +896,7 @@ func CompleteMultipart(ctx *context.Context) { | |||
| return | |||
| } | |||
| attachment.UpdateDatasetUpdateUnix() | |||
| go repo_service.IncreaseRepoDatasetNum(dataset.ID) | |||
| repository, _ := models.GetRepositoryByID(dataset.RepoID) | |||
| notification.NotifyOtherTask(ctx.User, repository, fmt.Sprint(repository.IsPrivate, attachment.IsPrivate), attachment.Name, models.ActionUploadAttachment) | |||
| if attachment.DatasetID != 0 { | |||
| @@ -14,7 +14,13 @@ import ( | |||
| ) | |||
| func CloudbrainDurationStatisticHour() { | |||
| if setting.IsCloudbrainTimingEnabled { | |||
| defer func() { | |||
| err := recover() | |||
| if err == nil { | |||
| return | |||
| } | |||
| }() | |||
| if setting.IsCloudbrainTimingEnabled { | |||
| var statisticTime time.Time | |||
| var count int64 | |||
| recordDurationUpdateTime, err := models.GetDurationRecordUpdateTime() | |||
| @@ -166,6 +166,8 @@ func RepoStatisticDaily(date string) { | |||
| repoStat.NumIssuesGrowth = repoStat.NumIssues - repoStatisticFourMonthsAgo.NumIssues | |||
| } | |||
| models.SyncStatDataToRepo(repo) | |||
| if _, err = models.InsertRepoStat(&repoStat); err != nil { | |||
| log.Error("InsertRepoStat failed(%s): %v", projectName, err) | |||
| log.Error("failed statistic: %s", projectName) | |||
| @@ -371,7 +371,18 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
| m.Get("/images/custom", repo.GetCustomImages) | |||
| m.Get("/images/star", repo.GetStarImages) | |||
| m.Get("/repos", routers.ExploreRepos) | |||
| m.Group("/repos", func() { | |||
| //m.Get("", routers.ExploreRepos) | |||
| m.Get("", routers.GetRepoSearchPage) | |||
| m.Group("/square", func() { | |||
| m.Get("", routers.GetRepoSquarePage) | |||
| m.Get("/tab", routers.RepoSquare) | |||
| m.Get("/active-user", routers.ActiveUser) | |||
| m.Get("/active-org", routers.ActiveOrg) | |||
| }) | |||
| m.Get("/search", routers.RepoFind) | |||
| }) | |||
| m.Get("/datasets", routers.ExploreDatasets) | |||
| m.Get("/users", routers.ExploreUsers) | |||
| m.Get("/organizations", routers.ExploreOrganizations) | |||
| @@ -0,0 +1,88 @@ | |||
| package repository | |||
| import ( | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/git" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/modules/redis/redis_client" | |||
| "code.gitea.io/gitea/modules/redis/redis_key" | |||
| "encoding/json" | |||
| "github.com/patrickmn/go-cache" | |||
| "time" | |||
| ) | |||
| var repoContributorCache = cache.New(5*time.Minute, 1*time.Minute) | |||
| type ContributorCacheVal struct { | |||
| Contributors []*models.ContributorInfo | |||
| Total int | |||
| } | |||
| func GetRepoTopNContributors(repo *models.Repository, N int) ([]*models.ContributorInfo, int) { | |||
| val, _ := redis_client.Get(redis_key.RepoTopNContributors(repo.ID, N)) | |||
| if val != "" { | |||
| log.Debug("Get RepoTopNContributors from redis,repo.ID = %d value = %v", repo.ID, val) | |||
| temp := &ContributorCacheVal{} | |||
| json.Unmarshal([]byte(val), temp) | |||
| return temp.Contributors, temp.Total | |||
| } | |||
| contributorInfos, total := getRepoTopNContributorsFromDisk(repo, N) | |||
| log.Debug("Get RepoTopNContributors from disk,repo.ID = %d ", repo.ID) | |||
| jsonVal, err := json.Marshal(&ContributorCacheVal{Contributors: contributorInfos, Total: total}) | |||
| if err == nil { | |||
| redis_client.Setex(redis_key.RepoTopNContributors(repo.ID, N), string(jsonVal), 2*time.Minute) | |||
| } | |||
| return contributorInfos, total | |||
| } | |||
| func getRepoTopNContributorsFromDisk(repo *models.Repository, N int) ([]*models.ContributorInfo, int) { | |||
| contributorInfos := make([]*models.ContributorInfo, 0) | |||
| branchName := GetDefaultBranchName(repo) | |||
| if branchName == "" { | |||
| return contributorInfos, 0 | |||
| } | |||
| contributors, err := git.GetContributors(repo.RepoPath(), branchName) | |||
| if err == nil && contributors != nil { | |||
| contributorInfoHash := make(map[string]*models.ContributorInfo) | |||
| for _, c := range contributors { | |||
| if len(contributorInfos) >= N { | |||
| break | |||
| } | |||
| if c.Email == "" { | |||
| 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 = &models.ContributorInfo{ | |||
| user.RelAvatarLink(), user.Name, user.Email, c.CommitCnt, | |||
| } | |||
| 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 = &models.ContributorInfo{ | |||
| "", "", c.Email, c.CommitCnt, | |||
| } | |||
| contributorInfos = append(contributorInfos, newContributor) | |||
| contributorInfoHash[c.Email] = newContributor | |||
| } | |||
| } | |||
| } | |||
| } | |||
| return contributorInfos, len(contributors) | |||
| } | |||
| @@ -5,18 +5,19 @@ | |||
| package repository | |||
| import ( | |||
| "fmt" | |||
| "io/ioutil" | |||
| "net/http" | |||
| "os" | |||
| "strings" | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/git" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/modules/notification" | |||
| repo_module "code.gitea.io/gitea/modules/repository" | |||
| "code.gitea.io/gitea/modules/setting" | |||
| pull_service "code.gitea.io/gitea/services/pull" | |||
| "fmt" | |||
| "io/ioutil" | |||
| "net/http" | |||
| "os" | |||
| "strings" | |||
| "xorm.io/xorm" | |||
| ) | |||
| const SHELL_FLAG_ON = 1 | |||
| @@ -328,3 +329,47 @@ func IsUploadFileInvalidErr(err error) bool { | |||
| _, ok := err.(UploadFileInvalidErr) | |||
| return ok | |||
| } | |||
| func IncreaseRepoDatasetNum(datasetID int64, engines ...*xorm.Engine) error { | |||
| dataset, err := models.GetDatasetByID(datasetID) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| return models.OperateRepoDatasetNum(dataset.RepoID, 1, engines...) | |||
| } | |||
| func IncreaseRepoModelNum(repoId int64, engines ...*xorm.Engine) error { | |||
| return models.OperateRepoModelNum(repoId, 1, engines...) | |||
| } | |||
| func ResetRepoModelNum(repoId int64) error { | |||
| return models.ResetRepoModelNum(repoId) | |||
| } | |||
| func DecreaseRepoDatasetNum(datasetID int64, engines ...*xorm.Engine) error { | |||
| dataset, err := models.GetDatasetByID(datasetID) | |||
| if err != nil { | |||
| return err | |||
| } | |||
| return models.OperateRepoDatasetNum(dataset.RepoID, -1, engines...) | |||
| } | |||
| func DecreaseRepoModelNum(repoId int64, engines ...*xorm.Engine) error { | |||
| return models.OperateRepoModelNum(repoId, -1, engines...) | |||
| } | |||
| func GetDefaultBranchName(repo *models.Repository) string { | |||
| gitRepo, err := git.OpenRepository(repo.RepoPath()) | |||
| if err != nil { | |||
| return "" | |||
| } | |||
| defer gitRepo.Close() | |||
| if len(repo.DefaultBranch) > 0 && gitRepo.IsBranchExist(repo.DefaultBranch) { | |||
| return repo.DefaultBranch | |||
| } | |||
| brs, _, err := gitRepo.GetBranches(0, 0) | |||
| if len(brs) > 0 { | |||
| return brs[0] | |||
| } | |||
| return "" | |||
| } | |||
| @@ -0,0 +1,315 @@ | |||
| package repository | |||
| import ( | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/modules/setting" | |||
| "encoding/json" | |||
| "github.com/patrickmn/go-cache" | |||
| "time" | |||
| ) | |||
| var repoSquareCache = cache.New(2*time.Minute, 1*time.Minute) | |||
| const ( | |||
| RREFERED_CACHE = "PreferredRepos" | |||
| REPO_BANNER_CACHE = "RepoBanner" | |||
| TOPICS_CACHE = "RepoTopics" | |||
| RECOMMEND_CACHE = "RecommendRepos" | |||
| ) | |||
| func GetBanners() []map[string]string { | |||
| v, success := repoSquareCache.Get(REPO_BANNER_CACHE) | |||
| if success { | |||
| log.Debug("GetBanners from cache,value = %v", v) | |||
| if v == nil { | |||
| return nil | |||
| } | |||
| r := v.([]map[string]string) | |||
| return r | |||
| } | |||
| repoMap := getMapContent("repos/square_banner") | |||
| repoSquareCache.Set(REPO_BANNER_CACHE, repoMap, 1*time.Minute) | |||
| return repoMap | |||
| } | |||
| func GetTopics() []string { | |||
| v, success := repoSquareCache.Get(TOPICS_CACHE) | |||
| if success { | |||
| log.Debug("GetTopics from cache,value = %v", v) | |||
| if v == nil { | |||
| return nil | |||
| } | |||
| r := v.([]string) | |||
| return r | |||
| } | |||
| topics := getArrayContent("repos/recommend_topics") | |||
| repoSquareCache.Set(TOPICS_CACHE, topics, 1*time.Minute) | |||
| return topics | |||
| } | |||
| func getMapContent(fileName string) []map[string]string { | |||
| url := setting.RecommentRepoAddr + fileName | |||
| result, err := RecommendContentFromPromote(url) | |||
| remap := make([]map[string]string, 0) | |||
| if err == nil { | |||
| json.Unmarshal([]byte(result), &remap) | |||
| } | |||
| return remap | |||
| } | |||
| func getArrayContent(fileName string) []string { | |||
| url := setting.RecommentRepoAddr + fileName | |||
| result, err := RecommendContentFromPromote(url) | |||
| r := make([]string, 0) | |||
| if err == nil { | |||
| json.Unmarshal([]byte(result), &r) | |||
| } | |||
| return r | |||
| } | |||
| func GetRecommendRepos() []map[string]interface{} { | |||
| v, success := repoSquareCache.Get(RECOMMEND_CACHE) | |||
| if success { | |||
| log.Debug("GetRecommendRepos from cache,value = %v", v) | |||
| if v == nil { | |||
| return nil | |||
| } | |||
| r := v.([]map[string]interface{}) | |||
| return r | |||
| } | |||
| repoMap := getMapContent("home/projects") | |||
| r, _ := GetRecommendRepoFromPromote(repoMap) | |||
| repoSquareCache.Set(RECOMMEND_CACHE, r, 1*time.Minute) | |||
| return r | |||
| } | |||
| func GetPreferredRepos() ([]*models.Repository4Card, error) { | |||
| v, success := repoSquareCache.Get(RREFERED_CACHE) | |||
| if success { | |||
| log.Debug("GetPreferredRepos from cache,value = %v", v) | |||
| if v == nil { | |||
| return nil, nil | |||
| } | |||
| r := v.([]*models.Repository4Card) | |||
| return r, nil | |||
| } | |||
| repos, err := models.GetSelectedRepos(models.FindSelectedReposOpts{ | |||
| ListOptions: models.ListOptions{ | |||
| PageSize: 10, | |||
| Page: 1, | |||
| }, | |||
| OnlyPublic: true, | |||
| }) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| result := make([]*models.Repository4Card, len(repos)) | |||
| for i, r := range repos { | |||
| result[i] = r.ToCardFormat() | |||
| } | |||
| repoSquareCache.Set(RREFERED_CACHE, result, 1*time.Minute) | |||
| return result, nil | |||
| } | |||
| func GetIncubationRepos() ([]*models.Repository4Card, error) { | |||
| org, err := models.GetOrgByName(setting.IncubationSourceOrgName) | |||
| if models.IsErrOrgNotExist(err) { | |||
| return make([]*models.Repository4Card, 0), nil | |||
| } | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| repos, err := models.GetSelectedRepos(models.FindSelectedReposOpts{ | |||
| ListOptions: models.ListOptions{ | |||
| PageSize: 10, | |||
| Page: 1, | |||
| }, | |||
| OrgId: org.ID, | |||
| OnlyPublic: true, | |||
| }) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| result := make([]*models.Repository4Card, len(repos)) | |||
| for i, r := range repos { | |||
| result[i] = r.ToCardFormat() | |||
| } | |||
| return result, nil | |||
| } | |||
| func GetHotPaperRepos() ([]*models.Repository4Card, error) { | |||
| rlist, _, err := models.SearchRepository(&models.SearchRepoOptions{ | |||
| ListOptions: models.ListOptions{ | |||
| Page: 1, | |||
| PageSize: 10, | |||
| }, | |||
| OrderBy: models.SearchOrderByLastMonthVisitsReverse + "," + models.SearchOrderByRecentUpdated, | |||
| TopicOnly: true, | |||
| TopicName: setting.PaperRepoTopicName, | |||
| AllPublic: true, | |||
| }) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| result := make([]*models.Repository4Card, len(rlist)) | |||
| for i, r := range rlist { | |||
| result[i] = r.ToCardFormat() | |||
| } | |||
| return result, nil | |||
| } | |||
| type FindReposOptions struct { | |||
| models.ListOptions | |||
| Actor *models.User | |||
| Sort string | |||
| Keyword string | |||
| Topic string | |||
| Private bool | |||
| OwnerID int64 | |||
| } | |||
| func FindRepos(opts FindReposOptions) (*models.FindReposResponse, error) { | |||
| var ( | |||
| repos []*models.Repository | |||
| count int64 | |||
| err error | |||
| orderBy models.SearchOrderBy | |||
| ) | |||
| switch opts.Sort { | |||
| //1.近期热门:按最近1个月浏览量倒序排序,最近1个月浏览量>最近更新>项目名称升序 | |||
| case "mostpopular": | |||
| orderBy = models.SearchOrderByLastMonthVisitsReverse + "," + models.SearchOrderByRecentUpdated + "," + models.SearchOrderByAlphabetically | |||
| //2.近期活跃:按提交增长量(最近4个月commit数)倒序排序,提交增长量>最近更新>项目名称升序。 | |||
| case "mostactive": | |||
| orderBy = models.SearchOrderByLastFourMonthCommitsReverse + "," + models.SearchOrderByRecentUpdated + "," + models.SearchOrderByAlphabetically | |||
| //3.最近更新:按最近更新>项目名称升序排序。 | |||
| case "recentupdate": | |||
| orderBy = models.SearchOrderByRecentUpdated + "," + models.SearchOrderByAlphabetically | |||
| //4.最近创建:按项目创建时间排序,最近的排前面。最近创建>项目名称升序。 | |||
| case "newest": | |||
| orderBy = models.SearchOrderByNewest + "," + models.SearchOrderByAlphabetically | |||
| //5.点赞最多:按点赞数倒序排序。点赞数>最近更新>项目名称升序。 | |||
| case "moststars": | |||
| orderBy = models.SearchOrderByStarsReverse + "," + models.SearchOrderByRecentUpdated + "," + models.SearchOrderByAlphabetically | |||
| //6.派生最多:按派生数倒序排序。派生数>最近更新>项目名称升序。 | |||
| case "mostforks": | |||
| orderBy = models.SearchOrderByForksReverse + "," + models.SearchOrderByRecentUpdated + "," + models.SearchOrderByAlphabetically | |||
| //7.数据集最多:按项目包含的数据集文件数量倒序排序,数据集文件数>最近更新>项目名称升序。 | |||
| case "mostdatasets": | |||
| orderBy = models.SearchOrderByDatasetCntReverse + "," + models.SearchOrderByRecentUpdated + "," + models.SearchOrderByAlphabetically | |||
| //8.AI任务最多:按项目包含的AI任务数量倒序排序,AI任务数>最近更新>项目名称升序。 | |||
| case "mostaitasks": | |||
| orderBy = models.SearchOrderByAiTaskCntReverse + "," + models.SearchOrderByRecentUpdated + "," + models.SearchOrderByAlphabetically | |||
| //9.模型最多:按项目包含的模型数量倒序排序,模型大小为0则不统计。模型数>最近更新>项目名称升序。 | |||
| case "mostmodels": | |||
| orderBy = models.SearchOrderByModelCntReverse + "," + models.SearchOrderByRecentUpdated + "," + models.SearchOrderByAlphabetically | |||
| default: | |||
| orderBy = models.SearchOrderByLastMonthVisitsReverse + "," + models.SearchOrderByRecentUpdated + "," + models.SearchOrderByAlphabetically | |||
| } | |||
| repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ | |||
| ListOptions: opts.ListOptions, | |||
| Actor: opts.Actor, | |||
| OrderBy: orderBy, | |||
| Private: opts.Private, | |||
| Keyword: opts.Keyword, | |||
| OwnerID: opts.OwnerID, | |||
| AllPublic: true, | |||
| AllLimited: true, | |||
| TopicName: opts.Topic, | |||
| IncludeDescription: setting.UI.SearchRepoDescription, | |||
| }) | |||
| if err != nil { | |||
| log.Error("FindRepos error when SearchRepository.%v", err) | |||
| return nil, err | |||
| } | |||
| result := make([]*models.Repository4Card, len(repos)) | |||
| for i, r := range repos { | |||
| t := r.ToCardFormat() | |||
| contributors, _ := GetRepoTopNContributors(r, 6) | |||
| t.Contributors = contributors | |||
| result[i] = t | |||
| } | |||
| return &models.FindReposResponse{ | |||
| Repos: result, | |||
| Total: count, | |||
| Page: opts.Page, | |||
| PageSize: opts.PageSize, | |||
| }, nil | |||
| } | |||
| type ActiveUser struct { | |||
| User *models.User4Front | |||
| Followed bool | |||
| ShowButton bool | |||
| } | |||
| func GetActiveUser4Square(currentUserId int64) ([]*ActiveUser, error) { | |||
| result := make([]*ActiveUser, 0) | |||
| userIds, err := models.QueryLast30DaysHighestIndexUsers(5) | |||
| if err != nil { | |||
| log.Error("ActiveUser err. %v", err) | |||
| return result, err | |||
| } | |||
| if len(userIds) == 0 { | |||
| return result, nil | |||
| } | |||
| users, err := models.GetUsersByIDs(userIds) | |||
| if err != nil { | |||
| return result, nil | |||
| } | |||
| usersMap := make(map[int64]*models.User) | |||
| for _, v := range users { | |||
| usersMap[v.ID] = v | |||
| } | |||
| for i := 0; i < len(userIds); i++ { | |||
| userId := userIds[i] | |||
| user := usersMap[userId] | |||
| if user == nil { | |||
| continue | |||
| } | |||
| isFollowed := false | |||
| if currentUserId != 0 { | |||
| isFollowed = models.IsFollowing(currentUserId, userId) | |||
| } | |||
| a := &ActiveUser{ | |||
| Followed: isFollowed, | |||
| User: user.ToFrontFormat(), | |||
| ShowButton: currentUserId != userId, | |||
| } | |||
| result = append(result, a) | |||
| } | |||
| return result, nil | |||
| } | |||
| func GetActiveOrgs() ([]*models.User4Front, error) { | |||
| orgScores, err := models.FindTopNOpenIOrgs(5) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| orgs := make([]*models.User4Front, len(orgScores)) | |||
| for i, v := range orgScores { | |||
| orgs[i] = v.ToFrontFormat() | |||
| } | |||
| return orgs, nil | |||
| } | |||
| func RefreshRepoStatData() { | |||
| repos, err := models.GetAllRepositories() | |||
| if err != nil { | |||
| log.Error("RefreshRepoStatData GetAllRepositories failed: %v", err.Error()) | |||
| return | |||
| } | |||
| for _, repo := range repos { | |||
| models.SyncStatDataToRepo(repo) | |||
| } | |||
| } | |||
| @@ -35,7 +35,7 @@ | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a> | |||
| <a class="item" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "custom.head.project"}}</a> | |||
| <a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a> | |||
| <div class="ui simple dropdown item" > | |||
| {{.i18n.Tr "repo.model_manager"}} | |||
| @@ -48,7 +48,7 @@ | |||
| {{.i18n.Tr "explore"}} | |||
| <i class="dropdown icon"></i> | |||
| <div class="menu"> | |||
| <a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a> | |||
| <!--<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>--> | |||
| <a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a> | |||
| <a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a> | |||
| {{if .IsOperator}} | |||
| @@ -75,7 +75,7 @@ | |||
| </div> | |||
| </div> | |||
| <a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a> | |||
| <a class="item" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "custom.head.project"}}</a> | |||
| <a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a> | |||
| <div class="ui simple dropdown item" > | |||
| {{.i18n.Tr "repo.model_manager"}} | |||
| @@ -89,7 +89,7 @@ | |||
| {{.i18n.Tr "explore"}} | |||
| <i class="dropdown icon"></i> | |||
| <div class="menu" > | |||
| <a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a> | |||
| <!--<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>--> | |||
| <a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a> | |||
| <a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a> | |||
| {{if .IsOperator}} | |||
| @@ -100,7 +100,7 @@ | |||
| </div> | |||
| </div> | |||
| {{else if .IsLandingPageExplore}} | |||
| <a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "home"}}</a> | |||
| <a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "home"}}</a> | |||
| {{else if .IsLandingPageOrganizations}} | |||
| <a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "home"}}</a> | |||
| {{end}} | |||
| @@ -32,7 +32,7 @@ | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a> | |||
| <a class="item" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "custom.head.project"}}</a> | |||
| <a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a> | |||
| <div class="ui simple dropdown item" > | |||
| {{.i18n.Tr "repo.model_manager"}} | |||
| @@ -45,7 +45,7 @@ | |||
| {{.i18n.Tr "explore"}} | |||
| <i class="dropdown icon"></i> | |||
| <div class="menu"> | |||
| <a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a> | |||
| <!--<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>--> | |||
| <a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a> | |||
| <a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a> | |||
| {{if .IsOperator}} | |||
| @@ -71,7 +71,7 @@ | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a> | |||
| <a class="item" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "custom.head.project"}}</a> | |||
| <a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a> | |||
| <div class="ui simple dropdown item" > | |||
| {{.i18n.Tr "repo.model_manager"}} | |||
| @@ -84,7 +84,7 @@ | |||
| {{.i18n.Tr "explore"}} | |||
| <i class="dropdown icon"></i> | |||
| <div class="menu"> | |||
| <a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a> | |||
| <!--<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>--> | |||
| <a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a> | |||
| <a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a> | |||
| {{if .IsOperator}} | |||
| @@ -95,7 +95,7 @@ | |||
| </div> | |||
| </div> | |||
| {{else if .IsLandingPageExplore}} | |||
| <a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "home"}}</a> | |||
| <a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "home"}}</a> | |||
| {{else if .IsLandingPageOrganizations}} | |||
| <a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "home"}}</a> | |||
| {{end}} | |||
| @@ -24,7 +24,7 @@ | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a> | |||
| <a class="item" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "custom.head.project"}}</a> | |||
| <a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a> | |||
| <div class="ui simple dropdown item" > | |||
| {{.i18n.Tr "repo.model_manager"}} | |||
| @@ -37,7 +37,7 @@ | |||
| {{.i18n.Tr "explore"}} | |||
| <i class="dropdown icon"></i> | |||
| <div class="menu"> | |||
| <a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a> | |||
| <!--<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>--> | |||
| <a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a> | |||
| <a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a> | |||
| {{if .IsOperator}} | |||
| @@ -64,7 +64,7 @@ | |||
| </div> | |||
| </div> | |||
| <a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a> | |||
| <a class="item" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "custom.head.project"}}</a> | |||
| <a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a> | |||
| <div class="ui simple dropdown item" > | |||
| {{.i18n.Tr "repo.model_manager"}} | |||
| @@ -77,7 +77,7 @@ | |||
| {{.i18n.Tr "explore"}} | |||
| <i class="dropdown icon"></i> | |||
| <div class="menu"> | |||
| <a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a> | |||
| <!--<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>--> | |||
| <a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a> | |||
| <a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a> | |||
| {{if .IsOperator}} | |||
| @@ -88,7 +88,7 @@ | |||
| </div> | |||
| </div> | |||
| {{else if .IsLandingPageExplore}} | |||
| <a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "home"}}</a> | |||
| <a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "home"}}</a> | |||
| {{else if .IsLandingPageOrganizations}} | |||
| <a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "home"}}</a> | |||
| {{end}} | |||
| @@ -34,7 +34,7 @@ | |||
| </div> | |||
| </div> | |||
| <a class="item" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "custom.head.project"}}</a> | |||
| <a class="item" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "custom.head.project"}}</a> | |||
| <a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a> | |||
| <div class="ui simple dropdown item" > | |||
| {{.i18n.Tr "repo.model_manager"}} | |||
| @@ -47,7 +47,7 @@ | |||
| {{.i18n.Tr "explore"}} | |||
| <i class="dropdown icon"></i> | |||
| <div class="menu"> | |||
| <a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a> | |||
| <!--<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>--> | |||
| <a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a> | |||
| <a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a> | |||
| {{if .IsOperator}} | |||
| @@ -87,7 +87,7 @@ | |||
| {{.i18n.Tr "explore"}} | |||
| <i class="dropdown icon"></i> | |||
| <div class="menu" > | |||
| <a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a> | |||
| <!--<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>--> | |||
| <a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a> | |||
| <a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a> | |||
| {{if .IsOperator}} | |||
| @@ -98,7 +98,7 @@ | |||
| </div> | |||
| </div> | |||
| {{else if .IsLandingPageExplore}} | |||
| <a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "home"}}</a> | |||
| <a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos/square">{{.i18n.Tr "home"}}</a> | |||
| {{else if .IsLandingPageOrganizations}} | |||
| <a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "home"}}</a> | |||
| {{end}} | |||
| @@ -1,6 +1,6 @@ | |||
| <div class="tablet only mobile only sixteen wide mobile sixteen wide tablet column row"> | |||
| <div class="ui secondary pointing tabular top attached borderless menu navbar"> | |||
| <a class="{{if .PageIsExploreRepositories}}active{{end}} item" href="{{AppSubUrl}}/explore/repos"> | |||
| <a class="{{if .PageIsExploreRepositories}}active{{end}} item" href="{{AppSubUrl}}/explore/repos/square"> | |||
| {{svg "octicon-repo" 16}} {{.i18n.Tr "explore.repos"}} | |||
| </a> | |||
| <a class="{{if .PageIsDatasets}}active{{end}} item" href="{{AppSubUrl}}/explore/datasets"> | |||
| @@ -24,7 +24,7 @@ | |||
| <div class="computer only three wide computer column"> | |||
| <div class="ui grid"> | |||
| <div class="sixteen wide column ui secondary sticky pointing tabular vertical menu"> | |||
| <a class="{{if .PageIsExploreRepositories}}active{{end}} item" href="{{AppSubUrl}}/explore/repos"> | |||
| <a class="{{if .PageIsExploreRepositories}}active{{end}} item" href="{{AppSubUrl}}/explore/repos/square"> | |||
| {{svg "octicon-repo" 16}} {{.i18n.Tr "explore.repos"}} | |||
| </a> | |||
| <a class="{{if .PageIsDatasets}}active{{end}} item" href="{{AppSubUrl}}/explore/datasets"> | |||
| @@ -0,0 +1,8 @@ | |||
| {{template "base/head_home" .}} | |||
| <link rel="stylesheet" href="{{StaticUrlPrefix}}/css/vp-repos-search.css?v={{MD5 AppVer}}" /> | |||
| <script> | |||
| var staticSquareTopics = {{ .SquareTopics }}; | |||
| </script> | |||
| <div id="__vue-root"></div> | |||
| <script src="{{StaticUrlPrefix}}/js/vp-repos-search.js?v={{MD5 AppVer}}"></script> | |||
| {{template "base/footer" .}} | |||
| @@ -0,0 +1,16 @@ | |||
| {{template "base/head_home" .}} | |||
| {{ if .SquareBanners }} | |||
| {{ range .SquareBanners }} | |||
| <img preload style="height:0;width:0;position:absolute;left:-2000px;" src="{{.src}}" /> | |||
| {{ end }} | |||
| {{ end }} | |||
| <link rel="stylesheet" href="{{StaticUrlPrefix}}/css/vp-repos-square.css?v={{MD5 AppVer}}" /> | |||
| <script> | |||
| var staticSquareBanners = {{ .SquareBanners }}; | |||
| var staticSquarePreferredRepos = {{ .SquarePreferredRepos }}; | |||
| var staticSquareTopics = {{ .SquareTopics }}; | |||
| var staticSquareRecommendRepos = {{ .SquareRecommendRepos }}; | |||
| </script> | |||
| <div id="__vue-root"></div> | |||
| <script src="{{StaticUrlPrefix}}/js/vp-repos-square.js?v={{MD5 AppVer}}"></script> | |||
| {{template "base/footer" .}} | |||
| @@ -10,7 +10,7 @@ | |||
| {{.i18n.Tr "home.wecome_AI_plt"}} | |||
| </div> | |||
| <div class="content"> | |||
| <p class="ui text grey">{{.i18n.Tr "home.explore_AI"}} <a href="{{AppSubUrl}}/explore/repos"> {{.i18n.Tr "home.repositories"}}</a> {{.i18n.Tr "home.or_t"}} <a href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "home.datasets"}}</a></p> | |||
| <p class="ui text grey">{{.i18n.Tr "home.explore_AI"}} <a href="{{AppSubUrl}}/explore/repos/square"> {{.i18n.Tr "home.repositories"}}</a> {{.i18n.Tr "home.or_t"}} <a href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "home.datasets"}}</a></p> | |||
| <p><span class="ui text grey">{{.i18n.Tr "home.use_plt__fuction"}}</span> <a class="mini ui blue button" href="{{AppSubUrl}}/repo/create{{if .ContextUser.IsOrganization}}?org={{.ContextUser.ID}}{{end}}" >{{.i18n.Tr "repo.create_repo"}}</a></p> | |||
| <p class="ui text grey">{{.i18n.Tr "home.provide_resoure"}}</p> | |||
| </div> | |||
| @@ -50,7 +50,7 @@ import initImage from "./features/images.js"; | |||
| import selectDataset from "./components/dataset/selectDataset.vue"; | |||
| import referenceDataset from "./components/dataset/referenceDataset.vue"; | |||
| // import $ from 'jquery.js' | |||
| import router from "./router/index.js"; | |||
| // import router from "./router/index.js"; | |||
| import { Message } from "element-ui"; | |||
| import { i18nVue } from "./features/i18nVue.js"; | |||
| @@ -5214,7 +5214,7 @@ function initTopToHome() { | |||
| $(window).scroll(function (e) { | |||
| const scrollTop = $(document).scrollTop(); | |||
| const winHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight; | |||
| if (scrollTop > winHeight * 1.2) { | |||
| if (scrollTop > winHeight * 0.5) { | |||
| topToHomeEl.fadeIn(); | |||
| } else { | |||
| topToHomeEl.fadeOut(); | |||
| @@ -0,0 +1,12 @@ | |||
| import service from '../service'; | |||
| // 获取promote配置数据 | |||
| export const getPromoteData = (filePathName) => { | |||
| return service({ | |||
| url: '/dashboard/invitation', | |||
| method: 'get', | |||
| params: { | |||
| filename: filePathName | |||
| }, | |||
| }); | |||
| } | |||
| @@ -0,0 +1,69 @@ | |||
| import service from '../service'; | |||
| // 获取首页数据 | |||
| export const getHomePageData = () => { | |||
| return service({ | |||
| url: '/recommend/home', | |||
| method: 'get', | |||
| params: {}, | |||
| }); | |||
| } | |||
| // 获取项目广场上方tab数据 tab=preferred 项目优选|incubation 启智孵化管道|hot-paper 热门论文项目 | |||
| export const getReposSquareTabData = (tab) => { | |||
| return service({ | |||
| url: '/explore/repos/square/tab', | |||
| method: 'get', | |||
| params: { | |||
| type: tab | |||
| }, | |||
| }); | |||
| } | |||
| // 搜索项目 | |||
| // q string 否 关键词 | |||
| // topic string 否 标签名 | |||
| // sort string 是 mostpopular 近期热门 | mostactive 近期活跃 | recentupdate 最近更新 | newest 最近创建 | |||
| // moststars 点赞最多 | mostforks 派生最多 | mostdatasets 数据集最多 | mostaitasks AI任务最多 | mostmodels 模型最多 | |||
| // pageSize int 是 每页大小,可选值为15 | 30 | 50 | |||
| // page int 是 页码 | |||
| export const getReposListData = (params) => { | |||
| return service({ | |||
| url: '/explore/repos/search', | |||
| method: 'get', | |||
| params: { | |||
| q: params.q || '', | |||
| topic: params.topic || '', | |||
| sort: params.sort || 'mostpopular', | |||
| pageSize: params.pageSize || 15, | |||
| page: params.page || 1, | |||
| }, | |||
| }); | |||
| } | |||
| // 获取活跃用户列表 | |||
| export const getActiveUsers = () => { | |||
| return service({ | |||
| url: '/explore/repos/square/active-user', | |||
| method: 'get', | |||
| params: {}, | |||
| }); | |||
| } | |||
| // 关注用户 | |||
| export const followingUsers = (userName, isFollowing) => { | |||
| return service({ | |||
| url: `/api/v1/user/following/${userName}`, | |||
| method: isFollowing ? 'put' : 'delete', | |||
| params: {}, | |||
| }); | |||
| } | |||
| // 获取活跃组织列表 | |||
| export const getActiveOrgs = () => { | |||
| return service({ | |||
| url: '/explore/repos/square/active-org', | |||
| method: 'get', | |||
| params: {}, | |||
| }); | |||
| } | |||
| @@ -177,21 +177,21 @@ const en = { | |||
| Activated: 'Activated', | |||
| notActive: 'Not active', | |||
| }, | |||
| tranformImageFailed:'Picture desensitization failed', | |||
| originPicture:'Origin picture', | |||
| desensitizationPicture:'Desensitization picture', | |||
| desensitizationObject:'Desensitization object', | |||
| example:'Example', | |||
| startDesensitization:'Start desensitization', | |||
| all:'All', | |||
| onlyFace:'Only face', | |||
| onlyLicensePlate:'Only license plate', | |||
| dragThePictureHere:'Drag the picture here', | |||
| or:' or ', | |||
| clickUpload:'Click upload', | |||
| dataDesensitizationModelExperience:'Data desensitization model experience', | |||
| dataDesensitizationModelDesc:'Use AI technology to desensitize the face and license plate number in the picture. For more information about this model, please visit the project', | |||
| limitFilesUpload:'Only jpg/jpeg/png files can be uploaded', | |||
| tranformImageFailed: 'Picture desensitization failed', | |||
| originPicture: 'Origin picture', | |||
| desensitizationPicture: 'Desensitization picture', | |||
| desensitizationObject: 'Desensitization object', | |||
| example: 'Example', | |||
| startDesensitization: 'Start desensitization', | |||
| all: 'All', | |||
| onlyFace: 'Only face', | |||
| onlyLicensePlate: 'Only license plate', | |||
| dragThePictureHere: 'Drag the picture here', | |||
| or: ' or ', | |||
| clickUpload: 'Click upload', | |||
| dataDesensitizationModelExperience: 'Data desensitization model experience', | |||
| dataDesensitizationModelDesc: 'Use AI technology to desensitize the face and license plate number in the picture. For more information about this model, please visit the project', | |||
| limitFilesUpload: 'Only jpg/jpeg/png files can be uploaded', | |||
| limitSizeUpload: 'The size of the uploaded file cannot exceed 20M!', | |||
| notebook: { | |||
| createNewNotebook: "Create new notebook debug task", | |||
| @@ -285,6 +285,59 @@ const en = { | |||
| modelAccessPublic:'Public', | |||
| modelAccessPrivate:'Private', | |||
| }, | |||
| repos: { | |||
| activeOrganization: 'Active Organization', | |||
| activeUsers: 'Active Users', | |||
| follow: 'Follow', | |||
| unFollow: 'Unfollow', | |||
| selectedFields: 'Recommend Repositories', | |||
| mostPopular: 'Most Popular', | |||
| mostActive: 'Most Active', | |||
| newest: 'Newest', | |||
| recentlyUpdated: 'Recently Updated', | |||
| mostStars: 'Most Stars', | |||
| mostForks: 'Most Forks', | |||
| mostDatasets: 'Most Datasets', | |||
| mostAiTasks: 'Most AI Tasks', | |||
| mostModels: 'Most Models', | |||
| dataset: 'Datasets', | |||
| model: 'Models', | |||
| aiTask: 'AI Tasks', | |||
| updated: 'Updated', | |||
| contributors: 'Contributors', | |||
| searchRepositories: 'Search Repositories', | |||
| search: 'Search', | |||
| allFields: 'All Fields', | |||
| preferred: 'Preferred', | |||
| openIIncubation: 'OpenI Incubation', | |||
| hotPapers: 'Hot Papers', | |||
| watch: 'Watch', | |||
| star: 'Star', | |||
| fork: 'Fork', | |||
| noReposfound: 'No matching repositories found.', | |||
| }, | |||
| timeObj: { | |||
| ago: '{msg} ago', | |||
| from_now: '{msg} from now', | |||
| now: 'now', | |||
| future: 'future', | |||
| '1s': '1 second', | |||
| '1m': '1 minute', | |||
| '1h': '1 hour', | |||
| '1d': '1 day', | |||
| '1w': '1 week', | |||
| '1mon': '1 month', | |||
| '1y': '1 year', | |||
| seconds: '{msg} seconds', | |||
| minutes: '{msg} minutes', | |||
| hours: '{msg} hours', | |||
| days: '{msg} days', | |||
| weeks: '{msg} weeks', | |||
| months: '{msg} months', | |||
| years: '{msg} years', | |||
| raw_seconds: 'seconds', | |||
| raw_minutes: 'minutes', | |||
| }, | |||
| } | |||
| export default en; | |||
| @@ -220,8 +220,24 @@ const zh = { | |||
| graphicMemory: "显存", | |||
| memory: "内存", | |||
| sharedMemory: "共享内存", | |||
| tips:'本次新建的调试任务会放在您名下项目openi-notebook中,如果没有该项目系统会自动新建一个。' | |||
| tips: '本次新建的调试任务会放在您名下项目openi-notebook中,如果没有该项目系统会自动新建一个。' | |||
| }, | |||
| tranformImageFailed: '图片脱敏失败', | |||
| originPicture: '原始图片', | |||
| desensitizationPicture: '脱敏图片', | |||
| desensitizationObject: '脱敏对象', | |||
| example: '示例', | |||
| startDesensitization: '开始处理', | |||
| all: '全部', | |||
| onlyFace: '仅人脸', | |||
| onlyLicensePlate: '仅车牌', | |||
| dragThePictureHere: '拖动图片到这里', | |||
| or: '或', | |||
| clickUpload: '点击上传', | |||
| dataDesensitizationModelExperience: '数据脱敏模型体验', | |||
| dataDesensitizationModelDesc: '利用人工智能AI技术,把图片中的人脸、车牌号码进行脱敏处理。该模型更多信息请访问项目', | |||
| limitFilesUpload: '只能上传 jpg/jpeg/png 格式的文件', | |||
| limitSizeUpload: '上传文件大小不能超过 20M !', | |||
| modelManage: { | |||
| modelManage: '模型管理', | |||
| modelName: '模型名称', | |||
| @@ -286,8 +302,59 @@ const zh = { | |||
| modelAccessPublic:'公开', | |||
| modelAccessPrivate:'私有', | |||
| }, | |||
| }; | |||
| repos: { | |||
| activeOrganization: '活跃组织', | |||
| activeUsers: '活跃用户', | |||
| follow: '关注', | |||
| unFollow: '取消关注', | |||
| selectedFields: '领域精选', | |||
| mostPopular: '近期热门', | |||
| mostActive: '近期活跃', | |||
| newest: '最近创建', | |||
| recentlyUpdated: '最近更新', | |||
| mostStars: '点赞最多', | |||
| mostForks: '派生最多', | |||
| mostDatasets: '数据集最多', | |||
| mostAiTasks: 'AI任务最多', | |||
| mostModels: '模型最多', | |||
| dataset: '数据集', | |||
| model: '模型', | |||
| aiTask: 'AI任务', | |||
| updated: '最后更新于', | |||
| contributors: '贡献者', | |||
| searchRepositories: '搜项目', | |||
| search: '搜索', | |||
| allFields: '全部领域', | |||
| preferred: '项目优选', | |||
| openIIncubation: '启智孵化管道', | |||
| hotPapers: '热门论文项目', | |||
| watch: '关注', | |||
| star: '点赞', | |||
| fork: '派生', | |||
| noReposfound: '未找到匹配的项目。', | |||
| }, | |||
| timeObj: { | |||
| ago: '{msg}前', | |||
| from_now: '{msg} 之后', | |||
| now: '现在', | |||
| future: '将来', | |||
| '1s': '1 秒', | |||
| '1m': '1 分钟', | |||
| '1h': '1 小时', | |||
| '1d': '1 天', | |||
| '1w': '1 周', | |||
| '1mon': '1 个月', | |||
| '1y': '1 年', | |||
| seconds: '{msg} 秒', | |||
| minutes: '{msg} 分钟', | |||
| hours: '{msg} 小时', | |||
| days: '{msg} 天', | |||
| weeks: '{msg} 周', | |||
| months: '{msg} 个月', | |||
| years: '{msg} 年', | |||
| raw_seconds: '秒', | |||
| raw_minutes: '分钟', | |||
| }, | |||
| } | |||
| export default zh; | |||
| @@ -0,0 +1,123 @@ | |||
| <template> | |||
| <div> | |||
| <div class="container"> | |||
| <div class="title"> | |||
| <i style="margin-left:10px;margin-right:8px;font-size:20px;" class="ri-blaze-line"></i> | |||
| <span>{{ $t('repos.activeOrganization') }}</span> | |||
| </div> | |||
| <div class="content"> | |||
| <div class="item" v-for="(item, index) in list" :key="index"> | |||
| <div class="item-l"> | |||
| <a class="name" :href="`/${item.Name}`" :title="item.Name"> | |||
| <img class="avatar" :src="item.RelAvatarLink"> | |||
| <div class="name-c"><span>{{ item.Name }}</span></div> | |||
| </a> | |||
| </div> | |||
| <div class="item-r"> | |||
| <i class="ri-user-2-line" style="color:rgb(250, 140, 22);margin-right:4px;"></i> | |||
| <span>{{ item.NumMembers }}</span> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| import { getActiveOrgs } from '~/apis/modules/repos'; | |||
| export default { | |||
| name: "ActiveOrgs", | |||
| props: {}, | |||
| components: {}, | |||
| data() { | |||
| return { | |||
| list: [], | |||
| }; | |||
| }, | |||
| methods: {}, | |||
| mounted() { | |||
| getActiveOrgs().then(res => { | |||
| res = res.data; | |||
| if (res.Code == 0) { | |||
| this.list = res.Data.Orgs || []; | |||
| } else { | |||
| this.list = []; | |||
| } | |||
| }).catch(err => { | |||
| console.log(err); | |||
| this.list = []; | |||
| }); | |||
| }, | |||
| }; | |||
| </script> | |||
| <style scoped lang="less"> | |||
| .title { | |||
| height: 43px; | |||
| border-color: rgba(16, 16, 16, 0.05); | |||
| border-width: 1px 0px; | |||
| border-style: solid; | |||
| display: flex; | |||
| align-items: center; | |||
| font-size: 18px; | |||
| color: rgba(47, 9, 69, 0.74); | |||
| background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(-1.007%2C%200.0010000000000001494%2C%20-0.000018400023883213844%2C%20-1.007%2C%201.003%2C%200.008)%22%3E%3Cstop%20stop-color%3D%22%23eee9da%22%20stop-opacity%3D%220%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23f3e7f7%22%20stop-opacity%3D%220.26%22%20offset%3D%220.29%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23d0e7ff%22%20stop-opacity%3D%220.3%22%20offset%3D%221%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E"); | |||
| } | |||
| .content { | |||
| padding: 6px 0; | |||
| } | |||
| .item { | |||
| display: flex; | |||
| align-items: center; | |||
| height: 48px; | |||
| padding: 0 8px; | |||
| font-size: 14px; | |||
| color: rgba(16, 16, 16, 1); | |||
| } | |||
| .item>div { | |||
| display: flex; | |||
| align-items: center; | |||
| } | |||
| .item-l { | |||
| flex: 1; | |||
| overflow: hidden; | |||
| a { | |||
| display: flex; | |||
| align-items: center; | |||
| overflow: hidden; | |||
| } | |||
| } | |||
| .item-r { | |||
| width: 80px; | |||
| justify-content: flex-end; | |||
| } | |||
| .item .avatar { | |||
| width: 32px; | |||
| height: 32px; | |||
| border-radius: 50%; | |||
| margin-right: 10px; | |||
| } | |||
| .item .name-c { | |||
| flex: 1; | |||
| overflow: hidden; | |||
| width: 100%; | |||
| text-overflow: ellipsis; | |||
| white-space: nowrap; | |||
| } | |||
| .item .name { | |||
| color: rgba(16, 16, 16, 1); | |||
| &:hover { | |||
| opacity: 0.8; | |||
| } | |||
| } | |||
| </style> | |||
| @@ -0,0 +1,156 @@ | |||
| <template> | |||
| <div> | |||
| <div class="container"> | |||
| <div class="title"> | |||
| <i style="margin-left:10px;margin-right:8px;font-size:20px;" class="ri-account-pin-circle-line"></i> | |||
| <span>{{ $t('repos.activeUsers') }}</span> | |||
| </div> | |||
| <div class="content"> | |||
| <div class="item" v-for="(item, index) in list" :key="index"> | |||
| <div class="item-l"> | |||
| <a class="name" :href="`/${item.User.Name}`" :title="(item.User.FullName || item.User.Name)"> | |||
| <img class="avatar" :src="item.User.RelAvatarLink"> | |||
| <div class="name-c"> | |||
| {{ item.User.FullName || item.User.Name }} | |||
| </div> | |||
| </a> | |||
| </div> | |||
| <div class="item-r"> | |||
| <template v-if="item.ShowButton"> | |||
| <a class="op-btn" v-if="!item.Followed" href="javascript:;" @click="following(item, index, true)"> | |||
| {{ $t('repos.follow') }}</a> | |||
| <a class="op-btn" v-if="item.Followed" href="javascript:;" @click="following(item, index, false)"> | |||
| {{ $t('repos.unFollow') }}</a> | |||
| </template> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| import { getActiveUsers, followingUsers } from '~/apis/modules/repos'; | |||
| export default { | |||
| name: "ActiveUsers", | |||
| props: {}, | |||
| components: {}, | |||
| data() { | |||
| return { | |||
| list: [], | |||
| }; | |||
| }, | |||
| methods: { | |||
| following(userInfo, index, followingOrNot) { | |||
| followingUsers(userInfo.User.Name, followingOrNot).then(res => { | |||
| if (res.status == 204) { // 成功 | |||
| userInfo.Followed = !userInfo.Followed; | |||
| } else { | |||
| console.log(res); | |||
| } | |||
| }).catch(err => { | |||
| if (err.response.status == 401) { // 未登陆 | |||
| window.location.href = `/user/login?redirect_to=${encodeURIComponent(window.location.href)}`; | |||
| } else { | |||
| console.log(err); | |||
| } | |||
| }); | |||
| } | |||
| }, | |||
| mounted() { | |||
| getActiveUsers().then(res => { | |||
| res = res.data; | |||
| if (res.Code == 0) { | |||
| this.list = res.Data.Users || []; | |||
| } else { | |||
| this.list = []; | |||
| } | |||
| }).catch(err => { | |||
| console.log(err); | |||
| this.list = []; | |||
| }); | |||
| }, | |||
| }; | |||
| </script> | |||
| <style scoped lang="less"> | |||
| .title { | |||
| height: 43px; | |||
| border-color: rgba(16, 16, 16, 0.05); | |||
| border-width: 1px 0px; | |||
| border-style: solid; | |||
| display: flex; | |||
| align-items: center; | |||
| font-size: 18px; | |||
| color: rgba(47, 9, 69, 0.74); | |||
| background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(-1.007%2C%200.0010000000000001494%2C%20-0.000018400023883213844%2C%20-1.007%2C%201.003%2C%200.008)%22%3E%3Cstop%20stop-color%3D%22%23eee9da%22%20stop-opacity%3D%220%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23f3e7f7%22%20stop-opacity%3D%220.26%22%20offset%3D%220.29%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23d0e7ff%22%20stop-opacity%3D%220.3%22%20offset%3D%221%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E"); | |||
| } | |||
| .content { | |||
| padding: 6px 0; | |||
| } | |||
| .item { | |||
| display: flex; | |||
| align-items: center; | |||
| height: 48px; | |||
| padding: 0 8px; | |||
| font-size: 14px; | |||
| color: rgba(16, 16, 16, 1); | |||
| } | |||
| .item>div { | |||
| display: flex; | |||
| align-items: center; | |||
| } | |||
| .item-l { | |||
| flex: 1; | |||
| overflow: hidden; | |||
| a { | |||
| display: flex; | |||
| align-items: center; | |||
| overflow: hidden; | |||
| } | |||
| } | |||
| .item-r { | |||
| width: 80px; | |||
| justify-content: flex-end; | |||
| } | |||
| .item .avatar { | |||
| width: 32px; | |||
| height: 32px; | |||
| border-radius: 50%; | |||
| margin-right: 10px; | |||
| } | |||
| .item .name-c { | |||
| flex: 1; | |||
| overflow: hidden; | |||
| width: 100%; | |||
| text-overflow: ellipsis; | |||
| white-space: nowrap; | |||
| } | |||
| .item .name { | |||
| color: rgba(16, 16, 16, 1); | |||
| &:hover { | |||
| opacity: 0.8; | |||
| } | |||
| } | |||
| .item .op-btn { | |||
| border-color: rgb(50, 145, 248); | |||
| border-width: 1px; | |||
| border-style: solid; | |||
| color: rgb(50, 145, 248); | |||
| font-size: 12px; | |||
| padding: 0px 6px; | |||
| border-radius: 3px; | |||
| } | |||
| </style> | |||
| @@ -0,0 +1,151 @@ | |||
| <template> | |||
| <div class="repo-selected-bg"> | |||
| <div class="ui container _repo_container _repo-selected-container" style="padding-top:3rem;padding-bottom:3rem;"> | |||
| <div class="_repo_title"><span>{{ $t('repos.selectedFields') }}</span></div> | |||
| <div class="_repo-selected-list"> | |||
| <div class="swiper-wrapper" id="_repo-selected"></div> | |||
| <div class="swiper-pagination _repo-selected-swiper-pagination"></div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| import { getHomePageData } from '~/apis/modules/repos'; | |||
| import LetterAvatar from '~/utils/letteravatar'; | |||
| export default { | |||
| name: "RecommendRepos", | |||
| props: { | |||
| static: { type: Boolean, default: false }, | |||
| staticSwiperData: { type: Array, default: () => [] }, | |||
| }, | |||
| components: {}, | |||
| data() { | |||
| return { | |||
| swiperHandler: null, | |||
| }; | |||
| }, | |||
| methods: { | |||
| renderRepos(json) { | |||
| var selectedRepoEl = document.getElementById("_repo-selected"); | |||
| var html = ""; | |||
| if (json != null && json.length > 0) { | |||
| var repoMap = {}; | |||
| for (var i = 0, iLen = json.length; i < iLen; i++) { | |||
| var repo = json[i]; | |||
| var label = repo.Label; | |||
| if (repoMap[label]) { | |||
| repoMap[label].push(repo); | |||
| } else { | |||
| repoMap[label] = [repo]; | |||
| } | |||
| } | |||
| for (var label in repoMap) { | |||
| var repos = repoMap[label]; | |||
| var labelSearch = repos[0].Label; | |||
| html += `<div class="swiper-slide"><div><a style="color:rgb(50, 145, 248);font-size:16px;font-weight:550;" href="/explore/repos?q=&topic=${labelSearch}&sort=hot"># ${label}</a></div>`; | |||
| for (var i = 0, iLen = repos.length; i < iLen; i++) { | |||
| if (i >= 4) break; | |||
| var repo = repos[i]; | |||
| html += `<div class="ui fluid card"> | |||
| <div class="content"> | |||
| ${repo["Avatar"] ? `<img style="border-radius: 100%;" class="left floated mini ui image" src="${repo["Avatar"]}">` : `<img style="border-radius: 100%;" class="left floated mini ui image" avatar="${repo["OwnerName"]}">`} | |||
| <span class="header nowrap" style="color:rgb(50, 145, 248);font-size:14px;" href="javascript:;" title="${repo["Alias"]}">${repo["Alias"]}</span> | |||
| <div class="description nowrap-2" style="rgba(136,136,136,1);;font-size:12px;" title="${repo["Description"]}">${repo["Description"]}</div> | |||
| </div> | |||
| <a style="position:absolute;height:100%;width:100%;" href="/${repo["OwnerName"]}/${repo["Name"]}"></a> | |||
| </div>`; | |||
| } | |||
| html += '</div>' | |||
| } | |||
| this.swiperHandler = new Swiper("._repo-selected-list", { | |||
| slidesPerView: 1, | |||
| spaceBetween: 25, | |||
| pagination: { | |||
| el: "._repo-selected-swiper-pagination", | |||
| clickable: true, | |||
| }, | |||
| autoplay: { | |||
| delay: 4500, | |||
| disableOnInteraction: false, | |||
| }, | |||
| breakpoints: { | |||
| 768: { | |||
| slidesPerView: 3, | |||
| }, | |||
| 1024: { | |||
| slidesPerView: 4, | |||
| }, | |||
| 1200: { | |||
| slidesPerView: 4, | |||
| }, | |||
| 1600: { | |||
| slidesPerView: 4, | |||
| } | |||
| }, | |||
| }); | |||
| selectedRepoEl.innerHTML = html; | |||
| this.swiperHandler.updateSlides(); | |||
| this.swiperHandler.updateProgress(); | |||
| LetterAvatar.transform(); | |||
| } | |||
| } | |||
| }, | |||
| mounted() { | |||
| if (this.static) { | |||
| this.renderRepos(this.staticSwiperData); | |||
| } else { | |||
| getHomePageData().then(res => { | |||
| this.renderRepos(res.data.repo); | |||
| }).catch(err => { | |||
| console.log(err); | |||
| }); | |||
| } | |||
| }, | |||
| }; | |||
| </script> | |||
| <style scoped lang="less"> | |||
| .repo-selected-bg { | |||
| background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(0.11899999999999993%2C%201.217%2C%20-0.24039506172839506%2C%200.11899999999999993%2C%200.269%2C%20-0.22)%22%3E%3Cstop%20stop-color%3D%22%23ffffff%22%20stop-opacity%3D%220.47%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23e5e7eb%22%20stop-opacity%3D%220.3%22%20offset%3D%221%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E"); | |||
| } | |||
| ._repo_title { | |||
| font-size: 18px; | |||
| color: rgb(16, 16, 16); | |||
| text-align: center; | |||
| margin-bottom: 1em; | |||
| font-weight: bold; | |||
| } | |||
| ._repo-selected-list { | |||
| overflow: hidden; | |||
| padding: 1em 1em 3em 1em; | |||
| text-align: left; | |||
| position: relative; | |||
| } | |||
| /deep/._repo-selected-swiper-pagination .swiper-pagination-bullet { | |||
| width: 8px; | |||
| height: 8px; | |||
| border-radius: 100%; | |||
| background: #76cbed; | |||
| } | |||
| /deep/._repo-selected-swiper-pagination .swiper-pagination-bullet-active { | |||
| width: 40px; | |||
| border-radius: 4px; | |||
| } | |||
| /deep/ ._repo-selected-list .card { | |||
| border-radius: 6px; | |||
| background-color: #FFF; | |||
| box-shadow: 0px 5px 10px 0px rgba(105, 192, 255, .3); | |||
| border: 1px solid rgba(105, 192, 255, .4); | |||
| } | |||
| /deep/ ._repo-selected-list .header { | |||
| line-height: 40px !important; | |||
| } | |||
| </style> | |||
| @@ -0,0 +1,115 @@ | |||
| <template> | |||
| <div> | |||
| <div class="item" :class="(focusIndex == index) ? 'item-focus' : ''" v-for="(item, index) in list" :key="item.key"> | |||
| <a href="javascript:;" @click="changeFilters(item, index)">{{ item.label }}</a> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| export default { | |||
| name: "ReposFilters", | |||
| props: { | |||
| defaultsort: { type: String, default: 'mostpopular' }, | |||
| }, | |||
| components: {}, | |||
| data() { | |||
| return { | |||
| focusIndex: 0, | |||
| list: [{ | |||
| key: 'mostpopular', | |||
| label: this.$t('repos.mostPopular'), | |||
| }, { | |||
| key: 'mostactive', | |||
| label: this.$t('repos.mostActive'), | |||
| }, { | |||
| key: 'recentupdate', | |||
| label: this.$t('repos.recentlyUpdated'), | |||
| }, { | |||
| key: 'newest', | |||
| label: this.$t('repos.newest'), | |||
| }, { | |||
| key: 'moststars', | |||
| label: this.$t('repos.mostStars'), | |||
| }, { | |||
| key: 'mostforks', | |||
| label: this.$t('repos.mostForks'), | |||
| }, { | |||
| key: 'mostdatasets', | |||
| label: this.$t('repos.mostDatasets'), | |||
| }, { | |||
| key: 'mostaitasks', | |||
| label: this.$t('repos.mostAiTasks'), | |||
| }, { | |||
| key: 'mostmodels', | |||
| label: this.$t('repos.mostModels'), | |||
| }] | |||
| }; | |||
| }, | |||
| methods: { | |||
| changeFilters(item, index) { | |||
| this.focusIndex = index; | |||
| this.$emit('change', this.list[this.focusIndex]); | |||
| }, | |||
| setDefaultFilter(sort) { | |||
| const index = this.list.findIndex((item) => item.key == sort); | |||
| this.focusIndex = index >= 0 ? index : 0; | |||
| } | |||
| }, | |||
| mounted() { | |||
| }, | |||
| }; | |||
| </script> | |||
| <style scoped lang="less"> | |||
| .item { | |||
| height: 40px; | |||
| border-color: rgba(16, 16, 16, 0.05); | |||
| border-width: 0px 0px 1px; | |||
| border-style: solid; | |||
| color: rgba(16, 16, 16, 0.8); | |||
| font-size: 14px; | |||
| padding: 0px; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: flex-end; | |||
| position: relative; | |||
| } | |||
| .item a { | |||
| color: inherit; | |||
| height: 100%; | |||
| display: flex; | |||
| align-items: center; | |||
| &:hover { | |||
| opacity: 0.8; | |||
| } | |||
| } | |||
| .item-focus { | |||
| font-weight: bold; | |||
| border-color: rgba(0, 108, 205, 0.3); | |||
| color: rgb(50, 145, 248); | |||
| a { | |||
| cursor: default; | |||
| &:hover { | |||
| opacity: 1; | |||
| } | |||
| } | |||
| } | |||
| .item-focus:before { | |||
| content: ""; | |||
| position: absolute; | |||
| width: 7px; | |||
| height: 7px; | |||
| bottom: -4px; | |||
| background: rgb(178, 211, 240); | |||
| left: 0; | |||
| border-radius: 100%; | |||
| } | |||
| </style> | |||
| @@ -0,0 +1,316 @@ | |||
| <template> | |||
| <div> | |||
| <div class="item"> | |||
| <div class="item-top"> | |||
| <img v-if="data.RelAvatarLink" class="avatar" :src="data.RelAvatarLink" /> | |||
| <img v-else class="avatar" :avatar="data.OwnerName" /> | |||
| <div class="content"> | |||
| <div class="title"> | |||
| <div class="title-l"> | |||
| <a :href="`/${data.OwnerName}/${data.Name}`" :title="`${data.OwnerName}/${data.Name}`"> | |||
| <span class="title-1">{{ data.OwnerName }}</span> | |||
| <span class="title-1"> / </span> | |||
| <span class="title-2" v-html="data.NameShow"></span> | |||
| </a> | |||
| <i v-if="data.IsArchived" class="archive icon archived-icon"></i> | |||
| <svg v-if="data.IsFork" class="svg octicon-repo-forked" width="15" height="15" aria-hidden="true"> | |||
| <use xlink:href="#octicon-repo-forked"></use> | |||
| </svg> | |||
| <svg v-if="data.IsMirror" class="svg octicon-repo-clone" width="15" height="15" aria-hidden="true"> | |||
| <use xlink:href="#octicon-repo-clone"></use> | |||
| </svg> | |||
| <svg v-if="(data.IsPrivate || data.IsOwnerPrivate)" style="color:#a1882b!important" class="svg octicon-lock" width="15" height="15" | |||
| aria-hidden="true"> | |||
| <use xlink:href="#octicon-lock"></use> | |||
| </svg> | |||
| </div> | |||
| <span class="title-r"> | |||
| <span class="t-item" :title="$t('repos.watch')"> | |||
| <i class="ri-eye-line"></i> | |||
| <span>{{ data.NumWatches }}</span> | |||
| </span> | |||
| <span class="t-item" :title="$t('repos.star')"> | |||
| <i class="ri-star-line"></i> | |||
| <span>{{ data.NumStars }}</span> | |||
| </span> | |||
| <span class="t-item" :title="$t('repos.fork')"> | |||
| <svg class="svg octicon-repo-forked" width="13" height="13" aria-hidden="true"> | |||
| <use xlink:href="#octicon-repo-forked"></use> | |||
| </svg> | |||
| <span>{{ data.NumForks }}</span></span> | |||
| </span> | |||
| </div> | |||
| <div class="descr" v-show="data.DescriptionShow" v-html="data.DescriptionShow"></div> | |||
| <div class="tags" v-show="data.Topics && data.Topics.length"> | |||
| <a v-for="(item, index) in data.TopicsShow" :key="index" class="tag" | |||
| :class="(item.topic.toLocaleLowerCase() == topic.toLocaleLowerCase() ? 'tag-focus' : '')" | |||
| :href="`/explore/repos?q=&topic=${item.topic}&sort=hot`" v-html="item.topicShow"></a> | |||
| </div> | |||
| <div class="repo-datas" v-show="(data.DatasetCnt > 0) || (data.ModelCnt > 0) || (data.AiTaskCnt > 0)"> | |||
| <span class="repo-datas-item" v-show="(data.DatasetCnt > 0)"> | |||
| <i class="ri-stack-line"></i> | |||
| <span class="label">{{ $t('repos.dataset') }}:</span> | |||
| <span class="value">{{ data.DatasetCnt }}</span> | |||
| </span> | |||
| <span class="repo-datas-item" v-show="(data.ModelCnt > 0)"> | |||
| <i class="ri-send-plane-2-line"></i> | |||
| <span class="label">{{ $t('repos.model') }}:</span> | |||
| <span class="value">{{ data.ModelCnt }}</span> | |||
| </span> | |||
| <span class="repo-datas-item" v-show="(data.AiTaskCnt > 0)"> | |||
| <i class="ri-order-play-line"></i> | |||
| <span class="label">{{ $t('repos.aiTask') }}:</span> | |||
| <span class="value">{{ data.AiTaskCnt }}</span> | |||
| </span> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="item-bottom"> | |||
| <div> | |||
| <span>{{ $t('repos.updated') }}</span> | |||
| <el-tooltip effect="dark" :content="dateFormat(data.UpdatedUnix)" placement="top-start"> | |||
| <span>{{ calcFromNow(data.UpdatedUnix) }}</span> | |||
| </el-tooltip> | |||
| <span style="margin-left:8px;" v-if="data.PrimaryLanguage"><i class="color-icon" | |||
| :style="{ backgroundColor: data.PrimaryLanguage.Color }"></i>{{ data.PrimaryLanguage.Language }}</span> | |||
| </div> | |||
| <div class="contributors"> | |||
| <span class="contributors-count" v-show="data.Contributors && data.Contributors.length"> | |||
| {{ $t('repos.contributors') }} | |||
| </span> | |||
| <span class="contributors-avatar"> | |||
| <a :href="item.UserName ? `/${item.UserName}` : `mailto:${item.Email}`" class="avatar-c" | |||
| v-for="(item, index) in data.Contributors" :key="index"> | |||
| <img class="avatar" v-show="item.UserName" :src="item.RelAvatarLink"> | |||
| <span class="avatar" v-show="!item.UserName" :style="{ backgroundColor: item.bgColor }"> | |||
| {{ item.Email[0].toLocaleUpperCase() }}</span> | |||
| </a> | |||
| </span> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| import relativeTime from 'dayjs/plugin/relativeTime'; | |||
| import localizedFormat from 'dayjs/plugin/localizedFormat'; | |||
| import 'dayjs/locale/zh-cn'; | |||
| import 'dayjs/locale/en'; | |||
| import dayjs from 'dayjs'; | |||
| import { lang } from '~/langs'; | |||
| import { timeSinceUnix } from '~/utils'; | |||
| dayjs.locale(lang == 'zh-CN' ? 'zh-cn' : 'en'); | |||
| dayjs.extend(relativeTime); | |||
| dayjs.extend(localizedFormat); | |||
| export default { | |||
| name: "ReposItem", | |||
| props: { | |||
| data: { type: Object, default: () => ({}) }, | |||
| topic: { type: String, default: '' } | |||
| }, | |||
| components: {}, | |||
| data() { | |||
| return { | |||
| contributors: [], | |||
| }; | |||
| }, | |||
| methods: { | |||
| calcFromNow(unix) { | |||
| // return dayjs(unix * 1000).fromNow(); | |||
| return timeSinceUnix(unix, Date.now() / 1000); | |||
| }, | |||
| dateFormat(unix) { | |||
| return lang == 'zh-CN' ? dayjs(unix * 1000).format('YYYY年MM月DD日 HH时mm分ss秒') : | |||
| dayjs(unix * 1000).format('ddd, D MMM YYYY HH:mm:ss [CST]'); | |||
| } | |||
| }, | |||
| mounted() { }, | |||
| }; | |||
| </script> | |||
| <style scoped lang="less"> | |||
| .item { | |||
| width: 100%; | |||
| border-color: rgba(157, 197, 226, 0.4); | |||
| border-width: 1px; | |||
| border-style: solid; | |||
| box-shadow: rgb(157 197 226 / 20%) 0px 5px 10px 0px; | |||
| border-radius: 15px; | |||
| font-size: 14px; | |||
| padding: 20px 26px 10px 26px; | |||
| margin-bottom: 40px; | |||
| } | |||
| .item-top { | |||
| display: flex; | |||
| } | |||
| .item-top .avatar { | |||
| width: 38px; | |||
| height: 38px; | |||
| margin-right: 10px; | |||
| border-radius: 100%; | |||
| } | |||
| .content { | |||
| flex: 1; | |||
| overflow: hidden; | |||
| } | |||
| .content .title { | |||
| display: flex; | |||
| align-items: center; | |||
| height: 30px; | |||
| margin: 4px 0 8px; | |||
| } | |||
| .content .title-l { | |||
| flex: 1; | |||
| overflow: hidden; | |||
| width: 100%; | |||
| text-overflow: ellipsis; | |||
| white-space: nowrap; | |||
| } | |||
| .content .title-1 { | |||
| font-size: 18px; | |||
| color: rgba(16, 16, 16, 0.6); | |||
| } | |||
| .content .title-2 { | |||
| font-size: 18px; | |||
| color: rgba(16, 16, 16, 1); | |||
| font-weight: bold; | |||
| margin-right: 3px; | |||
| } | |||
| .content .title-r { | |||
| display: flex; | |||
| align-items: center; | |||
| font-weight: 400; | |||
| font-size: 12px; | |||
| color: rgba(26, 40, 51, 1); | |||
| justify-content: flex-end; | |||
| } | |||
| .content .t-item { | |||
| margin-left: 12px; | |||
| display: flex; | |||
| align-items: center; | |||
| } | |||
| .content .t-item i { | |||
| margin-right: 4px; | |||
| } | |||
| .content .descr { | |||
| font-weight: 300; | |||
| font-size: 14px; | |||
| color: rgba(16, 16, 16, 0.8); | |||
| margin-bottom: 16px; | |||
| overflow: hidden; | |||
| text-overflow: ellipsis; | |||
| word-break: break-all; | |||
| display: -webkit-box; | |||
| -webkit-box-orient: vertical; | |||
| -webkit-line-clamp: 6; | |||
| max-height: 120px; | |||
| white-space: break-spaces; | |||
| } | |||
| .content .tags { | |||
| margin-bottom: 16px; | |||
| overflow: hidden; | |||
| text-overflow: ellipsis; | |||
| white-space: nowrap; | |||
| } | |||
| .content .tag { | |||
| color: rgba(16, 16, 16, 0.8); | |||
| border-radius: 4px; | |||
| font-size: 14px; | |||
| background: rgba(232, 232, 232, 0.6); | |||
| padding: 2px 6px; | |||
| margin-right: 8px; | |||
| &.tag-focus { | |||
| color: red; | |||
| } | |||
| } | |||
| .content .repo-datas { | |||
| display: flex; | |||
| align-items: center; | |||
| margin-top: 20px; | |||
| margin-bottom: 10px; | |||
| } | |||
| .content .repo-datas-item { | |||
| display: flex; | |||
| align-items: center; | |||
| margin-right: 24px; | |||
| } | |||
| .content .repo-datas-item i { | |||
| color: rgba(2, 107, 251, 0.54); | |||
| margin-right: 4px; | |||
| font-size: 16px; | |||
| } | |||
| .content .repo-datas-item .label { | |||
| color: rgba(2, 107, 251, 0.54); | |||
| margin-right: 4px; | |||
| } | |||
| .content .repo-datas-item .value { | |||
| font-weight: bold; | |||
| } | |||
| .item-bottom { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: space-between; | |||
| border-top: 1px solid rgba(157, 197, 226, 0.2); | |||
| margin-top: 10px; | |||
| padding-top: 10px; | |||
| font-size: 12px; | |||
| color: rgba(16, 16, 16, 0.6); | |||
| } | |||
| .item-bottom .contributors { | |||
| display: flex; | |||
| align-items: center; | |||
| } | |||
| .item-bottom .contributors-avatar { | |||
| display: flex; | |||
| align-items: center; | |||
| margin-left: 16px; | |||
| .avatar-c { | |||
| img[src=""], | |||
| img:not([src]) { | |||
| // opacity: 0; | |||
| } | |||
| } | |||
| } | |||
| .item-bottom .avatar { | |||
| display: block; | |||
| width: 25px; | |||
| height: 25px; | |||
| margin-left: -6px; | |||
| border-radius: 100%; | |||
| border: 1px solid white; | |||
| font-size: 16px; | |||
| line-height: 24px; | |||
| text-align: center; | |||
| color: white; | |||
| background-color: white; | |||
| font-weight: bold; | |||
| } | |||
| </style> | |||
| @@ -0,0 +1,161 @@ | |||
| <template> | |||
| <div class="list-container"> | |||
| <div style="min-height:540px;" v-loading="loading"> | |||
| <div class="repos-item-container" v-for="(item, index) in list" :key="item.ID"> | |||
| <ReposItem :data="item" :topic="topic"></ReposItem> | |||
| </div> | |||
| <div v-show="(!list.length && !loading)" class="repos-no-data">{{ $t('repos.noReposfound') }}</div> | |||
| </div> | |||
| <div class="center"> | |||
| <el-pagination ref="paginationRef" background @current-change="currentChange" @size-change="sizeChange" | |||
| :current-page.sync="iPage" :page-sizes="iPageSizes" :page-size.sync="iPageSize" | |||
| layout="total, sizes, prev, pager, next, jumper" :total="total"> | |||
| </el-pagination> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| import ReposItem from '../components/ReposItem.vue'; | |||
| import { getReposListData } from '~/apis/modules/repos'; | |||
| import LetterAvatar from '~/utils/letteravatar'; | |||
| export default { | |||
| name: "ReposList", | |||
| props: { | |||
| q: { type: String, default: '' }, | |||
| sort: { type: String, default: 'mostpopular' }, | |||
| topic: { type: String, default: '' }, | |||
| page: { type: Number, default: 1 }, | |||
| pageSize: { type: Number, default: 15 }, | |||
| pageSizes: { type: Array, default: () => [15, 30, 50] } | |||
| }, | |||
| components: { ReposItem }, | |||
| data() { | |||
| return { | |||
| loading: false, | |||
| list: [], | |||
| iPageSizes: [15, 30, 50], | |||
| iPageSize: 15, | |||
| iPage: 1, | |||
| total: 0, | |||
| }; | |||
| }, | |||
| methods: { | |||
| getListData() { | |||
| this.loading = true; | |||
| getReposListData({ | |||
| q: this.q || '', | |||
| topic: this.topic || '', | |||
| sort: this.sort || 'mostpopular', | |||
| pageSize: this.iPageSize || 15, | |||
| page: this.iPage || 1, | |||
| }).then(res => { | |||
| res = res.data; | |||
| this.loading = false; | |||
| if (res.Code == 0) { | |||
| const list = res.Data.Repos || []; | |||
| this.list = list.map((item) => { | |||
| item.Contributors = (item.Contributors || []).map((_item) => { | |||
| return { | |||
| ..._item, | |||
| bgColor: this.randomColor(_item.Email[0].toLocaleUpperCase()), | |||
| } | |||
| }); | |||
| const contributors = item.Contributors || []; | |||
| return { | |||
| ...item, | |||
| NameShow: this.handlerSearchStr(item.Alias, this.q), | |||
| DescriptionShow: this.handlerSearchStr(item.Description, this.q), | |||
| TopicsShow: (item.Topics || []).map((_item) => { | |||
| return { | |||
| topic: _item, | |||
| topicShow: this.handlerSearchStr(_item, this.q) | |||
| } | |||
| }), | |||
| } | |||
| }); | |||
| this.total = res.Data.Total; | |||
| this.iPage = this.iPage; | |||
| this.iPageSize = this.iPageSize; | |||
| this.$nextTick(() => { | |||
| LetterAvatar.transform(); | |||
| }); | |||
| } else { | |||
| this.list = []; | |||
| this.total = 0; | |||
| this.iPage = this.iPage; | |||
| this.iPageSize = this.iPageSize; | |||
| } | |||
| }).catch(err => { | |||
| console.log(err); | |||
| this.loading = false; | |||
| this.list = []; | |||
| this.total = 0; | |||
| this.iPage = this.iPage; | |||
| this.iPageSize = this.iPageSize; | |||
| }); | |||
| }, | |||
| search() { | |||
| this.getListData(); | |||
| }, | |||
| currentChange(page) { | |||
| this.iPage = page; | |||
| this.$emit('current-change', { | |||
| page: this.iPage, | |||
| pageSize: this.iPageSize, | |||
| }); | |||
| }, | |||
| sizeChange(pageSize) { | |||
| this.iPageSize = pageSize; | |||
| this.$emit('size-change', { | |||
| page: this.iPage, | |||
| pageSize: this.iPageSize, | |||
| }); | |||
| }, | |||
| handlerSearchStr(oStr, searchKey) { | |||
| if (!searchKey) return oStr; | |||
| return oStr.replace(new RegExp(`(${searchKey})`, 'ig'), `<font color="red">$1</font>`); | |||
| }, | |||
| randomColor(t) { | |||
| const tIndex = t.charCodeAt(0); | |||
| const colorList = ["#1abc9c", "#2ecc71", "#3498db", "#9b59b6", "#34495e", "#16a085", "#27ae60", "#2980b9", "#8e44ad", | |||
| "#2c3e50", "#f1c40f", "#e67e22", "#e74c3c", "#00bcd4", "#95a5a6", "#f39c12", "#d35400", "#c0392b", "#bdc3c7", "#7f8c8d"]; | |||
| return colorList[tIndex % colorList.length]; | |||
| } | |||
| }, | |||
| watch: { | |||
| page: { | |||
| handler(val) { | |||
| this.iPage = val; | |||
| }, | |||
| immediate: true, | |||
| }, | |||
| pageSize: { | |||
| handler(val) { | |||
| this.iPageSize = val; | |||
| }, | |||
| immediate: true, | |||
| } | |||
| }, | |||
| mounted() { }, | |||
| }; | |||
| </script> | |||
| <style scoped lang="less"> | |||
| .list-container { | |||
| margin-left: 12px; | |||
| margin-right: 12px; | |||
| } | |||
| .center { | |||
| text-align: center; | |||
| } | |||
| .repos-no-data { | |||
| height: 60px; | |||
| display: flex; | |||
| justify-content: center; | |||
| align-items: center; | |||
| } | |||
| </style> | |||
| @@ -0,0 +1,255 @@ | |||
| <template> | |||
| <div> | |||
| <div class="_repo_search"> | |||
| <div class="_repo_search_input_c"> | |||
| <div class="_repo_search_input"> | |||
| <input type="text" v-model="searchInputValue" :placeholder="$t('repos.searchRepositories')" autocomplete="off" | |||
| @keyup.enter="search" /> | |||
| </div> | |||
| <div class="_repo_search_btn" @click="search"> | |||
| <svg xmlns="http://www.w3.org/2000/svg" | |||
| class="styles__StyledSVGIconPathComponent-sc-16fsqc8-0 kdvdTY svg-icon-path-icon fill" viewBox="0 0 32 32" | |||
| width="24" height="24"> | |||
| <defs data-reactroot=""> | |||
| <linearGradient id="ilac9fwnydq3lcx1,1,rs,1,f0dwf0xj,ezu9f0dw,f000,00lwrsktrs,rs1bhv8urs" x1="0" x2="100%" | |||
| y1="0" y2="0" | |||
| gradientTransform="matrix(-0.7069999999999999, -0.707, 0.707, -0.7069999999999999, 16, 38.624)" | |||
| gradientUnits="userSpaceOnUse"> | |||
| <stop stop-color="#c9ffbf" stop-opacity="1" offset="0"></stop> | |||
| <stop stop-color="#0ca451" stop-opacity="1" offset="1"></stop> | |||
| </linearGradient> | |||
| </defs> | |||
| <g> | |||
| <path fill="url(#ilac9fwnydq3lcx1,1,rs,1,f0dwf0xj,ezu9f0dw,f000,00lwrsktrs,rs1bhv8urs)" | |||
| d="M14.667 2.667c6.624 0 12 5.376 12 12s-5.376 12-12 12-12-5.376-12-12 5.376-12 12-12zM14.667 24c5.156 0 9.333-4.177 9.333-9.333 0-5.157-4.177-9.333-9.333-9.333-5.157 0-9.333 4.176-9.333 9.333 0 5.156 4.176 9.333 9.333 9.333zM25.98 24.095l3.772 3.771-1.887 1.887-3.771-3.772 1.885-1.885z"> | |||
| </path> | |||
| </g> | |||
| </svg> | |||
| <span style="margin-left:10px;">{{ $t('repos.search') }}</span> | |||
| </div> | |||
| </div> | |||
| <div class="_repo_search_label_c"> | |||
| <div class="_repo_search_label"> | |||
| <a v-if="type == 'square'" :href="`/explore/repos?q=${searchInputValue.trim()}&topic=${item.v}&sort=${sort}`" | |||
| :style="{ backgroundColor: topicColors[index % topicColors.length] }" v-for="(item, index) in topics" | |||
| :key="index">{{ item.v }}</a> | |||
| <a v-if="type == 'search'" href="javascript:;" @click="changeTopic({ k: '', v: '' })" | |||
| style="font-weight:bold;" | |||
| :style="{ backgroundColor: selectTopic == '' ? selectedColor : defaultColor, color: selectTopic == '' ? 'white' : '#40485b' }">{{ | |||
| $t('repos.allFields') | |||
| }}</a> | |||
| <a v-if="type == 'search'" href="javascript:;" @click="changeTopic(item)" | |||
| :style="{ backgroundColor: selectTopic.toLocaleLowerCase() == item.k ? selectedColor : defaultColor, color: selectTopic.toLocaleLowerCase() == item.k ? 'white' : '#40485b' }" | |||
| v-for="(item, index) in topics" :key="index">{{ item.v }}</a> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| import { getPromoteData } from '~/apis/modules/common'; | |||
| const COLOR_LIST = [ | |||
| 'rgb(255, 104, 104)', | |||
| 'rgb(22, 132, 252)', | |||
| 'rgb(2, 202, 253)', | |||
| 'rgb(164, 145, 215)', | |||
| 'rgb(232, 64, 247)', | |||
| 'rgb(245, 182, 110)', | |||
| 'rgb(54, 187, 166)', | |||
| 'rgb(123, 50, 178)' | |||
| ]; | |||
| export default { | |||
| name: "SearchBar", | |||
| props: { | |||
| type: { type: String, default: 'square' }, // square|search | |||
| searchValue: { type: String, default: '' }, | |||
| topic: { type: String, default: '' }, | |||
| sort: { type: String, default: '' }, | |||
| static: { type: Boolean, default: false }, | |||
| staticTopicsData: { type: String, default: '[]' }, | |||
| }, | |||
| components: {}, | |||
| data() { | |||
| return { | |||
| searchInputValue: '', | |||
| topicColors: COLOR_LIST, | |||
| defaultColor: '#F6F6F6', | |||
| selectedColor: '', | |||
| selectTopic: '', | |||
| topicOri: [], | |||
| topics: [], | |||
| }; | |||
| }, | |||
| methods: { | |||
| setDefaultSearch(params) { | |||
| this.searchInputValue = params.q || ''; | |||
| this.selectTopic = params.topic || ''; | |||
| this.selectTopic && this.changeTopic({ | |||
| k: this.selectTopic.toLocaleLowerCase(), | |||
| v: this.selectTopic, | |||
| }, true); | |||
| }, | |||
| changeTopic(topicItem, noSearch) { | |||
| const index_ori = this.topicOri.findIndex((item) => { | |||
| return item.k == this.selectTopic.toLocaleLowerCase(); | |||
| }); | |||
| if (index_ori < 0 && this.selectTopic) { | |||
| const index = this.topics.findIndex((item) => { | |||
| return item.k == this.selectTopic.toLocaleLowerCase(); | |||
| }); | |||
| if (index > -1) { | |||
| this.topics.splice(index, 1); | |||
| } | |||
| } | |||
| this.selectTopic = topicItem.v; | |||
| if (this.selectTopic && this.topics.indexOf(this.selectTopic) < 0) { | |||
| const index = this.topics.findIndex(item => { | |||
| return item.k == this.selectTopic.toLocaleLowerCase(); | |||
| }) | |||
| if (index < 0) { | |||
| this.topics.push({ | |||
| k: this.selectTopic.toLocaleLowerCase(), | |||
| v: this.selectTopic, | |||
| }); | |||
| } | |||
| } | |||
| !noSearch && this.search(); | |||
| }, | |||
| handlerTopicsData(data) { | |||
| try { | |||
| const topicsData = JSON.parse(data); | |||
| const topics = topicsData.map((item) => { | |||
| return { | |||
| k: item.trim().toLocaleLowerCase(), | |||
| v: item.trim(), | |||
| } | |||
| }); | |||
| this.topicOri = JSON.parse(JSON.stringify(topics)); | |||
| this.topics = topics; | |||
| const selectTopic_key = this.selectTopic.toLocaleLowerCase(); | |||
| if (selectTopic_key) { | |||
| const index = this.topics.findIndex((item) => { | |||
| return item.k == selectTopic_key; | |||
| }); | |||
| if (index < 0) { | |||
| this.topics.push({ | |||
| k: this.selectTopic.toLocaleLowerCase(), | |||
| v: this.selectTopic, | |||
| }); | |||
| } | |||
| } | |||
| } catch (err) { | |||
| console.log(err); | |||
| } | |||
| }, | |||
| search() { | |||
| this.searchInputValue = this.searchInputValue.trim(); | |||
| if (this.type == 'square') { | |||
| window.location.href = `/explore/repos?q=${this.searchInputValue}&sort=${this.sort}&topic=${this.selectTopic}`; | |||
| } else { | |||
| this.$emit('change', { | |||
| q: this.searchInputValue, | |||
| topic: this.selectTopic, | |||
| }); | |||
| } | |||
| } | |||
| }, | |||
| mounted() { | |||
| if (this.static) { | |||
| try { | |||
| this.handlerTopicsData(this.staticTopicsData); | |||
| } catch (err) { | |||
| console.log(err); | |||
| } | |||
| } else { | |||
| getPromoteData('/repos/recommend_topics').then(res => { | |||
| const data = res.data; | |||
| this.handlerTopicsData(data); | |||
| }).catch(err => { | |||
| console.log(err); | |||
| this.handlerTopicsData('[]'); | |||
| }); | |||
| } | |||
| }, | |||
| }; | |||
| </script> | |||
| <style scoped lang="less"> | |||
| ._repo_search { | |||
| margin: 54px 0; | |||
| } | |||
| ._repo_search_input_c { | |||
| margin: 0 0 35px; | |||
| display: flex; | |||
| justify-content: center; | |||
| align-items: center; | |||
| } | |||
| ._repo_search_input { | |||
| display: flex; | |||
| align-items: center; | |||
| width: 437px; | |||
| height: 41px; | |||
| border-color: rgba(47, 9, 69, 0.64); | |||
| border-width: 1px; | |||
| border-style: solid; | |||
| color: rgba(16, 16, 16, 0.5); | |||
| border-radius: 20px; | |||
| font-size: 14px; | |||
| padding: 20px; | |||
| text-align: left; | |||
| line-height: 20px; | |||
| background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(6.123233995736765e-17%2C%20-0.9999999999999999%2C%200.008802475794500678%2C%206.123233995736765e-17%2C%201%2C%201.003)%22%3E%3Cstop%20stop-color%3D%22%23eeeade%22%20stop-opacity%3D%220.2%22%20offset%3D%220.76%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23ece0e9%22%20stop-opacity%3D%221%22%20offset%3D%221%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E"); | |||
| } | |||
| ._repo_search_input input { | |||
| width: 100%; | |||
| height: 30px; | |||
| border: none; | |||
| background: transparent; | |||
| outline: none; | |||
| } | |||
| ._repo_search_btn { | |||
| margin-left: 10px; | |||
| width: 110px; | |||
| height: 40px; | |||
| border-style: none; | |||
| border-color: unset; | |||
| color: rgb(255, 255, 255); | |||
| border-radius: 21px; | |||
| font-size: 14px; | |||
| text-align: center; | |||
| font-weight: normal; | |||
| font-style: normal; | |||
| background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(6.123233995736766e-17%2C%201%2C%20-0.17728531855955676%2C%206.123233995736766e-17%2C%201%2C%200)%22%3E%3Cstop%20stop-color%3D%22%232f0945%22%20stop-opacity%3D%221%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23341a7b%22%20stop-opacity%3D%221%22%20offset%3D%221%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E"); | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| cursor: pointer; | |||
| } | |||
| ._repo_search_label_c { | |||
| display: flex; | |||
| justify-content: center; | |||
| } | |||
| ._repo_search_label { | |||
| display: flex; | |||
| justify-content: center; | |||
| flex-wrap: wrap; | |||
| max-width: 1200px; | |||
| } | |||
| ._repo_search_label a { | |||
| background-color: rgb(2, 202, 253); | |||
| color: rgb(255, 255, 255); | |||
| border-radius: 5px; | |||
| margin: 0 5px 10px 5px; | |||
| padding: 5px 10px; | |||
| cursor: pointer; | |||
| font-size: 12px; | |||
| } | |||
| </style> | |||
| @@ -0,0 +1,363 @@ | |||
| <template> | |||
| <div class="ui _repo_container_bg"> | |||
| <div class="ui container _repo_container _content_container"> | |||
| <div class="_repo_top_left"> | |||
| <a :href="bannerData[0] ? bannerData[0].url : ''"> | |||
| <div class="_repo_top_left_img"> | |||
| <img :src="bannerData[0] ? bannerData[0].src : ''"> | |||
| </div> | |||
| </a> | |||
| </div> | |||
| <div class="_repo_top_middle"> | |||
| <div class="_repo_top_middle_header"> | |||
| <div class="_repo_top_mid_item" :class="(tabIndex == index) ? '_foucs' : ''" v-for="(item, index) in tabs" | |||
| :key="index" @click="changeTab(item, index)"> | |||
| <i :class="item.icon"></i> | |||
| <span>{{ item.label }}</span> | |||
| </div> | |||
| </div> | |||
| <div class="_repo_top_middle_content"> | |||
| <div class="_repo_top_mid_repo_list"> | |||
| <div class="swiper-wrapper" id="_repo_top_mid_repo"></div> | |||
| <div class="swiper-pagination _repo_top-swiper-pagination"></div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="_repo_top_right"> | |||
| <a :href="bannerData[1] ? bannerData[1].url : ''"> | |||
| <div class="_repo_top_right_img"> | |||
| <img :src="bannerData[1] ? bannerData[1].src : ''"> | |||
| </div> | |||
| </a> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| import { getPromoteData } from '~/apis/modules/common'; | |||
| import { getReposSquareTabData } from '~/apis/modules/repos'; | |||
| import LetterAvatar from '~/utils/letteravatar'; | |||
| export default { | |||
| name: "SquareTop", | |||
| props: { | |||
| static: { type: Boolean, default: false }, | |||
| staticSwiperData: { type: Array, default: () => [] }, | |||
| staticBannerData: { type: String, default: '[]' }, | |||
| }, | |||
| components: {}, | |||
| data() { | |||
| return { | |||
| swiperHandler: null, | |||
| tabIndex: 0, | |||
| tabs: [{ | |||
| key: 'preferred', | |||
| icon: 'ri-fire-line', | |||
| label: this.$t('repos.preferred'), | |||
| }, { | |||
| key: 'incubation', | |||
| icon: 'ri-award-line', | |||
| label: this.$t('repos.openIIncubation'), | |||
| }, { | |||
| key: 'hot-paper', | |||
| icon: 'ri-file-damage-line', | |||
| label: this.$t('repos.hotPapers'), | |||
| }], | |||
| bannerData: [], | |||
| }; | |||
| }, | |||
| methods: { | |||
| initSwiper() { | |||
| this.swiperHandler = new Swiper("._repo_top_mid_repo_list", { | |||
| slidesPerView: 1, | |||
| spaceBetween: 20, | |||
| pagination: { | |||
| el: "._repo_top-swiper-pagination", | |||
| clickable: true, | |||
| }, | |||
| autoplay: { | |||
| delay: 4500, | |||
| disableOnInteraction: false, | |||
| }, | |||
| breakpoints: { | |||
| 768: { | |||
| slidesPerView: 2, | |||
| }, | |||
| 1024: { | |||
| slidesPerView: 2, | |||
| }, | |||
| 1200: { | |||
| slidesPerView: 3, | |||
| }, | |||
| }, | |||
| }); | |||
| }, | |||
| renderSwiper(data) { | |||
| const swiperEl = document.getElementById("_repo_top_mid_repo"); | |||
| let html = ''; | |||
| const width = swiperEl.parentNode.clientWidth; | |||
| for (let i = 0, iLen = data.length; i < iLen; i++) { | |||
| html += `<div class="swiper-slide">`; | |||
| for (let j = i; j < i + 2; j++) { | |||
| let dataJ = data[j]; | |||
| if (dataJ === undefined) break; | |||
| html += `<div class="_repo_sw_card"><div style="display:flex;"> | |||
| ${dataJ["RelAvatarLink"] ? `<img style="border-radius:100%;width:35px;height:35px;margin-bottom:0.6em;" class="left floated mini ui image" src="${dataJ["RelAvatarLink"]}">` | |||
| : `<img style="border-radius:100%;width:35px;height:35px;margin-bottom:0.6em;" class="left floated mini ui image" avatar="${dataJ["OwnerName"]}">`} | |||
| <span class="header nowrap" style="color:rgb(50, 145, 248);font-size:14px;height:35px;line-height:35px;" href="javascript:;" title="${dataJ["Alias"]}">${dataJ["Alias"]}</span> | |||
| </div><div class="_repo_sw_card_descr _repo_nowrap_line_3" title="${dataJ.Description}">${dataJ.Description}</div> | |||
| <a href="/${dataJ.OwnerName}/${dataJ.Name}" style="position:absolute;width:100%;height:100%;top:0;left:0;"></a> | |||
| </div>`; | |||
| } | |||
| html += `</div>`; | |||
| i++; | |||
| } | |||
| this.swiperHandler.removeAllSlides(); | |||
| swiperEl.innerHTML = html; | |||
| this.swiperHandler.updateSlides(); | |||
| this.swiperHandler.updateProgress(); | |||
| LetterAvatar.transform(); | |||
| }, | |||
| getBannerData() { | |||
| getPromoteData('/repos/square_banner').then(res => { | |||
| const data = res.data; | |||
| try { | |||
| const list = JSON.parse(data); | |||
| this.bannerData = list; | |||
| } catch (err) { | |||
| console.log(err); | |||
| } | |||
| }).catch(err => { | |||
| this.bannerData = []; | |||
| console.log(err); | |||
| }); | |||
| }, | |||
| getTabData() { | |||
| getReposSquareTabData(this.tabs[this.tabIndex].key).then(res => { | |||
| res = res.data; | |||
| if (res.Code == 0) { | |||
| const data = res.Data.Repos || []; | |||
| this.renderSwiper(data); | |||
| } else { | |||
| this.renderSwiper([]); | |||
| } | |||
| }).catch(err => { | |||
| console.log(err); | |||
| this.renderSwiper([]); | |||
| }); | |||
| }, | |||
| changeTab(item, index) { | |||
| this.tabIndex = index; | |||
| this.getTabData(); | |||
| }, | |||
| }, | |||
| mounted() { | |||
| this.initSwiper(); | |||
| if (this.static) { | |||
| try { | |||
| this.bannerData = JSON.parse(this.staticBannerData); | |||
| } catch (err) { | |||
| console.log(err); | |||
| } | |||
| this.renderSwiper(this.staticSwiperData); | |||
| } else { | |||
| this.getBannerData(); | |||
| this.getTabData(); | |||
| } | |||
| }, | |||
| }; | |||
| </script> | |||
| <style scoped lang="less"> | |||
| ._repo_container_bg { | |||
| width: 100%; | |||
| height: 450px; | |||
| background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(0.4999999999999999%2C%20-0.8660000000000001%2C%200.07722000385802469%2C%200.4999999999999999%2C%20-0.183%2C%200.683)%22%3E%3Cstop%20stop-color%3D%22%239aceec%22%20stop-opacity%3D%221%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23eeeeee%22%20stop-opacity%3D%221%22%20offset%3D%220.99%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E"); | |||
| position: relative; | |||
| } | |||
| ._content_container { | |||
| display: flex !important; | |||
| margin: 0 auto !important; | |||
| height: 100% !important; | |||
| } | |||
| ._repo_top_left { | |||
| width: 243px; | |||
| height: 100%; | |||
| position: relative; | |||
| } | |||
| ._repo_top_left_img { | |||
| width: 100%; | |||
| height: 346px; | |||
| position: absolute; | |||
| bottom: 0; | |||
| border-top-left-radius: 10px; | |||
| overflow: hidden; | |||
| } | |||
| ._repo_top_left_img img { | |||
| height: 100%; | |||
| width: 100%; | |||
| } | |||
| ._content_container img[src=""], | |||
| img:not([src]) { | |||
| opacity: 0; | |||
| } | |||
| ._repo_top_right { | |||
| width: 243px; | |||
| height: 100%; | |||
| position: relative; | |||
| } | |||
| ._repo_top_right_img { | |||
| width: 100%; | |||
| height: 346px; | |||
| position: absolute; | |||
| bottom: 0; | |||
| border-top-right-radius: 10px; | |||
| overflow: hidden; | |||
| } | |||
| ._repo_top_right_img img { | |||
| height: 100%; | |||
| width: 100%; | |||
| } | |||
| ._repo_top_middle { | |||
| flex: 1; | |||
| height: 100%; | |||
| position: relative; | |||
| } | |||
| ._repo_top_middle_header { | |||
| width: 100%; | |||
| height: 42px; | |||
| position: absolute; | |||
| bottom: 346px; | |||
| display: flex; | |||
| align-items: center; | |||
| background: raba(0, 0, 255, 0.3); | |||
| padding: 0 20px; | |||
| } | |||
| ._repo_top_mid_item { | |||
| padding: 0px 16px; | |||
| font-size: 18px; | |||
| color: rgba(47, 9, 69, 0.64); | |||
| height: 100%; | |||
| display: flex; | |||
| align-items: center; | |||
| cursor: pointer; | |||
| box-sizing: border-box; | |||
| border-bottom: 3px solid transparent; | |||
| } | |||
| ._repo_top_mid_item i { | |||
| margin-right: 5px; | |||
| } | |||
| ._repo_top_mid_item._foucs { | |||
| background-color: rgba(255, 255, 255, 0.3); | |||
| color: rgb(16, 16, 16); | |||
| cursor: default; | |||
| border-bottom: 3px solid rgba(16, 16, 16, 0.8); | |||
| } | |||
| ._repo_top_middle_content { | |||
| width: 100%; | |||
| height: 346px; | |||
| position: absolute; | |||
| bottom: 0; | |||
| background: rgba(255, 255, 255, 0.3); | |||
| padding: 20px 20px; | |||
| overflow: hidden; | |||
| } | |||
| ._repo_top_mid_repo_list { | |||
| overflow: hidden; | |||
| } | |||
| /deep/._repo_sw_card { | |||
| height: 128px; | |||
| border-color: rgb(255, 255, 255); | |||
| border-width: 1px; | |||
| border-style: solid; | |||
| font-size: 14px; | |||
| padding: 12px; | |||
| background: rgba(255, 255, 255, 0.6); | |||
| margin-bottom: 20px; | |||
| box-sizing: border-box; | |||
| position: relative; | |||
| } | |||
| /deep/._repo_nowrap { | |||
| overflow: hidden; | |||
| text-overflow: ellipsis; | |||
| white-space: nowrap; | |||
| } | |||
| /deep/._repo_nowrap_line_3 { | |||
| overflow: hidden; | |||
| text-overflow: ellipsis; | |||
| word-break: break-all; | |||
| font-size: 12px; | |||
| color: rgb(136, 136, 136); | |||
| display: -webkit-box; | |||
| -webkit-box-orient: vertical; | |||
| -webkit-line-clamp: 3; | |||
| max-height: 65px; | |||
| white-space: break-spaces; | |||
| } | |||
| /deep/._repo_sw_card a { | |||
| color: inherit; | |||
| } | |||
| /deep/._repo_sw_card_title { | |||
| font-weight: 700; | |||
| font-size: 14px; | |||
| color: rgba(26, 40, 51, 1); | |||
| margin-bottom: 10px; | |||
| } | |||
| /deep/._repo_sw_card_descr { | |||
| font-size: 12px; | |||
| color: rgba(80, 85, 89, 1); | |||
| margin-bottom: 10px; | |||
| min-height: 42px; | |||
| } | |||
| /deep/._repo_sw_card_label { | |||
| color: rgb(26, 40, 51); | |||
| font-size: 14px; | |||
| display: flex; | |||
| width: 100%; | |||
| position: relative; | |||
| } | |||
| /deep/._repo_sw_card_label span { | |||
| border-radius: 4px; | |||
| background: rgba(232, 232, 232, 0.6); | |||
| padding: 0px 6px 2px; | |||
| margin-right: 10px; | |||
| max-width: 50%; | |||
| } | |||
| /deep/._repo_top_mid_repo_list .swiper-pagination-bullet { | |||
| width: 8px; | |||
| height: 8px; | |||
| border-radius: 100%; | |||
| background: #76cbed; | |||
| } | |||
| /deep/._repo_top_mid_repo_list .swiper-pagination-bullet-active { | |||
| width: 40px; | |||
| border-radius: 4px; | |||
| } | |||
| </style> | |||
| @@ -0,0 +1,123 @@ | |||
| <template> | |||
| <div> | |||
| <div class="ui container"> | |||
| <SearchBar ref="searchBarRef" type="search" :static="true" :staticTopicsData="staticSquareTopics" | |||
| :sort="reposListSortType" :searchValue="reposListQurey" :topic="reposListTopic" @change="searchBarChange"> | |||
| </SearchBar> | |||
| </div> | |||
| <div class="ui container"> | |||
| <div class="ui grid"> | |||
| <div class="computer only ui two wide computer column"> | |||
| <ReposFilters ref="reposFiltersRef" @change="filtersChange"></ReposFilters> | |||
| </div> | |||
| <div class="ui sixteen wide mobile twelve wide tablet ten wide computer column"> | |||
| <ReposList ref="reposListRef" :sort="reposListSortType" :q="reposListQurey" :topic="reposListTopic" | |||
| :page="page" :pageSize="pageSize" :pageSizes="pageSizes" @current-change="currentChange" | |||
| @size-change="sizeChange"> | |||
| </ReposList> | |||
| </div> | |||
| <div class="computer only ui four wide computer column"> | |||
| <div> | |||
| <ActiveUsers></ActiveUsers> | |||
| </div> | |||
| <div class="active-org-c"> | |||
| <ActiveOrgs></ActiveOrgs> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| import SearchBar from '../components/SearchBar.vue'; | |||
| import ReposFilters from '../components/ReposFilters.vue'; | |||
| import ReposList from '../components/ReposList.vue'; | |||
| import ActiveUsers from '../components/ActiveUsers.vue'; | |||
| import ActiveOrgs from '../components/ActiveOrgs.vue'; | |||
| import { getUrlSearchParams } from '~/utils'; | |||
| const staticSquareTopics = JSON.stringify(window.staticSquareTopics || []); | |||
| export default { | |||
| data() { | |||
| return { | |||
| reposListSortType: 'mostpopular', | |||
| reposListQurey: '', | |||
| reposListTopic: '', | |||
| page: 1, | |||
| pageSize: 15, | |||
| pageSizes: [15, 30, 50], | |||
| staticSquareTopics: staticSquareTopics, | |||
| }; | |||
| }, | |||
| components: { | |||
| SearchBar, | |||
| ReposFilters, | |||
| ReposList, | |||
| ActiveUsers, | |||
| ActiveOrgs, | |||
| }, | |||
| methods: { | |||
| filtersChange(condition) { | |||
| this.page = 1; | |||
| this.reposListSortType = condition.key; | |||
| this.search(); | |||
| }, | |||
| searchBarChange(params) { | |||
| this.page = 1; | |||
| this.reposListQurey = params.q || ''; | |||
| this.reposListTopic = params.topic || ''; | |||
| this.search(); | |||
| }, | |||
| currentChange({ page, pageSize }) { | |||
| this.page = page; | |||
| this.search(); | |||
| }, | |||
| sizeChange({ page, pageSize }) { | |||
| this.page = 1; | |||
| this.pageSize = pageSize; | |||
| this.search(); | |||
| }, | |||
| search() { | |||
| window.location.href = `/explore/repos?q=${this.reposListQurey.trim()}&sort=${this.reposListSortType}&topic=${this.reposListTopic.trim()}&page=${this.page}&pageSize=${this.pageSize}`; | |||
| } | |||
| }, | |||
| beforeMount() { | |||
| const urlParams = getUrlSearchParams(); | |||
| this.reposListQurey = urlParams.q || ''; | |||
| this.reposListTopic = urlParams.topic || ''; | |||
| this.reposListSortType = urlParams.sort || 'mostpopular'; | |||
| this.page = Number(urlParams.page) || 1; | |||
| this.pageSize = this.pageSizes.indexOf(Number(urlParams.pageSize)) >= 0 ? Number(urlParams.pageSize) : 15; | |||
| }, | |||
| mounted() { | |||
| this.$nextTick(() => { | |||
| this.$refs.reposFiltersRef.setDefaultFilter(this.reposListSortType); | |||
| this.$refs.searchBarRef.setDefaultSearch({ | |||
| q: this.reposListQurey, | |||
| topic: this.reposListTopic, | |||
| }); | |||
| this.$refs.reposListRef.search(); | |||
| }); | |||
| window.addEventListener('pageshow', function (e) { | |||
| if (e.persisted) { | |||
| window.location.reload(); | |||
| } | |||
| }, false); | |||
| }, | |||
| beforeDestroy() { }, | |||
| }; | |||
| </script> | |||
| <style scoped lang="less"> | |||
| .recommend-repos-c { | |||
| margin: 0 0 54px; | |||
| } | |||
| .active-org-c { | |||
| margin-top: 32px; | |||
| } | |||
| </style> | |||
| @@ -0,0 +1,17 @@ | |||
| import Vue from 'vue'; | |||
| import ElementUI from 'element-ui'; | |||
| import 'element-ui/lib/theme-chalk/index.css'; | |||
| import localeEn from 'element-ui/lib/locale/lang/en'; | |||
| import localeZh from 'element-ui/lib/locale/lang/zh-CN'; | |||
| import { i18n, lang } from '~/langs'; | |||
| import App from './index.vue'; | |||
| Vue.use(ElementUI, { | |||
| locale: lang === 'zh-CN' ? localeZh : localeEn, | |||
| size: 'small', | |||
| }); | |||
| new Vue({ | |||
| i18n, | |||
| render: (h) => h(App), | |||
| }).$mount('#__vue-root'); | |||
| @@ -0,0 +1,146 @@ | |||
| <template> | |||
| <div> | |||
| <div> | |||
| <SquareTop :static="true" :staticBannerData="staticSquareBanners" :staticSwiperData="staticSquarePreferredRepos"> | |||
| </SquareTop> | |||
| </div> | |||
| <div class="ui container"> | |||
| <SearchBar :static="true" :staticTopicsData="staticSquareTopics" ref="searchBarRef" type="square" :sort="``" | |||
| :searchValue="reposListQurey" :topic="``" @change="searchBarChange"></SearchBar> | |||
| </div> | |||
| <div class="recommend-repos-c"> | |||
| <RecommendRepos :static="true" :staticSwiperData="staticSquareRecommendRepos"></RecommendRepos> | |||
| <a name="search"></a> | |||
| </div> | |||
| <div class="ui container"> | |||
| <div class="ui grid"> | |||
| <div class="computer only ui two wide computer column"> | |||
| <ReposFilters ref="reposFiltersRef" @change="filtersChange"></ReposFilters> | |||
| </div> | |||
| <div class="ui sixteen wide mobile twelve wide tablet ten wide computer column"> | |||
| <ReposList ref="reposListRef" :sort="reposListSortType" :q="reposListQurey" :topic="reposListTopic" | |||
| :page="page" :pageSize="pageSize" :pageSizes="pageSizes" @current-change="currentChange" | |||
| @size-change="sizeChange"> | |||
| </ReposList> | |||
| </div> | |||
| <div class="computer only ui four wide computer column"> | |||
| <div> | |||
| <ActiveUsers></ActiveUsers> | |||
| </div> | |||
| <div class="active-org-c"> | |||
| <ActiveOrgs></ActiveOrgs> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </template> | |||
| <script> | |||
| import SquareTop from '../components/SquareTop.vue'; | |||
| import SearchBar from '../components/SearchBar.vue'; | |||
| import RecommendRepos from '../components/RecommendRepos.vue'; | |||
| import ReposFilters from '../components/ReposFilters.vue'; | |||
| import ReposList from '../components/ReposList.vue'; | |||
| import ActiveUsers from '../components/ActiveUsers.vue'; | |||
| import ActiveOrgs from '../components/ActiveOrgs.vue'; | |||
| import { getUrlSearchParams } from '~/utils'; | |||
| const staticSquareBanners = JSON.stringify(window.staticSquareBanners || []); | |||
| const staticSquarePreferredRepos = window.staticSquarePreferredRepos || []; | |||
| const staticSquareTopics = JSON.stringify(window.staticSquareTopics || []); | |||
| const staticSquareRecommendRepos = window.staticSquareRecommendRepos || []; | |||
| export default { | |||
| data() { | |||
| return { | |||
| reposListSortType: 'mostpopular', | |||
| reposListQurey: '', | |||
| reposListTopic: '', | |||
| page: 1, | |||
| pageSize: 15, | |||
| pageSizes: [15, 30, 50], | |||
| staticSquareBanners: staticSquareBanners, | |||
| staticSquarePreferredRepos: staticSquarePreferredRepos, | |||
| staticSquareTopics: staticSquareTopics, | |||
| staticSquareRecommendRepos: staticSquareRecommendRepos, | |||
| }; | |||
| }, | |||
| components: { | |||
| SquareTop, | |||
| SearchBar, | |||
| RecommendRepos, | |||
| ReposFilters, | |||
| ReposList, | |||
| ActiveUsers, | |||
| ActiveOrgs, | |||
| }, | |||
| methods: { | |||
| filtersChange(condition) { | |||
| this.page = 1; | |||
| this.reposListSortType = condition.key; | |||
| this.search(); | |||
| }, | |||
| searchBarChange(params) { | |||
| this.page = 1; | |||
| this.reposListQurey = params.q || ''; | |||
| this.reposListTopic = params.topic || ''; | |||
| this.search(); | |||
| }, | |||
| currentChange({ page, pageSize }) { | |||
| this.page = page; | |||
| this.search(); | |||
| }, | |||
| sizeChange({ page, pageSize }) { | |||
| this.page = 1; | |||
| this.pageSize = pageSize; | |||
| this.search(); | |||
| }, | |||
| search() { | |||
| window.location.href = `/explore/repos/square?q=${this.reposListQurey.trim()}&sort=${this.reposListSortType}&topic=${this.reposListTopic.trim()}&page=${this.page}&pageSize=${this.pageSize}`; | |||
| } | |||
| }, | |||
| beforeMount() { | |||
| const urlParams = getUrlSearchParams(); | |||
| this.reposListQurey = urlParams.q || ''; | |||
| this.reposListTopic = urlParams.topic || ''; | |||
| this.reposListSortType = urlParams.sort || 'mostpopular'; | |||
| this.page = Number(urlParams.page) || 1; | |||
| this.pageSize = this.pageSizes.indexOf(Number(urlParams.pageSize)) >= 0 ? Number(urlParams.pageSize) : 15; | |||
| }, | |||
| mounted() { | |||
| this.$nextTick(() => { | |||
| this.$refs.reposFiltersRef.setDefaultFilter(this.reposListSortType); | |||
| this.$refs.searchBarRef.setDefaultSearch({ | |||
| q: this.reposListQurey, | |||
| topic: this.reposListTopic, | |||
| }); | |||
| const urlParams = getUrlSearchParams(); | |||
| const page = Number(urlParams.page) || 1; | |||
| const reposListSortType = urlParams.sort; | |||
| if (page != 1 || reposListSortType) { | |||
| window.location.href = '#search'; | |||
| } | |||
| this.$refs.reposListRef.search(); | |||
| }); | |||
| window.addEventListener('pageshow', function (e) { | |||
| if (e.persisted) { | |||
| window.location.reload(); | |||
| } | |||
| }, false); | |||
| }, | |||
| beforeDestroy() { }, | |||
| }; | |||
| </script> | |||
| <style scoped lang="less"> | |||
| .recommend-repos-c { | |||
| margin: 0 0 54px; | |||
| } | |||
| .active-org-c { | |||
| margin-top: 32px; | |||
| } | |||
| </style> | |||
| @@ -0,0 +1,17 @@ | |||
| import Vue from 'vue'; | |||
| import ElementUI from 'element-ui'; | |||
| import 'element-ui/lib/theme-chalk/index.css'; | |||
| import localeEn from 'element-ui/lib/locale/lang/en'; | |||
| import localeZh from 'element-ui/lib/locale/lang/zh-CN'; | |||
| import { i18n, lang } from '~/langs'; | |||
| import App from './index.vue'; | |||
| Vue.use(ElementUI, { | |||
| locale: lang === 'zh-CN' ? localeZh : localeEn, | |||
| size: 'small', | |||
| }); | |||
| new Vue({ | |||
| i18n, | |||
| render: (h) => h(App), | |||
| }).$mount('#__vue-root'); | |||
| @@ -27,7 +27,7 @@ export const transFileSize = (srcSize) => { | |||
| srcSize = parseFloat(srcSize); | |||
| const index = Math.floor(Math.log(srcSize) / Math.log(1024)); | |||
| const size = (srcSize / Math.pow(1024, index)).toFixed(2); | |||
| return size + ' ' + unitArr[index]; | |||
| return size + ' ' + unitArr[index]; | |||
| }; | |||
| export const renderSpecStr = (spec, showPoint) => { | |||
| @@ -39,3 +39,91 @@ export const renderSpecStr = (spec, showPoint) => { | |||
| var specStr = `${ngpu}, CPU: ${spec.CpuCores}, ${gpuMemStr}${i18n.t('resourcesManagement.mem')}: ${spec.MemGiB}GB${sharedMemStr}${pointStr}`; | |||
| return specStr; | |||
| }; | |||
| const Minute = 60; | |||
| const Hour = 60 * Minute; | |||
| const Day = 24 * Hour; | |||
| const Week = 7 * Day; | |||
| const Month = 30 * Day; | |||
| const Year = 12 * Month; | |||
| const computeTimeDiff = (diff) => { | |||
| let diffStr = ''; | |||
| switch (true) { | |||
| case diff <= 0: | |||
| diff = 0; | |||
| diffStr = i18n.t('timeObj.now'); | |||
| break; | |||
| case diff < 2: | |||
| diff = 0; | |||
| diffStr = i18n.t('timeObj.1s'); | |||
| break; | |||
| case diff < 1 * Minute: | |||
| diffStr = i18n.t('timeObj.seconds', { msg: Math.floor(diff) }); | |||
| diff = 0; | |||
| break; | |||
| case diff < 2 * Minute: | |||
| diff -= 1 * Minute; | |||
| diffStr = i18n.t('timeObj.1m'); | |||
| break; | |||
| case diff < 1 * Hour: | |||
| diffStr = i18n.t('timeObj.minutes', { msg: Math.floor(diff / Minute) }); | |||
| diff -= diff / Minute * Minute; | |||
| break; | |||
| case diff < 2 * Hour: | |||
| diff -= 1 * Hour; | |||
| diffStr = i18n.t('timeObj.1h'); | |||
| break; | |||
| case diff < 1 * Day: | |||
| diffStr = i18n.t('timeObj.hours', { msg: Math.floor(diff / Hour) }); | |||
| diff -= diff / Hour * Hour; | |||
| break; | |||
| case diff < 2 * Day: | |||
| diff -= 1 * Day; | |||
| diffStr = i18n.t('timeObj.1d'); | |||
| break; | |||
| case diff < 1 * Week: | |||
| diffStr = i18n.t('timeObj.days', { msg: Math.floor(diff / Day) }); | |||
| diff -= diff / Day * Day; | |||
| break; | |||
| case diff < 2 * Week: | |||
| diff -= 1 * Week; | |||
| diffStr = i18n.t('timeObj.1w'); | |||
| break; | |||
| case diff < 1 * Month: | |||
| diffStr = i18n.t('timeObj.weeks', { msg: Math.floor(diff / Week) }); | |||
| diff -= diff / Week * Week; | |||
| break; | |||
| case diff < 2 * Month: | |||
| diff -= 1 * Month; | |||
| diffStr = i18n.t('timeObj.1mon'); | |||
| break; | |||
| case diff < 1 * Year: | |||
| diffStr = i18n.t('timeObj.months', { msg: Math.floor(diff / Month) }); | |||
| diff -= diff / Month * Month; | |||
| break; | |||
| case diff < 2 * Year: | |||
| diff -= 1 * Year; | |||
| diffStr = i18n.t('timeObj.1y'); | |||
| break; | |||
| default: | |||
| diffStr = i18n.t('timeObj.years', { msg: Math.floor(diff / Year) }); | |||
| diff -= (diff / Year) * Year; | |||
| break; | |||
| } | |||
| return { diff, diffStr }; | |||
| }; | |||
| export const timeSinceUnix = (then, now) => { | |||
| let lbl = 'timeObj.ago'; | |||
| let diff = now - then; | |||
| if (then > now) { | |||
| lbl = 'timeObj.from_now'; | |||
| diff = then - now; | |||
| } | |||
| if (diff <= 0) { | |||
| return i18n.t('timeObj.now'); | |||
| } | |||
| const out = computeTimeDiff(diff); | |||
| return i18n.t(lbl, { msg: out.diffStr }); | |||
| }; | |||
| @@ -0,0 +1,75 @@ | |||
| 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); | |||
| } | |||
| let initials1 = initials.toUpperCase(); | |||
| if (window.devicePixelRatio) { | |||
| size = size * window.devicePixelRatio; | |||
| } | |||
| charIndex = (initials == "?" ? 72 : initials.charCodeAt(0)) - 64; | |||
| colourIndex = charIndex % 20; | |||
| canvas = document.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(initials1, size / 2, size / 1.5); | |||
| dataURI = canvas.toDataURL(); | |||
| canvas = null; | |||
| return dataURI; | |||
| } | |||
| LetterAvatar.transform = function () { | |||
| Array.prototype.forEach.call( | |||
| document.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); | |||
| } | |||
| ); | |||
| }; | |||
| export default LetterAvatar; | |||
| @@ -22,8 +22,8 @@ for (const path of glob('web_src/less/themes/*.less')) { | |||
| const standalone = {}; | |||
| const stadalonePaths = [ | |||
| ...glob('web_src/js/standalone/*.js'), | |||
| ...glob('web_src/less/standalone/*.less'), | |||
| ...glob('web_src/js/standalone/**/*.js'), | |||
| ...glob('web_src/less/standalone/**/*.less'), | |||
| ]; | |||
| for (const path of stadalonePaths) { | |||
| standalone[parse(path).name] = [path]; | |||
| @@ -22,8 +22,8 @@ for (const path of glob('web_src/less/themes/*.less')) { | |||
| const standalone = {}; | |||
| const stadalonePaths = [ | |||
| ...glob('web_src/js/standalone/*.js'), | |||
| ...glob('web_src/less/standalone/*.less'), | |||
| ...glob('web_src/js/standalone/**/*.js'), | |||
| ...glob('web_src/less/standalone/**/*.less'), | |||
| ]; | |||
| for (const path of stadalonePaths) { | |||
| standalone[parse(path).name] = [path]; | |||