mirror of
https://github.com/gogs/gogs.git
synced 2026-02-28 09:10:57 +01:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
348c75c91b | ||
|
|
76ebdb265b | ||
|
|
88ae3510ff | ||
|
|
c07899701a | ||
|
|
ab42671c63 | ||
|
|
b3ac33cbcf | ||
|
|
3b94162803 | ||
|
|
52aade232d | ||
|
|
3a9276307c | ||
|
|
ca6326c937 | ||
|
|
8da16ac302 | ||
|
|
761bb3cf53 | ||
|
|
c1c269d9ef | ||
|
|
9edac05e05 | ||
|
|
9c1620d49c | ||
|
|
65bb6eb284 | ||
|
|
aff55ff105 | ||
|
|
4a67bb5806 | ||
|
|
1afafde3b3 | ||
|
|
ab634ce61a | ||
|
|
9d06ebd01a | ||
|
|
09723ec0e5 | ||
|
|
864761c2d0 | ||
|
|
abe7f7bc36 | ||
|
|
717bcc4ad8 | ||
|
|
11ffdac3f8 | ||
|
|
cf7d5d0c56 |
5
.gitattributes
vendored
5
.gitattributes
vendored
@@ -1,8 +1,7 @@
|
||||
public/conf/gitignore/* linguist-vendored
|
||||
public/conf/license/* linguist-vendored
|
||||
conf/gitignore/* linguist-vendored
|
||||
conf/license/* linguist-vendored
|
||||
public/assets/* linguist-vendored
|
||||
public/plugins/* linguist-vendored
|
||||
public/plugins/* linguist-vendored
|
||||
public/css/themes/* linguist-vendored
|
||||
public/css/github.min.css linguist-vendored
|
||||
public/css/semantic-2.2.7.min.css linguist-vendored
|
||||
|
||||
@@ -50,7 +50,7 @@ The goal of this project is to make the easiest, fastest, and most painless way
|
||||
- Gravatar and Federated avatar with custom source
|
||||
- Mail service
|
||||
- Administration panel
|
||||
- Supports MySQL, PostgreSQL, SQLite3, MSSQL and [TiDB](https://github.com/pingcap/tidb) (experimental)
|
||||
- Supports MySQL, PostgreSQL, SQLite3, MSSQL and [TiDB](https://github.com/pingcap/tidb) (via MySQL protocol)
|
||||
- Multi-language support ([23 languages](https://crowdin.com/project/gogs))
|
||||
|
||||
## Hardware Requirements
|
||||
|
||||
@@ -9,7 +9,7 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自
|
||||
|
||||
## 项目概览
|
||||
|
||||
- 有关基本用法和变更日志,请通过 [使用手册](https://gogs.io/docs/intro/) 查看。
|
||||
- 有关基本用法和变更日志,请通过 [使用手册](https://gogs.io/docs/intro) 查看。
|
||||
- 想要先睹为快?直接去 [在线体验](https://try.gogs.io/gogs/gogs) 。
|
||||
- 使用过程中遇到问题?尝试从 [故障排查](https://gogs.io/docs/intro/troubleshooting.html) 页面或 [用户论坛](https://discuss.gogs.io/) 获取帮助。
|
||||
- 希望帮助多国语言界面的翻译吗?请立即访问 [详情页面](https://gogs.io/docs/features/i18n.html)!
|
||||
@@ -31,7 +31,7 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自
|
||||
- 支持 Jupyter Notebook
|
||||
- 支持邮件服务
|
||||
- 支持后台管理面板
|
||||
- 支持 MySQL、PostgreSQL、SQLite3、MSSQL 和 [TiDB](https://github.com/pingcap/tidb)(实验性支持) 数据库
|
||||
- 支持 MySQL、PostgreSQL、SQLite3、MSSQL 和 [TiDB](https://github.com/pingcap/tidb)(通过 MySQL 协议)数据库
|
||||
- 支持多语言本地化([23 种语言]([more](https://crowdin.com/project/gogs)))
|
||||
|
||||
## 硬件要求
|
||||
|
||||
11
cmd/web.go
11
cmd/web.go
@@ -230,7 +230,6 @@ func runWeb(ctx *cli.Context) error {
|
||||
})
|
||||
|
||||
m.Group("/user", func() {
|
||||
// r.Get("/feeds", binding.Bind(form.Feeds{}), user.Feeds)
|
||||
m.Any("/activate", user.Activate)
|
||||
m.Any("/activate_email", user.ActivateEmail)
|
||||
m.Get("/email2user", user.Email2User)
|
||||
@@ -460,7 +459,10 @@ func runWeb(ctx *cli.Context) error {
|
||||
m.Group("/:username/:reponame", func() {
|
||||
m.Get("/issues", repo.RetrieveLabels, repo.Issues)
|
||||
m.Get("/issues/:index", repo.ViewIssue)
|
||||
|
||||
m.Get("/labels/", repo.RetrieveLabels, repo.Labels)
|
||||
m.Get("/milestones", repo.Milestones)
|
||||
}, ignSignIn, context.RepoAssignment(true))
|
||||
m.Group("/:username/:reponame", func() {
|
||||
// FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest.
|
||||
// So they can apply their own enable/disable logic on routers.
|
||||
m.Group("/issues", func() {
|
||||
@@ -477,10 +479,7 @@ func runWeb(ctx *cli.Context) error {
|
||||
m.Post("", repo.UpdateCommentContent)
|
||||
m.Post("/delete", repo.DeleteComment)
|
||||
})
|
||||
|
||||
m.Get("/labels/", repo.RetrieveLabels, repo.Labels)
|
||||
m.Get("/milestones", repo.Milestones)
|
||||
}, ignSignIn, context.RepoAssignment(true))
|
||||
}, reqSignIn, context.RepoAssignment(true))
|
||||
m.Group("/:username/:reponame", func() {
|
||||
m.Group("/wiki", func() {
|
||||
m.Get("/?:page", repo.Wiki)
|
||||
|
||||
@@ -271,8 +271,8 @@ COOKIE_NAME = i_like_gogits
|
||||
COOKIE_SECURE = false
|
||||
; Enable set cookie, default is true
|
||||
ENABLE_SET_COOKIE = true
|
||||
; Session GC time interval, default is 86400
|
||||
GC_INTERVAL_TIME = 86400
|
||||
; Session GC time interval, default is 3600
|
||||
GC_INTERVAL_TIME = 3600
|
||||
; Session life time, default is 86400
|
||||
SESSION_LIFE_TIME = 86400
|
||||
; Cookie name for CSRF
|
||||
|
||||
@@ -42,6 +42,7 @@ Ilya Makarov
|
||||
Jamie Mansfield <dev AT jamierocks DOT uk>
|
||||
Javier Ortiz Bultron <javier DOT ortiz DOT 78 AT gmail DOT com>
|
||||
Jean THOMAS <contact AT tibounise DOT com>
|
||||
John Behm <jxsl13 AT googlemail DOT com>
|
||||
Jonas De Kegel <jonasgithub [AT] gmail [DOT] com>
|
||||
Joubert RedRat <me+github AT redrat DOT com DOT br>
|
||||
Juraj Bubniak <contact AT jbub DOT eu>
|
||||
|
||||
@@ -82,7 +82,7 @@ app_url=URL aplikace
|
||||
app_url_helper=Toto ovlivňuje URL klonů skrze HTTP/HTTPS a odkazů v e-mailech.
|
||||
log_root_path=Adresář systémových záznamů
|
||||
log_root_path_helper=Adresář, kam se budou zapisovat systémové záznamy.
|
||||
enable_console_mode=Enable Console Mode
|
||||
enable_console_mode=Povolit režim konzoly
|
||||
enable_console_mode_popup=In addition to file mode, also print logs to console.
|
||||
|
||||
optional_title=Dodatečná nastavení
|
||||
@@ -258,7 +258,7 @@ ssh_keys=Klíče SSH
|
||||
social=Sociální účty
|
||||
applications=Aplikace
|
||||
orgs=Organizace
|
||||
repos=Repositories
|
||||
repos=Repositáře
|
||||
delete=Smazat účet
|
||||
uid=UID
|
||||
|
||||
@@ -347,8 +347,8 @@ orgs.none=Nejste členem žádné organizace.
|
||||
orgs.leave_title=Opustit organizaci
|
||||
orgs.leave_desc=Opuštěním organizace ztratíte přístup do všech repositářů a k týmům. Chcete pokračovat?
|
||||
|
||||
repos.leave=Leave
|
||||
repos.leave_title=Leave repository
|
||||
repos.leave=Opustit
|
||||
repos.leave_title=Opustit repositář
|
||||
repos.leave_desc=You will lose access to the repository after you left. Do you want to continue?
|
||||
repos.leave_success=You have left repository '%s' successfully!
|
||||
|
||||
@@ -751,7 +751,7 @@ settings.webhook_deletion_success=Webový háček byl úspěšně smazán!
|
||||
settings.webhook.test_delivery=Test doručitelnosti
|
||||
settings.webhook.test_delivery_desc=Odeslat falešnou událost doručení odeslání pro test vašeho nastavení webových háčků
|
||||
settings.webhook.test_delivery_success=Testovací webový háček byl přidán do fronty doručení. Bude to trvat několik sekund, než se ukáže v historii doručení.
|
||||
settings.webhook.redelivery=Redelivery
|
||||
settings.webhook.redelivery=Opětovné doručení
|
||||
settings.webhook.redelivery_success=Hook task '%s' has been readded to delivery queue. It may take few seconds to update delivery status in history.
|
||||
settings.webhook.request=Požadavek
|
||||
settings.webhook.response=Odpověď
|
||||
|
||||
@@ -82,8 +82,8 @@ app_url=Anwendungs-URL
|
||||
app_url_helper=Dies hat Auswirkung auf die HTTP/HTTPS Klon-URLs und den Inhalt der E-Mails.
|
||||
log_root_path=Logdateipfad
|
||||
log_root_path_helper=Verzeichnis in das Logdateien geschrieben werden.
|
||||
enable_console_mode=Enable Console Mode
|
||||
enable_console_mode_popup=In addition to file mode, also print logs to console.
|
||||
enable_console_mode=Konsolen-Modus einschalten
|
||||
enable_console_mode_popup=Zusätzlich zum Datei-Modus, zeige Logs auch in der Konsole.
|
||||
|
||||
optional_title=Optionale Einstellungen
|
||||
email_title=E-Mail-Service Einstellungen
|
||||
@@ -347,10 +347,10 @@ orgs.none=Sie sind kein Mitglied einer Organisation.
|
||||
orgs.leave_title=Organisation verlassen
|
||||
orgs.leave_desc=Sie verlieren den Zugriff auf alle Repositories und Teams nach dem Verlassen der Organisation. Möchten Sie fortfahren?
|
||||
|
||||
repos.leave=Leave
|
||||
repos.leave_title=Leave repository
|
||||
repos.leave_desc=You will lose access to the repository after you left. Do you want to continue?
|
||||
repos.leave_success=You have left repository '%s' successfully!
|
||||
repos.leave=Verlassen
|
||||
repos.leave_title=Repository verlassen
|
||||
repos.leave_desc=Der Zugriff zum Repository wird verloren gehen, nachdem diese verlassen wird. Möchten Sie fortfahren?
|
||||
repos.leave_success=Sie haben die Repository '%s' erfolgreich verlassen!
|
||||
|
||||
delete_account=Konto löschen
|
||||
delete_prompt=Diese Aktion wird Ihr Konto dauerhaft löschen und kann <strong>NICHT</strong> rückgängig gemacht werden!
|
||||
@@ -400,7 +400,7 @@ migrate_type_helper=Dieses Repository wird ein <span class="text blue">Mirror</s
|
||||
migrate_repo=Repository migrieren
|
||||
migrate.clone_address=Adresse kopieren
|
||||
migrate.clone_address_desc=Dies kann eine HTTP/HTTPS/GIT-URL sein.
|
||||
migrate.clone_address_desc_import_local=Sie dürfen auch eine Repository vom lokalen Serverpfad migrieren.
|
||||
migrate.clone_address_desc_import_local=Sie dürfen auch ein Repository vom lokalen Serverpfad migrieren.
|
||||
migrate.permission_denied=Ihnen fehlen die Rechte zum Importieren lokaler Repositories.
|
||||
migrate.invalid_local_path=Der lokale Pfad ist ungültig, existiert nicht oder ist kein Ordner.
|
||||
migrate.failed=Fehler bei Migration: %v
|
||||
@@ -422,7 +422,7 @@ quick_guide=Kurzanleitung
|
||||
clone_this_repo=Dieses Repository klonen
|
||||
create_new_repo_command=Erstellen Sie ein neues Repository mittels der Kommandozeile
|
||||
push_exist_repo=Bestehendes Repository von der Kommandozeile pushen
|
||||
bare_message=This repository does not have any content yet.
|
||||
bare_message=Diese Repository hat noch keinen Inhalt.
|
||||
|
||||
files=Dateien
|
||||
branch=Branch
|
||||
@@ -658,7 +658,7 @@ settings.collaboration.write=Schreibrechte
|
||||
settings.collaboration.read=Leserechte
|
||||
settings.collaboration.undefined=Nicht definiert
|
||||
settings.branches=Branches
|
||||
settings.branches_bare=You cannot manage branches for bare repository. Please push some content first.
|
||||
settings.branches_bare=Branches leerer Repositories können nicht verwaltet werden. Bitte erst Datei(en) pushen.
|
||||
settings.default_branch=Standard-Branch
|
||||
settings.default_branch_desc=Der Standard-Branch gilt als Basis für Commits, Pull-Requests und Online-Bearbeitung.
|
||||
settings.update=Aktualisieren
|
||||
@@ -691,13 +691,13 @@ settings.change_reponame_prompt=Diese Änderung wirkt sich darauf aus, wie sich
|
||||
settings.advanced_settings=Erweiterte Einstellungen
|
||||
settings.wiki_desc=Wiki einschalten
|
||||
settings.use_internal_wiki=Eingebautes Wiki verwenden
|
||||
settings.allow_public_wiki_desc=Allow public access to wiki when repository is private
|
||||
settings.allow_public_wiki_desc=Erlaube öffentlichen Zugang zum Wiki, auch wenn die Repository privat ist.
|
||||
settings.use_external_wiki=Externes Wiki verwenden
|
||||
settings.external_wiki_url=Externe Wiki URL
|
||||
settings.external_wiki_url_desc=Besucher werden auf diese URL umgeleitet, wenn sie auf den Tab klicken.
|
||||
settings.issues_desc=Issue-Tracker einschalten
|
||||
settings.use_internal_issue_tracker=Eingebauten Issue-Tracker verwenden
|
||||
settings.allow_public_issues_desc=Allow public access to issues when repository is private
|
||||
settings.allow_public_issues_desc=Erlaube öffentlichen Zugriff auf Issues, auch wenn die Repository privat ist.
|
||||
settings.use_external_issue_tracker=Externes Issue-System verwenden
|
||||
settings.external_tracker_url=URL eines externen Issue Trackers
|
||||
settings.external_tracker_url_desc=Besucher werden auf diese URL umgeleitet, wenn sie auf den Tab klicken.
|
||||
@@ -751,8 +751,8 @@ settings.webhook_deletion_success=Webhook wurde erfolgreich entfernt!
|
||||
settings.webhook.test_delivery=Senden testen
|
||||
settings.webhook.test_delivery_desc=Sendet ein simuliertes Push-Ereignis, um die Webhook-Einstellungen zu testen
|
||||
settings.webhook.test_delivery_success=Test-Webhook wurde zur Auslieferungswarteschlange hinzugefügt. Es kann einige Sekunden dauern, bevor es in der Auslieferungshistorie erscheint.
|
||||
settings.webhook.redelivery=Redelivery
|
||||
settings.webhook.redelivery_success=Hook task '%s' has been readded to delivery queue. It may take few seconds to update delivery status in history.
|
||||
settings.webhook.redelivery=Erneuter Versand
|
||||
settings.webhook.redelivery_success=Hook-Task '%s' wurde wieder zur Auslieferungswarteschlange hinzugefügt. Es kann einige Sekunden, bis sich der Auslieferungsstatus in der History aktualisiert hat.
|
||||
settings.webhook.request=Anfrage
|
||||
settings.webhook.response=Antwort
|
||||
settings.webhook.headers=Kopfzeilen
|
||||
|
||||
@@ -662,6 +662,7 @@ settings.branches_bare = You cannot manage branches for bare repository. Please
|
||||
settings.default_branch = Default Branch
|
||||
settings.default_branch_desc = The default branch is considered the "base" branch for code commits, pull requests and online editing.
|
||||
settings.update = Update
|
||||
settings.update_default_branch_unsupported = Change default branch is not supported by the Git version on server.
|
||||
settings.update_default_branch_success = Default branch of this repository has been updated successfully!
|
||||
settings.protected_branches = Protected Branches
|
||||
settings.protected_branches_desc = Protect branches from force pushing, accidental deletion and whitelist code committers.
|
||||
|
||||
@@ -75,15 +75,15 @@ domain_helper=Questo influisce sugli URL per il clonaggio via SSH.
|
||||
ssh_port=Porta SSH
|
||||
ssh_port_helper=Numero di porta utilizzato dal server SSH, lasciare vuoto per disabilitare l'integrazione SSH.
|
||||
use_builtin_ssh_server=Usa il server SSH integrato
|
||||
use_builtin_ssh_server_popup=Start builtin SSH server for Git operations to distinguish from system SSH daemon.
|
||||
use_builtin_ssh_server_popup=Avvia il server SSH integrato per le operazioni Git per distinguerle dal demone SSH di sistema.
|
||||
http_port=Porta HTTP
|
||||
http_port_helper=Porta di ascolto dell'applicazione.
|
||||
app_url=URL Applicazione
|
||||
app_url_helper=Questo influisce sugli URL per il clonaggio via HTTP/HTTPS e da qualche parte nella posta elettronica.
|
||||
log_root_path=Percorso dei log
|
||||
log_root_path_helper=Directory in cui scrivere i file di log.
|
||||
enable_console_mode=Enable Console Mode
|
||||
enable_console_mode_popup=In addition to file mode, also print logs to console.
|
||||
enable_console_mode=Abilita modalità Terminale
|
||||
enable_console_mode_popup=In aggiunta alla modalità file, invia i log anche al terminale.
|
||||
|
||||
optional_title=Impostazioni Facoltative
|
||||
email_title=Impostazioni E-mail
|
||||
|
||||
@@ -658,7 +658,7 @@ settings.collaboration.write=Запис
|
||||
settings.collaboration.read=Читання
|
||||
settings.collaboration.undefined=Не визначено
|
||||
settings.branches=Гілки
|
||||
settings.branches_bare=You cannot manage branches for bare repository. Please push some content first.
|
||||
settings.branches_bare=Ви не можете керувати гілками у bare-репозиторії. Спочатку додайте у нього якогось вмісту, будь ласка.
|
||||
settings.default_branch=Гілка за замовчуванням
|
||||
settings.default_branch_desc=Гілка за замовчанням вважається базовою для комітів, запросів злиття та онлайн редагувань.
|
||||
settings.update=Оновлення
|
||||
|
||||
@@ -1259,7 +1259,7 @@ delete_tag=删除了 <a href="%[1]s">%[3]s</a> 的标签 <code>%[2]s</code>
|
||||
[tool]
|
||||
ago=之前
|
||||
from_now=之后
|
||||
now=现在
|
||||
now=刚刚
|
||||
1s=1 秒%s
|
||||
1m=1 分钟%s
|
||||
1h=1 小时%s
|
||||
|
||||
@@ -73,7 +73,7 @@ Most of settings are obvious and easy to understand, but there are some settings
|
||||
|
||||
Full documentation of application settings can be found [here](https://gogs.io/docs/advanced/configuration_cheat_sheet.html).
|
||||
|
||||
### Container Options
|
||||
### Container Options
|
||||
|
||||
This container have some options available via environment variables, these options are opt-in features that can help the administration of this container:
|
||||
|
||||
|
||||
2
gogs.go
2
gogs.go
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
const APP_VER = "0.10.31.0327 / 0.11 RC"
|
||||
const APP_VER = "0.11.0.0403"
|
||||
|
||||
func init() {
|
||||
setting.AppVer = APP_VER
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
api "github.com/gogits/go-gogs-client"
|
||||
|
||||
"github.com/gogits/gogs/models/errors"
|
||||
"github.com/gogits/gogs/modules/markdown"
|
||||
"github.com/gogits/gogs/modules/markup"
|
||||
)
|
||||
|
||||
// CommentType defines whether a comment is just a simple comment, an action (like close) or a reference.
|
||||
@@ -168,7 +168,7 @@ func (c *Comment) EventTag() string {
|
||||
// mailParticipants sends new comment emails to repository watchers
|
||||
// and mentioned people.
|
||||
func (cmt *Comment) mailParticipants(e Engine, opType ActionType, issue *Issue) (err error) {
|
||||
mentions := markdown.FindAllMentions(cmt.Content)
|
||||
mentions := markup.FindAllMentions(cmt.Content)
|
||||
if err = updateIssueMentions(e, cmt.IssueID, mentions); err != nil {
|
||||
return fmt.Errorf("UpdateIssueMentions [%d]: %v", cmt.IssueID, err)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
log "gopkg.in/clog.v1"
|
||||
|
||||
"github.com/gogits/gogs/modules/mailer"
|
||||
"github.com/gogits/gogs/modules/markdown"
|
||||
"github.com/gogits/gogs/modules/markup"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
@@ -162,7 +162,7 @@ func mailIssueCommentToParticipants(issue *Issue, doer *User, mentions []string)
|
||||
// MailParticipants sends new issue thread created emails to repository watchers
|
||||
// and mentioned people.
|
||||
func (issue *Issue) MailParticipants() (err error) {
|
||||
mentions := markdown.FindAllMentions(issue.Content)
|
||||
mentions := markup.FindAllMentions(issue.Content)
|
||||
if err = updateIssueMentions(x, issue.ID, mentions); err != nil {
|
||||
return fmt.Errorf("UpdateIssueMentions [%d]: %v", issue.ID, err)
|
||||
}
|
||||
|
||||
@@ -5,10 +5,18 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
)
|
||||
|
||||
func removeInvalidProtectBranchWhitelist(x *xorm.Engine) error {
|
||||
_, err := x.Exec("DELETE FROM protect_branch_whitelist WHERE protect_branch_id = 0")
|
||||
exist, err := x.IsTableExist("protect_branch_whitelist")
|
||||
if err != nil {
|
||||
return fmt.Errorf("IsTableExist: %v", err)
|
||||
} else if !exist {
|
||||
return nil
|
||||
}
|
||||
_, err = x.Exec("DELETE FROM protect_branch_whitelist WHERE protect_branch_id = 0")
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import (
|
||||
|
||||
"github.com/gogits/gogs/models/errors"
|
||||
"github.com/gogits/gogs/modules/bindata"
|
||||
"github.com/gogits/gogs/modules/markdown"
|
||||
"github.com/gogits/gogs/modules/markup"
|
||||
"github.com/gogits/gogs/modules/process"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
"github.com/gogits/gogs/modules/sync"
|
||||
@@ -219,7 +219,7 @@ func (repo *Repository) AfterSet(colName string, _ xorm.Cell) {
|
||||
repo.NumOpenMilestones = repo.NumMilestones - repo.NumClosedMilestones
|
||||
case "external_tracker_style":
|
||||
if len(repo.ExternalTrackerStyle) == 0 {
|
||||
repo.ExternalTrackerStyle = markdown.ISSUE_NAME_STYLE_NUMERIC
|
||||
repo.ExternalTrackerStyle = markup.ISSUE_NAME_STYLE_NUMERIC
|
||||
}
|
||||
case "created_unix":
|
||||
repo.Created = time.Unix(repo.CreatedUnix, 0).Local()
|
||||
@@ -356,10 +356,10 @@ func (repo *Repository) ComposeMetas() map[string]string {
|
||||
"repo": repo.Name,
|
||||
}
|
||||
switch repo.ExternalTrackerStyle {
|
||||
case markdown.ISSUE_NAME_STYLE_ALPHANUMERIC:
|
||||
repo.ExternalMetas["style"] = markdown.ISSUE_NAME_STYLE_ALPHANUMERIC
|
||||
case markup.ISSUE_NAME_STYLE_ALPHANUMERIC:
|
||||
repo.ExternalMetas["style"] = markup.ISSUE_NAME_STYLE_ALPHANUMERIC
|
||||
default:
|
||||
repo.ExternalMetas["style"] = markdown.ISSUE_NAME_STYLE_NUMERIC
|
||||
repo.ExternalMetas["style"] = markup.ISSUE_NAME_STYLE_NUMERIC
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package models_test
|
||||
|
||||
import (
|
||||
. "github.com/gogits/gogs/models"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"testing"
|
||||
|
||||
"github.com/gogits/gogs/modules/markdown"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
. "github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/markup"
|
||||
)
|
||||
|
||||
func TestRepo(t *testing.T) {
|
||||
@@ -24,7 +25,7 @@ func TestRepo(t *testing.T) {
|
||||
Convey("It should be nil even if other settings are present", func() {
|
||||
repo.EnableExternalTracker = false
|
||||
repo.ExternalTrackerFormat = "http://someurl.com/{user}/{repo}/{issue}"
|
||||
repo.ExternalTrackerStyle = markdown.ISSUE_NAME_STYLE_NUMERIC
|
||||
repo.ExternalTrackerStyle = markup.ISSUE_NAME_STYLE_NUMERIC
|
||||
So(repo.ComposeMetas(), ShouldEqual, map[string]string(nil))
|
||||
})
|
||||
})
|
||||
@@ -33,17 +34,17 @@ func TestRepo(t *testing.T) {
|
||||
repo.EnableExternalTracker = true
|
||||
Convey("It should default to numeric issue style", func() {
|
||||
metas := repo.ComposeMetas()
|
||||
So(metas["style"], ShouldEqual, markdown.ISSUE_NAME_STYLE_NUMERIC)
|
||||
So(metas["style"], ShouldEqual, markup.ISSUE_NAME_STYLE_NUMERIC)
|
||||
})
|
||||
Convey("It should pass through numeric issue style setting", func() {
|
||||
repo.ExternalTrackerStyle = markdown.ISSUE_NAME_STYLE_NUMERIC
|
||||
repo.ExternalTrackerStyle = markup.ISSUE_NAME_STYLE_NUMERIC
|
||||
metas := repo.ComposeMetas()
|
||||
So(metas["style"], ShouldEqual, markdown.ISSUE_NAME_STYLE_NUMERIC)
|
||||
So(metas["style"], ShouldEqual, markup.ISSUE_NAME_STYLE_NUMERIC)
|
||||
})
|
||||
Convey("It should pass through alphanumeric issue style setting", func() {
|
||||
repo.ExternalTrackerStyle = markdown.ISSUE_NAME_STYLE_ALPHANUMERIC
|
||||
repo.ExternalTrackerStyle = markup.ISSUE_NAME_STYLE_ALPHANUMERIC
|
||||
metas := repo.ComposeMetas()
|
||||
So(metas["style"], ShouldEqual, markdown.ISSUE_NAME_STYLE_ALPHANUMERIC)
|
||||
So(metas["style"], ShouldEqual, markup.ISSUE_NAME_STYLE_ALPHANUMERIC)
|
||||
})
|
||||
Convey("It should contain the user name", func() {
|
||||
metas := repo.ComposeMetas()
|
||||
|
||||
@@ -67,7 +67,7 @@ func SignedInID(ctx *macaron.Context, sess session.Store) int64 {
|
||||
if id, ok := uid.(int64); ok {
|
||||
if _, err := models.GetUserByID(id); err != nil {
|
||||
if !errors.IsUserNotExist(err) {
|
||||
log.Error(2, "GetUserById: %v", err)
|
||||
log.Error(2, "GetUserByID: %v", err)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
77
modules/base/file.go
Normal file
77
modules/base/file.go
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright 2017 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package base
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IsTextFile returns true if file content format is plain text or empty.
|
||||
func IsTextFile(data []byte) bool {
|
||||
if len(data) == 0 {
|
||||
return true
|
||||
}
|
||||
return strings.Contains(http.DetectContentType(data), "text/")
|
||||
}
|
||||
|
||||
func IsImageFile(data []byte) bool {
|
||||
return strings.Contains(http.DetectContentType(data), "image/")
|
||||
}
|
||||
|
||||
func IsPDFFile(data []byte) bool {
|
||||
return strings.Contains(http.DetectContentType(data), "application/pdf")
|
||||
}
|
||||
|
||||
func IsVideoFile(data []byte) bool {
|
||||
return strings.Contains(http.DetectContentType(data), "video/")
|
||||
}
|
||||
|
||||
const (
|
||||
Byte = 1
|
||||
KByte = Byte * 1024
|
||||
MByte = KByte * 1024
|
||||
GByte = MByte * 1024
|
||||
TByte = GByte * 1024
|
||||
PByte = TByte * 1024
|
||||
EByte = PByte * 1024
|
||||
)
|
||||
|
||||
var bytesSizeTable = map[string]uint64{
|
||||
"b": Byte,
|
||||
"kb": KByte,
|
||||
"mb": MByte,
|
||||
"gb": GByte,
|
||||
"tb": TByte,
|
||||
"pb": PByte,
|
||||
"eb": EByte,
|
||||
}
|
||||
|
||||
func logn(n, b float64) float64 {
|
||||
return math.Log(n) / math.Log(b)
|
||||
}
|
||||
|
||||
func humanateBytes(s uint64, base float64, sizes []string) string {
|
||||
if s < 10 {
|
||||
return fmt.Sprintf("%d B", s)
|
||||
}
|
||||
e := math.Floor(logn(float64(s), base))
|
||||
suffix := sizes[int(e)]
|
||||
val := float64(s) / math.Pow(base, math.Floor(e))
|
||||
f := "%.0f"
|
||||
if val < 10 {
|
||||
f = "%.1f"
|
||||
}
|
||||
|
||||
return fmt.Sprintf(f+" %s", val, suffix)
|
||||
}
|
||||
|
||||
// FileSize calculates the file size and generate user-friendly string.
|
||||
func FileSize(s int64) string {
|
||||
sizes := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB"}
|
||||
return humanateBytes(uint64(s), 1024, sizes)
|
||||
}
|
||||
@@ -12,9 +12,7 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"math"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
@@ -346,51 +344,6 @@ func TimeSince(t time.Time, lang string) template.HTML {
|
||||
return template.HTML(fmt.Sprintf(`<span class="time-since" title="%s">%s</span>`, t.Format(setting.TimeFormat), timeSince(t, lang)))
|
||||
}
|
||||
|
||||
const (
|
||||
Byte = 1
|
||||
KByte = Byte * 1024
|
||||
MByte = KByte * 1024
|
||||
GByte = MByte * 1024
|
||||
TByte = GByte * 1024
|
||||
PByte = TByte * 1024
|
||||
EByte = PByte * 1024
|
||||
)
|
||||
|
||||
var bytesSizeTable = map[string]uint64{
|
||||
"b": Byte,
|
||||
"kb": KByte,
|
||||
"mb": MByte,
|
||||
"gb": GByte,
|
||||
"tb": TByte,
|
||||
"pb": PByte,
|
||||
"eb": EByte,
|
||||
}
|
||||
|
||||
func logn(n, b float64) float64 {
|
||||
return math.Log(n) / math.Log(b)
|
||||
}
|
||||
|
||||
func humanateBytes(s uint64, base float64, sizes []string) string {
|
||||
if s < 10 {
|
||||
return fmt.Sprintf("%d B", s)
|
||||
}
|
||||
e := math.Floor(logn(float64(s), base))
|
||||
suffix := sizes[int(e)]
|
||||
val := float64(s) / math.Pow(base, math.Floor(e))
|
||||
f := "%.0f"
|
||||
if val < 10 {
|
||||
f = "%.1f"
|
||||
}
|
||||
|
||||
return fmt.Sprintf(f+" %s", val, suffix)
|
||||
}
|
||||
|
||||
// FileSize calculates the file size and generate user-friendly string.
|
||||
func FileSize(s int64) string {
|
||||
sizes := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB"}
|
||||
return humanateBytes(uint64(s), 1024, sizes)
|
||||
}
|
||||
|
||||
// Subtract deals with subtraction of all types of number.
|
||||
func Subtract(left interface{}, right interface{}) interface{} {
|
||||
var rleft, rright int64
|
||||
@@ -491,23 +444,3 @@ func Int64sToMap(ints []int64) map[int64]bool {
|
||||
func IsLetter(ch rune) bool {
|
||||
return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= 0x80 && unicode.IsLetter(ch)
|
||||
}
|
||||
|
||||
// IsTextFile returns true if file content format is plain text or empty.
|
||||
func IsTextFile(data []byte) bool {
|
||||
if len(data) == 0 {
|
||||
return true
|
||||
}
|
||||
return strings.Index(http.DetectContentType(data), "text/") != -1
|
||||
}
|
||||
|
||||
func IsImageFile(data []byte) bool {
|
||||
return strings.Index(http.DetectContentType(data), "image/") != -1
|
||||
}
|
||||
|
||||
func IsPDFFile(data []byte) bool {
|
||||
return strings.Index(http.DetectContentType(data), "application/pdf") != -1
|
||||
}
|
||||
|
||||
func IsVideoFile(data []byte) bool {
|
||||
return strings.Index(http.DetectContentType(data), "video/") != -1
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -79,12 +79,17 @@ func (ctx *Context) HasValue(name string) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// HTML calls Context.HTML and converts template name to string.
|
||||
// HTML responses template with given status.
|
||||
func (ctx *Context) HTML(status int, name base.TplName) {
|
||||
log.Trace("Template: %s", name)
|
||||
ctx.Context.HTML(status, string(name))
|
||||
}
|
||||
|
||||
// Success responses template with status 200.
|
||||
func (c *Context) Success(name base.TplName) {
|
||||
c.HTML(200, name)
|
||||
}
|
||||
|
||||
// RenderWithErr used for page has form validation but need to prompt error to users.
|
||||
func (ctx *Context) RenderWithErr(msg string, tpl base.TplName, f interface{}) {
|
||||
if f != nil {
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"gopkg.in/macaron.v1"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/markdown"
|
||||
"github.com/gogits/gogs/modules/markup"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
@@ -174,7 +174,7 @@ func composeTplData(subject, body, link string) map[string]interface{} {
|
||||
|
||||
func composeIssueMessage(issue Issue, repo Repository, doer User, tplName base.TplName, tos []string, info string) *Message {
|
||||
subject := issue.MailSubject()
|
||||
body := string(markdown.RenderSpecialLink([]byte(issue.Content()), repo.HTMLURL(), repo.ComposeMetas()))
|
||||
body := string(markup.RenderSpecialLink([]byte(issue.Content()), repo.HTMLURL(), repo.ComposeMetas()))
|
||||
data := composeTplData(subject, body, issue.HTMLURL())
|
||||
data["Doer"] = doer
|
||||
content, err := mailRender.HTMLString(string(tplName), data)
|
||||
|
||||
167
modules/markup/markdown.go
Normal file
167
modules/markup/markdown.go
Normal file
@@ -0,0 +1,167 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package markup
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/russross/blackfriday"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
// IsMarkdownFile reports whether name looks like a Markdown file based on its extension.
|
||||
func IsMarkdownFile(name string) bool {
|
||||
extension := strings.ToLower(filepath.Ext(name))
|
||||
for _, ext := range setting.Markdown.FileExtensions {
|
||||
if strings.ToLower(ext) == extension {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MarkdownRenderer is a extended version of underlying Markdown render object.
|
||||
type MarkdownRenderer struct {
|
||||
blackfriday.Renderer
|
||||
urlPrefix string
|
||||
}
|
||||
|
||||
var validLinksPattern = regexp.MustCompile(`^[a-z][\w-]+://|^mailto:`)
|
||||
|
||||
// isLink reports whether link fits valid format.
|
||||
func isLink(link []byte) bool {
|
||||
return validLinksPattern.Match(link)
|
||||
}
|
||||
|
||||
// Link defines how formal links should be processed to produce corresponding HTML elements.
|
||||
func (r *MarkdownRenderer) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
|
||||
if len(link) > 0 && !isLink(link) {
|
||||
if link[0] != '#' {
|
||||
link = []byte(path.Join(r.urlPrefix, string(link)))
|
||||
}
|
||||
}
|
||||
|
||||
r.Renderer.Link(out, link, title, content)
|
||||
}
|
||||
|
||||
// AutoLink defines how auto-detected links should be processed to produce corresponding HTML elements.
|
||||
// Reference for kind: https://github.com/russross/blackfriday/blob/master/markdown.go#L69-L76
|
||||
func (r *MarkdownRenderer) AutoLink(out *bytes.Buffer, link []byte, kind int) {
|
||||
if kind != blackfriday.LINK_TYPE_NORMAL {
|
||||
r.Renderer.AutoLink(out, link, kind)
|
||||
return
|
||||
}
|
||||
|
||||
// Since this method could only possibly serve one link at a time,
|
||||
// we do not need to find all.
|
||||
if bytes.HasPrefix(link, []byte(setting.AppUrl)) {
|
||||
m := CommitPattern.Find(link)
|
||||
if m != nil {
|
||||
m = bytes.TrimSpace(m)
|
||||
i := strings.Index(string(m), "commit/")
|
||||
j := strings.Index(string(m), "#")
|
||||
if j == -1 {
|
||||
j = len(m)
|
||||
}
|
||||
out.WriteString(fmt.Sprintf(` <code><a href="%s">%s</a></code>`, m, base.ShortSha(string(m[i+7:j]))))
|
||||
return
|
||||
}
|
||||
|
||||
m = IssueFullPattern.Find(link)
|
||||
if m != nil {
|
||||
m = bytes.TrimSpace(m)
|
||||
i := strings.Index(string(m), "issues/")
|
||||
j := strings.Index(string(m), "#")
|
||||
if j == -1 {
|
||||
j = len(m)
|
||||
}
|
||||
|
||||
index := string(m[i+7 : j])
|
||||
fullRepoURL := setting.AppUrl + strings.TrimPrefix(r.urlPrefix, "/")
|
||||
var link string
|
||||
if strings.HasPrefix(string(m), fullRepoURL) {
|
||||
// Use a short issue reference if the URL refers to this repository
|
||||
link = fmt.Sprintf(`<a href="%s">#%s</a>`, m, index)
|
||||
} else {
|
||||
// Use a cross-repository issue reference if the URL refers to a different repository
|
||||
repo := string(m[len(setting.AppUrl) : i-1])
|
||||
link = fmt.Sprintf(`<a href="%s">%s#%s</a>`, m, repo, index)
|
||||
}
|
||||
out.WriteString(link)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
r.Renderer.AutoLink(out, link, kind)
|
||||
}
|
||||
|
||||
// ListItem defines how list items should be processed to produce corresponding HTML elements.
|
||||
func (options *MarkdownRenderer) ListItem(out *bytes.Buffer, text []byte, flags int) {
|
||||
// Detect procedures to draw checkboxes.
|
||||
switch {
|
||||
case bytes.HasPrefix(text, []byte("[ ] ")):
|
||||
text = append([]byte(`<input type="checkbox" disabled="" />`), text[3:]...)
|
||||
case bytes.HasPrefix(text, []byte("[x] ")):
|
||||
text = append([]byte(`<input type="checkbox" disabled="" checked="" />`), text[3:]...)
|
||||
}
|
||||
options.Renderer.ListItem(out, text, flags)
|
||||
}
|
||||
|
||||
// RawMarkdown renders Markdown to HTML without handling special links.
|
||||
func RawMarkdown(body []byte, urlPrefix string) []byte {
|
||||
htmlFlags := 0
|
||||
htmlFlags |= blackfriday.HTML_SKIP_STYLE
|
||||
htmlFlags |= blackfriday.HTML_OMIT_CONTENTS
|
||||
|
||||
if setting.Smartypants.Enabled {
|
||||
htmlFlags |= blackfriday.HTML_USE_SMARTYPANTS
|
||||
if setting.Smartypants.Fractions {
|
||||
htmlFlags |= blackfriday.HTML_SMARTYPANTS_FRACTIONS
|
||||
}
|
||||
if setting.Smartypants.Dashes {
|
||||
htmlFlags |= blackfriday.HTML_SMARTYPANTS_DASHES
|
||||
}
|
||||
if setting.Smartypants.LatexDashes {
|
||||
htmlFlags |= blackfriday.HTML_SMARTYPANTS_LATEX_DASHES
|
||||
}
|
||||
if setting.Smartypants.AngledQuotes {
|
||||
htmlFlags |= blackfriday.HTML_SMARTYPANTS_ANGLED_QUOTES
|
||||
}
|
||||
}
|
||||
|
||||
renderer := &MarkdownRenderer{
|
||||
Renderer: blackfriday.HtmlRenderer(htmlFlags, "", ""),
|
||||
urlPrefix: urlPrefix,
|
||||
}
|
||||
|
||||
// set up the parser
|
||||
extensions := 0
|
||||
extensions |= blackfriday.EXTENSION_NO_INTRA_EMPHASIS
|
||||
extensions |= blackfriday.EXTENSION_TABLES
|
||||
extensions |= blackfriday.EXTENSION_FENCED_CODE
|
||||
extensions |= blackfriday.EXTENSION_AUTOLINK
|
||||
extensions |= blackfriday.EXTENSION_STRIKETHROUGH
|
||||
extensions |= blackfriday.EXTENSION_SPACE_HEADERS
|
||||
extensions |= blackfriday.EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK
|
||||
|
||||
if setting.Markdown.EnableHardLineBreak {
|
||||
extensions |= blackfriday.EXTENSION_HARD_LINE_BREAK
|
||||
}
|
||||
|
||||
body = blackfriday.Markdown(body, renderer, extensions)
|
||||
return body
|
||||
}
|
||||
|
||||
// Markdown takes a string or []byte and renders to HTML in Markdown syntax with special links.
|
||||
func Markdown(input interface{}, urlPrefix string, metas map[string]string) []byte {
|
||||
return Render(MARKDOWN, input, urlPrefix, metas)
|
||||
}
|
||||
111
modules/markup/markdown_test.go
Normal file
111
modules/markup/markdown_test.go
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package markup_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/russross/blackfriday"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
. "github.com/gogits/gogs/modules/markup"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
func Test_IsMarkdownFile(t *testing.T) {
|
||||
setting.Markdown.FileExtensions = strings.Split(".md,.markdown,.mdown,.mkd", ",")
|
||||
Convey("Detect Markdown file extension", t, func() {
|
||||
testCases := []struct {
|
||||
ext string
|
||||
match bool
|
||||
}{
|
||||
{".md", true},
|
||||
{".markdown", true},
|
||||
{".mdown", true},
|
||||
{".mkd", true},
|
||||
{".org", false},
|
||||
{".rst", false},
|
||||
{".asciidoc", false},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
So(IsMarkdownFile(tc.ext), ShouldEqual, tc.match)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Markdown(t *testing.T) {
|
||||
Convey("Rendering an issue URL", t, func() {
|
||||
setting.AppUrl = "http://localhost:3000/"
|
||||
htmlFlags := 0
|
||||
htmlFlags |= blackfriday.HTML_SKIP_STYLE
|
||||
htmlFlags |= blackfriday.HTML_OMIT_CONTENTS
|
||||
renderer := &MarkdownRenderer{
|
||||
Renderer: blackfriday.HtmlRenderer(htmlFlags, "", ""),
|
||||
}
|
||||
buffer := new(bytes.Buffer)
|
||||
Convey("To the internal issue tracker", func() {
|
||||
Convey("It should render valid issue URLs", func() {
|
||||
testCases := []string{
|
||||
"http://localhost:3000/user/repo/issues/3333", "<a href=\"http://localhost:3000/user/repo/issues/3333\">#3333</a>",
|
||||
}
|
||||
|
||||
for i := 0; i < len(testCases); i += 2 {
|
||||
renderer.AutoLink(buffer, []byte(testCases[i]), blackfriday.LINK_TYPE_NORMAL)
|
||||
|
||||
line, _ := buffer.ReadString(0)
|
||||
So(line, ShouldEqual, testCases[i+1])
|
||||
}
|
||||
})
|
||||
Convey("It should render but not change non-issue URLs", func() {
|
||||
testCases := []string{
|
||||
"http://1111/2222/ssss-issues/3333?param=blah&blahh=333", "<a href=\"http://1111/2222/ssss-issues/3333?param=blah&blahh=333\">http://1111/2222/ssss-issues/3333?param=blah&blahh=333</a>",
|
||||
"http://test.com/issues/33333", "<a href=\"http://test.com/issues/33333\">http://test.com/issues/33333</a>",
|
||||
"http://test.com/issues/3", "<a href=\"http://test.com/issues/3\">http://test.com/issues/3</a>",
|
||||
"http://issues/333", "<a href=\"http://issues/333\">http://issues/333</a>",
|
||||
"https://issues/333", "<a href=\"https://issues/333\">https://issues/333</a>",
|
||||
"http://tissues/0", "<a href=\"http://tissues/0\">http://tissues/0</a>",
|
||||
}
|
||||
|
||||
for i := 0; i < len(testCases); i += 2 {
|
||||
renderer.AutoLink(buffer, []byte(testCases[i]), blackfriday.LINK_TYPE_NORMAL)
|
||||
|
||||
line, _ := buffer.ReadString(0)
|
||||
So(line, ShouldEqual, testCases[i+1])
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Rendering a commit URL", t, func() {
|
||||
setting.AppUrl = "http://localhost:3000/"
|
||||
htmlFlags := 0
|
||||
htmlFlags |= blackfriday.HTML_SKIP_STYLE
|
||||
htmlFlags |= blackfriday.HTML_OMIT_CONTENTS
|
||||
renderer := &MarkdownRenderer{
|
||||
Renderer: blackfriday.HtmlRenderer(htmlFlags, "", ""),
|
||||
}
|
||||
buffer := new(bytes.Buffer)
|
||||
Convey("To the internal issue tracker", func() {
|
||||
Convey("It should correctly convert URLs", func() {
|
||||
testCases := []string{
|
||||
"http://localhost:3000/user/project/commit/d8a994ef243349f321568f9e36d5c3f444b99cae", " <code><a href=\"http://localhost:3000/user/project/commit/d8a994ef243349f321568f9e36d5c3f444b99cae\">d8a994ef24</a></code>",
|
||||
"http://localhost:3000/user/project/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2", " <code><a href=\"http://localhost:3000/user/project/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2\">d8a994ef24</a></code>",
|
||||
"https://external-link.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2", "<a href=\"https://external-link.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2\">https://external-link.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2</a>",
|
||||
"https://commit/d8a994ef243349f321568f9e36d5c3f444b99cae", "<a href=\"https://commit/d8a994ef243349f321568f9e36d5c3f444b99cae\">https://commit/d8a994ef243349f321568f9e36d5c3f444b99cae</a>",
|
||||
}
|
||||
|
||||
for i := 0; i < len(testCases); i += 2 {
|
||||
renderer.AutoLink(buffer, []byte(testCases[i]), blackfriday.LINK_TYPE_NORMAL)
|
||||
|
||||
line, _ := buffer.ReadString(0)
|
||||
So(line, ShouldEqual, testCases[i+1])
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1,79 +1,33 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2017 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package markdown
|
||||
package markup
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"github.com/russross/blackfriday"
|
||||
"golang.org/x/net/html"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
// IsReadmeFile reports whether name looks like a README file based on its extension.
|
||||
func IsReadmeFile(name string) bool {
|
||||
return strings.HasPrefix(strings.ToLower(name), "readme")
|
||||
}
|
||||
|
||||
const (
|
||||
ISSUE_NAME_STYLE_NUMERIC = "numeric"
|
||||
ISSUE_NAME_STYLE_ALPHANUMERIC = "alphanumeric"
|
||||
)
|
||||
|
||||
var Sanitizer = bluemonday.UGCPolicy()
|
||||
|
||||
// BuildSanitizer initializes sanitizer with allowed attributes based on settings.
|
||||
// This function should only be called once during entire application lifecycle.
|
||||
func BuildSanitizer() {
|
||||
// Normal markdown-stuff
|
||||
Sanitizer.AllowAttrs("class").Matching(regexp.MustCompile(`[\p{L}\p{N}\s\-_',:\[\]!\./\\\(\)&]*`)).OnElements("code")
|
||||
|
||||
// Checkboxes
|
||||
Sanitizer.AllowAttrs("type").Matching(regexp.MustCompile(`^checkbox$`)).OnElements("input")
|
||||
Sanitizer.AllowAttrs("checked", "disabled").OnElements("input")
|
||||
|
||||
// Custom URL-Schemes
|
||||
Sanitizer.AllowURLSchemes(setting.Markdown.CustomURLSchemes...)
|
||||
}
|
||||
|
||||
var validLinksPattern = regexp.MustCompile(`^[a-z][\w-]+://|^mailto:`)
|
||||
|
||||
// isLink reports whether link fits valid format.
|
||||
func isLink(link []byte) bool {
|
||||
return validLinksPattern.Match(link)
|
||||
}
|
||||
|
||||
// IsMarkdownFile reports whether name looks like a Markdown file
|
||||
// based on its extension.
|
||||
func IsMarkdownFile(name string) bool {
|
||||
extension := strings.ToLower(filepath.Ext(name))
|
||||
for _, ext := range setting.Markdown.FileExtensions {
|
||||
if strings.ToLower(ext) == extension {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsReadmeFile reports whether name looks like a README file
|
||||
// based on its extension.
|
||||
func IsReadmeFile(name string) bool {
|
||||
name = strings.ToLower(name)
|
||||
if len(name) < 6 {
|
||||
return false
|
||||
} else if len(name) == 6 {
|
||||
return name == "readme"
|
||||
}
|
||||
return name[:7] == "readme."
|
||||
}
|
||||
|
||||
var (
|
||||
// MentionPattern matches string that mentions someone, e.g. @Unknwon
|
||||
MentionPattern = regexp.MustCompile(`(\s|^|\W)@[0-9a-zA-Z-_\.]+`)
|
||||
@@ -109,94 +63,6 @@ func FindAllMentions(content string) []string {
|
||||
return mentions
|
||||
}
|
||||
|
||||
// Renderer is a extended version of underlying render object.
|
||||
type Renderer struct {
|
||||
blackfriday.Renderer
|
||||
urlPrefix string
|
||||
}
|
||||
|
||||
// Link defines how formal links should be processed to produce corresponding HTML elements.
|
||||
func (r *Renderer) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
|
||||
if len(link) > 0 && !isLink(link) {
|
||||
if link[0] != '#' {
|
||||
link = []byte(path.Join(r.urlPrefix, string(link)))
|
||||
}
|
||||
}
|
||||
|
||||
r.Renderer.Link(out, link, title, content)
|
||||
}
|
||||
|
||||
// AutoLink defines how auto-detected links should be processed to produce corresponding HTML elements.
|
||||
// Reference for kind: https://github.com/russross/blackfriday/blob/master/markdown.go#L69-L76
|
||||
func (r *Renderer) AutoLink(out *bytes.Buffer, link []byte, kind int) {
|
||||
if kind != blackfriday.LINK_TYPE_NORMAL {
|
||||
r.Renderer.AutoLink(out, link, kind)
|
||||
return
|
||||
}
|
||||
|
||||
// Since this method could only possibly serve one link at a time,
|
||||
// we do not need to find all.
|
||||
if bytes.HasPrefix(link, []byte(setting.AppUrl)) {
|
||||
m := CommitPattern.Find(link)
|
||||
if m != nil {
|
||||
m = bytes.TrimSpace(m)
|
||||
i := strings.Index(string(m), "commit/")
|
||||
j := strings.Index(string(m), "#")
|
||||
if j == -1 {
|
||||
j = len(m)
|
||||
}
|
||||
out.WriteString(fmt.Sprintf(` <code><a href="%s">%s</a></code>`, m, base.ShortSha(string(m[i+7:j]))))
|
||||
return
|
||||
}
|
||||
|
||||
m = IssueFullPattern.Find(link)
|
||||
if m != nil {
|
||||
m = bytes.TrimSpace(m)
|
||||
i := strings.Index(string(m), "issues/")
|
||||
j := strings.Index(string(m), "#")
|
||||
if j == -1 {
|
||||
j = len(m)
|
||||
}
|
||||
|
||||
index := string(m[i+7 : j])
|
||||
fullRepoURL := setting.AppUrl + strings.TrimPrefix(r.urlPrefix, "/")
|
||||
var link string
|
||||
if strings.HasPrefix(string(m), fullRepoURL) {
|
||||
// Use a short issue reference if the URL refers to this repository
|
||||
link = fmt.Sprintf(`<a href="%s">#%s</a>`, m, index)
|
||||
} else {
|
||||
// Use a cross-repository issue reference if the URL refers to a different repository
|
||||
repo := string(m[len(setting.AppUrl) : i-1])
|
||||
link = fmt.Sprintf(`<a href="%s">%s#%s</a>`, m, repo, index)
|
||||
}
|
||||
out.WriteString(link)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
r.Renderer.AutoLink(out, link, kind)
|
||||
}
|
||||
|
||||
// ListItem defines how list items should be processed to produce corresponding HTML elements.
|
||||
func (options *Renderer) ListItem(out *bytes.Buffer, text []byte, flags int) {
|
||||
// Detect procedures to draw checkboxes.
|
||||
switch {
|
||||
case bytes.HasPrefix(text, []byte("[ ] ")):
|
||||
text = append([]byte(`<input type="checkbox" disabled="" />`), text[3:]...)
|
||||
case bytes.HasPrefix(text, []byte("[x] ")):
|
||||
text = append([]byte(`<input type="checkbox" disabled="" checked="" />`), text[3:]...)
|
||||
}
|
||||
options.Renderer.ListItem(out, text, flags)
|
||||
}
|
||||
|
||||
// Note: this section is for purpose of increase performance and
|
||||
// reduce memory allocation at runtime since they are constant literals.
|
||||
var (
|
||||
pound = []byte("#")
|
||||
space = " "
|
||||
spaceEncoded = "%20"
|
||||
)
|
||||
|
||||
// cutoutVerbosePrefix cutouts URL prefix including sub-path to
|
||||
// return a clean unified string of request URL path.
|
||||
func cutoutVerbosePrefix(prefix string) string {
|
||||
@@ -246,6 +112,10 @@ func RenderIssueIndexPattern(rawBytes []byte, urlPrefix string, metas map[string
|
||||
return rawBytes
|
||||
}
|
||||
|
||||
// Note: this section is for purpose of increase performance and
|
||||
// reduce memory allocation at runtime since they are constant literals.
|
||||
var pound = []byte("#")
|
||||
|
||||
// RenderCrossReferenceIssueIndexPattern renders issue indexes from other repositories to corresponding links.
|
||||
func RenderCrossReferenceIssueIndexPattern(rawBytes []byte, urlPrefix string, metas map[string]string) []byte {
|
||||
ms := CrossReferenceIssueNumericPattern.FindAll(rawBytes, -1)
|
||||
@@ -289,51 +159,6 @@ func RenderSpecialLink(rawBytes []byte, urlPrefix string, metas map[string]strin
|
||||
return rawBytes
|
||||
}
|
||||
|
||||
// RenderRaw renders Markdown to HTML without handling special links.
|
||||
func RenderRaw(body []byte, urlPrefix string) []byte {
|
||||
htmlFlags := 0
|
||||
htmlFlags |= blackfriday.HTML_SKIP_STYLE
|
||||
htmlFlags |= blackfriday.HTML_OMIT_CONTENTS
|
||||
|
||||
if setting.Smartypants.Enabled {
|
||||
htmlFlags |= blackfriday.HTML_USE_SMARTYPANTS
|
||||
if setting.Smartypants.Fractions {
|
||||
htmlFlags |= blackfriday.HTML_SMARTYPANTS_FRACTIONS
|
||||
}
|
||||
if setting.Smartypants.Dashes {
|
||||
htmlFlags |= blackfriday.HTML_SMARTYPANTS_DASHES
|
||||
}
|
||||
if setting.Smartypants.LatexDashes {
|
||||
htmlFlags |= blackfriday.HTML_SMARTYPANTS_LATEX_DASHES
|
||||
}
|
||||
if setting.Smartypants.AngledQuotes {
|
||||
htmlFlags |= blackfriday.HTML_SMARTYPANTS_ANGLED_QUOTES
|
||||
}
|
||||
}
|
||||
|
||||
renderer := &Renderer{
|
||||
Renderer: blackfriday.HtmlRenderer(htmlFlags, "", ""),
|
||||
urlPrefix: urlPrefix,
|
||||
}
|
||||
|
||||
// set up the parser
|
||||
extensions := 0
|
||||
extensions |= blackfriday.EXTENSION_NO_INTRA_EMPHASIS
|
||||
extensions |= blackfriday.EXTENSION_TABLES
|
||||
extensions |= blackfriday.EXTENSION_FENCED_CODE
|
||||
extensions |= blackfriday.EXTENSION_AUTOLINK
|
||||
extensions |= blackfriday.EXTENSION_STRIKETHROUGH
|
||||
extensions |= blackfriday.EXTENSION_SPACE_HEADERS
|
||||
extensions |= blackfriday.EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK
|
||||
|
||||
if setting.Markdown.EnableHardLineBreak {
|
||||
extensions |= blackfriday.EXTENSION_HARD_LINE_BREAK
|
||||
}
|
||||
|
||||
body = blackfriday.Markdown(body, renderer, extensions)
|
||||
return body
|
||||
}
|
||||
|
||||
var (
|
||||
leftAngleBracket = []byte("</")
|
||||
rightAngleBracket = []byte(">")
|
||||
@@ -343,8 +168,8 @@ var noEndTags = []string{"input", "br", "hr", "img"}
|
||||
|
||||
// wrapImgWithLink warps link to standalone <img> tags.
|
||||
func wrapImgWithLink(urlPrefix string, buf *bytes.Buffer, token html.Token) {
|
||||
var src, alt string
|
||||
// Extract "src" and "alt" attributes
|
||||
var src, alt string
|
||||
for i := range token.Attr {
|
||||
switch token.Attr[i].Key {
|
||||
case "src":
|
||||
@@ -399,9 +224,9 @@ func wrapImgWithLink(urlPrefix string, buf *bytes.Buffer, token html.Token) {
|
||||
buf.WriteString(`</a>`)
|
||||
}
|
||||
|
||||
// PostProcess treats different types of HTML differently,
|
||||
// postProcessHTML treats different types of HTML differently,
|
||||
// and only renders special links for plain text blocks.
|
||||
func PostProcess(rawHTML []byte, urlPrefix string, metas map[string]string) []byte {
|
||||
func postProcessHTML(rawHTML []byte, urlPrefix string, metas map[string]string) []byte {
|
||||
startTags := make([]string, 0, 5)
|
||||
buf := bytes.NewBuffer(nil)
|
||||
tokenizer := html.NewTokenizer(bytes.NewReader(rawHTML))
|
||||
@@ -475,16 +300,36 @@ OUTER_LOOP:
|
||||
return rawHTML
|
||||
}
|
||||
|
||||
// Render renders Markdown to HTML with special links.
|
||||
func Render(rawBytes []byte, urlPrefix string, metas map[string]string) []byte {
|
||||
urlPrefix = strings.Replace(urlPrefix, space, spaceEncoded, -1)
|
||||
result := RenderRaw(rawBytes, urlPrefix)
|
||||
result = PostProcess(result, urlPrefix, metas)
|
||||
result = Sanitizer.SanitizeBytes(result)
|
||||
return result
|
||||
}
|
||||
type Type string
|
||||
|
||||
// RenderString renders Markdown to HTML with special links and returns string type.
|
||||
func RenderString(raw, urlPrefix string, metas map[string]string) string {
|
||||
return string(Render([]byte(raw), urlPrefix, metas))
|
||||
const (
|
||||
UNRECOGNIZED Type = "unrecognized"
|
||||
MARKDOWN Type = "markdown"
|
||||
ORG_MODE Type = "orgmode"
|
||||
)
|
||||
|
||||
// Render takes a string or []byte and renders to HTML in given type of syntax with special links.
|
||||
func Render(typ Type, input interface{}, urlPrefix string, metas map[string]string) []byte {
|
||||
var rawBytes []byte
|
||||
switch v := input.(type) {
|
||||
case []byte:
|
||||
rawBytes = v
|
||||
case string:
|
||||
rawBytes = []byte(v)
|
||||
default:
|
||||
panic(fmt.Sprintf("unrecognized input content type: %T", input))
|
||||
}
|
||||
|
||||
urlPrefix = strings.Replace(urlPrefix, " ", "%20", -1)
|
||||
var rawHTML []byte
|
||||
switch typ {
|
||||
case MARKDOWN:
|
||||
rawHTML = RawMarkdown(rawBytes, urlPrefix)
|
||||
case ORG_MODE:
|
||||
default:
|
||||
return rawBytes // Do nothing if syntax type is not recognized
|
||||
}
|
||||
|
||||
rawHTML = postProcessHTML(rawHTML, urlPrefix, metas)
|
||||
return SanitizeBytes(rawHTML)
|
||||
}
|
||||
@@ -1,17 +1,63 @@
|
||||
package markdown_test
|
||||
// Copyright 2017 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package markup_test
|
||||
|
||||
import (
|
||||
. "github.com/gogits/gogs/modules/markdown"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"bytes"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
. "github.com/gogits/gogs/modules/markup"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
"github.com/russross/blackfriday"
|
||||
)
|
||||
|
||||
func TestMarkdown(t *testing.T) {
|
||||
Convey("Rendering an issue mention", t, func() {
|
||||
func Test_IsReadmeFile(t *testing.T) {
|
||||
Convey("Detect README file extension", t, func() {
|
||||
testCases := []struct {
|
||||
ext string
|
||||
match bool
|
||||
}{
|
||||
{"readme", true},
|
||||
{"README", true},
|
||||
{"readme.md", true},
|
||||
{"readme.markdown", true},
|
||||
{"readme.mdown", true},
|
||||
{"readme.mkd", true},
|
||||
{"readme.org", true},
|
||||
{"readme.rst", true},
|
||||
{"readme.asciidoc", true},
|
||||
{"readme_ZH", true},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
So(IsReadmeFile(tc.ext), ShouldEqual, tc.match)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_FindAllMentions(t *testing.T) {
|
||||
Convey("Find all mention patterns", t, func() {
|
||||
testCases := []struct {
|
||||
content string
|
||||
matches string
|
||||
}{
|
||||
{"@Unknwon, what do you think?", "Unknwon"},
|
||||
{"@Unknwon what do you think?", "Unknwon"},
|
||||
{"Hi @Unknwon, sounds good to me", "Unknwon"},
|
||||
{"cc/ @Unknwon @User", "Unknwon,User"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
So(strings.Join(FindAllMentions(tc.content), ","), ShouldEqual, tc.matches)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_RenderIssueIndexPattern(t *testing.T) {
|
||||
Convey("Rendering an issue reference", t, func() {
|
||||
var (
|
||||
urlPrefix = "/prefix"
|
||||
metas map[string]string = nil
|
||||
@@ -235,74 +281,4 @@ func TestMarkdown(t *testing.T) {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Rendering an issue URL", t, func() {
|
||||
setting.AppUrl = "http://localhost:3000/"
|
||||
htmlFlags := 0
|
||||
htmlFlags |= blackfriday.HTML_SKIP_STYLE
|
||||
htmlFlags |= blackfriday.HTML_OMIT_CONTENTS
|
||||
renderer := &Renderer{
|
||||
Renderer: blackfriday.HtmlRenderer(htmlFlags, "", ""),
|
||||
}
|
||||
buffer := new(bytes.Buffer)
|
||||
Convey("To the internal issue tracker", func() {
|
||||
Convey("It should render valid issue URLs", func() {
|
||||
testCases := []string{
|
||||
"http://localhost:3000/user/repo/issues/3333", "<a href=\"http://localhost:3000/user/repo/issues/3333\">#3333</a>",
|
||||
}
|
||||
|
||||
for i := 0; i < len(testCases); i += 2 {
|
||||
renderer.AutoLink(buffer, []byte(testCases[i]), blackfriday.LINK_TYPE_NORMAL)
|
||||
|
||||
line, _ := buffer.ReadString(0)
|
||||
So(line, ShouldEqual, testCases[i+1])
|
||||
}
|
||||
})
|
||||
Convey("It should render but not change non-issue URLs", func() {
|
||||
testCases := []string{
|
||||
"http://1111/2222/ssss-issues/3333?param=blah&blahh=333", "<a href=\"http://1111/2222/ssss-issues/3333?param=blah&blahh=333\">http://1111/2222/ssss-issues/3333?param=blah&blahh=333</a>",
|
||||
"http://test.com/issues/33333", "<a href=\"http://test.com/issues/33333\">http://test.com/issues/33333</a>",
|
||||
"http://test.com/issues/3", "<a href=\"http://test.com/issues/3\">http://test.com/issues/3</a>",
|
||||
"http://issues/333", "<a href=\"http://issues/333\">http://issues/333</a>",
|
||||
"https://issues/333", "<a href=\"https://issues/333\">https://issues/333</a>",
|
||||
"http://tissues/0", "<a href=\"http://tissues/0\">http://tissues/0</a>",
|
||||
}
|
||||
|
||||
for i := 0; i < len(testCases); i += 2 {
|
||||
renderer.AutoLink(buffer, []byte(testCases[i]), blackfriday.LINK_TYPE_NORMAL)
|
||||
|
||||
line, _ := buffer.ReadString(0)
|
||||
So(line, ShouldEqual, testCases[i+1])
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Rendering a commit URL", t, func() {
|
||||
setting.AppUrl = "http://localhost:3000/"
|
||||
htmlFlags := 0
|
||||
htmlFlags |= blackfriday.HTML_SKIP_STYLE
|
||||
htmlFlags |= blackfriday.HTML_OMIT_CONTENTS
|
||||
renderer := &Renderer{
|
||||
Renderer: blackfriday.HtmlRenderer(htmlFlags, "", ""),
|
||||
}
|
||||
buffer := new(bytes.Buffer)
|
||||
Convey("To the internal issue tracker", func() {
|
||||
Convey("It should correctly convert URLs", func() {
|
||||
testCases := []string{
|
||||
"http://localhost:3000/user/project/commit/d8a994ef243349f321568f9e36d5c3f444b99cae", " <code><a href=\"http://localhost:3000/user/project/commit/d8a994ef243349f321568f9e36d5c3f444b99cae\">d8a994ef24</a></code>",
|
||||
"http://localhost:3000/user/project/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2", " <code><a href=\"http://localhost:3000/user/project/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2\">d8a994ef24</a></code>",
|
||||
"https://external-link.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2", "<a href=\"https://external-link.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2\">https://external-link.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2</a>",
|
||||
"https://commit/d8a994ef243349f321568f9e36d5c3f444b99cae", "<a href=\"https://commit/d8a994ef243349f321568f9e36d5c3f444b99cae\">https://commit/d8a994ef243349f321568f9e36d5c3f444b99cae</a>",
|
||||
}
|
||||
|
||||
for i := 0; i < len(testCases); i += 2 {
|
||||
renderer.AutoLink(buffer, []byte(testCases[i]), blackfriday.LINK_TYPE_NORMAL)
|
||||
|
||||
line, _ := buffer.ReadString(0)
|
||||
So(line, ShouldEqual, testCases[i+1])
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
51
modules/markup/sanitizer.go
Normal file
51
modules/markup/sanitizer.go
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright 2017 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package markup
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"sync"
|
||||
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
// Sanitizer is a protection wrapper of *bluemonday.Policy which does not allow
|
||||
// any modification to the underlying policies once it's been created.
|
||||
type Sanitizer struct {
|
||||
policy *bluemonday.Policy
|
||||
init sync.Once
|
||||
}
|
||||
|
||||
var sanitizer = &Sanitizer{}
|
||||
|
||||
// NewSanitizer initializes sanitizer with allowed attributes based on settings.
|
||||
// Multiple calls to this function will only create one instance of Sanitizer during
|
||||
// entire application lifecycle.
|
||||
func NewSanitizer() {
|
||||
sanitizer.init.Do(func() {
|
||||
sanitizer.policy = bluemonday.UGCPolicy()
|
||||
// We only want to allow HighlightJS specific classes for code blocks
|
||||
sanitizer.policy.AllowAttrs("class").Matching(regexp.MustCompile(`^language-\w+$`)).OnElements("code")
|
||||
|
||||
// Checkboxes
|
||||
sanitizer.policy.AllowAttrs("type").Matching(regexp.MustCompile(`^checkbox$`)).OnElements("input")
|
||||
sanitizer.policy.AllowAttrs("checked", "disabled").OnElements("input")
|
||||
|
||||
// Custom URL-Schemes
|
||||
sanitizer.policy.AllowURLSchemes(setting.Markdown.CustomURLSchemes...)
|
||||
})
|
||||
}
|
||||
|
||||
// Sanitize takes a string that contains a HTML fragment or document and applies policy whitelist.
|
||||
func Sanitize(s string) string {
|
||||
return sanitizer.policy.Sanitize(s)
|
||||
}
|
||||
|
||||
// SanitizeBytes takes a []byte slice that contains a HTML fragment or document and applies policy whitelist.
|
||||
func SanitizeBytes(b []byte) []byte {
|
||||
return sanitizer.policy.SanitizeBytes(b)
|
||||
}
|
||||
38
modules/markup/sanitizer_test.go
Normal file
38
modules/markup/sanitizer_test.go
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2017 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package markup_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
. "github.com/gogits/gogs/modules/markup"
|
||||
)
|
||||
|
||||
func Test_Sanitizer(t *testing.T) {
|
||||
NewSanitizer()
|
||||
Convey("Sanitize HTML string and bytes", t, func() {
|
||||
testCases := []string{
|
||||
// Regular
|
||||
`<a onblur="alert(secret)" href="http://www.google.com">Google</a>`, `<a href="http://www.google.com" rel="nofollow">Google</a>`,
|
||||
|
||||
// Code highlighting class
|
||||
`<code class="random string"></code>`, `<code></code>`,
|
||||
`<code class="language-random ui tab active menu attached animating sidebar following bar center"></code>`, `<code></code>`,
|
||||
`<code class="language-go"></code>`, `<code class="language-go"></code>`,
|
||||
|
||||
// Input checkbox
|
||||
`<input type="hidden">`, ``,
|
||||
`<input type="checkbox">`, `<input type="checkbox">`,
|
||||
`<input checked disabled autofocus>`, `<input checked="" disabled="">`,
|
||||
}
|
||||
|
||||
for i := 0; i < len(testCases); i += 2 {
|
||||
So(Sanitize(testCases[i]), ShouldEqual, testCases[i+1])
|
||||
So(string(SanitizeBytes([]byte(testCases[i]))), ShouldEqual, testCases[i+1])
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -773,7 +773,7 @@ func newSessionService() {
|
||||
SessionConfig.CookieName = Cfg.Section("session").Key("COOKIE_NAME").MustString("i_like_gogits")
|
||||
SessionConfig.CookiePath = AppSubUrl
|
||||
SessionConfig.Secure = Cfg.Section("session").Key("COOKIE_SECURE").MustBool()
|
||||
SessionConfig.Gclifetime = Cfg.Section("session").Key("GC_INTERVAL_TIME").MustInt64(86400)
|
||||
SessionConfig.Gclifetime = Cfg.Section("session").Key("GC_INTERVAL_TIME").MustInt64(3600)
|
||||
SessionConfig.Maxlifetime = Cfg.Section("session").Key("SESSION_LIFE_TIME").MustInt64(86400)
|
||||
CSRFCookieName = Cfg.Section("session").Key("CSRF_COOKIE_NAME").MustString("_csrf")
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/markdown"
|
||||
"github.com/gogits/gogs/modules/markup"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
@@ -125,7 +125,7 @@ func Safe(raw string) template.HTML {
|
||||
}
|
||||
|
||||
func Str2html(raw string) template.HTML {
|
||||
return template.HTML(markdown.Sanitizer.Sanitize(raw))
|
||||
return template.HTML(markup.Sanitize(raw))
|
||||
}
|
||||
|
||||
func List(l *list.List) chan interface{} {
|
||||
@@ -201,7 +201,7 @@ func ReplaceLeft(s, old, new string) string {
|
||||
// RenderCommitMessage renders commit message with XSS-safe and special links.
|
||||
func RenderCommitMessage(full bool, msg, urlPrefix string, metas map[string]string) template.HTML {
|
||||
cleanMsg := template.HTMLEscapeString(msg)
|
||||
fullMessage := string(markdown.RenderIssueIndexPattern([]byte(cleanMsg), urlPrefix, metas))
|
||||
fullMessage := string(markup.RenderIssueIndexPattern([]byte(cleanMsg), urlPrefix, metas))
|
||||
msgLines := strings.Split(strings.TrimSpace(fullMessage), "\n")
|
||||
numLines := len(msgLines)
|
||||
if numLines == 0 {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"CodeKitInfo": "This is a CodeKit 2.x project configuration file. It is designed to sync project settings across multiple machines. MODIFYING THE CONTENTS OF THIS FILE IS A POOR LIFE DECISION. If you do so, you will likely cause CodeKit to crash. This file is not useful unless accompanied by the project that created it in CodeKit 2. This file is not backwards-compatible with CodeKit 1.x. For more information, see: http:\/\/incident57.com\/codekit",
|
||||
"creatorBuild": "19127",
|
||||
"creatorBuild": "19115",
|
||||
"files": {
|
||||
"\/css\/github.min.css": {
|
||||
"fileType": 16,
|
||||
@@ -66,7 +66,7 @@
|
||||
"fileType": 32768,
|
||||
"ignore": 0,
|
||||
"ignoreWasSetByUser": 0,
|
||||
"initialSize": 514087,
|
||||
"initialSize": 4048,
|
||||
"inputAbbreviatedPath": "\/img\/avatar_default.png",
|
||||
"outputAbbreviatedPath": "\/img\/avatar_default.png",
|
||||
"outputPathIsOutsideProject": 0,
|
||||
|
||||
@@ -5,10 +5,20 @@
|
||||
background-size: contain;
|
||||
}
|
||||
body {
|
||||
font-family: "Helvetica Neue", "Microsoft YaHei", Arial, Helvetica, sans-serif !important;
|
||||
font-family: "PingFang SC", "Helvetica Neue", "Microsoft YaHei", SimSun, Arial, Helvetica, sans-serif !important;
|
||||
background-color: #fff;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
.ui.header,
|
||||
.ui.input input,
|
||||
.ui.button {
|
||||
font-family: "PingFang SC", 'Hiragino Sans GB', "Helvetica Neue", "Microsoft YaHei", SimSun, Arial, Helvetica, sans-serif !important;
|
||||
}
|
||||
img {
|
||||
border-radius: 3px;
|
||||
}
|
||||
@@ -1442,7 +1452,8 @@ footer .ui.language .menu {
|
||||
}
|
||||
.repository.file.list #file-content .plain-text {
|
||||
font-size: 14px;
|
||||
padding: 10px 15px;
|
||||
padding: 15px 15px 10px 15px;
|
||||
font-family: Consolas;
|
||||
}
|
||||
.repository.file.list #file-content .code-view * {
|
||||
font-size: 12px;
|
||||
|
||||
@@ -341,7 +341,7 @@ function initRepository() {
|
||||
if ($('.repository.view.issue').length > 0) {
|
||||
// Edit issue title
|
||||
var $issueTitle = $('#issue-title');
|
||||
var $editInput = $('#edit-title-input input');
|
||||
var $editInput = $('#edit-title-input').find('input');
|
||||
var editTitleToggle = function () {
|
||||
$issueTitle.toggle();
|
||||
$('.not-in-edit').toggle();
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
@footer-margin: 40px;
|
||||
|
||||
body {
|
||||
font-family: "Helvetica Neue", "Microsoft YaHei", Arial, Helvetica, sans-serif !important;
|
||||
font-family: "PingFang SC", "Helvetica Neue", "Microsoft YaHei", SimSun, Arial, Helvetica, sans-serif !important;
|
||||
background-color: #fff;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
h1, h2, h3, h4, h5,
|
||||
.ui.header,
|
||||
.ui.input input,
|
||||
.ui.button {
|
||||
font-family: "PingFang SC", 'Hiragino Sans GB', "Helvetica Neue", "Microsoft YaHei", SimSun, Arial, Helvetica, sans-serif !important;
|
||||
}
|
||||
img {
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
@@ -403,7 +403,8 @@
|
||||
|
||||
.plain-text {
|
||||
font-size: 14px;
|
||||
padding: 10px 15px;
|
||||
padding: 15px 15px 10px 15px;
|
||||
font-family: Consolas;
|
||||
}
|
||||
.code-view {
|
||||
* {
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
api "github.com/gogits/go-gogs-client"
|
||||
|
||||
"github.com/gogits/gogs/modules/context"
|
||||
"github.com/gogits/gogs/modules/markdown"
|
||||
"github.com/gogits/gogs/modules/markup"
|
||||
)
|
||||
|
||||
// https://github.com/gogits/go-gogs-client/wiki/Miscellaneous#render-an-arbitrary-markdown-document
|
||||
@@ -25,9 +25,9 @@ func Markdown(ctx *context.APIContext, form api.MarkdownOption) {
|
||||
|
||||
switch form.Mode {
|
||||
case "gfm":
|
||||
ctx.Write(markdown.Render([]byte(form.Text), form.Context, nil))
|
||||
ctx.Write(markup.Markdown([]byte(form.Text), form.Context, nil))
|
||||
default:
|
||||
ctx.Write(markdown.RenderRaw([]byte(form.Text), ""))
|
||||
ctx.Write(markup.RawMarkdown([]byte(form.Text), ""))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,5 +38,5 @@ func MarkdownRaw(ctx *context.APIContext) {
|
||||
ctx.Error(422, "", err)
|
||||
return
|
||||
}
|
||||
ctx.Write(markdown.RenderRaw(body, ""))
|
||||
ctx.Write(markup.RawMarkdown(body, ""))
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
"github.com/gogits/gogs/modules/cron"
|
||||
"github.com/gogits/gogs/modules/form"
|
||||
"github.com/gogits/gogs/modules/mailer"
|
||||
"github.com/gogits/gogs/modules/markdown"
|
||||
"github.com/gogits/gogs/modules/markup"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
"github.com/gogits/gogs/modules/ssh"
|
||||
"github.com/gogits/gogs/modules/template/highlight"
|
||||
@@ -62,7 +62,7 @@ func GlobalInit() {
|
||||
|
||||
if setting.InstallLock {
|
||||
highlight.NewContext()
|
||||
markdown.BuildSanitizer()
|
||||
markup.NewSanitizer()
|
||||
if err := models.NewEngine(); err != nil {
|
||||
log.Fatal(2, "Fail to initialize ORM engine: %v", err)
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/context"
|
||||
"github.com/gogits/gogs/modules/form"
|
||||
"github.com/gogits/gogs/modules/markdown"
|
||||
"github.com/gogits/gogs/modules/markup"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
@@ -541,8 +541,7 @@ func viewIssue(ctx *context.Context, isPullList bool) {
|
||||
ctx.Data["PageIsIssueList"] = true
|
||||
}
|
||||
|
||||
issue.RenderedContent = string(markdown.Render([]byte(issue.Content), ctx.Repo.RepoLink,
|
||||
ctx.Repo.Repository.ComposeMetas()))
|
||||
issue.RenderedContent = string(markup.Markdown(issue.Content, ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas()))
|
||||
|
||||
repo := ctx.Repo.Repository
|
||||
|
||||
@@ -608,8 +607,7 @@ func viewIssue(ctx *context.Context, isPullList bool) {
|
||||
participants[0] = issue.Poster
|
||||
for _, comment = range issue.Comments {
|
||||
if comment.Type == models.COMMENT_TYPE_COMMENT {
|
||||
comment.RenderedContent = string(markdown.Render([]byte(comment.Content), ctx.Repo.RepoLink,
|
||||
ctx.Repo.Repository.ComposeMetas()))
|
||||
comment.RenderedContent = string(markup.Markdown(comment.Content, ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas()))
|
||||
|
||||
// Check tag.
|
||||
tag, ok = marked[comment.PosterID]
|
||||
@@ -727,8 +725,8 @@ func UpdateIssueContent(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(200, map[string]interface{}{
|
||||
"content": string(markdown.Render([]byte(issue.Content), ctx.Query("context"), ctx.Repo.Repository.ComposeMetas())),
|
||||
ctx.JSON(200, map[string]string{
|
||||
"content": string(markup.Markdown(issue.Content, ctx.Query("context"), ctx.Repo.Repository.ComposeMetas())),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -938,8 +936,8 @@ func UpdateCommentContent(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(200, map[string]interface{}{
|
||||
"content": string(markdown.Render([]byte(comment.Content), ctx.Query("context"), ctx.Repo.Repository.ComposeMetas())),
|
||||
ctx.JSON(200, map[string]string{
|
||||
"content": string(markup.Markdown(comment.Content, ctx.Query("context"), ctx.Repo.Repository.ComposeMetas())),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1092,7 +1090,7 @@ func Milestones(ctx *context.Context) {
|
||||
if m.NumOpenIssues+m.NumClosedIssues > 0 {
|
||||
m.Completeness = m.NumClosedIssues * 100 / (m.NumOpenIssues + m.NumClosedIssues)
|
||||
}
|
||||
m.RenderedContent = string(markdown.Render([]byte(m.Content), ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas()))
|
||||
m.RenderedContent = string(markup.Markdown(m.Content, ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas()))
|
||||
}
|
||||
ctx.Data["Milestones"] = miles
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/context"
|
||||
"github.com/gogits/gogs/modules/form"
|
||||
"github.com/gogits/gogs/modules/markdown"
|
||||
"github.com/gogits/gogs/modules/markup"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
@@ -83,7 +83,7 @@ func Releases(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
r.Note = markdown.RenderString(r.Note, ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas())
|
||||
r.Note = string(markup.Markdown(r.Note, ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas()))
|
||||
results[i] = r
|
||||
break
|
||||
}
|
||||
@@ -132,7 +132,7 @@ func Releases(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
r.Note = markdown.RenderString(r.Note, ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas())
|
||||
r.Note = string(markup.Markdown(r.Note, ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas()))
|
||||
}
|
||||
|
||||
if len(drafts) > 0 {
|
||||
|
||||
@@ -404,6 +404,10 @@ func UpdateDefaultBranch(ctx *context.Context) {
|
||||
ctx.Handle(500, "SetDefaultBranch", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Flash.Warning(ctx.Tr("repo.settings.update_default_branch_unsupported"))
|
||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/branches")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/context"
|
||||
"github.com/gogits/gogs/modules/markdown"
|
||||
"github.com/gogits/gogs/modules/markup"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
"github.com/gogits/gogs/modules/template"
|
||||
"github.com/gogits/gogs/modules/template/highlight"
|
||||
@@ -55,7 +55,7 @@ func renderDirectory(ctx *context.Context, treeLink string) {
|
||||
|
||||
var readmeFile *git.Blob
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() || !markdown.IsReadmeFile(entry.Name()) {
|
||||
if entry.IsDir() || !markup.IsReadmeFile(entry.Name()) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -86,9 +86,9 @@ func renderDirectory(ctx *context.Context, treeLink string) {
|
||||
d, _ := ioutil.ReadAll(dataRc)
|
||||
buf = append(buf, d...)
|
||||
switch {
|
||||
case markdown.IsMarkdownFile(readmeFile.Name()):
|
||||
case markup.IsMarkdownFile(readmeFile.Name()):
|
||||
ctx.Data["IsMarkdown"] = true
|
||||
buf = markdown.Render(buf, treeLink, ctx.Repo.Repository.ComposeMetas())
|
||||
buf = markup.Markdown(buf, treeLink, ctx.Repo.Repository.ComposeMetas())
|
||||
default:
|
||||
buf = bytes.Replace(buf, []byte("\n"), []byte(`<br>`), -1)
|
||||
}
|
||||
@@ -153,14 +153,14 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
|
||||
d, _ := ioutil.ReadAll(dataRc)
|
||||
buf = append(buf, d...)
|
||||
|
||||
isMarkdown := markdown.IsMarkdownFile(blob.Name())
|
||||
isMarkdown := markup.IsMarkdownFile(blob.Name())
|
||||
ctx.Data["IsMarkdown"] = isMarkdown
|
||||
ctx.Data["ReadmeExist"] = isMarkdown && markdown.IsReadmeFile(blob.Name())
|
||||
ctx.Data["ReadmeExist"] = isMarkdown && markup.IsReadmeFile(blob.Name())
|
||||
|
||||
ctx.Data["IsIPythonNotebook"] = strings.HasSuffix(blob.Name(), ".ipynb")
|
||||
|
||||
if isMarkdown {
|
||||
ctx.Data["FileContent"] = string(markdown.Render(buf, path.Dir(treeLink), ctx.Repo.Repository.ComposeMetas()))
|
||||
ctx.Data["FileContent"] = string(markup.Markdown(buf, path.Dir(treeLink), ctx.Repo.Repository.ComposeMetas()))
|
||||
} else {
|
||||
// Building code view blocks with line number on server side.
|
||||
var fileContent string
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/context"
|
||||
"github.com/gogits/gogs/modules/form"
|
||||
"github.com/gogits/gogs/modules/markdown"
|
||||
"github.com/gogits/gogs/modules/markup"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -107,7 +107,7 @@ func renderWikiPage(ctx *context.Context, isViewPage bool) (*git.Repository, str
|
||||
return nil, ""
|
||||
}
|
||||
if isViewPage {
|
||||
ctx.Data["content"] = string(markdown.Render(data, ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas()))
|
||||
ctx.Data["content"] = string(markup.Markdown(data, ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas()))
|
||||
} else {
|
||||
ctx.Data["content"] = string(data)
|
||||
}
|
||||
|
||||
@@ -154,9 +154,6 @@ func SignInPost(ctx *context.Context, f form.SignIn) {
|
||||
func SignOut(ctx *context.Context) {
|
||||
ctx.Session.Delete("uid")
|
||||
ctx.Session.Delete("uname")
|
||||
ctx.Session.Delete("socialId")
|
||||
ctx.Session.Delete("socialName")
|
||||
ctx.Session.Delete("socialEmail")
|
||||
ctx.SetCookie(setting.CookieUserName, "", -1, setting.AppSubUrl)
|
||||
ctx.SetCookie(setting.CookieRememberName, "", -1, setting.AppSubUrl)
|
||||
ctx.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubUrl)
|
||||
|
||||
@@ -124,8 +124,9 @@ func Dashboard(ctx *context.Context) {
|
||||
|
||||
var err error
|
||||
var repos, mirrors []*models.Repository
|
||||
var repoCount int64
|
||||
if ctxUser.IsOrganization() {
|
||||
repos, _, err = ctxUser.GetUserRepositories(ctx.User.ID, 1, setting.UI.User.RepoPagingNum)
|
||||
repos, repoCount, err = ctxUser.GetUserRepositories(ctx.User.ID, 1, setting.UI.User.RepoPagingNum)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "GetUserRepositories", err)
|
||||
return
|
||||
@@ -142,6 +143,7 @@ func Dashboard(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
repos = ctxUser.Repos
|
||||
repoCount = int64(ctxUser.NumRepos)
|
||||
|
||||
mirrors, err = ctxUser.GetMirrorRepositories()
|
||||
if err != nil {
|
||||
@@ -150,6 +152,7 @@ func Dashboard(ctx *context.Context) {
|
||||
}
|
||||
}
|
||||
ctx.Data["Repos"] = repos
|
||||
ctx.Data["RepoCount"] = repoCount
|
||||
ctx.Data["MaxShowRepoNum"] = setting.UI.User.RepoPagingNum
|
||||
|
||||
if err := models.MirrorRepositoryList(mirrors).LoadAttributes(); err != nil {
|
||||
|
||||
@@ -36,10 +36,16 @@ const (
|
||||
SECURITY base.TplName = "user/security"
|
||||
)
|
||||
|
||||
func Settings(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsSettingsProfile"] = true
|
||||
ctx.HTML(200, SETTINGS_PROFILE)
|
||||
func Settings(c *context.Context) {
|
||||
c.Data["Title"] = c.Tr("settings")
|
||||
c.Data["PageIsSettingsProfile"] = true
|
||||
c.Data["origin_name"] = c.User.Name
|
||||
c.Data["name"] = c.User.Name
|
||||
c.Data["full_name"] = c.User.FullName
|
||||
c.Data["email"] = c.User.Email
|
||||
c.Data["website"] = c.User.Website
|
||||
c.Data["location"] = c.User.Location
|
||||
c.Success(SETTINGS_PROFILE)
|
||||
}
|
||||
|
||||
func handleUsernameChange(ctx *context.Context, newName string) {
|
||||
@@ -80,6 +86,7 @@ func handleUsernameChange(ctx *context.Context, newName string) {
|
||||
func SettingsPost(ctx *context.Context, f form.UpdateProfile) {
|
||||
ctx.Data["Title"] = ctx.Tr("settings")
|
||||
ctx.Data["PageIsSettingsProfile"] = true
|
||||
ctx.Data["origin_name"] = ctx.User.Name
|
||||
|
||||
if ctx.HasError() {
|
||||
ctx.HTML(200, SETTINGS_PROFILE)
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.10.31.0327 / 0.11 RC
|
||||
0.11.0.0403
|
||||
@@ -3,6 +3,11 @@
|
||||
<p>{{.Flash.ErrorMsg | Str2html}}</p>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .Flash.WarningMsg}}
|
||||
<div class="ui warning message">
|
||||
<p>{{.Flash.WarningMsg | Str2html}}</p>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .Flash.SuccessMsg}}
|
||||
<div class="ui positive message">
|
||||
<p>{{.Flash.SuccessMsg | Str2html}}</p>
|
||||
|
||||
@@ -36,14 +36,14 @@
|
||||
</div>
|
||||
|
||||
<div class="ui five wide column">
|
||||
<h4 class="ui top attached header">
|
||||
<div class="ui top attached header">
|
||||
<strong>{{.i18n.Tr "org.people"}}</strong>
|
||||
{{if .IsOrganizationMember}}
|
||||
<div class="ui right">
|
||||
<a class="text grey" href="{{.OrgLink}}/members">{{.Org.NumMembers}} <span class="octicon octicon-chevron-right"></span></a>
|
||||
</div>
|
||||
{{end}}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="ui attached segment members">
|
||||
{{$isMember := .IsOrganizationMember}}
|
||||
{{range .Members}}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div id="file-content" class="{{TabSizeClass .Editorconfig .FileName}}">
|
||||
<h4 class="ui top attached header" id="{{if .ReadmeExist}}repo-readme{{else}}repo-read-file{{end}}">
|
||||
{{if .ReadmeExist}}
|
||||
<i class="book icon ui left"></i>
|
||||
<i class="octicon octicon-book"></i>
|
||||
{{if .ReadmeInList}}
|
||||
<strong>{{.FileName}}</strong>
|
||||
{{else}}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<form class="ui form" action="{{.Link}}" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<h3 class="ui top attached header">
|
||||
{{if .IsSocialLogin}}{{.i18n.Tr "social_sign_in" | Str2html}}{{else}}{{.i18n.Tr "sign_up"}}{{end}}
|
||||
{{.i18n.Tr "sign_up"}}
|
||||
</h3>
|
||||
<div class="ui attached segment">
|
||||
{{template "base/alert" .}}
|
||||
@@ -45,7 +45,7 @@
|
||||
</div>
|
||||
<div class="inline field">
|
||||
<label></label>
|
||||
<a href="{{AppSubUrl}}/user/login">{{if .IsSocialLogin}}{{.i18n.Tr "auth.social_register_hepler_msg"}}{{else}}{{.i18n.Tr "auth.register_hepler_msg"}}{{end}}</a>
|
||||
<a href="{{AppSubUrl}}/user/login">{{.i18n.Tr "auth.register_hepler_msg"}}</a>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
@@ -18,15 +18,15 @@
|
||||
<a class="item" data-tab="mirrors">{{.i18n.Tr "mirror"}}</a>
|
||||
</div>
|
||||
<div class="ui tab active list" data-tab="repos">
|
||||
<h4 class="ui top attached header">
|
||||
{{.i18n.Tr "home.my_repos"}} <span class="ui grey label">{{.ContextUser.NumRepos}}</span>
|
||||
<div class="ui top attached header">
|
||||
{{.i18n.Tr "home.my_repos"}} <span class="ui grey label">{{.RepoCount}}</span>
|
||||
<div class="ui right">
|
||||
<a class="poping up" href="{{AppSubUrl}}/repo/create{{if .ContextUser.IsOrganization}}?org={{.ContextUser.ID}}{{end}}" data-content="{{.i18n.Tr "new_repo"}}" data-variation="tiny inverted" data-position="left center">
|
||||
<i class="plus icon"></i>
|
||||
<span class="sr-only">{{.i18n.Tr "new_repo"}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="ui attached table segment">
|
||||
<ul class="repo-owner-name-list">
|
||||
{{range .Repos}}
|
||||
@@ -74,7 +74,7 @@
|
||||
|
||||
{{if not .ContextUser.IsOrganization}}
|
||||
<div class="ui tab list" data-tab="orgs">
|
||||
<h4 class="ui top attached header">
|
||||
<div class="ui top attached header">
|
||||
{{.i18n.Tr "home.my_orgs"}} <span class="ui grey label">{{.ContextUser.GetOrganizationCount}}</span>
|
||||
<div class="ui right">
|
||||
<a class="poping up" href="{{AppSubUrl}}/org/create" data-content="{{.i18n.Tr "new_org"}}" data-variation="tiny inverted" data-position="left center">
|
||||
@@ -82,7 +82,7 @@
|
||||
<span class="sr-only">{{.i18n.Tr "new_org"}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="ui attached table segment">
|
||||
<ul class="repo-owner-name-list">
|
||||
{{range .ContextUser.Orgs}}
|
||||
@@ -102,7 +102,7 @@
|
||||
{{end}}
|
||||
|
||||
<div class="ui tab list" data-tab="mirrors">
|
||||
<h4 class="ui top attached header">
|
||||
<div class="ui top attached header">
|
||||
{{.i18n.Tr "home.my_mirrors"}} <span class="ui grey label">{{.MirrorCount}}</span>
|
||||
<div class="ui right">
|
||||
<a class="poping up" href="{{AppSubUrl}}/repo/migrate?mirror=1" data-content="{{.i18n.Tr "new_mirror"}}" data-variation="tiny inverted" data-position="left center">
|
||||
@@ -110,7 +110,7 @@
|
||||
<span class="sr-only">{{.i18n.Tr "new_mirror"}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="ui attached table segment">
|
||||
<ul class="repo-owner-name-list">
|
||||
{{range .Mirrors}}
|
||||
|
||||
@@ -13,27 +13,27 @@
|
||||
<form class="ui form" action="{{.Link}}" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="required field {{if .Err_Name}}error{{end}}">
|
||||
<label for="username">{{.i18n.Tr "username"}}<span class="text red hide" id="name-change-prompt"> {{.i18n.Tr "settings.change_username_prompt"}}</span></label>
|
||||
<input id="username" name="name" value="{{.SignedUser.Name}}" data-name="{{.SignedUser.Name}}" autofocus required {{if not .SignedUser.IsLocal}}readonly{{end}}>
|
||||
<label for="username">{{.i18n.Tr "username"}}<span class="text red {{if eq .name .origin_name}}hide{{end}}" id="name-change-prompt"> {{.i18n.Tr "settings.change_username_prompt"}}</span></label>
|
||||
<input id="username" name="name" value="{{.name}}" data-name="{{.origin_name}}" autofocus required {{if not .SignedUser.IsLocal}}readonly{{end}}>
|
||||
{{if not .SignedUser.IsLocal}}
|
||||
<p class="help text blue">{{$.i18n.Tr "settings.password_username_disabled"}}</p>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="field {{if .Err_FullName}}error{{end}}">
|
||||
<label for="full_name">{{.i18n.Tr "settings.full_name"}}</label>
|
||||
<input id="full_name" name="full_name" value="{{.SignedUser.FullName}}">
|
||||
<input id="full_name" name="full_name" value="{{.full_name}}">
|
||||
</div>
|
||||
<div class="required field {{if .Err_Email}}error{{end}}">
|
||||
<label for="email">{{.i18n.Tr "email"}}</label>
|
||||
<input id="email" name="email" value="{{.SignedUser.Email}}" required>
|
||||
<input id="email" name="email" value="{{.email}}" required>
|
||||
</div>
|
||||
<div class="field {{if .Err_Website}}error{{end}}">
|
||||
<label for="website">{{.i18n.Tr "settings.website"}}</label>
|
||||
<input id="website" name="website" type="url" value="{{.SignedUser.Website}}">
|
||||
<input id="website" name="website" type="url" value="{{.website}}">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="location">{{.i18n.Tr "settings.location"}}</label>
|
||||
<input id="location" name="location" value="{{.SignedUser.Location}}">
|
||||
<input id="location" name="location" value="{{.location}}">
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
|
||||
2
vendor/github.com/go-macaron/captcha/captcha.go
generated
vendored
2
vendor/github.com/go-macaron/captcha/captcha.go
generated
vendored
@@ -68,7 +68,7 @@ func (c *Captcha) CreateHtml() template.HTML {
|
||||
panic(fmt.Errorf("fail to create captcha: %v", err))
|
||||
}
|
||||
return template.HTML(fmt.Sprintf(`<input type="hidden" name="%s" value="%s">
|
||||
<a class="captcha" href="javascript:">
|
||||
<a class="captcha" href="javascript:" tabindex="-1">
|
||||
<img onclick="this.src=('%s%s%s.png?reload='+(new Date()).getTime())" class="captcha-img" src="%s%s%s.png">
|
||||
</a>`, c.FieldIdName, value, c.SubURL, c.URLPrefix, value, c.SubURL, c.URLPrefix, value))
|
||||
}
|
||||
|
||||
2
vendor/github.com/gogits/git-module/README.md
generated
vendored
2
vendor/github.com/gogits/git-module/README.md
generated
vendored
@@ -5,7 +5,7 @@ Package git-module is a Go module for Git access through shell commands.
|
||||
## Limitations
|
||||
|
||||
- Go version must be at least **1.4**.
|
||||
- Git version must be no less than **1.7.1**, and greater than or equal to **1.8.0** is recommended.
|
||||
- Git version must be no less than **1.7.1**, and greater than or equal to **1.8.3** is recommended.
|
||||
- For Windows users, try use as new a version as possible.
|
||||
|
||||
## License
|
||||
|
||||
2
vendor/github.com/gogits/git-module/repo.go
generated
vendored
2
vendor/github.com/gogits/git-module/repo.go
generated
vendored
@@ -262,7 +262,7 @@ func GetRepoSize(repoPath string) (*CountObject, error) {
|
||||
case strings.HasPrefix(line, _STAT_SIZE):
|
||||
countObject.Size = com.StrTo(line[6:]).MustInt64() * 1024
|
||||
case strings.HasPrefix(line, _STAT_IN_PACK):
|
||||
countObject.InPack = com.StrTo(line[9:]).MustInt64() * 1024
|
||||
countObject.InPack = com.StrTo(line[9:]).MustInt64()
|
||||
case strings.HasPrefix(line, _STAT_PACKS):
|
||||
countObject.Packs = com.StrTo(line[7:]).MustInt64()
|
||||
case strings.HasPrefix(line, _STAT_SIZE_PACK):
|
||||
|
||||
12
vendor/vendor.json
vendored
12
vendor/vendor.json
vendored
@@ -75,10 +75,10 @@
|
||||
"revisionTime": "2015-10-13T08:11:02Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "ifjG+PNGY7+sNm3WoA3UntmUTa4=",
|
||||
"checksumSHA1": "kWnClaQsy/2YLvx2SrSUGgSyhzk=",
|
||||
"path": "github.com/go-macaron/captcha",
|
||||
"revision": "8aa5919789ab301e865595eb4b1114d6b9847deb",
|
||||
"revisionTime": "2015-11-23T22:51:53Z"
|
||||
"revision": "a9d45b762e2537878323cd8a4147644378f09be1",
|
||||
"revisionTime": "2017-03-28T22:26:02Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "i52HJHpyI3avut3QAIrrD0h7gmY=",
|
||||
@@ -159,10 +159,10 @@
|
||||
"revisionTime": "2016-08-10T03:50:02Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "0BVytmK2I0QObcFqVyVWWU544DA=",
|
||||
"checksumSHA1": "WVDLV+YcA77fg1kgddSinycf3Do=",
|
||||
"path": "github.com/gogits/git-module",
|
||||
"revision": "e0ab95e3e61573e88bb808ae8052fd669324d80f",
|
||||
"revisionTime": "2017-03-17T02:27:16Z"
|
||||
"revision": "f4026b57acf35d799d5dec95066f3a10c572604e",
|
||||
"revisionTime": "2017-04-03T19:07:43Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "D2kVXl0QpIw6t3891Sl7IM9wL+w=",
|
||||
|
||||
Reference in New Issue
Block a user