Merge branch 'master' of https://github.com/go-gitea/gitea
# Conflicts: # options/locale/locale_en-US.ini
This commit is contained in:
commit
c0cd0c3710
|
|
@ -28,6 +28,8 @@ for SOURCE in $(find ${ROOT}/content -type f -iname *.en-us.md); do
|
|||
if [[ ! -f ${DEST} ]]; then
|
||||
echo "Creating fallback for ${DEST#${ROOT}/content/}"
|
||||
cp ${SOURCE} ${DEST}
|
||||
sed -i.bak "s/en\-us/${LOCALE}/g" ${DEST}
|
||||
rm ${DEST}.bak
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
|
|
|||
|
|
@ -3,3 +3,4 @@
|
|||
repo_id: 1
|
||||
hook_id: 1
|
||||
uuid: uuid1
|
||||
is_delivered: true
|
||||
|
|
|
|||
|
|
@ -51,9 +51,10 @@ type Issue struct {
|
|||
UpdatedUnix util.TimeStamp `xorm:"INDEX updated"`
|
||||
ClosedUnix util.TimeStamp `xorm:"INDEX"`
|
||||
|
||||
Attachments []*Attachment `xorm:"-"`
|
||||
Comments []*Comment `xorm:"-"`
|
||||
Reactions ReactionList `xorm:"-"`
|
||||
Attachments []*Attachment `xorm:"-"`
|
||||
Comments []*Comment `xorm:"-"`
|
||||
Reactions ReactionList `xorm:"-"`
|
||||
TotalTrackedTime int64 `xorm:"-"`
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
@ -69,6 +70,15 @@ func init() {
|
|||
issueTasksDonePat = regexp.MustCompile(issueTasksDoneRegexpStr)
|
||||
}
|
||||
|
||||
func (issue *Issue) loadTotalTimes(e Engine) (err error) {
|
||||
opts := FindTrackedTimesOptions{IssueID: issue.ID}
|
||||
issue.TotalTrackedTime, err = opts.ToSession(e).SumInt(&TrackedTime{}, "time")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (issue *Issue) loadRepo(e Engine) (err error) {
|
||||
if issue.Repo == nil {
|
||||
issue.Repo, err = getRepositoryByID(e, issue.RepoID)
|
||||
|
|
@ -79,6 +89,15 @@ func (issue *Issue) loadRepo(e Engine) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
// IsTimetrackerEnabled returns true if the repo enables timetracking
|
||||
func (issue *Issue) IsTimetrackerEnabled() bool {
|
||||
if err := issue.loadRepo(x); err != nil {
|
||||
log.Error(4, fmt.Sprintf("loadRepo: %v", err))
|
||||
return false
|
||||
}
|
||||
return issue.Repo.IsTimetrackerEnabled()
|
||||
}
|
||||
|
||||
// GetPullRequest returns the issue pull request
|
||||
func (issue *Issue) GetPullRequest() (pr *PullRequest, err error) {
|
||||
if !issue.IsPull {
|
||||
|
|
@ -225,6 +244,11 @@ func (issue *Issue) loadAttributes(e Engine) (err error) {
|
|||
if err = issue.loadComments(e); err != nil {
|
||||
return err
|
||||
}
|
||||
if issue.IsTimetrackerEnabled() {
|
||||
if err = issue.loadTotalTimes(e); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return issue.loadReactions(e)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -441,7 +441,7 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
|
|||
}
|
||||
|
||||
// update the issue's updated_unix column
|
||||
if err = updateIssueCols(e, opts.Issue); err != nil {
|
||||
if err = updateIssueCols(e, opts.Issue, "updated_unix"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -290,6 +290,50 @@ func (issues IssueList) loadComments(e Engine) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (issues IssueList) loadTotalTrackedTimes(e Engine) (err error) {
|
||||
type totalTimesByIssue struct {
|
||||
IssueID int64
|
||||
Time int64
|
||||
}
|
||||
if len(issues) == 0 {
|
||||
return nil
|
||||
}
|
||||
var trackedTimes = make(map[int64]int64, len(issues))
|
||||
|
||||
var ids = make([]int64, 0, len(issues))
|
||||
for _, issue := range issues {
|
||||
if issue.Repo.IsTimetrackerEnabled() {
|
||||
ids = append(ids, issue.ID)
|
||||
}
|
||||
}
|
||||
|
||||
// select issue_id, sum(time) from tracked_time where issue_id in (<issue ids in current page>) group by issue_id
|
||||
rows, err := e.Table("tracked_time").
|
||||
Select("issue_id, sum(time) as time").
|
||||
In("issue_id", ids).
|
||||
GroupBy("issue_id").
|
||||
Rows(new(totalTimesByIssue))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var totalTime totalTimesByIssue
|
||||
err = rows.Scan(&totalTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
trackedTimes[totalTime.IssueID] = totalTime.Time
|
||||
}
|
||||
|
||||
for _, issue := range issues {
|
||||
issue.TotalTrackedTime = trackedTimes[issue.ID]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadAttributes loads all attributes, expect for attachments and comments
|
||||
func (issues IssueList) loadAttributes(e Engine) (err error) {
|
||||
if _, err = issues.loadRepositories(e); err != nil {
|
||||
|
|
@ -316,6 +360,10 @@ func (issues IssueList) loadAttributes(e Engine) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
if err = issues.loadTotalTrackedTimes(e); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ package models
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
|
@ -29,7 +31,7 @@ func TestIssueList_LoadRepositories(t *testing.T) {
|
|||
|
||||
func TestIssueList_LoadAttributes(t *testing.T) {
|
||||
assert.NoError(t, PrepareTestDatabase())
|
||||
|
||||
setting.Service.EnableTimetracking = true
|
||||
issueList := IssueList{
|
||||
AssertExistsAndLoadBean(t, &Issue{ID: 1}).(*Issue),
|
||||
AssertExistsAndLoadBean(t, &Issue{ID: 2}).(*Issue),
|
||||
|
|
@ -61,5 +63,10 @@ func TestIssueList_LoadAttributes(t *testing.T) {
|
|||
for _, comment := range issue.Comments {
|
||||
assert.EqualValues(t, issue.ID, comment.IssueID)
|
||||
}
|
||||
if issue.ID == int64(1) {
|
||||
assert.Equal(t, int64(400), issue.TotalTrackedTime)
|
||||
} else if issue.ID == int64(2) {
|
||||
assert.Equal(t, int64(3662), issue.TotalTrackedTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ type Milestone struct {
|
|||
DeadlineString string `xorm:"-"`
|
||||
DeadlineUnix util.TimeStamp
|
||||
ClosedDateUnix util.TimeStamp
|
||||
|
||||
TotalTrackedTime int64 `xorm:"-"`
|
||||
}
|
||||
|
||||
// BeforeUpdate is invoked from XORM before updating this object.
|
||||
|
|
@ -118,14 +120,69 @@ func GetMilestoneByRepoID(repoID, id int64) (*Milestone, error) {
|
|||
return getMilestoneByRepoID(x, repoID, id)
|
||||
}
|
||||
|
||||
// MilestoneList is a list of milestones offering additional functionality
|
||||
type MilestoneList []*Milestone
|
||||
|
||||
func (milestones MilestoneList) loadTotalTrackedTimes(e Engine) error {
|
||||
type totalTimesByMilestone struct {
|
||||
MilestoneID int64
|
||||
Time int64
|
||||
}
|
||||
if len(milestones) == 0 {
|
||||
return nil
|
||||
}
|
||||
var trackedTimes = make(map[int64]int64, len(milestones))
|
||||
|
||||
// Get total tracked time by milestone_id
|
||||
rows, err := e.Table("issue").
|
||||
Join("INNER", "milestone", "issue.milestone_id = milestone.id").
|
||||
Join("LEFT", "tracked_time", "tracked_time.issue_id = issue.id").
|
||||
Select("milestone_id, sum(time) as time").
|
||||
In("milestone_id", milestones.getMilestoneIDs()).
|
||||
GroupBy("milestone_id").
|
||||
Rows(new(totalTimesByMilestone))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var totalTime totalTimesByMilestone
|
||||
err = rows.Scan(&totalTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
trackedTimes[totalTime.MilestoneID] = totalTime.Time
|
||||
}
|
||||
|
||||
for _, milestone := range milestones {
|
||||
milestone.TotalTrackedTime = trackedTimes[milestone.ID]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadTotalTrackedTimes loads for every milestone in the list the TotalTrackedTime by a batch request
|
||||
func (milestones MilestoneList) LoadTotalTrackedTimes() error {
|
||||
return milestones.loadTotalTrackedTimes(x)
|
||||
}
|
||||
|
||||
func (milestones MilestoneList) getMilestoneIDs() []int64 {
|
||||
var ids = make([]int64, 0, len(milestones))
|
||||
for _, ms := range milestones {
|
||||
ids = append(ids, ms.ID)
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// GetMilestonesByRepoID returns all milestones of a repository.
|
||||
func GetMilestonesByRepoID(repoID int64) ([]*Milestone, error) {
|
||||
func GetMilestonesByRepoID(repoID int64) (MilestoneList, error) {
|
||||
miles := make([]*Milestone, 0, 10)
|
||||
return miles, x.Where("repo_id = ?", repoID).Find(&miles)
|
||||
}
|
||||
|
||||
// GetMilestones returns a list of milestones of given repository and status.
|
||||
func GetMilestones(repoID int64, page int, isClosed bool, sortType string) ([]*Milestone, error) {
|
||||
func GetMilestones(repoID int64, page int, isClosed bool, sortType string) (MilestoneList, error) {
|
||||
miles := make([]*Milestone, 0, setting.UI.IssuePagingNum)
|
||||
sess := x.Where("repo_id = ? AND is_closed = ?", repoID, isClosed)
|
||||
if page > 0 {
|
||||
|
|
@ -146,7 +203,6 @@ func GetMilestones(repoID int64, page int, isClosed bool, sortType string) ([]*M
|
|||
default:
|
||||
sess.Asc("deadline_unix")
|
||||
}
|
||||
|
||||
return miles, sess.Find(&miles)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -253,3 +253,14 @@ func TestDeleteMilestoneByRepoID(t *testing.T) {
|
|||
|
||||
assert.NoError(t, DeleteMilestoneByRepoID(NonexistentID, NonexistentID))
|
||||
}
|
||||
|
||||
func TestMilestoneList_LoadTotalTrackedTimes(t *testing.T) {
|
||||
assert.NoError(t, PrepareTestDatabase())
|
||||
miles := MilestoneList{
|
||||
AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone),
|
||||
}
|
||||
|
||||
assert.NoError(t, miles.LoadTotalTrackedTimes())
|
||||
|
||||
assert.Equal(t, miles[0].TotalTrackedTime, int64(3662))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ func CreateOrStopIssueStopwatch(user *User, issue *Issue) error {
|
|||
Doer: user,
|
||||
Issue: issue,
|
||||
Repo: issue.Repo,
|
||||
Content: secToTime(timediff),
|
||||
Content: SecToTime(timediff),
|
||||
Type: CommentTypeStopTracking,
|
||||
}); err != nil {
|
||||
return err
|
||||
|
|
@ -124,7 +124,8 @@ func CancelStopwatch(user *User, issue *Issue) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func secToTime(duration int64) string {
|
||||
// SecToTime converts an amount of seconds to a human-readable string (example: 66s -> 1min 6s)
|
||||
func SecToTime(duration int64) string {
|
||||
seconds := duration % 60
|
||||
minutes := (duration / (60)) % 60
|
||||
hours := duration / (60 * 60)
|
||||
|
|
|
|||
|
|
@ -279,3 +279,11 @@ func TestGetUserIssueStats(t *testing.T) {
|
|||
assert.Equal(t, test.ExpectedIssueStats, *stats)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue_loadTotalTimes(t *testing.T) {
|
||||
assert.NoError(t, PrepareTestDatabase())
|
||||
ms, err := GetIssueByID(2)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, ms.loadTotalTimes(x))
|
||||
assert.Equal(t, int64(3662), ms.TotalTrackedTime)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
api "code.gitea.io/sdk/gitea"
|
||||
|
||||
"github.com/go-xorm/builder"
|
||||
"github.com/go-xorm/xorm"
|
||||
)
|
||||
|
||||
// TrackedTime represents a time that was spent for a specific issue.
|
||||
|
|
@ -44,6 +45,7 @@ type FindTrackedTimesOptions struct {
|
|||
IssueID int64
|
||||
UserID int64
|
||||
RepositoryID int64
|
||||
MilestoneID int64
|
||||
}
|
||||
|
||||
// ToCond will convert each condition into a xorm-Cond
|
||||
|
|
@ -58,16 +60,23 @@ func (opts *FindTrackedTimesOptions) ToCond() builder.Cond {
|
|||
if opts.RepositoryID != 0 {
|
||||
cond = cond.And(builder.Eq{"issue.repo_id": opts.RepositoryID})
|
||||
}
|
||||
if opts.MilestoneID != 0 {
|
||||
cond = cond.And(builder.Eq{"issue.milestone_id": opts.MilestoneID})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
// ToSession will convert the given options to a xorm Session by using the conditions from ToCond and joining with issue table if required
|
||||
func (opts *FindTrackedTimesOptions) ToSession(e Engine) *xorm.Session {
|
||||
if opts.RepositoryID > 0 || opts.MilestoneID > 0 {
|
||||
return e.Join("INNER", "issue", "issue.id = tracked_time.issue_id").Where(opts.ToCond())
|
||||
}
|
||||
return x.Where(opts.ToCond())
|
||||
}
|
||||
|
||||
// GetTrackedTimes returns all tracked times that fit to the given options.
|
||||
func GetTrackedTimes(options FindTrackedTimesOptions) (trackedTimes []*TrackedTime, err error) {
|
||||
if options.RepositoryID > 0 {
|
||||
err = x.Join("INNER", "issue", "issue.id = tracked_time.issue_id").Where(options.ToCond()).Find(&trackedTimes)
|
||||
return
|
||||
}
|
||||
err = x.Where(options.ToCond()).Find(&trackedTimes)
|
||||
err = options.ToSession(x).Find(&trackedTimes)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -85,7 +94,7 @@ func AddTime(user *User, issue *Issue, time int64) (*TrackedTime, error) {
|
|||
Issue: issue,
|
||||
Repo: issue.Repo,
|
||||
Doer: user,
|
||||
Content: secToTime(time),
|
||||
Content: SecToTime(time),
|
||||
Type: CommentTypeAddTimeManual,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -115,7 +124,7 @@ func TotalTimes(options FindTrackedTimesOptions) (map[*User]string, error) {
|
|||
}
|
||||
return nil, err
|
||||
}
|
||||
totalTimes[user] = secToTime(total)
|
||||
totalTimes[user] = SecToTime(total)
|
||||
}
|
||||
return totalTimes, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,14 +97,17 @@ func GetActiveOAuth2Providers() ([]string, map[string]OAuth2Provider, error) {
|
|||
}
|
||||
|
||||
// InitOAuth2 initialize the OAuth2 lib and register all active OAuth2 providers in the library
|
||||
func InitOAuth2() {
|
||||
oauth2.Init()
|
||||
func InitOAuth2() error {
|
||||
if err := oauth2.Init(x); err != nil {
|
||||
return err
|
||||
}
|
||||
loginSources, _ := GetActiveOAuth2ProviderLoginSources()
|
||||
|
||||
for _, source := range loginSources {
|
||||
oAuth2Config := source.OAuth2()
|
||||
oauth2.RegisterProvider(source.Name, oAuth2Config.Provider, oAuth2Config.ClientID, oAuth2Config.ClientSecret, oAuth2Config.OpenIDConnectAutoDiscoveryURL, oAuth2Config.CustomURLMapping)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// wrapOpenIDConnectInitializeError is used to wrap the error but this cannot be done in modules/auth/oauth2
|
||||
|
|
|
|||
|
|
@ -7,13 +7,12 @@ package oauth2
|
|||
import (
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/go-xorm/xorm"
|
||||
"github.com/lafriks/xormstore"
|
||||
"github.com/markbates/goth"
|
||||
"github.com/markbates/goth/gothic"
|
||||
"github.com/markbates/goth/providers/bitbucket"
|
||||
|
|
@ -41,13 +40,14 @@ type CustomURLMapping struct {
|
|||
}
|
||||
|
||||
// Init initialize the setup of the OAuth2 library
|
||||
func Init() {
|
||||
sessionDir := filepath.Join(setting.AppDataPath, "sessions", "oauth2")
|
||||
if err := os.MkdirAll(sessionDir, 0700); err != nil {
|
||||
log.Fatal(4, "Fail to create dir %s: %v", sessionDir, err)
|
||||
}
|
||||
func Init(x *xorm.Engine) error {
|
||||
store, err := xormstore.NewOptions(x, xormstore.Options{
|
||||
TableName: "oauth2_session",
|
||||
}, []byte(sessionUsersStoreKey))
|
||||
|
||||
store := sessions.NewFilesystemStore(sessionDir, []byte(sessionUsersStoreKey))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// according to the Goth lib:
|
||||
// set the maxLength of the cookies stored on the disk to a larger number to prevent issues with:
|
||||
// securecookie: the value is too long
|
||||
|
|
@ -65,6 +65,7 @@ func Init() {
|
|||
return req.Header.Get(providerHeaderKey), nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Auth OAuth2 auth service
|
||||
|
|
|
|||
|
|
@ -179,8 +179,9 @@ func NewFuncMap() []template.FuncMap {
|
|||
}
|
||||
return dict, nil
|
||||
},
|
||||
"Printf": fmt.Sprintf,
|
||||
"Escape": Escape,
|
||||
"Printf": fmt.Sprintf,
|
||||
"Escape": Escape,
|
||||
"Sec2Time": models.SecToTime,
|
||||
}}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,10 +13,11 @@ import (
|
|||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
|
||||
"net/http/httptest"
|
||||
|
||||
"github.com/go-macaron/session"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/macaron.v1"
|
||||
"net/http/httptest"
|
||||
)
|
||||
|
||||
// MockContext mock context for unit tests
|
||||
|
|
@ -48,6 +49,16 @@ func LoadRepo(t *testing.T, ctx *context.Context, repoID int64) {
|
|||
ctx.Repo.RepoLink = ctx.Repo.Repository.Link()
|
||||
}
|
||||
|
||||
// LoadRepoCommit loads a repo's commit into a test context.
|
||||
func LoadRepoCommit(t *testing.T, ctx *context.Context) {
|
||||
gitRepo, err := git.OpenRepository(ctx.Repo.Repository.RepoPath())
|
||||
assert.NoError(t, err)
|
||||
branch, err := gitRepo.GetHEADBranch()
|
||||
assert.NoError(t, err)
|
||||
ctx.Repo.Commit, err = gitRepo.GetBranchCommit(branch.Name)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
// LoadUser load a user into a test context.
|
||||
func LoadUser(t *testing.T, ctx *context.Context, userID int64) {
|
||||
ctx.User = models.AssertExistsAndLoadBean(t, &models.User{ID: userID}).(*models.User)
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ password=Passwort
|
|||
re_type=Passwort erneut eingeben
|
||||
captcha=CAPTCHA
|
||||
twofa=Zwei-Faktor-Authentifizierung
|
||||
twofa_scratch=Zwei-Faktor-Scratch-Code
|
||||
twofa_scratch=Zwei-Faktor-Einmalpasswort
|
||||
passcode=PIN
|
||||
|
||||
repository=Repository
|
||||
|
|
@ -81,10 +81,12 @@ err_empty_admin_password=Das Administrator-Passwort darf nicht leer sein.
|
|||
|
||||
general_title=Allgemeine Einstellungen
|
||||
app_name=Seitentitel
|
||||
app_name_helper=Gebe hier den Namen deines Unternehmens ein.
|
||||
repo_path=Repository-Verzeichnis
|
||||
repo_path_helper=Remote-Git-Repositories werden in diesem Verzeichnis gespeichert.
|
||||
lfs_path=Git LFS-Wurzelpfad
|
||||
lfs_path_helper=In diesem Verzeichnis werden die Dateien von Git LFS abgespeichert. Leer lassen um LFS zu deaktivieren.
|
||||
run_user=Ausführen als
|
||||
run_user_helper=Gebe den Betriebssystem-Benutzernamen ein, unter welchem Gitea laufen soll. Beachte, dass dieser Nutzer Zugriff auf den Repository-Ordner haben muss.
|
||||
domain=SSH Server-Domain
|
||||
domain_helper=Domain oder Host-Adresse für die SSH-URL.
|
||||
|
|
@ -144,6 +146,7 @@ default_allow_create_organization=Erstellen von Organisationen standarmäßig er
|
|||
default_allow_create_organization_popup=Neuen Nutzern das Erstellen von Organisationen standardmäßig erlauben.
|
||||
default_enable_timetracking=Zeiterfassung standardmäßig aktivieren
|
||||
default_enable_timetracking_popup=Zeiterfassung standardmäßig für neue Repositories aktivieren.
|
||||
no_reply_address_helper=Domain-Namen für Benutzer mit einer versteckten Emailadresse. Zum Beispiel wird der Benutzername "Joe" in Git als "joe@noreply.example.org" protokolliert, wenn die versteckte E-Mail-Domäne "noreply.example.org" festgelegt ist.
|
||||
|
||||
[home]
|
||||
uname_holder=E-Mail-Adresse oder Benutzername
|
||||
|
|
@ -185,23 +188,29 @@ confirmation_mail_sent_prompt=Eine neue Bestätigungs-E-Mail wurde an <b>%s</b>
|
|||
reset_password_mail_sent_prompt=Eine E-Mail wurde an <b>%s</b> gesendet. Bitte überprüfe dein Postfach innerhalb der nächsten %s, um das Passwort zurückzusetzen.
|
||||
active_your_account=Aktiviere dein Konto
|
||||
prohibit_login=Anmelden verboten
|
||||
resent_limit_prompt=Du hast bereits eine Aktivierungs-E-Mail angefordert. Bitte warte 3 Minuten und probiere es dann nochmal.
|
||||
has_unconfirmed_mail=Hallo %s, du hast eine unbestätigte E-Mail-Adresse (<b>%s</b>). Wenn du keine Bestätigungs-E-Mail erhalten hast oder eine neue senden möchtest, klicke bitte auf den folgenden Button.
|
||||
resend_mail=Aktivierungs-E-Mail erneut verschicken
|
||||
email_not_associate=Diese E-Mail-Adresse ist mit keinem Konto verknüpft.
|
||||
send_reset_mail=E-Mail zum Passwort-zurücksetzen erneut verschicken
|
||||
reset_password=Passwort zurücksetzen
|
||||
invalid_code=Dein Bestätigungs-Code ist ungültig oder abgelaufen.
|
||||
reset_password_helper=Passwort zurückzusetzen
|
||||
password_too_short=Das Passwort muss mindenstens %d Zeichen lang sein.
|
||||
non_local_account=Benutzer, die nicht von Gitea verwaltet werden können ihre Passwörter nicht über das Web Interface ändern.
|
||||
verify=Verifizieren
|
||||
scratch_code=Einmalpasswort
|
||||
use_scratch_code=Einmalpasswort verwenden
|
||||
twofa_scratch_used=Du hast dein Einmalpasswort verwendet. Du wurdest zu den Einstellung der Zwei-Faktor-Authentifizierung umgeleitet, dort kannst du dein Gerät abmelden oder ein neues Einmalpasswort erzeugen.
|
||||
twofa_passcode_incorrect=Ungültige PIN. Wenn du dein Gerät verloren hast, verwende dein Einmalpasswort.
|
||||
twofa_scratch_token_incorrect=Das Einmalpasswort ist falsch.
|
||||
login_userpass=Anmelden
|
||||
login_openid=OpenID
|
||||
openid_connect_submit=Verbinden
|
||||
openid_connect_title=Mit bestehendem Konto verbinden
|
||||
openid_register_title=Neues Konto einrichten
|
||||
openid_signin_desc=Gib deine OpenID-URI ein. Zum Beispiel: https://anne.me, bob.openid.org.cn oder gnusocial.net/carry.
|
||||
disable_forgot_password_mail=Das Zurücksetzen von Passwörtern wurde deaktiviert. Bitte wende dich an den Administrator.
|
||||
|
||||
[mail]
|
||||
activate_account=Bitte aktiviere dein Konto
|
||||
|
|
@ -236,6 +245,9 @@ TreeName=Dateipfad
|
|||
Content=Inhalt
|
||||
|
||||
require_error=` darf nicht leer sein.`
|
||||
alpha_dash_error=` sollte nur Buchstaben, Zahlen, Bindestriche ('-') und Unterstriche ('_') enthalten`
|
||||
alpha_dash_dot_error=` sollte nur Buchstaben, Zahlen, Bindestriche ('-'), Unterstriche ('_') und Punkte ('.') enthalten`
|
||||
git_ref_name_error=` muss ein wohlgeformter Git-Referenzname sein.`
|
||||
size_error=` muss die Größe %s haben.`
|
||||
min_size_error=` muss mindestens %s Zeichen enthalten.`
|
||||
max_size_error=` darf höchstens %s Zeichen enthalten.`
|
||||
|
|
@ -250,8 +262,11 @@ username_been_taken=Der Benutzername ist bereits vergeben.
|
|||
repo_name_been_taken=Der Repository-Name wird schon verwendet.
|
||||
org_name_been_taken=Der Organisationsname ist bereits vergeben.
|
||||
team_name_been_taken=Der Teamname ist bereits vergeben.
|
||||
team_no_units_error=Das Team muss auf mindestens einen Bereich Zugriff haben.
|
||||
email_been_used=Die E-Mail-Adresse wird bereits verwendet.
|
||||
openid_been_used=Die OpenID-Adresse "%s" wird bereits verwendet.
|
||||
username_password_incorrect=Benutzername oder Passwort ist falsch.
|
||||
enterred_invalid_repo_name=Der eingegebenen Repository-Name ist falsch.
|
||||
user_not_exist=Dieser Benutzer ist nicht vorhanden.
|
||||
|
||||
auth_failed=Authentifizierung fehlgeschlagen: %v
|
||||
|
|
@ -290,11 +305,13 @@ organization=Organisationen
|
|||
uid=Uid
|
||||
|
||||
public_profile=Öffentliches Profil
|
||||
password_username_disabled=Benutzer, die nicht von Gitea verwaltet werden können ihren Benutzernamen nicht ändern. Bitte kontaktiere deinen Administrator für mehr Details.
|
||||
full_name=Vollständiger Name
|
||||
website=Webseite
|
||||
location=Standort
|
||||
update_profile=Profil aktualisieren
|
||||
update_profile_success=Dein Profil wurde aktualisiert.
|
||||
change_username=Dein Benutzername wurde geändert.
|
||||
continue=Weiter
|
||||
cancel=Abbrechen
|
||||
|
||||
|
|
@ -309,6 +326,7 @@ change_password=Passwort aktualisieren
|
|||
old_password=Aktuelles Passwort
|
||||
new_password=Neues Passwort
|
||||
retype_new_password=Neues Passwort erneut eingeben
|
||||
password_change_disabled=Benutzer, die nicht von Gitea verwaltet werden, können ihr Passwort im Web Interface nicht ändern.
|
||||
|
||||
emails=E-Mail-Adressen
|
||||
email_desc=Deine primäre E-Mail-Adresse wird für Benachrichtigungen und andere Funktionen verwendet.
|
||||
|
|
@ -372,6 +390,7 @@ delete_account_title=Benutzerkonto löschen
|
|||
[repo]
|
||||
owner=Besitzer
|
||||
repo_name=Repository-Name
|
||||
repo_name_helper=Ein guter Repository-Name besteht normalerweise aus kurzen, unvergesslichen und einzigartigen Schlagwörtern.
|
||||
visibility=Sichtbarkeit
|
||||
fork_repo=Repository forken
|
||||
fork_from=Fork von
|
||||
|
|
|
|||
|
|
@ -737,6 +737,7 @@ issues.add_time_sum_to_small = No time was entered.
|
|||
issues.cancel_tracking = Cancel
|
||||
issues.cancel_tracking_history = `cancelled time tracking %s`
|
||||
issues.time_spent_total = Total time spent
|
||||
issues.time_spent_from_all_authors = `Total Time Spent: %s`
|
||||
issues.dependency.title = Dependencies
|
||||
issues.dependency.issue_no_dependencies = This issue currently doesn't have any dependencies.
|
||||
issues.dependency.pr_no_dependencies = This pull request currently doesn't have any dependencies.
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,10 +1,15 @@
|
|||
app_desc=Зручний сервіс, власного Git хостінгу
|
||||
|
||||
home=Головна
|
||||
dashboard=Панель інструментів
|
||||
explore=Огляд
|
||||
help=Довідка
|
||||
sign_in=Увійти
|
||||
sign_in_with=Увійти через
|
||||
sign_out=Вийти
|
||||
sign_up=Реєстрація
|
||||
link_account=Прив'язати обліковий запис
|
||||
link_account_signin_or_signup=Увійдіть з уже існуючими обліковими даними або зареєструйтеся для зв'язку з цим аккаунтом.
|
||||
register=Реєстрація
|
||||
website=Web-сайт
|
||||
version=Версія
|
||||
|
|
@ -12,10 +17,17 @@ page=Сторінка
|
|||
template=Шаблон
|
||||
language=Мова
|
||||
notifications=Сповіщення
|
||||
create_new=Створити…
|
||||
user_profile_and_more=Профіль і налаштування…
|
||||
signed_in_as=Увійшов як
|
||||
enable_javascript=Цей веб-сайт працює краще з JavaScript.
|
||||
|
||||
username=Ім'я кристувача
|
||||
email=Адреса електронної пошти
|
||||
password=Пароль
|
||||
re_type=Введіть пароль ще раз
|
||||
captcha=CAPTCHA
|
||||
twofa=Двофакторна авторизація
|
||||
passcode=Код доступу
|
||||
|
||||
repository=Репозиторій
|
||||
|
|
@ -29,6 +41,9 @@ new_org=Нова організація
|
|||
manage_org=Керування організаціями
|
||||
account_settings=Параметри облікового запису
|
||||
settings=Параметри
|
||||
your_profile=Профіль
|
||||
your_starred=Обрані
|
||||
your_settings=Параметри
|
||||
|
||||
all=Усі
|
||||
sources=Джерела
|
||||
|
|
@ -36,38 +51,71 @@ mirrors=Дзеркала
|
|||
collaborative=Співпраця
|
||||
forks=Відгалуження
|
||||
|
||||
pull_requests=Запити до злиття
|
||||
activities=Дії
|
||||
pull_requests=Запити на злиття
|
||||
issues=Питання
|
||||
|
||||
cancel=Відміна
|
||||
|
||||
[install]
|
||||
install=Встановлення
|
||||
title=Початкова конфігурація
|
||||
docker_helper=Якщо ви запускаєте Gitea всередині Docker, будь ласка уважно прочитайте <a target="_blank" rel="noopener" href="%s">документацію</a> перед тим, як що-небудь змінити на цій сторінці.
|
||||
requite_db_desc=Gitea потребує MySQL, PostgreSQL, MSSQL, SQLite3 або TiDB.
|
||||
db_title=Налаштування бази даних
|
||||
db_type=Тип бази даних
|
||||
host=Хост
|
||||
user=Ім'я кристувача
|
||||
password=Пароль
|
||||
db_name=Ім'я бази даних
|
||||
ssl_mode=SSL
|
||||
path=Шлях
|
||||
err_empty_db_path=Шлях до бази даних SQLite3 або TiDB не може бути порожнім.
|
||||
no_admin_and_disable_registration=Ви не можете вимкнути реєстрацію до створення облікового запису адміністратора.
|
||||
|
||||
general_title=Загальні налаштування
|
||||
app_name=Назва сайту
|
||||
repo_path=Кореневий шлях репозиторія
|
||||
repo_path_helper=Всі вилучені Git репозиторії будуть збережені в цей каталог.
|
||||
lfs_path=Кореневої шлях Git LFS
|
||||
lfs_path_helper=У цій папці будуть зберігатися файли Git LFS. Залиште порожнім, щоб відключити LFS.
|
||||
ssh_port=Порт SSH сервера
|
||||
ssh_port_helper=Номер порту, який використовує SSH сервер. Залиште порожнім, щоб відключити SSH.
|
||||
app_url=Базова URL-адреса Gitea
|
||||
log_root_path=Шлях до лог файлу
|
||||
|
||||
optional_title=Додаткові налаштування
|
||||
email_title=Налаштування Email
|
||||
smtp_host=SMTP хост
|
||||
smtp_from=Відправляти Email від імені
|
||||
mailer_user=SMTP Ім'я кристувача
|
||||
mailer_password=SMTP Пароль
|
||||
mail_notify=Дозволити поштові повідомлення
|
||||
disable_gravatar=Вимкнути Gravatar
|
||||
federated_avatar_lookup=Увімкнути зовнішні аватари
|
||||
federated_avatar_lookup_popup=Увімкнути зовнішний Аватар за допомогою Libravatar.
|
||||
openid_signin=Увімкнути реєстрацію за допомогою OpenID
|
||||
enable_captcha=Увімкнути CAPTCHA
|
||||
enable_captcha_popup=Вимагати перевірку CAPTCHA при самостійній реєстрації користувача.
|
||||
admin_name=Ім'я кристувача Адміністратора
|
||||
admin_password=Пароль
|
||||
confirm_password=Підтвердження пароля
|
||||
admin_email=Адреса електронної пошти
|
||||
install_btn_confirm=Встановлення Gitea
|
||||
test_git_failed=Не в змозі перевірити 'git' команду: %v
|
||||
save_config_failed=Не в змозі зберегти конфігурацію: %v
|
||||
install_success=Ласкаво просимо! Дякуємо вам за вибір Gitea. Розважайтеся, і будьте обережні!
|
||||
|
||||
[home]
|
||||
password_holder=Пароль
|
||||
switch_dashboard_context=Змінити дошку
|
||||
my_repos=Мої репозиторії
|
||||
show_more_repos=Показати більше репозиторіїв…
|
||||
collaborative_repos=Спільні репозиторії
|
||||
my_orgs=Мої організації
|
||||
my_mirrors=Мої дзеркала
|
||||
view_home=Переглянути %s
|
||||
search_repos=Шукати репозиторій…
|
||||
|
||||
issues.in_your_repos=В ваших репозиторіях
|
||||
|
||||
|
|
@ -76,21 +124,43 @@ repos=Репозиторії
|
|||
users=Користувачі
|
||||
organizations=Організації
|
||||
search=Пошук
|
||||
code=Код
|
||||
|
||||
[auth]
|
||||
create_new_account=Реєстрація аккаунта
|
||||
register_helper_msg=Вже зареєстровані? Увійдіть зараз!
|
||||
disable_register_prompt=Вибачте, можливість реєстрації відключена. Будь ласка, зв'яжіться з адміністратором сайту.
|
||||
remember_me=Запам'ятати мене
|
||||
forgot_password_title=Забув пароль
|
||||
forgot_password=Забули пароль?
|
||||
sign_up_now=Потрібен аккаунт? Зареєструватися.
|
||||
confirmation_mail_sent_prompt=Новий лист для підтвердження було направлено на <b>%s</b>, будь ласка, перевірте вашу поштову скриньку протягом% s для завершення реєстрації.
|
||||
reset_password_mail_sent_prompt=Лист для підтвердження було направлено на <b>%s</b>. Будь ласка, перевірте вашу поштову скриньку протягом% s для скидання пароля.
|
||||
active_your_account=Активувати обліковий запис
|
||||
prohibit_login=Вхід заборонений
|
||||
prohibit_login_desc=Вхід для вашого профілю був заборонений, будь ласка, зв'яжіться з адміністратором сайту.
|
||||
resent_limit_prompt=Вибачте, ви вже запросили активацію по електронній пошті нещодавно. Будь ласка, зачекайте 3 хвилини, а потім спробуйте ще раз.
|
||||
has_unconfirmed_mail=Привіт %s, у вас є непідтвердженими адреси (<b>%s</b>). Якщо ви не отримали підтвердження електронною поштою або треба відправити нове, будь ласка, натисніть на кнопку нижче.
|
||||
resend_mail=Натисніть тут, щоб вислати лист активації знову
|
||||
email_not_associate=Ця електронна пошта не пов'язана ні з одним обліковим записом.
|
||||
send_reset_mail=Натисніть сюди, щоб відправити лист для скидання пароля
|
||||
reset_password=Скинути пароль
|
||||
invalid_code=Цей код підтвердження недійсний або закінчився.
|
||||
reset_password_helper=Натисніть тут для скидання пароля
|
||||
password_too_short=Довжина пароля не може бути меншою за %d.
|
||||
non_local_account=Нелокальні акаунти не можуть змінити пароль через Gitea.
|
||||
verify=Підтвердити
|
||||
scratch_code=Одноразовий пароль
|
||||
use_scratch_code=Використовувати одноразовий пароль
|
||||
twofa_scratch_used=Ви використовували scratch-код. Ви були перенаправлені на сторінку налаштувань для генерації нового коду або відключення двуфакторной аутентифікації.
|
||||
twofa_passcode_incorrect=Ваш пароль є невірним. Якщо ви втратили пристрій, використовуйте ваш одноразовий пароль.
|
||||
twofa_scratch_token_incorrect=Невірний одноразовий пароль.
|
||||
login_userpass=Увійти
|
||||
login_openid=OpenID
|
||||
openid_connect_submit=Під’єднатися
|
||||
openid_connect_title=Підключитися до існуючого облікового запису
|
||||
openid_register_title=Створити новий обліковий запис
|
||||
disable_forgot_password_mail=На жаль скидання пароля відключене. Будь ласка, зв'яжіться з адміністратором сайту.
|
||||
|
||||
[mail]
|
||||
activate_account=Будь ласка, активуйте ваш обліковий запис
|
||||
|
|
@ -102,14 +172,17 @@ register_notify=Ласкаво просимо у Gitea
|
|||
[modal]
|
||||
yes=Так
|
||||
no=Ні
|
||||
modify=Оновлення
|
||||
|
||||
[form]
|
||||
UserName=Ім’я користувача
|
||||
RepoName=Назва репозиторію
|
||||
Email=Адреса електронної пошти
|
||||
Password=Пароль
|
||||
Retype=Введіть пароль ще раз
|
||||
SSHTitle=Iм'я SSH ключа
|
||||
HttpsUrl=Адреса HTTPS
|
||||
PayloadUrl=URL обробника
|
||||
TeamName=Назва команди
|
||||
AuthName=Назва авторизації
|
||||
AdminEmail=Email адміністратора
|
||||
|
|
@ -121,10 +194,18 @@ TreeName=Шлях до файлу
|
|||
Content=Зміст
|
||||
|
||||
require_error=` не може бути пустим.`
|
||||
git_ref_name_error=` має бути правильним посилальним ім'ям Git.`
|
||||
email_error=` не є адресою електронної пошти.`
|
||||
unknown_error=Невідома помилка:
|
||||
password_not_match=Паролі не співпадають.
|
||||
|
||||
username_been_taken=Ім'я користувача вже зайнято.
|
||||
repo_name_been_taken=Ім'я репозіторію вже використовується.
|
||||
email_been_used=Ця електронна адреса вже використовується.
|
||||
username_password_incorrect=Неправильне ім'я користувача або пароль.
|
||||
user_not_exist=Даний користувач не існує.
|
||||
|
||||
auth_failed=Помилка автентифікації: %v
|
||||
|
||||
|
||||
|
||||
|
|
@ -133,6 +214,7 @@ join_on=Приєднався
|
|||
repositories=Репозиторії
|
||||
activity=Публічна активність
|
||||
followers=Підписники
|
||||
starred=Обрані Репозиторії
|
||||
following=Слідкувати
|
||||
follow=Підписатися
|
||||
unfollow=Відписатися
|
||||
|
|
@ -144,10 +226,14 @@ profile=Профіль
|
|||
password=Пароль
|
||||
security=Безпека
|
||||
avatar=Аватар
|
||||
ssh_gpg_keys=SSH / GPG ключи
|
||||
social=Соціальні акаунти
|
||||
applications=Токени Доступу
|
||||
orgs=Керування організаціями
|
||||
repos=Репозиторії
|
||||
delete=Видалити обліковий запис
|
||||
twofa=Двофакторна авторизація
|
||||
organization=Організації
|
||||
uid=Ідентифікатор Uid
|
||||
|
||||
public_profile=Загальнодоступний профіль
|
||||
|
|
@ -159,16 +245,35 @@ update_profile_success=Профіль успішно оновлено.
|
|||
continue=Продовжити
|
||||
cancel=Відміна
|
||||
|
||||
federated_avatar_lookup=Знайти зовнішній аватар
|
||||
enable_custom_avatar=Увімкнути користувацькі аватари
|
||||
choose_new_avatar=Оберіть новий аватар
|
||||
delete_current_avatar=Видалити поточний аватар
|
||||
|
||||
change_password=Оновити пароль
|
||||
old_password=Поточний пароль
|
||||
new_password=Новий пароль
|
||||
|
||||
emails=Адреса електронної пошти
|
||||
email_desc=Ваша основна адреса електронної пошти використовуватиметься для сповіщення та інших операцій.
|
||||
primary=Основний
|
||||
delete_email=Видалити
|
||||
|
||||
manage_ssh_keys=Керувати SSH ключами
|
||||
manage_gpg_keys=Керувати GPG ключами
|
||||
add_key=Додати ключ
|
||||
ssh_helper=<strong>Потрібна допомога?</strong> Дивіться гід на GitHub з <a href="%s"> генерації ключів SSH</a> або виправлення <a href="%s">типових неполадок SSH</a>.
|
||||
add_new_key=Додати SSH ключ
|
||||
add_new_gpg_key=Додати GPG ключ
|
||||
key_id=ID ключа
|
||||
key_name=Ім'я ключа
|
||||
key_content=Зміст
|
||||
delete_key=Видалити
|
||||
ssh_key_deletion=Видалити SSH ключ
|
||||
gpg_key_deletion=Видалити GPG ключ
|
||||
add_on=Додано
|
||||
valid_until=Дійсний до
|
||||
valid_forever=Дійсний завжди
|
||||
last_used=Останнє використання
|
||||
no_activity=Жодної діяльності
|
||||
can_read_info=Читати
|
||||
|
|
@ -178,7 +283,11 @@ token_state_desc=Цей токен використовувався в оста
|
|||
show_openid=Показати у профілю
|
||||
hide_openid=Не показувати у профілі
|
||||
|
||||
manage_social=Керувати зв'язаними аккаунтами соціальних мереж
|
||||
|
||||
generate_new_token=Згенерувати новий токен
|
||||
token_name=Ім'я токену
|
||||
generate_token=Згенерувати токен
|
||||
delete_token=Видалити
|
||||
|
||||
|
||||
|
|
@ -186,37 +295,49 @@ delete_token=Видалити
|
|||
|
||||
delete_account=Видалити ваш обліковий запис
|
||||
confirm_delete_account=Підтвердження видалення
|
||||
delete_account_title=Видалити цей обліковий запис
|
||||
|
||||
[repo]
|
||||
owner=Власник
|
||||
repo_name=Назва репозиторію
|
||||
visibility=Видимість
|
||||
visiblity_helper=Зробити репозиторій приватним
|
||||
fork_repo=Відгалужити репозиторій
|
||||
fork_from=Відгалужена з
|
||||
repo_desc=Опис
|
||||
repo_lang=Мова
|
||||
repo_gitignore_helper=Виберіть шаблон .gitignore.
|
||||
license=Ліцензія
|
||||
license_helper=Виберіть ліцензійний файл.
|
||||
readme_helper=Виберіть шаблон README.
|
||||
create_repo=Створити репозиторій
|
||||
default_branch=Головна гілка
|
||||
mirror_prune=Очистити
|
||||
watchers=Спостерігачі
|
||||
|
||||
form.reach_limit_of_creation=Ви досягли максимальної кількості %d створених репозиторіїв.
|
||||
|
||||
migrate_type=Тип міграції
|
||||
migrate_type_helper=Даний репозиторій буде <span class="text blue">дзеркалом</span>
|
||||
migrate_repo=Перенесення репозиторія
|
||||
migrate.failed=Міграція не вдалася: %v
|
||||
|
||||
forked_from=відгалужено від
|
||||
copy_link=Копіювати
|
||||
copied=Скопійовано
|
||||
unwatch=Не стежити
|
||||
watch=Слідкувати
|
||||
unstar=Зняти зірку
|
||||
star=Зірка
|
||||
unstar=Видалити із обраних
|
||||
star=В обрані
|
||||
fork=Відгалуження
|
||||
download_archive=Скачати репозиторій
|
||||
|
||||
no_desc=Без опису
|
||||
quick_guide=Короткий посібник
|
||||
clone_this_repo=Кнонувати цей репозиторій
|
||||
create_new_repo_command=Створити новий репозиторій з командного рядка
|
||||
push_exist_repo=Опублікувати існуючий репозиторій з командного рядка
|
||||
bare_message=Цей репозиторій порожній.
|
||||
|
||||
code=Код
|
||||
branch=Гілка
|
||||
|
|
@ -225,7 +346,7 @@ filter_branch_and_tag=Фільтрувати гілку або тег
|
|||
branches=Гілки
|
||||
tags=Теги
|
||||
issues=Питання
|
||||
pulls=Запити до злиття
|
||||
pulls=Запити на злиття
|
||||
labels=Мітки
|
||||
milestones=Етап
|
||||
commits=Зміни
|
||||
|
|
@ -236,13 +357,26 @@ file_history=Історія
|
|||
file_view_raw=Перегляд Raw
|
||||
file_permalink=Постійне посилання
|
||||
|
||||
editor.new_file=Новий файл
|
||||
editor.upload_file=Завантажити файл
|
||||
editor.edit_file=Редагування файла
|
||||
editor.preview_changes=Попередній перегляд змін
|
||||
editor.edit_this_file=Редагування файла
|
||||
editor.delete_this_file=Видалити файл
|
||||
editor.name_your_file=Дайте назву файлу…
|
||||
editor.or=або
|
||||
editor.cancel_lower=Скасувати
|
||||
editor.commit_changes=Зафіксувати зміни
|
||||
editor.add_tmpl=Додати '%s/<filename>'
|
||||
editor.add=Додати '%s'
|
||||
editor.update=Оновити '%s'
|
||||
editor.delete=Видалити '%s'
|
||||
editor.cancel=Відміна
|
||||
editor.upload_files_to_dir=Завантажувати файли до '%s'
|
||||
|
||||
commits.commits=Зміни
|
||||
commits.find=Пошук
|
||||
commits.search_all=Усі гілки
|
||||
commits.author=Автор
|
||||
commits.message=Повідомлення
|
||||
commits.date=Дата
|
||||
|
|
@ -263,17 +397,54 @@ issues.new.assignee=Призначено
|
|||
issues.new.clear_assignee=Прибрати відповідального
|
||||
issues.new.no_assignee=Немає відповідального
|
||||
issues.new_label=Нова мітка
|
||||
issues.new_label_placeholder=Назва мітки
|
||||
issues.new_label_desc_placeholder=Опис
|
||||
issues.create_label=Створити мітку
|
||||
issues.label_templates.helper=Оберіть набір міток
|
||||
issues.label_templates.fail_to_load_file=Не вдалося завантажити файл шаблона мітки '%s': %v
|
||||
issues.deleted_milestone=`(видалено)`
|
||||
issues.open_tab=%d відкрито
|
||||
issues.close_tab=%d закрито
|
||||
issues.filter_label=Мітка
|
||||
issues.filter_milestone=Етап
|
||||
issues.filter_milestone_no_select=Всі етапи
|
||||
issues.filter_assignee=Відповідальний
|
||||
issues.filter_type=Тип
|
||||
issues.filter_type.all_issues=Всі проблемы
|
||||
issues.filter_type.created_by_you=Створено вами
|
||||
issues.filter_type.mentioning_you=Вас згадано
|
||||
issues.filter_sort=Сортувати
|
||||
issues.filter_sort.latest=Найновіші
|
||||
issues.filter_sort.oldest=Найстаріші
|
||||
issues.filter_sort.recentupdate=Нещодавно оновлено
|
||||
issues.filter_sort.leastupdate=Найдавніше оновлені
|
||||
issues.filter_sort.mostcomment=Найбільш коментовані
|
||||
issues.filter_sort.leastcomment=Найменш коментовані
|
||||
issues.action_open=Відкрити
|
||||
issues.action_close=Закрити
|
||||
issues.action_label=Мітка
|
||||
issues.action_milestone=Етап
|
||||
issues.action_milestone_no_select=Етап відсутній
|
||||
issues.action_assignee=Відповідальний
|
||||
issues.action_assignee_no_select=Немає відповідального
|
||||
issues.opened_by=%[1]s відкрито <a href="%[2]s">%[3]s</a>
|
||||
issues.opened_by_fake=%[1]s відкрито %[2]s
|
||||
issues.previous=Попередній
|
||||
issues.next=Далі
|
||||
issues.open_title=Відкрити
|
||||
issues.closed_title=Закриті
|
||||
issues.num_comments=%d коментарів
|
||||
issues.commented_at=`відкоментовано <a href="#%s">%s</a>`
|
||||
issues.delete_comment_confirm=Ви впевнені, що хочете видалити цей коментар?
|
||||
issues.close_issue=Закрити
|
||||
issues.close_comment_issue=Прокоментувати і закрити
|
||||
issues.reopen_issue=Відкрити знову
|
||||
issues.create_comment=Коментар
|
||||
issues.closed_at=`закрито <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.commit_ref_at=`згадано цю проблему в коміті <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.collaborator=Співавтор
|
||||
issues.owner=Власник
|
||||
issues.sign_in_require_desc=<a href="%s">Підпишіться</a> щоб приєднатися до обговорення.
|
||||
issues.edit=Редагувати
|
||||
issues.cancel=Відміна
|
||||
issues.save=Зберегти
|
||||
|
|
@ -287,18 +458,35 @@ issues.label.filter_sort.alphabetically=За абеткою
|
|||
issues.label.filter_sort.reverse_alphabetically=Зворотною абеткою
|
||||
issues.label.filter_sort.by_size=Розмір
|
||||
issues.label.filter_sort.reverse_by_size=Зворотний розмір
|
||||
issues.num_participants=%d учасників
|
||||
issues.attachment.open_tab=`Натисніть щоб побачити "%s" у новій вкладці`
|
||||
issues.subscribe=Підписатися
|
||||
issues.unsubscribe=Відписатися
|
||||
issues.start_tracking_short=Запустити
|
||||
issues.stop_tracking=Стоп
|
||||
issues.add_time_short=Додати час
|
||||
issues.add_time_cancel=Відміна
|
||||
issues.add_time_hours=Години
|
||||
issues.add_time_minutes=Хвилини
|
||||
issues.cancel_tracking=Відміна
|
||||
|
||||
pulls.new=Новий запит на злиття
|
||||
pulls.compare_changes=Новий запит на злиття
|
||||
pulls.filter_branch=Фільтр по гілці
|
||||
pulls.no_results=Результатів не знайдено.
|
||||
pulls.create=Створити запит на злиття
|
||||
pulls.title_desc=хоче злити %[1]d комітів з <code>%[2]s</code> до <code>%[3]s</code>
|
||||
pulls.tab_commits=Комітів
|
||||
pulls.reopen_to_merge=Будь ласка перевідкрийте цей запит щоб здіснити операцію злиття.
|
||||
pulls.merged=Злито
|
||||
pulls.can_auto_merge_desc=Цей запит можна об'єднати автоматично.
|
||||
pulls.merge_pull_request=Об'єднати запит на злиття
|
||||
|
||||
milestones.new=Новий етап
|
||||
milestones.open_tab=%d відкрито
|
||||
milestones.close_tab=%d закрито
|
||||
milestones.closed=Закрито %s
|
||||
milestones.no_due_date=Немає дати завершення
|
||||
milestones.open=Відкрити
|
||||
milestones.close=Закрити
|
||||
milestones.create=Створити етап
|
||||
|
|
@ -308,14 +496,31 @@ milestones.due_date=Дата завершення (опціонально)
|
|||
milestones.clear=Очистити
|
||||
milestones.edit=Редагувати етап
|
||||
milestones.cancel=Відміна
|
||||
milestones.modify=Оновити етап
|
||||
|
||||
|
||||
wiki=Wiki
|
||||
wiki.page=Сторінка
|
||||
wiki.filter_page=Фільтр сторінок
|
||||
wiki.new_page=Сторінка
|
||||
wiki.save_page=Зберегти сторінку
|
||||
wiki.edit_page_button=Редагувати
|
||||
wiki.new_page_button=Нова сторінка
|
||||
wiki.delete_page_button=Видалити сторінку
|
||||
wiki.pages=Сторінки
|
||||
wiki.last_updated=Останні оновлення %s
|
||||
|
||||
activity=Активність
|
||||
activity.period.filter_label=Період:
|
||||
activity.period.daily=1 день
|
||||
activity.period.halfweekly=3 дні
|
||||
activity.period.weekly=1 тиждень
|
||||
activity.period.monthly=1 місяць
|
||||
activity.overview=Огляд
|
||||
activity.active_prs_count_n=<strong>%d</strong> Активні запити на злиття
|
||||
activity.merged_prs_count_1=Об'єднати запит на злиття
|
||||
activity.merged_prs_count_n=Об'єднати запити на злиття
|
||||
activity.merged_prs_label=Злито
|
||||
activity.closed_issue_label=Закриті
|
||||
activity.new_issues_count_1=Нове обговорення
|
||||
activity.new_issues_count_n=Нове обговорення
|
||||
|
|
@ -327,42 +532,114 @@ search=Пошук
|
|||
search.search_repo=Пошук репозиторію
|
||||
|
||||
settings=Параметри
|
||||
settings.options=Репозиторій
|
||||
settings.collaboration.admin=Адміністратор
|
||||
settings.collaboration.write=Написати
|
||||
settings.collaboration.read=Читати
|
||||
settings.collaboration.undefined=Не визначено
|
||||
settings.hooks=Webhooks
|
||||
settings.basic_settings=Базові налаштування
|
||||
settings.mirror_settings=Налаштування дзеркала
|
||||
settings.update_settings=Оновити налаштування
|
||||
settings.advanced_settings=Додаткові налаштування
|
||||
settings.tracker_issue_style.numeric=Цифровий
|
||||
settings.tracker_issue_style.alphanumeric=Буквено-цифровий
|
||||
settings.danger_zone=Небезпечна зона
|
||||
settings.transfer_owner=Новий власник
|
||||
settings.add_webhook=Додати Webhook
|
||||
settings.webhook_deletion=Видалити Webhook
|
||||
settings.webhook.request=Запит
|
||||
settings.secret=Секрет
|
||||
settings.slack_username=Ім'я кристувача
|
||||
settings.slack_icon_url=URL іконки
|
||||
settings.discord_username=Ім'я кристувача
|
||||
settings.discord_icon_url=URL іконки
|
||||
settings.slack_color=Колір
|
||||
settings.event_create=Створити
|
||||
settings.event_pull_request=Запити до злиття
|
||||
settings.event_push=Push
|
||||
settings.event_repository=Репозиторій
|
||||
settings.update_webhook=Оновити Webhook
|
||||
settings.slack_token=Токен
|
||||
settings.slack_domain=Домен
|
||||
settings.slack_channel=Канал
|
||||
settings.deploy_keys=Ключи для розгортування
|
||||
settings.add_deploy_key=Додати ключ для розгортування
|
||||
settings.is_writable=Включити доступ для запису
|
||||
settings.title=Заголовок
|
||||
settings.branches=Гілки
|
||||
settings.protected_branch_can_push=Дозволити push?
|
||||
settings.protected_branch_can_push_yes=Ви можете виконувати push
|
||||
settings.protected_branch_can_push_no=Ви не можете виконувати push
|
||||
settings.add_protected_branch=Увімкнути захист
|
||||
settings.delete_protected_branch=Вимкнути захист
|
||||
settings.choose_branch=Оберіть гілку…
|
||||
|
||||
diff.browse_source=Переглянути джерело
|
||||
diff.commit=коміт
|
||||
diff.show_split_view=Розділений перегляд
|
||||
diff.view_file=Переглянути файл
|
||||
diff.too_many_files=Деякі файли не було показано, через те що забагато файлів було змінено
|
||||
|
||||
release.releases=Релізи
|
||||
release.new_release=Новий реліз
|
||||
release.draft=Чернетка
|
||||
release.prerelease=Пре-реліз
|
||||
release.stable=Стабільний
|
||||
release.edit=редагувати
|
||||
release.ahead=<strong>%d</strong> комітів %s після цього релізу
|
||||
release.tag_name=Назва тегу
|
||||
release.target=Ціль
|
||||
release.title=Заголовок
|
||||
release.preview=Переглянути
|
||||
release.loading=Завантаження…
|
||||
release.cancel=Відміна
|
||||
release.edit_release=Оновити реліз
|
||||
release.delete_release=Видалити реліз
|
||||
release.deletion=Видалити реліз
|
||||
|
||||
branch.delete_head=Видалити
|
||||
|
||||
topic.done=Готово
|
||||
|
||||
[org]
|
||||
org_name_holder=Назва організації
|
||||
org_full_name_holder=Повна назва організації
|
||||
|
||||
|
||||
settings=Налаштування
|
||||
settings.update_settings=Оновити налаштування
|
||||
|
||||
|
||||
teams.settings=Налаштування
|
||||
teams.update_settings=Оновити налаштування
|
||||
teams.add_team_repository=Додати репозиторій команди
|
||||
|
||||
[admin]
|
||||
|
||||
dashboard.delete_inactivate_accounts_success=Усі неактивні облікові записи успішно видалено.
|
||||
dashboard.current_memory_usage=Поточне використання пам'яті
|
||||
dashboard.memory_obtained=Отримано пам'яті
|
||||
|
||||
users.name=Ім'я кристувача
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
config.db_config=Налаштування бази даних
|
||||
config.db_type=Тип
|
||||
config.db_host=Хост
|
||||
config.db_name=Ім'я
|
||||
config.db_user=Ім'я кристувача
|
||||
config.db_ssl_mode=SSL
|
||||
config.db_ssl_mode_helper=(тільки для "postgres")
|
||||
config.db_path=Шлях
|
||||
config.db_path_helper=(для "sqlite3" і "tidb")
|
||||
|
||||
|
||||
|
||||
config.webhook_config=Налаштування Webhook
|
||||
|
||||
|
||||
|
||||
|
|
@ -374,12 +651,21 @@ teams.add_team_repository=Додати репозиторій команди
|
|||
|
||||
|
||||
[action]
|
||||
merge_pull_request=`запит на злиття злито <a href="%s/pulls/%s">%s#%[2]s</a>`
|
||||
|
||||
[tool]
|
||||
|
||||
[dropzone]
|
||||
file_too_big=Розмір файлу ({{filesize}} MB), що більше ніж максимальний розмір: ({{maxFilesize}} MB).
|
||||
remove_file=Видалити файл
|
||||
|
||||
[notification]
|
||||
notifications=Сповіщення
|
||||
unread=Непрочитані
|
||||
read=Прочитані
|
||||
mark_as_read=Позначити як прочитане
|
||||
mark_as_unread=Позначити як непрочитане
|
||||
mark_all_as_read=Позначити всі як прочитані
|
||||
|
||||
[gpg]
|
||||
|
||||
|
|
|
|||
40
public/swagger.v1.json
vendored
40
public/swagger.v1.json
vendored
|
|
@ -1565,6 +1565,46 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/repos/{owner}/{repo}/hooks/{id}/tests": {
|
||||
"post": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"repository"
|
||||
],
|
||||
"summary": "Test a push webhook",
|
||||
"operationId": "repoTestHook",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "owner of the repo",
|
||||
"name": "owner",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "name of the repo",
|
||||
"name": "repo",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "id of the hook to test",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"$ref": "#/responses/empty"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/repos/{owner}/{repo}/issue/{index}/comments": {
|
||||
"get": {
|
||||
"produces": [
|
||||
|
|
|
|||
|
|
@ -382,9 +382,12 @@ func RegisterRoutes(m *macaron.Macaron) {
|
|||
m.Group("/hooks", func() {
|
||||
m.Combo("").Get(repo.ListHooks).
|
||||
Post(bind(api.CreateHookOption{}), repo.CreateHook)
|
||||
m.Combo("/:id").Get(repo.GetHook).
|
||||
Patch(bind(api.EditHookOption{}), repo.EditHook).
|
||||
Delete(repo.DeleteHook)
|
||||
m.Group("/:id", func() {
|
||||
m.Combo("").Get(repo.GetHook).
|
||||
Patch(bind(api.EditHookOption{}), repo.EditHook).
|
||||
Delete(repo.DeleteHook)
|
||||
m.Post("/tests", context.RepoRef(), repo.TestHook)
|
||||
})
|
||||
}, reqToken(), reqRepoWriter())
|
||||
m.Group("/collaborators", func() {
|
||||
m.Get("", repo.ListCollaborators)
|
||||
|
|
|
|||
|
|
@ -123,13 +123,10 @@ func EditTeam(ctx *context.APIContext, form api.EditTeamOption) {
|
|||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/Team"
|
||||
team := &models.Team{
|
||||
ID: ctx.Org.Team.ID,
|
||||
OrgID: ctx.Org.Team.OrgID,
|
||||
Name: form.Name,
|
||||
Description: form.Description,
|
||||
Authorize: models.ParseAccessMode(form.Permission),
|
||||
}
|
||||
team := ctx.Org.Team
|
||||
team.Name = form.Name
|
||||
team.Description = form.Description
|
||||
team.Authorize = models.ParseAccessMode(form.Permission)
|
||||
if err := models.UpdateTeam(team, true); err != nil {
|
||||
ctx.Error(500, "EditTeam", err)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@
|
|||
package repo
|
||||
|
||||
import (
|
||||
"code.gitea.io/git"
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/routers/api/v1/convert"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
|
||||
api "code.gitea.io/sdk/gitea"
|
||||
)
|
||||
|
||||
|
|
@ -82,6 +82,62 @@ func GetHook(ctx *context.APIContext) {
|
|||
ctx.JSON(200, convert.ToHook(repo.RepoLink, hook))
|
||||
}
|
||||
|
||||
// TestHook tests a hook
|
||||
func TestHook(ctx *context.APIContext) {
|
||||
// swagger:operation POST /repos/{owner}/{repo}/hooks/{id}/tests repository repoTestHook
|
||||
// ---
|
||||
// summary: Test a push webhook
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: id
|
||||
// in: path
|
||||
// description: id of the hook to test
|
||||
// type: integer
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
if ctx.Repo.Commit == nil {
|
||||
// if repo does not have any commits, then don't send a webhook
|
||||
ctx.Status(204)
|
||||
return
|
||||
}
|
||||
|
||||
hookID := ctx.ParamsInt64(":id")
|
||||
hook, err := utils.GetRepoHook(ctx, ctx.Repo.Repository.ID, hookID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := models.PrepareWebhook(hook, ctx.Repo.Repository, models.HookEventPush, &api.PushPayload{
|
||||
Ref: git.BranchPrefix + ctx.Repo.Repository.DefaultBranch,
|
||||
Before: ctx.Repo.Commit.ID.String(),
|
||||
After: ctx.Repo.Commit.ID.String(),
|
||||
Commits: []*api.PayloadCommit{
|
||||
convert.ToCommit(ctx.Repo.Repository, ctx.Repo.Commit),
|
||||
},
|
||||
Repo: ctx.Repo.Repository.APIFormat(models.AccessModeNone),
|
||||
Pusher: ctx.User.APIFormat(),
|
||||
Sender: ctx.User.APIFormat(),
|
||||
}); err != nil {
|
||||
ctx.Error(500, "PrepareWebhook: ", err)
|
||||
return
|
||||
}
|
||||
go models.HookQueue.Add(ctx.Repo.Repository.ID)
|
||||
ctx.Status(204)
|
||||
}
|
||||
|
||||
// CreateHook create a hook for a repository
|
||||
func CreateHook(ctx *context.APIContext, form api.CreateHookOption) {
|
||||
// swagger:operation POST /repos/{owner}/{repo}/hooks repository repoCreateHook
|
||||
|
|
|
|||
33
routers/api/v1/repo/hook_test.go
Normal file
33
routers/api/v1/repo/hook_test.go
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright 2018 The Gitea 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 repo
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTestHook(t *testing.T) {
|
||||
models.PrepareTestEnv(t)
|
||||
|
||||
ctx := test.MockContext(t, "user2/repo1/wiki/_pages")
|
||||
ctx.SetParams(":id", "1")
|
||||
test.LoadRepo(t, ctx, 1)
|
||||
test.LoadRepoCommit(t, ctx)
|
||||
test.LoadUser(t, ctx, 2)
|
||||
TestHook(&context.APIContext{Context: ctx, Org: nil})
|
||||
assert.EqualValues(t, http.StatusNoContent, ctx.Resp.Status())
|
||||
|
||||
models.AssertExistsAndLoadBean(t, &models.HookTask{
|
||||
RepoID: 1,
|
||||
HookID: 1,
|
||||
}, models.Cond("is_delivered=?", false))
|
||||
}
|
||||
16
routers/api/v1/repo/main_test.go
Normal file
16
routers/api/v1/repo/main_test.go
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright 2018 The Gitea 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 repo
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
models.MainTest(m, filepath.Join("..", "..", "..", ".."))
|
||||
}
|
||||
|
|
@ -60,7 +60,9 @@ func GlobalInit() {
|
|||
log.Fatal(4, "Failed to initialize ORM engine: %v", err)
|
||||
}
|
||||
models.HasEngine = true
|
||||
models.InitOAuth2()
|
||||
if err := models.InitOAuth2(); err != nil {
|
||||
log.Fatal(4, "Failed to initialize OAuth2 support: %v", err)
|
||||
}
|
||||
|
||||
models.LoadRepoConfig()
|
||||
models.NewRepoContext()
|
||||
|
|
|
|||
|
|
@ -1171,6 +1171,12 @@ func Milestones(ctx *context.Context) {
|
|||
ctx.ServerError("GetMilestones", err)
|
||||
return
|
||||
}
|
||||
if ctx.Repo.Repository.IsTimetrackerEnabled() {
|
||||
if miles.LoadTotalTrackedTimes(); err != nil {
|
||||
ctx.ServerError("LoadTotalTrackedTimes", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, m := range miles {
|
||||
m.RenderedContent = string(markdown.Render([]byte(m.Content), ctx.Repo.RepoLink, ctx.Repo.Repository.ComposeMetas()))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -198,6 +198,10 @@
|
|||
<span class="comment ui right"><i class="octicon octicon-comment"></i> {{.NumComments}}</span>
|
||||
{{end}}
|
||||
|
||||
{{if .TotalTrackedTime}}
|
||||
<span class="comment ui right"><i class="octicon octicon-clock"></i> {{.TotalTrackedTime | Sec2Time}}</span>
|
||||
{{end}}
|
||||
|
||||
<p class="desc">
|
||||
{{$.i18n.Tr "repo.issues.opened_by" $timeStr .Poster.HomeLink .Poster.Name | Safe}}
|
||||
{{$tasks := .GetTasks}}
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@
|
|||
<span class="issue-stats">
|
||||
<i class="octicon octicon-issue-opened"></i> {{$.i18n.Tr "repo.issues.open_tab" .NumOpenIssues}}
|
||||
<i class="octicon octicon-issue-closed"></i> {{$.i18n.Tr "repo.issues.close_tab" .NumClosedIssues}}
|
||||
{{if .TotalTrackedTime}}<i class="octicon octicon-clock"></i> {{.TotalTrackedTime|Sec2Time}}{{end}}
|
||||
</span>
|
||||
</div>
|
||||
{{if $.IsRepositoryWriter}}
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@
|
|||
{{if gt (len .WorkingUsers) 0}}
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui participants comments">
|
||||
<span class="text"><strong>{{.i18n.Tr "repo.issues.time_spent_total"}}</strong></span>
|
||||
<span class="text"><strong>{{.i18n.Tr "repo.issues.time_spent_from_all_authors" ($.Issue.TotalTrackedTime | Sec2Time) | Safe}}</strong></span>
|
||||
<div>
|
||||
{{range $user, $trackedtime := .WorkingUsers}}
|
||||
<div class="comment">
|
||||
|
|
|
|||
|
|
@ -79,6 +79,9 @@
|
|||
{{if .NumComments}}
|
||||
<span class="comment ui right"><i class="octicon octicon-comment"></i> {{.NumComments}}</span>
|
||||
{{end}}
|
||||
{{if .TotalTrackedTime}}
|
||||
<span class="comment ui right"><i class="octicon octicon-clock"></i> {{.TotalTrackedTime | Sec2Time}}</span>
|
||||
{{end}}
|
||||
|
||||
<p class="desc">
|
||||
{{$.i18n.Tr "repo.issues.opened_by" $timeStr .Poster.HomeLink .Poster.Name | Safe}}
|
||||
|
|
|
|||
75
vendor/github.com/lafriks/xormstore/Gopkg.lock
generated
vendored
Normal file
75
vendor/github.com/lafriks/xormstore/Gopkg.lock
generated
vendored
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/denisenkom/go-mssqldb"
|
||||
packages = ["."]
|
||||
revision = "ee492709d4324cdcb051d2ac266b77ddc380f5c5"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/go-sql-driver/mysql"
|
||||
packages = ["."]
|
||||
revision = "a0583e0143b1624142adab07e0e97fe106d99561"
|
||||
version = "v1.3"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/go-xorm/builder"
|
||||
packages = ["."]
|
||||
revision = "488224409dd8aa2ce7a5baf8d10d55764a913738"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/go-xorm/core"
|
||||
packages = ["."]
|
||||
revision = "da1adaf7a28ca792961721a34e6e04945200c890"
|
||||
version = "v0.5.7"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/go-xorm/xorm"
|
||||
packages = ["."]
|
||||
revision = "1933dd69e294c0a26c0266637067f24dbb25770c"
|
||||
version = "v0.6.4"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/gorilla/context"
|
||||
packages = ["."]
|
||||
revision = "1ea25387ff6f684839d82767c1733ff4d4d15d0a"
|
||||
version = "v1.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/gorilla/securecookie"
|
||||
packages = ["."]
|
||||
revision = "e59506cc896acb7f7bf732d4fdf5e25f7ccd8983"
|
||||
version = "v1.1.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/gorilla/sessions"
|
||||
packages = ["."]
|
||||
revision = "ca9ada44574153444b00d3fd9c8559e4cc95f896"
|
||||
version = "v1.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/lib/pq"
|
||||
packages = [".","oid"]
|
||||
revision = "88edab0803230a3898347e77b474f8c1820a1f20"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/mattn/go-sqlite3"
|
||||
packages = ["."]
|
||||
revision = "6c771bb9887719704b210e87e934f08be014bdb1"
|
||||
version = "v1.6.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = ["md4"]
|
||||
revision = "c7dcf104e3a7a1417abc0230cb0d5240d764159d"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "bba98a94e8c6668ae9556b4978bbffdfc5d4d535d522c8865465335bfaa2fc70"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
50
vendor/github.com/lafriks/xormstore/Gopkg.toml
generated
vendored
Normal file
50
vendor/github.com/lafriks/xormstore/Gopkg.toml
generated
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/go-sql-driver/mysql"
|
||||
version = "1.3.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/go-xorm/xorm"
|
||||
version = "0.6.4"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/gorilla/context"
|
||||
version = "1.1.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/gorilla/securecookie"
|
||||
version = "1.1.1"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/gorilla/sessions"
|
||||
version = "1.1.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/lib/pq"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/mattn/go-sqlite3"
|
||||
version = "1.6.0"
|
||||
19
vendor/github.com/lafriks/xormstore/LICENSE
generated
vendored
Normal file
19
vendor/github.com/lafriks/xormstore/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2018 Lauris Bukšis-Haberkorns, Mattias Wadman
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
48
vendor/github.com/lafriks/xormstore/README.md
generated
vendored
Normal file
48
vendor/github.com/lafriks/xormstore/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
[](https://godoc.org/github.com/lafriks/xormstore)
|
||||
[](https://travis-ci.org/lafriks/xormstore)
|
||||
[](https://codecov.io/gh/lafriks/xormstore)
|
||||
|
||||
#### XORM backend for gorilla sessions
|
||||
|
||||
go get github.com/lafriks/xormstore
|
||||
|
||||
#### Example
|
||||
|
||||
```go
|
||||
// initialize and setup cleanup
|
||||
store := xormstore.New(engine, []byte("secret"))
|
||||
// db cleanup every hour
|
||||
// close quit channel to stop cleanup
|
||||
quit := make(chan struct{})
|
||||
go store.PeriodicCleanup(1*time.Hour, quit)
|
||||
```
|
||||
|
||||
```go
|
||||
// in HTTP handler
|
||||
func handlerFunc(w http.ResponseWriter, r *http.Request) {
|
||||
session, err := store.Get(r, "session")
|
||||
session.Values["user_id"] = 123
|
||||
store.Save(r, w, session)
|
||||
http.Error(w, "", http.StatusOK)
|
||||
}
|
||||
```
|
||||
|
||||
For more details see [xormstore godoc documentation](https://godoc.org/github.com/lafriks/xormstore).
|
||||
|
||||
#### Testing
|
||||
|
||||
Just sqlite3 tests:
|
||||
|
||||
go test
|
||||
|
||||
All databases using docker:
|
||||
|
||||
./test
|
||||
|
||||
If docker is not local (docker-machine etc):
|
||||
|
||||
DOCKER_IP=$(docker-machine ip dev) ./test
|
||||
|
||||
#### License
|
||||
|
||||
xormstore is licensed under the MIT license. See [LICENSE](LICENSE) for the full license text.
|
||||
70
vendor/github.com/lafriks/xormstore/test
generated
vendored
Executable file
70
vendor/github.com/lafriks/xormstore/test
generated
vendored
Executable file
|
|
@ -0,0 +1,70 @@
|
|||
#!/bin/bash
|
||||
|
||||
DOCKER_IP=${DOCKER_IP:-127.0.0.1}
|
||||
|
||||
sqlite3() {
|
||||
DATABASE_URI="sqlite3://file:dummy?mode=memory&cache=shared" go test -v -race -cover -coverprofile=coverage.txt -covermode=atomic
|
||||
return $?
|
||||
}
|
||||
|
||||
postgres10() {
|
||||
ID=$(docker run -p 5432 -d postgres:10-alpine)
|
||||
PORT=$(docker port "$ID" 5432 | cut -d : -f 2)
|
||||
DATABASE_URI="postgres://user=postgres password=postgres dbname=postgres host=$DOCKER_IP port=$PORT sslmode=disable" go test -v -race -cover
|
||||
S=$?
|
||||
docker rm -vf "$ID" > /dev/null
|
||||
return $S
|
||||
}
|
||||
|
||||
postgres96() {
|
||||
ID=$(docker run -p 5432 -d postgres:9.6-alpine)
|
||||
PORT=$(docker port "$ID" 5432 | cut -d : -f 2)
|
||||
DATABASE_URI="postgres://user=postgres password=postgres dbname=postgres host=$DOCKER_IP port=$PORT sslmode=disable" go test -v -race -cover
|
||||
S=$?
|
||||
docker rm -vf "$ID" > /dev/null
|
||||
return $S
|
||||
}
|
||||
|
||||
postgres94() {
|
||||
ID=$(docker run -p 5432 -d postgres:9.4-alpine)
|
||||
PORT=$(docker port "$ID" 5432 | cut -d : -f 2)
|
||||
DATABASE_URI="postgres://user=postgres password=postgres dbname=postgres host=$DOCKER_IP port=$PORT sslmode=disable" go test -v -race -cover
|
||||
S=$?
|
||||
docker rm -vf "$ID" > /dev/null
|
||||
return $S
|
||||
}
|
||||
|
||||
mysql57() {
|
||||
ID=$(docker run \
|
||||
-e MYSQL_ROOT_PASSWORD=root \
|
||||
-e MYSQL_USER=mysql \
|
||||
-e MYSQL_PASSWORD=mysql \
|
||||
-e MYSQL_DATABASE=mysql \
|
||||
-p 3306 -d mysql:5.7)
|
||||
PORT=$(docker port "$ID" 3306 | cut -d : -f 2)
|
||||
DATABASE_URI="mysql://mysql:mysql@tcp($DOCKER_IP:$PORT)/mysql?charset=utf8&parseTime=True" go test -v -race -cover
|
||||
S=$?
|
||||
docker rm -vf "$ID" > /dev/null
|
||||
return $S
|
||||
}
|
||||
|
||||
mariadb10() {
|
||||
ID=$(docker run \
|
||||
-e MYSQL_ROOT_PASSWORD=root \
|
||||
-e MYSQL_USER=mysql \
|
||||
-e MYSQL_PASSWORD=mysql \
|
||||
-e MYSQL_DATABASE=mysql \
|
||||
-p 3306 -d mariadb:10)
|
||||
PORT=$(docker port "$ID" 3306 | cut -d : -f 2)
|
||||
DATABASE_URI="mysql://mysql:mysql@tcp($DOCKER_IP:$PORT)/mysql?charset=utf8&parseTime=True" go test -v -race -cover
|
||||
S=$?
|
||||
docker rm -vf "$ID" > /dev/null
|
||||
return $S
|
||||
}
|
||||
|
||||
sqlite3 || exit 1
|
||||
postgres94 || exit 1
|
||||
postgres96 || exit 1
|
||||
postgres10 || exit 1
|
||||
mysql57 || exit 1
|
||||
mariadb10 || exit 1
|
||||
60
vendor/github.com/lafriks/xormstore/util/time_stamp.go
generated
vendored
Normal file
60
vendor/github.com/lafriks/xormstore/util/time_stamp.go
generated
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// TimeStamp defines a timestamp
|
||||
type TimeStamp int64
|
||||
|
||||
// TimeStampNow returns now int64
|
||||
func TimeStampNow() TimeStamp {
|
||||
return TimeStamp(time.Now().Unix())
|
||||
}
|
||||
|
||||
// Add adds seconds and return sum
|
||||
func (ts TimeStamp) Add(seconds int64) TimeStamp {
|
||||
return ts + TimeStamp(seconds)
|
||||
}
|
||||
|
||||
// AddDuration adds time.Duration and return sum
|
||||
func (ts TimeStamp) AddDuration(interval time.Duration) TimeStamp {
|
||||
return ts + TimeStamp(interval/time.Second)
|
||||
}
|
||||
|
||||
// Year returns the time's year
|
||||
func (ts TimeStamp) Year() int {
|
||||
return ts.AsTime().Year()
|
||||
}
|
||||
|
||||
// AsTime convert timestamp as time.Time in Local locale
|
||||
func (ts TimeStamp) AsTime() (tm time.Time) {
|
||||
tm = time.Unix(int64(ts), 0).Local()
|
||||
return
|
||||
}
|
||||
|
||||
// AsTimePtr convert timestamp as *time.Time in Local locale
|
||||
func (ts TimeStamp) AsTimePtr() *time.Time {
|
||||
tm := time.Unix(int64(ts), 0).Local()
|
||||
return &tm
|
||||
}
|
||||
|
||||
// Format formats timestamp as
|
||||
func (ts TimeStamp) Format(f string) string {
|
||||
return ts.AsTime().Format(f)
|
||||
}
|
||||
|
||||
// FormatLong formats as RFC1123Z
|
||||
func (ts TimeStamp) FormatLong() string {
|
||||
return ts.Format(time.RFC1123Z)
|
||||
}
|
||||
|
||||
// FormatShort formats as short
|
||||
func (ts TimeStamp) FormatShort() string {
|
||||
return ts.Format("Jan 02, 2006")
|
||||
}
|
||||
|
||||
// IsZero is zero time
|
||||
func (ts TimeStamp) IsZero() bool {
|
||||
return ts.AsTime().IsZero()
|
||||
}
|
||||
251
vendor/github.com/lafriks/xormstore/xormstore.go
generated
vendored
Normal file
251
vendor/github.com/lafriks/xormstore/xormstore.go
generated
vendored
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
/*
|
||||
Package xormstore is a XORM backend for gorilla sessions
|
||||
|
||||
Simplest form:
|
||||
|
||||
store, err := xormstore.New(engine, []byte("secret-hash-key"))
|
||||
|
||||
All options:
|
||||
|
||||
store, err := xormstore.NewOptions(
|
||||
engine, // *xorm.Engine
|
||||
xormstore.Options{
|
||||
TableName: "sessions", // "sessions" is default
|
||||
SkipCreateTable: false, // false is default
|
||||
},
|
||||
[]byte("secret-hash-key"), // 32 or 64 bytes recommended, required
|
||||
[]byte("secret-encyption-key")) // nil, 16, 24 or 32 bytes, optional
|
||||
|
||||
if err != nil {
|
||||
// xormstore can not be initialized
|
||||
}
|
||||
|
||||
// some more settings, see sessions.Options
|
||||
store.SessionOpts.Secure = true
|
||||
store.SessionOpts.HttpOnly = true
|
||||
store.SessionOpts.MaxAge = 60 * 60 * 24 * 60
|
||||
|
||||
If you want periodic cleanup of expired sessions:
|
||||
|
||||
quit := make(chan struct{})
|
||||
go store.PeriodicCleanup(1*time.Hour, quit)
|
||||
|
||||
For more information about the keys see https://github.com/gorilla/securecookie
|
||||
|
||||
For API to use in HTTP handlers see https://github.com/gorilla/sessions
|
||||
*/
|
||||
package xormstore
|
||||
|
||||
import (
|
||||
"encoding/base32"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/lafriks/xormstore/util"
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
"github.com/gorilla/context"
|
||||
"github.com/gorilla/securecookie"
|
||||
"github.com/gorilla/sessions"
|
||||
)
|
||||
|
||||
const sessionIDLen = 32
|
||||
const defaultTableName = "sessions"
|
||||
const defaultMaxAge = 60 * 60 * 24 * 30 // 30 days
|
||||
const defaultPath = "/"
|
||||
|
||||
// Options for xormstore
|
||||
type Options struct {
|
||||
TableName string
|
||||
SkipCreateTable bool
|
||||
}
|
||||
|
||||
// Store represent a xormstore
|
||||
type Store struct {
|
||||
e *xorm.Engine
|
||||
opts Options
|
||||
Codecs []securecookie.Codec
|
||||
SessionOpts *sessions.Options
|
||||
}
|
||||
|
||||
type xormSession struct {
|
||||
ID string `xorm:"VARCHAR(400) PK NAME 'id'"`
|
||||
Data string `xorm:"TEXT"`
|
||||
CreatedUnix util.TimeStamp `xorm:"created"`
|
||||
UpdatedUnix util.TimeStamp `xorm:"updated"`
|
||||
ExpiresUnix util.TimeStamp `xorm:"INDEX"`
|
||||
|
||||
tableName string `xorm:"-"` // just to store table name for easier access
|
||||
}
|
||||
|
||||
// Define a type for context keys so that they can't clash with anything else stored in context
|
||||
type contextKey string
|
||||
|
||||
func (xs *xormSession) TableName() string {
|
||||
return xs.tableName
|
||||
}
|
||||
|
||||
// New creates a new xormstore session
|
||||
func New(e *xorm.Engine, keyPairs ...[]byte) (*Store, error) {
|
||||
return NewOptions(e, Options{}, keyPairs...)
|
||||
}
|
||||
|
||||
// NewOptions creates a new xormstore session with options
|
||||
func NewOptions(e *xorm.Engine, opts Options, keyPairs ...[]byte) (*Store, error) {
|
||||
st := &Store{
|
||||
e: e,
|
||||
opts: opts,
|
||||
Codecs: securecookie.CodecsFromPairs(keyPairs...),
|
||||
SessionOpts: &sessions.Options{
|
||||
Path: defaultPath,
|
||||
MaxAge: defaultMaxAge,
|
||||
},
|
||||
}
|
||||
if st.opts.TableName == "" {
|
||||
st.opts.TableName = defaultTableName
|
||||
}
|
||||
|
||||
if !st.opts.SkipCreateTable {
|
||||
if err := st.e.Sync2(&xormSession{tableName: st.opts.TableName}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return st, nil
|
||||
}
|
||||
|
||||
// Get returns a session for the given name after adding it to the registry.
|
||||
func (st *Store) Get(r *http.Request, name string) (*sessions.Session, error) {
|
||||
return sessions.GetRegistry(r).Get(st, name)
|
||||
}
|
||||
|
||||
// New creates a session with name without adding it to the registry.
|
||||
func (st *Store) New(r *http.Request, name string) (*sessions.Session, error) {
|
||||
session := sessions.NewSession(st, name)
|
||||
opts := *st.SessionOpts
|
||||
session.Options = &opts
|
||||
|
||||
st.MaxAge(st.SessionOpts.MaxAge)
|
||||
|
||||
// try fetch from db if there is a cookie
|
||||
if cookie, err := r.Cookie(name); err == nil {
|
||||
if err := securecookie.DecodeMulti(name, cookie.Value, &session.ID, st.Codecs...); err != nil {
|
||||
return session, nil
|
||||
}
|
||||
s := &xormSession{tableName: st.opts.TableName}
|
||||
if has, err := st.e.Where("id = ? AND expires_unix >= ?", session.ID, util.TimeStampNow()).Get(s); !has || err != nil {
|
||||
return session, nil
|
||||
}
|
||||
if err := securecookie.DecodeMulti(session.Name(), s.Data, &session.Values, st.Codecs...); err != nil {
|
||||
return session, nil
|
||||
}
|
||||
|
||||
context.Set(r, contextKey(name), s)
|
||||
}
|
||||
|
||||
return session, nil
|
||||
}
|
||||
|
||||
// Save session and set cookie header
|
||||
func (st *Store) Save(r *http.Request, w http.ResponseWriter, session *sessions.Session) error {
|
||||
s, _ := context.Get(r, contextKey(session.Name())).(*xormSession)
|
||||
|
||||
// delete if max age is < 0
|
||||
if session.Options.MaxAge < 0 {
|
||||
if s != nil {
|
||||
if _, err := st.e.Delete(&xormSession{
|
||||
ID: session.ID,
|
||||
tableName: st.opts.TableName,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
http.SetCookie(w, sessions.NewCookie(session.Name(), "", session.Options))
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := securecookie.EncodeMulti(session.Name(), session.Values, st.Codecs...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
now := util.TimeStampNow()
|
||||
expire := now.AddDuration(time.Second * time.Duration(session.Options.MaxAge))
|
||||
|
||||
if s == nil {
|
||||
// generate random session ID key suitable for storage in the db
|
||||
session.ID = strings.TrimRight(
|
||||
base32.StdEncoding.EncodeToString(
|
||||
securecookie.GenerateRandomKey(sessionIDLen)), "=")
|
||||
s = &xormSession{
|
||||
ID: session.ID,
|
||||
Data: data,
|
||||
CreatedUnix: now,
|
||||
UpdatedUnix: now,
|
||||
ExpiresUnix: expire,
|
||||
tableName: st.opts.TableName,
|
||||
}
|
||||
if _, err := st.e.Insert(s); err != nil {
|
||||
return err
|
||||
}
|
||||
context.Set(r, contextKey(session.Name()), s)
|
||||
} else {
|
||||
s.Data = data
|
||||
s.UpdatedUnix = now
|
||||
s.ExpiresUnix = expire
|
||||
if _, err := st.e.ID(s.ID).Cols("data", "updated_unix", "expires_unix").Update(s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// set session id cookie
|
||||
id, err := securecookie.EncodeMulti(session.Name(), session.ID, st.Codecs...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
http.SetCookie(w, sessions.NewCookie(session.Name(), id, session.Options))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MaxAge sets the maximum age for the store and the underlying cookie
|
||||
// implementation. Individual sessions can be deleted by setting
|
||||
// Options.MaxAge = -1 for that session.
|
||||
func (st *Store) MaxAge(age int) {
|
||||
st.SessionOpts.MaxAge = age
|
||||
for _, codec := range st.Codecs {
|
||||
if sc, ok := codec.(*securecookie.SecureCookie); ok {
|
||||
sc.MaxAge(age)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MaxLength restricts the maximum length of new sessions to l.
|
||||
// If l is 0 there is no limit to the size of a session, use with caution.
|
||||
// The default is 4096 (default for securecookie)
|
||||
func (st *Store) MaxLength(l int) {
|
||||
for _, c := range st.Codecs {
|
||||
if codec, ok := c.(*securecookie.SecureCookie); ok {
|
||||
codec.MaxLength(l)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup deletes expired sessions
|
||||
func (st *Store) Cleanup() {
|
||||
st.e.Where("expires_unix < ?", util.TimeStampNow()).Delete(&xormSession{tableName: st.opts.TableName})
|
||||
}
|
||||
|
||||
// PeriodicCleanup runs Cleanup every interval. Close quit channel to stop.
|
||||
func (st *Store) PeriodicCleanup(interval time.Duration, quit <-chan struct{}) {
|
||||
t := time.NewTicker(interval)
|
||||
defer t.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
st.Cleanup()
|
||||
case <-quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
165
vendor/github.com/russross/blackfriday/README.md
generated
vendored
165
vendor/github.com/russross/blackfriday/README.md
generated
vendored
|
|
@ -1,4 +1,6 @@
|
|||
Blackfriday [](https://travis-ci.org/russross/blackfriday) [](https://godoc.org/github.com/russross/blackfriday)
|
||||
Blackfriday
|
||||
[![Build Status][BuildSVG]][BuildURL]
|
||||
[![Godoc][GodocV2SVG]][GodocV2URL]
|
||||
===========
|
||||
|
||||
Blackfriday is a [Markdown][1] processor implemented in [Go][2]. It
|
||||
|
|
@ -8,7 +10,7 @@ punctuation substitutions, etc.), and it is safe for all utf-8
|
|||
(unicode) input.
|
||||
|
||||
HTML output is currently supported, along with Smartypants
|
||||
extensions. An experimental LaTeX output engine is also included.
|
||||
extensions.
|
||||
|
||||
It started as a translation from C of [Sundown][3].
|
||||
|
||||
|
|
@ -16,26 +18,71 @@ It started as a translation from C of [Sundown][3].
|
|||
Installation
|
||||
------------
|
||||
|
||||
Blackfriday is compatible with Go 1. If you are using an older
|
||||
release of Go, consider using v1.1 of blackfriday, which was based
|
||||
on the last stable release of Go prior to Go 1. You can find it as a
|
||||
tagged commit on github.
|
||||
Blackfriday is compatible with any modern Go release. With Go and git installed:
|
||||
|
||||
With Go 1 and git installed:
|
||||
go get -u gopkg.in/russross/blackfriday.v2
|
||||
|
||||
go get github.com/russross/blackfriday
|
||||
will download, compile, and install the package into your `$GOPATH` directory
|
||||
hierarchy.
|
||||
|
||||
will download, compile, and install the package into your `$GOPATH`
|
||||
directory hierarchy. Alternatively, you can achieve the same if you
|
||||
import it into a project:
|
||||
|
||||
import "github.com/russross/blackfriday"
|
||||
Versions
|
||||
--------
|
||||
|
||||
Currently maintained and recommended version of Blackfriday is `v2`. It's being
|
||||
developed on its own branch: https://github.com/russross/blackfriday/tree/v2 and the
|
||||
documentation is available at
|
||||
https://godoc.org/gopkg.in/russross/blackfriday.v2.
|
||||
|
||||
It is `go get`-able via [gopkg.in][6] at `gopkg.in/russross/blackfriday.v2`,
|
||||
but we highly recommend using package management tool like [dep][7] or
|
||||
[Glide][8] and make use of semantic versioning. With package management you
|
||||
should import `github.com/russross/blackfriday` and specify that you're using
|
||||
version 2.0.0.
|
||||
|
||||
Version 2 offers a number of improvements over v1:
|
||||
|
||||
* Cleaned up API
|
||||
* A separate call to [`Parse`][4], which produces an abstract syntax tree for
|
||||
the document
|
||||
* Latest bug fixes
|
||||
* Flexibility to easily add your own rendering extensions
|
||||
|
||||
Potential drawbacks:
|
||||
|
||||
* Our benchmarks show v2 to be slightly slower than v1. Currently in the
|
||||
ballpark of around 15%.
|
||||
* API breakage. If you can't afford modifying your code to adhere to the new API
|
||||
and don't care too much about the new features, v2 is probably not for you.
|
||||
* Several bug fixes are trailing behind and still need to be forward-ported to
|
||||
v2. See issue [#348](https://github.com/russross/blackfriday/issues/348) for
|
||||
tracking.
|
||||
|
||||
If you are still interested in the legacy `v1`, you can import it from
|
||||
`github.com/russross/blackfriday`. Documentation for the legacy v1 can be found
|
||||
here: https://godoc.org/github.com/russross/blackfriday
|
||||
|
||||
### Known issue with `dep`
|
||||
|
||||
There is a known problem with using Blackfriday v1 _transitively_ and `dep`.
|
||||
Currently `dep` prioritizes semver versions over anything else, and picks the
|
||||
latest one, plus it does not apply a `[[constraint]]` specifier to transitively
|
||||
pulled in packages. So if you're using something that uses Blackfriday v1, but
|
||||
that something does not use `dep` yet, you will get Blackfriday v2 pulled in and
|
||||
your first dependency will fail to build.
|
||||
|
||||
There are couple of fixes for it, documented here:
|
||||
https://github.com/golang/dep/blob/master/docs/FAQ.md#how-do-i-constrain-a-transitive-dependencys-version
|
||||
|
||||
Meanwhile, `dep` team is working on a more general solution to the constraints
|
||||
on transitive dependencies problem: https://github.com/golang/dep/issues/1124.
|
||||
|
||||
and `go get` without parameters.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
### v1
|
||||
|
||||
For basic usage, it is as simple as getting your input into a byte
|
||||
slice and calling:
|
||||
|
||||
|
|
@ -46,34 +93,57 @@ feature set, use this instead:
|
|||
|
||||
output := blackfriday.MarkdownCommon(input)
|
||||
|
||||
### v2
|
||||
|
||||
For the most sensible markdown processing, it is as simple as getting your input
|
||||
into a byte slice and calling:
|
||||
|
||||
```go
|
||||
output := blackfriday.Run(input)
|
||||
```
|
||||
|
||||
Your input will be parsed and the output rendered with a set of most popular
|
||||
extensions enabled. If you want the most basic feature set, corresponding with
|
||||
the bare Markdown specification, use:
|
||||
|
||||
```go
|
||||
output := blackfriday.Run(input, blackfriday.WithNoExtensions())
|
||||
```
|
||||
|
||||
### Sanitize untrusted content
|
||||
|
||||
Blackfriday itself does nothing to protect against malicious content. If you are
|
||||
dealing with user-supplied markdown, we recommend running blackfriday's output
|
||||
through HTML sanitizer such as
|
||||
[Bluemonday](https://github.com/microcosm-cc/bluemonday).
|
||||
dealing with user-supplied markdown, we recommend running Blackfriday's output
|
||||
through HTML sanitizer such as [Bluemonday][5].
|
||||
|
||||
Here's an example of simple usage of blackfriday together with bluemonday:
|
||||
Here's an example of simple usage of Blackfriday together with Bluemonday:
|
||||
|
||||
``` go
|
||||
```go
|
||||
import (
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"github.com/russross/blackfriday"
|
||||
"gopkg.in/russross/blackfriday.v2"
|
||||
)
|
||||
|
||||
// ...
|
||||
unsafe := blackfriday.MarkdownCommon(input)
|
||||
unsafe := blackfriday.Run(input)
|
||||
html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)
|
||||
```
|
||||
|
||||
### Custom options
|
||||
### Custom options, v1
|
||||
|
||||
If you want to customize the set of options, first get a renderer
|
||||
(currently either the HTML or LaTeX output engines), then use it to
|
||||
(currently only the HTML output engine), then use it to
|
||||
call the more general `Markdown` function. For examples, see the
|
||||
implementations of `MarkdownBasic` and `MarkdownCommon` in
|
||||
`markdown.go`.
|
||||
|
||||
### Custom options, v2
|
||||
|
||||
If you want to customize the set of options, use `blackfriday.WithExtensions`,
|
||||
`blackfriday.WithRenderer` and `blackfriday.WithRefOverride`.
|
||||
|
||||
### `blackfriday-tool`
|
||||
|
||||
You can also check out `blackfriday-tool` for a more complete example
|
||||
of how to use it. Download and install it using:
|
||||
|
||||
|
|
@ -93,6 +163,22 @@ installed in `$GOPATH/bin`. This is a statically-linked binary that
|
|||
can be copied to wherever you need it without worrying about
|
||||
dependencies and library versions.
|
||||
|
||||
### Sanitized anchor names
|
||||
|
||||
Blackfriday includes an algorithm for creating sanitized anchor names
|
||||
corresponding to a given input text. This algorithm is used to create
|
||||
anchors for headings when `EXTENSION_AUTO_HEADER_IDS` is enabled. The
|
||||
algorithm has a specification, so that other packages can create
|
||||
compatible anchor names and links to those anchors.
|
||||
|
||||
The specification is located at https://godoc.org/github.com/russross/blackfriday#hdr-Sanitized_Anchor_Names.
|
||||
|
||||
[`SanitizedAnchorName`](https://godoc.org/github.com/russross/blackfriday#SanitizedAnchorName) exposes this functionality, and can be used to
|
||||
create compatible links to the anchor names generated by blackfriday.
|
||||
This algorithm is also implemented in a small standalone package at
|
||||
[`github.com/shurcooL/sanitized_anchor_name`](https://godoc.org/github.com/shurcooL/sanitized_anchor_name). It can be useful for clients
|
||||
that want a small package and don't need full functionality of blackfriday.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
|
@ -233,7 +319,7 @@ are a few of note:
|
|||
|
||||
* [github_flavored_markdown](https://godoc.org/github.com/shurcooL/github_flavored_markdown):
|
||||
provides a GitHub Flavored Markdown renderer with fenced code block
|
||||
highlighting, clickable header anchor links.
|
||||
highlighting, clickable heading anchor links.
|
||||
|
||||
It's not customizable, and its goal is to produce HTML output
|
||||
equivalent to the [GitHub Markdown API endpoint](https://developer.github.com/v3/markdown/#render-a-markdown-document-in-raw-mode),
|
||||
|
|
@ -242,27 +328,18 @@ are a few of note:
|
|||
* [markdownfmt](https://github.com/shurcooL/markdownfmt): like gofmt,
|
||||
but for markdown.
|
||||
|
||||
* LaTeX output: renders output as LaTeX. This is currently part of the
|
||||
main Blackfriday repository, but may be split into its own project
|
||||
in the future. If you are interested in owning and maintaining the
|
||||
LaTeX output component, please be in touch.
|
||||
|
||||
It renders some basic documents, but is only experimental at this
|
||||
point. In particular, it does not do any inline escaping, so input
|
||||
that happens to look like LaTeX code will be passed through without
|
||||
modification.
|
||||
|
||||
* [Md2Vim](https://github.com/FooSoft/md2vim): transforms markdown files into vimdoc format.
|
||||
* [LaTeX output](https://bitbucket.org/ambrevar/blackfriday-latex):
|
||||
renders output as LaTeX.
|
||||
|
||||
|
||||
Todo
|
||||
TODO
|
||||
----
|
||||
|
||||
* More unit testing
|
||||
* Improve unicode support. It does not understand all unicode
|
||||
* Improve Unicode support. It does not understand all Unicode
|
||||
rules (about what constitutes a letter, a punctuation symbol,
|
||||
etc.), so it may fail to detect word boundaries correctly in
|
||||
some instances. It is safe on all utf-8 input.
|
||||
some instances. It is safe on all UTF-8 input.
|
||||
|
||||
|
||||
License
|
||||
|
|
@ -271,6 +348,16 @@ License
|
|||
[Blackfriday is distributed under the Simplified BSD License](LICENSE.txt)
|
||||
|
||||
|
||||
[1]: http://daringfireball.net/projects/markdown/ "Markdown"
|
||||
[2]: http://golang.org/ "Go Language"
|
||||
[1]: https://daringfireball.net/projects/markdown/ "Markdown"
|
||||
[2]: https://golang.org/ "Go Language"
|
||||
[3]: https://github.com/vmg/sundown "Sundown"
|
||||
[4]: https://godoc.org/gopkg.in/russross/blackfriday.v2#Parse "Parse func"
|
||||
[5]: https://github.com/microcosm-cc/bluemonday "Bluemonday"
|
||||
[6]: https://labix.org/gopkg.in "gopkg.in"
|
||||
[7]: https://github.com/golang/dep/ "dep"
|
||||
[8]: https://github.com/Masterminds/glide "Glide"
|
||||
|
||||
[BuildSVG]: https://travis-ci.org/russross/blackfriday.svg?branch=master
|
||||
[BuildURL]: https://travis-ci.org/russross/blackfriday
|
||||
[GodocV2SVG]: https://godoc.org/gopkg.in/russross/blackfriday.v2?status.svg
|
||||
[GodocV2URL]: https://godoc.org/gopkg.in/russross/blackfriday.v2
|
||||
|
|
|
|||
67
vendor/github.com/russross/blackfriday/block.go
generated
vendored
67
vendor/github.com/russross/blackfriday/block.go
generated
vendored
|
|
@ -15,8 +15,8 @@ package blackfriday
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/shurcooL/sanitized_anchor_name"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Parse block-level data.
|
||||
|
|
@ -93,7 +93,7 @@ func (p *parser) block(out *bytes.Buffer, data []byte) {
|
|||
|
||||
// fenced code block:
|
||||
//
|
||||
// ``` go
|
||||
// ``` go info string here
|
||||
// func fact(n int) int {
|
||||
// if n <= 1 {
|
||||
// return n
|
||||
|
|
@ -243,7 +243,7 @@ func (p *parser) prefixHeader(out *bytes.Buffer, data []byte) int {
|
|||
}
|
||||
if end > i {
|
||||
if id == "" && p.flags&EXTENSION_AUTO_HEADER_IDS != 0 {
|
||||
id = sanitized_anchor_name.Create(string(data[i:end]))
|
||||
id = SanitizedAnchorName(string(data[i:end]))
|
||||
}
|
||||
work := func() bool {
|
||||
p.inline(out, data[i:end])
|
||||
|
|
@ -563,7 +563,7 @@ func (*parser) isHRule(data []byte) bool {
|
|||
// and returns the end index if so, or 0 otherwise. It also returns the marker found.
|
||||
// If syntax is not nil, it gets set to the syntax specified in the fence line.
|
||||
// A final newline is mandatory to recognize the fence line, unless newlineOptional is true.
|
||||
func isFenceLine(data []byte, syntax *string, oldmarker string, newlineOptional bool) (end int, marker string) {
|
||||
func isFenceLine(data []byte, info *string, oldmarker string, newlineOptional bool) (end int, marker string) {
|
||||
i, size := 0, 0
|
||||
|
||||
// skip up to three spaces
|
||||
|
|
@ -599,9 +599,9 @@ func isFenceLine(data []byte, syntax *string, oldmarker string, newlineOptional
|
|||
}
|
||||
|
||||
// TODO(shurcooL): It's probably a good idea to simplify the 2 code paths here
|
||||
// into one, always get the syntax, and discard it if the caller doesn't care.
|
||||
if syntax != nil {
|
||||
syn := 0
|
||||
// into one, always get the info string, and discard it if the caller doesn't care.
|
||||
if info != nil {
|
||||
infoLength := 0
|
||||
i = skipChar(data, i, ' ')
|
||||
|
||||
if i >= len(data) {
|
||||
|
|
@ -611,14 +611,14 @@ func isFenceLine(data []byte, syntax *string, oldmarker string, newlineOptional
|
|||
return 0, ""
|
||||
}
|
||||
|
||||
syntaxStart := i
|
||||
infoStart := i
|
||||
|
||||
if data[i] == '{' {
|
||||
i++
|
||||
syntaxStart++
|
||||
infoStart++
|
||||
|
||||
for i < len(data) && data[i] != '}' && data[i] != '\n' {
|
||||
syn++
|
||||
infoLength++
|
||||
i++
|
||||
}
|
||||
|
||||
|
|
@ -628,24 +628,24 @@ func isFenceLine(data []byte, syntax *string, oldmarker string, newlineOptional
|
|||
|
||||
// strip all whitespace at the beginning and the end
|
||||
// of the {} block
|
||||
for syn > 0 && isspace(data[syntaxStart]) {
|
||||
syntaxStart++
|
||||
syn--
|
||||
for infoLength > 0 && isspace(data[infoStart]) {
|
||||
infoStart++
|
||||
infoLength--
|
||||
}
|
||||
|
||||
for syn > 0 && isspace(data[syntaxStart+syn-1]) {
|
||||
syn--
|
||||
for infoLength > 0 && isspace(data[infoStart+infoLength-1]) {
|
||||
infoLength--
|
||||
}
|
||||
|
||||
i++
|
||||
} else {
|
||||
for i < len(data) && !isspace(data[i]) {
|
||||
syn++
|
||||
for i < len(data) && !isverticalspace(data[i]) {
|
||||
infoLength++
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
*syntax = string(data[syntaxStart : syntaxStart+syn])
|
||||
*info = strings.TrimSpace(string(data[infoStart : infoStart+infoLength]))
|
||||
}
|
||||
|
||||
i = skipChar(data, i, ' ')
|
||||
|
|
@ -663,8 +663,8 @@ func isFenceLine(data []byte, syntax *string, oldmarker string, newlineOptional
|
|||
// or 0 otherwise. It writes to out if doRender is true, otherwise it has no side effects.
|
||||
// If doRender is true, a final newline is mandatory to recognize the fenced code block.
|
||||
func (p *parser) fencedCodeBlock(out *bytes.Buffer, data []byte, doRender bool) int {
|
||||
var syntax string
|
||||
beg, marker := isFenceLine(data, &syntax, "", false)
|
||||
var infoString string
|
||||
beg, marker := isFenceLine(data, &infoString, "", false)
|
||||
if beg == 0 || beg >= len(data) {
|
||||
return 0
|
||||
}
|
||||
|
|
@ -698,7 +698,7 @@ func (p *parser) fencedCodeBlock(out *bytes.Buffer, data []byte, doRender bool)
|
|||
}
|
||||
|
||||
if doRender {
|
||||
p.r.BlockCode(out, work.Bytes(), syntax)
|
||||
p.r.BlockCode(out, work.Bytes(), infoString)
|
||||
}
|
||||
|
||||
return beg
|
||||
|
|
@ -1364,7 +1364,7 @@ func (p *parser) paragraph(out *bytes.Buffer, data []byte) int {
|
|||
|
||||
id := ""
|
||||
if p.flags&EXTENSION_AUTO_HEADER_IDS != 0 {
|
||||
id = sanitized_anchor_name.Create(string(data[prev:eol]))
|
||||
id = SanitizedAnchorName(string(data[prev:eol]))
|
||||
}
|
||||
|
||||
p.r.Header(out, work, level, id)
|
||||
|
|
@ -1428,3 +1428,24 @@ func (p *parser) paragraph(out *bytes.Buffer, data []byte) int {
|
|||
p.renderParagraph(out, data[:i])
|
||||
return i
|
||||
}
|
||||
|
||||
// SanitizedAnchorName returns a sanitized anchor name for the given text.
|
||||
//
|
||||
// It implements the algorithm specified in the package comment.
|
||||
func SanitizedAnchorName(text string) string {
|
||||
var anchorName []rune
|
||||
futureDash := false
|
||||
for _, r := range text {
|
||||
switch {
|
||||
case unicode.IsLetter(r) || unicode.IsNumber(r):
|
||||
if futureDash && len(anchorName) > 0 {
|
||||
anchorName = append(anchorName, '-')
|
||||
}
|
||||
futureDash = false
|
||||
anchorName = append(anchorName, unicode.ToLower(r))
|
||||
default:
|
||||
futureDash = true
|
||||
}
|
||||
}
|
||||
return string(anchorName)
|
||||
}
|
||||
|
|
|
|||
32
vendor/github.com/russross/blackfriday/doc.go
generated
vendored
Normal file
32
vendor/github.com/russross/blackfriday/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// Package blackfriday is a Markdown processor.
|
||||
//
|
||||
// It translates plain text with simple formatting rules into HTML or LaTeX.
|
||||
//
|
||||
// Sanitized Anchor Names
|
||||
//
|
||||
// Blackfriday includes an algorithm for creating sanitized anchor names
|
||||
// corresponding to a given input text. This algorithm is used to create
|
||||
// anchors for headings when EXTENSION_AUTO_HEADER_IDS is enabled. The
|
||||
// algorithm is specified below, so that other packages can create
|
||||
// compatible anchor names and links to those anchors.
|
||||
//
|
||||
// The algorithm iterates over the input text, interpreted as UTF-8,
|
||||
// one Unicode code point (rune) at a time. All runes that are letters (category L)
|
||||
// or numbers (category N) are considered valid characters. They are mapped to
|
||||
// lower case, and included in the output. All other runes are considered
|
||||
// invalid characters. Invalid characters that preceed the first valid character,
|
||||
// as well as invalid character that follow the last valid character
|
||||
// are dropped completely. All other sequences of invalid characters
|
||||
// between two valid characters are replaced with a single dash character '-'.
|
||||
//
|
||||
// SanitizedAnchorName exposes this functionality, and can be used to
|
||||
// create compatible links to the anchor names generated by blackfriday.
|
||||
// This algorithm is also implemented in a small standalone package at
|
||||
// github.com/shurcooL/sanitized_anchor_name. It can be useful for clients
|
||||
// that want a small package and don't need full functionality of blackfriday.
|
||||
package blackfriday
|
||||
|
||||
// NOTE: Keep Sanitized Anchor Name algorithm in sync with package
|
||||
// github.com/shurcooL/sanitized_anchor_name.
|
||||
// Otherwise, users of sanitized_anchor_name will get anchor names
|
||||
// that are incompatible with those generated by blackfriday.
|
||||
31
vendor/github.com/russross/blackfriday/html.go
generated
vendored
31
vendor/github.com/russross/blackfriday/html.go
generated
vendored
|
|
@ -42,6 +42,7 @@ const (
|
|||
HTML_SMARTYPANTS_DASHES // enable smart dashes (with HTML_USE_SMARTYPANTS)
|
||||
HTML_SMARTYPANTS_LATEX_DASHES // enable LaTeX-style dashes (with HTML_USE_SMARTYPANTS and HTML_SMARTYPANTS_DASHES)
|
||||
HTML_SMARTYPANTS_ANGLED_QUOTES // enable angled double quotes (with HTML_USE_SMARTYPANTS) for double quotes rendering
|
||||
HTML_SMARTYPANTS_QUOTES_NBSP // enable "French guillemets" (with HTML_USE_SMARTYPANTS)
|
||||
HTML_FOOTNOTE_RETURN_LINKS // generate a link at the end of a footnote to return to the source
|
||||
)
|
||||
|
||||
|
|
@ -254,33 +255,21 @@ func (options *Html) HRule(out *bytes.Buffer) {
|
|||
out.WriteByte('\n')
|
||||
}
|
||||
|
||||
func (options *Html) BlockCode(out *bytes.Buffer, text []byte, lang string) {
|
||||
func (options *Html) BlockCode(out *bytes.Buffer, text []byte, info string) {
|
||||
doubleSpace(out)
|
||||
|
||||
// parse out the language names/classes
|
||||
count := 0
|
||||
for _, elt := range strings.Fields(lang) {
|
||||
if elt[0] == '.' {
|
||||
elt = elt[1:]
|
||||
}
|
||||
if len(elt) == 0 {
|
||||
continue
|
||||
}
|
||||
if count == 0 {
|
||||
out.WriteString("<pre><code class=\"language-")
|
||||
} else {
|
||||
out.WriteByte(' ')
|
||||
}
|
||||
attrEscape(out, []byte(elt))
|
||||
count++
|
||||
endOfLang := strings.IndexAny(info, "\t ")
|
||||
if endOfLang < 0 {
|
||||
endOfLang = len(info)
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
lang := info[:endOfLang]
|
||||
if len(lang) == 0 || lang == "." {
|
||||
out.WriteString("<pre><code>")
|
||||
} else {
|
||||
out.WriteString("<pre><code class=\"language-")
|
||||
attrEscape(out, []byte(lang))
|
||||
out.WriteString("\">")
|
||||
}
|
||||
|
||||
attrEscape(out, text)
|
||||
out.WriteString("</code></pre>\n")
|
||||
}
|
||||
|
|
@ -619,7 +608,7 @@ func (options *Html) FootnoteRef(out *bytes.Buffer, ref []byte, id int) {
|
|||
out.WriteString(`fnref:`)
|
||||
out.WriteString(options.parameters.FootnoteAnchorPrefix)
|
||||
out.Write(slug)
|
||||
out.WriteString(`"><a rel="footnote" href="#`)
|
||||
out.WriteString(`"><a href="#`)
|
||||
out.WriteString(`fn:`)
|
||||
out.WriteString(options.parameters.FootnoteAnchorPrefix)
|
||||
out.Write(slug)
|
||||
|
|
|
|||
8
vendor/github.com/russross/blackfriday/inline.go
generated
vendored
8
vendor/github.com/russross/blackfriday/inline.go
generated
vendored
|
|
@ -170,6 +170,10 @@ func lineBreak(p *parser, out *bytes.Buffer, data []byte, offset int) int {
|
|||
precededByBackslash := offset >= 1 && data[offset-1] == '\\' // see http://spec.commonmark.org/0.18/#example-527
|
||||
precededByBackslash = precededByBackslash && p.flags&EXTENSION_BACKSLASH_LINE_BREAK != 0
|
||||
|
||||
if p.flags&EXTENSION_JOIN_LINES != 0 {
|
||||
return 1
|
||||
}
|
||||
|
||||
// should there be a hard line break here?
|
||||
if p.flags&EXTENSION_HARD_LINE_BREAK == 0 && !precededByTwoSpaces && !precededByBackslash {
|
||||
return 0
|
||||
|
|
@ -484,6 +488,7 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
|
|||
}
|
||||
|
||||
p.notes = append(p.notes, ref)
|
||||
p.notesRecord[string(ref.link)] = struct{}{}
|
||||
|
||||
link = ref.link
|
||||
title = ref.title
|
||||
|
|
@ -494,9 +499,10 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
|
|||
return 0
|
||||
}
|
||||
|
||||
if t == linkDeferredFootnote {
|
||||
if t == linkDeferredFootnote && !p.isFootnote(lr) {
|
||||
lr.noteId = len(p.notes) + 1
|
||||
p.notes = append(p.notes, lr)
|
||||
p.notesRecord[string(lr.link)] = struct{}{}
|
||||
}
|
||||
|
||||
// keep link and title from reference
|
||||
|
|
|
|||
8
vendor/github.com/russross/blackfriday/latex.go
generated
vendored
8
vendor/github.com/russross/blackfriday/latex.go
generated
vendored
|
|
@ -17,6 +17,7 @@ package blackfriday
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Latex is a type that implements the Renderer interface for LaTeX output.
|
||||
|
|
@ -39,16 +40,17 @@ func (options *Latex) GetFlags() int {
|
|||
}
|
||||
|
||||
// render code chunks using verbatim, or listings if we have a language
|
||||
func (options *Latex) BlockCode(out *bytes.Buffer, text []byte, lang string) {
|
||||
if lang == "" {
|
||||
func (options *Latex) BlockCode(out *bytes.Buffer, text []byte, info string) {
|
||||
if info == "" {
|
||||
out.WriteString("\n\\begin{verbatim}\n")
|
||||
} else {
|
||||
lang := strings.Fields(info)[0]
|
||||
out.WriteString("\n\\begin{lstlisting}[language=")
|
||||
out.WriteString(lang)
|
||||
out.WriteString("]\n")
|
||||
}
|
||||
out.Write(text)
|
||||
if lang == "" {
|
||||
if info == "" {
|
||||
out.WriteString("\n\\end{verbatim}\n")
|
||||
} else {
|
||||
out.WriteString("\n\\end{lstlisting}\n")
|
||||
|
|
|
|||
27
vendor/github.com/russross/blackfriday/markdown.go
generated
vendored
27
vendor/github.com/russross/blackfriday/markdown.go
generated
vendored
|
|
@ -13,9 +13,6 @@
|
|||
//
|
||||
//
|
||||
|
||||
// Blackfriday markdown processor.
|
||||
//
|
||||
// Translates plain text with simple formatting rules into HTML or LaTeX.
|
||||
package blackfriday
|
||||
|
||||
import (
|
||||
|
|
@ -46,6 +43,7 @@ const (
|
|||
EXTENSION_AUTO_HEADER_IDS // Create the header ID from the text
|
||||
EXTENSION_BACKSLASH_LINE_BREAK // translate trailing backslashes into line breaks
|
||||
EXTENSION_DEFINITION_LISTS // render definition lists
|
||||
EXTENSION_JOIN_LINES // delete newline and join lines
|
||||
|
||||
commonHtmlFlags = 0 |
|
||||
HTML_USE_XHTML |
|
||||
|
|
@ -161,7 +159,7 @@ var blockTags = map[string]struct{}{
|
|||
// Currently Html and Latex implementations are provided
|
||||
type Renderer interface {
|
||||
// block-level callbacks
|
||||
BlockCode(out *bytes.Buffer, text []byte, lang string)
|
||||
BlockCode(out *bytes.Buffer, text []byte, infoString string)
|
||||
BlockQuote(out *bytes.Buffer, text []byte)
|
||||
BlockHtml(out *bytes.Buffer, text []byte)
|
||||
Header(out *bytes.Buffer, text func() bool, level int, id string)
|
||||
|
|
@ -220,7 +218,8 @@ type parser struct {
|
|||
// Footnotes need to be ordered as well as available to quickly check for
|
||||
// presence. If a ref is also a footnote, it's stored both in refs and here
|
||||
// in notes. Slice is nil if footnotes not enabled.
|
||||
notes []*reference
|
||||
notes []*reference
|
||||
notesRecord map[string]struct{}
|
||||
}
|
||||
|
||||
func (p *parser) getRef(refid string) (ref *reference, found bool) {
|
||||
|
|
@ -243,6 +242,11 @@ func (p *parser) getRef(refid string) (ref *reference, found bool) {
|
|||
return ref, found
|
||||
}
|
||||
|
||||
func (p *parser) isFootnote(ref *reference) bool {
|
||||
_, ok := p.notesRecord[string(ref.link)]
|
||||
return ok
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// Public interface
|
||||
|
|
@ -378,6 +382,7 @@ func MarkdownOptions(input []byte, renderer Renderer, opts Options) []byte {
|
|||
|
||||
if extensions&EXTENSION_FOOTNOTES != 0 {
|
||||
p.notes = make([]*reference, 0)
|
||||
p.notesRecord = make(map[string]struct{})
|
||||
}
|
||||
|
||||
first := firstPass(p, input)
|
||||
|
|
@ -799,7 +804,17 @@ func ispunct(c byte) bool {
|
|||
|
||||
// Test if a character is a whitespace character.
|
||||
func isspace(c byte) bool {
|
||||
return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'
|
||||
return ishorizontalspace(c) || isverticalspace(c)
|
||||
}
|
||||
|
||||
// Test if a character is a horizontal whitespace character.
|
||||
func ishorizontalspace(c byte) bool {
|
||||
return c == ' ' || c == '\t'
|
||||
}
|
||||
|
||||
// Test if a character is a vertical whitespace character.
|
||||
func isverticalspace(c byte) bool {
|
||||
return c == '\n' || c == '\r' || c == '\f' || c == '\v'
|
||||
}
|
||||
|
||||
// Test if a character is letter.
|
||||
|
|
|
|||
58
vendor/github.com/russross/blackfriday/smartypants.go
generated
vendored
58
vendor/github.com/russross/blackfriday/smartypants.go
generated
vendored
|
|
@ -39,7 +39,7 @@ func isdigit(c byte) bool {
|
|||
return c >= '0' && c <= '9'
|
||||
}
|
||||
|
||||
func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote byte, isOpen *bool) bool {
|
||||
func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote byte, isOpen *bool, addNBSP bool) bool {
|
||||
// edge of the buffer is likely to be a tag that we don't get to see,
|
||||
// so we treat it like text sometimes
|
||||
|
||||
|
|
@ -96,6 +96,12 @@ func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote
|
|||
*isOpen = false
|
||||
}
|
||||
|
||||
// Note that with the limited lookahead, this non-breaking
|
||||
// space will also be appended to single double quotes.
|
||||
if addNBSP && !*isOpen {
|
||||
out.WriteString(" ")
|
||||
}
|
||||
|
||||
out.WriteByte('&')
|
||||
if *isOpen {
|
||||
out.WriteByte('l')
|
||||
|
|
@ -104,6 +110,11 @@ func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote
|
|||
}
|
||||
out.WriteByte(quote)
|
||||
out.WriteString("quo;")
|
||||
|
||||
if addNBSP && *isOpen {
|
||||
out.WriteString(" ")
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -116,7 +127,7 @@ func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byt
|
|||
if len(text) >= 3 {
|
||||
nextChar = text[2]
|
||||
}
|
||||
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote) {
|
||||
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote, false) {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
|
@ -141,7 +152,7 @@ func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byt
|
|||
if len(text) > 1 {
|
||||
nextChar = text[1]
|
||||
}
|
||||
if smartQuoteHelper(out, previousChar, nextChar, 's', &smrt.inSingleQuote) {
|
||||
if smartQuoteHelper(out, previousChar, nextChar, 's', &smrt.inSingleQuote, false) {
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
@ -205,13 +216,13 @@ func smartDashLatex(out *bytes.Buffer, smrt *smartypantsData, previousChar byte,
|
|||
return 0
|
||||
}
|
||||
|
||||
func smartAmpVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte) int {
|
||||
func smartAmpVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte, addNBSP bool) int {
|
||||
if bytes.HasPrefix(text, []byte(""")) {
|
||||
nextChar := byte(0)
|
||||
if len(text) >= 7 {
|
||||
nextChar = text[6]
|
||||
}
|
||||
if smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote) {
|
||||
if smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote, addNBSP) {
|
||||
return 5
|
||||
}
|
||||
}
|
||||
|
|
@ -224,12 +235,15 @@ func smartAmpVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte
|
|||
return 0
|
||||
}
|
||||
|
||||
func smartAmp(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||
return smartAmpVariant(out, smrt, previousChar, text, 'd')
|
||||
}
|
||||
func smartAmp(angledQuotes, addNBSP bool) func(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||
var quote byte = 'd'
|
||||
if angledQuotes {
|
||||
quote = 'a'
|
||||
}
|
||||
|
||||
func smartAmpAngledQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||
return smartAmpVariant(out, smrt, previousChar, text, 'a')
|
||||
return func(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||
return smartAmpVariant(out, smrt, previousChar, text, quote, addNBSP)
|
||||
}
|
||||
}
|
||||
|
||||
func smartPeriod(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||
|
|
@ -253,7 +267,7 @@ func smartBacktick(out *bytes.Buffer, smrt *smartypantsData, previousChar byte,
|
|||
if len(text) >= 3 {
|
||||
nextChar = text[2]
|
||||
}
|
||||
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote) {
|
||||
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote, false) {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
|
@ -337,7 +351,7 @@ func smartDoubleQuoteVariant(out *bytes.Buffer, smrt *smartypantsData, previousC
|
|||
if len(text) > 1 {
|
||||
nextChar = text[1]
|
||||
}
|
||||
if !smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote) {
|
||||
if !smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote, false) {
|
||||
out.WriteString(""")
|
||||
}
|
||||
|
||||
|
|
@ -367,14 +381,30 @@ type smartCallback func(out *bytes.Buffer, smrt *smartypantsData, previousChar b
|
|||
|
||||
type smartypantsRenderer [256]smartCallback
|
||||
|
||||
var (
|
||||
smartAmpAngled = smartAmp(true, false)
|
||||
smartAmpAngledNBSP = smartAmp(true, true)
|
||||
smartAmpRegular = smartAmp(false, false)
|
||||
smartAmpRegularNBSP = smartAmp(false, true)
|
||||
)
|
||||
|
||||
func smartypants(flags int) *smartypantsRenderer {
|
||||
r := new(smartypantsRenderer)
|
||||
addNBSP := flags&HTML_SMARTYPANTS_QUOTES_NBSP != 0
|
||||
if flags&HTML_SMARTYPANTS_ANGLED_QUOTES == 0 {
|
||||
r['"'] = smartDoubleQuote
|
||||
r['&'] = smartAmp
|
||||
if !addNBSP {
|
||||
r['&'] = smartAmpRegular
|
||||
} else {
|
||||
r['&'] = smartAmpRegularNBSP
|
||||
}
|
||||
} else {
|
||||
r['"'] = smartAngledDoubleQuote
|
||||
r['&'] = smartAmpAngledQuote
|
||||
if !addNBSP {
|
||||
r['&'] = smartAmpAngled
|
||||
} else {
|
||||
r['&'] = smartAmpAngledNBSP
|
||||
}
|
||||
}
|
||||
r['\''] = smartSingleQuote
|
||||
r['('] = smartParens
|
||||
|
|
|
|||
18
vendor/vendor.json
vendored
18
vendor/vendor.json
vendored
|
|
@ -647,6 +647,18 @@
|
|||
"revision": "cb6bfca970f6908083f26f39a79009d608efd5cd",
|
||||
"revisionTime": "2016-10-16T15:41:25Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "/X7eCdN7MX8zgCjA9s0ktzgTPlA=",
|
||||
"path": "github.com/lafriks/xormstore",
|
||||
"revision": "3a80a383a04b29ec2e1bf61279dd948aa809335b",
|
||||
"revisionTime": "2018-04-09T10:45:24Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "Vxvfs8mukr9GOLSuGIPU4ODyOZc=",
|
||||
"path": "github.com/lafriks/xormstore/util",
|
||||
"revision": "c0e2f3dc1ecab3536617967e4b47ee5b9e2ca229",
|
||||
"revisionTime": "2018-03-11T19:16:53Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "QV4HZTfaXvhD+5PcGM2p+7aCYYI=",
|
||||
"path": "github.com/lib/pq",
|
||||
|
|
@ -1174,10 +1186,10 @@
|
|||
"revisionTime": "2016-09-12T16:18:15Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "c7jHQZk5ZEsFR9EXsWJXkszPBZA=",
|
||||
"checksumSHA1": "Ne3D+KJs1TU2trnDy1UCSwlXbAE=",
|
||||
"path": "github.com/russross/blackfriday",
|
||||
"revision": "5f33e7b7878355cd2b7e6b8eefc48a5472c69f70",
|
||||
"revisionTime": "2016-10-03T16:27:22Z"
|
||||
"revision": "11635eb403ff09dbc3a6b5a007ab5ab09151c229",
|
||||
"revisionTime": "2018-04-28T10:25:19Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "zmC8/3V4ls53DJlNTKDZwPSC/dA=",
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user