Parent
9314cf1a47
révision
e3a271cca7
14 fichiers modifiés avec 38 ajouts et 308 suppressions
|
@ -31,7 +31,6 @@ type Torrents struct {
|
|||
Downloads int `gorm:"column:downloads"`
|
||||
Filesize int64 `gorm:"column:filesize"`
|
||||
Description string `gorm:"column:description"`
|
||||
OwnerId int `gorm:"column:owner_id"`
|
||||
OldComments []byte `gorm:"column:comments"`
|
||||
Comments []Comment `gorm:"ForeignKey:TorrentId"`
|
||||
}
|
||||
|
|
|
@ -11,10 +11,10 @@ type omit bool
|
|||
type User struct {
|
||||
Id uint `json:"id"`
|
||||
|
||||
Email string `json:"email" sql:"size:255;unique"`
|
||||
Password string `json:"password" sql:"size:255"`
|
||||
Username string `json:"username" sql:"size:255;unique"`
|
||||
Description string `json:"description" sql:"size:100"`
|
||||
Email string `json:"email",sql:"size:255;unique"`
|
||||
Password string `json:"password",sql:"size:255"`
|
||||
Username string `json:"username",sql:"size:255;unique"`
|
||||
Description string `json:"description",sql:"size:100"`
|
||||
Token string `json:"token"`
|
||||
TokenExpiration time.Time `json:"tokenExperiation"`
|
||||
|
||||
|
@ -33,8 +33,8 @@ type User struct {
|
|||
DeletedAt *time.Time `json:"deletedAt"`
|
||||
LastLoginAt time.Time `json:"lastLoginAt"`
|
||||
CurrentLoginAt time.Time `json:"currentLoginAt"`
|
||||
LastLoginIp string `json:"lastLoginIp" sql:"size:100"`
|
||||
CurrentLoginIp string `json:"currentLoginIp" sql:"size:100"`
|
||||
LastLoginIp string `json:"lastLoginIp",sql:"size:100"`
|
||||
CurrentLoginIp string `json:"currentLoginIp",sql:"size:100"`
|
||||
|
||||
// Liking
|
||||
LikingCount int `json:"likingCount"`
|
||||
|
@ -46,7 +46,7 @@ type User struct {
|
|||
|
||||
Languages string
|
||||
Roles []Role `gorm:"many2many:users_roles;"` // Many To Many, users_roles
|
||||
Torrents []Torrents `gorm:"ForeignKey:owner_id"`
|
||||
Torrents []Torrents
|
||||
}
|
||||
|
||||
// UsersFollowers is a relation table to relate users each other.
|
||||
|
@ -58,8 +58,8 @@ type UsersFollowers struct {
|
|||
// PublicUser is a public user model that contains only a few information for everyone.
|
||||
type PublicUser struct {
|
||||
*User
|
||||
Email omit `json:"email,omitempty" sql:"size:255;unique"`
|
||||
Password omit `json:"password,omitempty" sql:"size:255"`
|
||||
Email omit `json:"email,omitempty",sql:"size:255;unique"`
|
||||
Password omit `json:"password,omitempty",sql:"size:255"`
|
||||
Token omit `json:"token,omitempty"`
|
||||
TokenExpiration omit `json:"tokenExperiation,omitempty"`
|
||||
|
||||
|
@ -74,8 +74,8 @@ type PublicUser struct {
|
|||
DeletedAt omit `json:"deletedAt,omitempty"`
|
||||
LastLoginAt omit `json:"lastLoginAt,omitempty"`
|
||||
CurrentLoginAt omit `json:"currentLoginAt,omitempty"`
|
||||
LastLoginIp omit `json:"lastLoginIp,omitempty" sql:"size:100"`
|
||||
CurrentLoginIp omit `json:"currentLoginIp,omitempty" sql:"size:100"`
|
||||
LastLoginIp omit `json:"lastLoginIp,omitempty",sql:"size:100"`
|
||||
CurrentLoginIp omit `json:"currentLoginIp,omitempty",sql:"size:100"`
|
||||
|
||||
//Connections omit `json:"connections,omitempty"`
|
||||
Languages omit `json:"languages,omitempty"`
|
||||
|
|
|
@ -218,108 +218,3 @@ div.container div.blockBody:nth-of-type(2) table tr:first-of-type th:last-of-typ
|
|||
#mainmenu .badgemenu {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
/* PROFILE PAGE */
|
||||
/* Profile container */
|
||||
.profile {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
/* Profile sidebar */
|
||||
.profile-sidebar {
|
||||
padding: 20px 0 10px 0;
|
||||
background: #182430;
|
||||
}
|
||||
|
||||
.profile-userpic img {
|
||||
float: none;
|
||||
margin: 0 auto;
|
||||
width: 50%;
|
||||
height: 50%;
|
||||
-webkit-border-radius: 50% !important;
|
||||
-moz-border-radius: 50% !important;
|
||||
border-radius: 50% !important;
|
||||
}
|
||||
|
||||
.profile-usertitle {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.profile-usertitle-name {
|
||||
color: #5a7391;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
|
||||
.profile-usertitle-job {
|
||||
text-transform: uppercase;
|
||||
color: #5b9bd1;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.profile-userbuttons {
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.profile-userbuttons .btn {
|
||||
text-transform: uppercase;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
padding: 6px 15px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.profile-userbuttons .btn:last-child {
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.profile-usermenu {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.profile-usermenu ul li {
|
||||
border-bottom: 1px solid #f0f4f7;
|
||||
}
|
||||
|
||||
.profile-usermenu ul li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.profile-usermenu ul li a {
|
||||
color: #93a3b5;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.profile-usermenu ul li a i {
|
||||
margin-right: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.profile-usermenu ul li a:hover {
|
||||
background-color: #fafcfd;
|
||||
color: #5b9bd1;
|
||||
}
|
||||
|
||||
.profile-usermenu ul li.active {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.profile-usermenu ul li.active a {
|
||||
color: #5b9bd1;
|
||||
background-color: #f6f9fb;
|
||||
border-left: 2px solid #5b9bd1;
|
||||
margin-left: -2px;
|
||||
}
|
||||
|
||||
/* Profile Content */
|
||||
.profile-content {
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
min-height: 460px;
|
||||
}
|
|
@ -7,7 +7,7 @@ import (
|
|||
|
||||
var TemplateDir = "templates"
|
||||
|
||||
var homeTemplate, searchTemplate, faqTemplate, uploadTemplate, viewTemplate, viewRegisterTemplate, viewLoginTemplate, viewRegisterSuccessTemplate, viewVerifySuccessTemplate, viewProfileTemplate *template.Template
|
||||
var homeTemplate, searchTemplate, faqTemplate, uploadTemplate, viewTemplate, viewRegisterTemplate, viewLoginTemplate, viewRegisterSuccessTemplate, viewVerifySuccessTemplate *template.Template
|
||||
|
||||
type templateLoader struct {
|
||||
templ **template.Template
|
||||
|
@ -63,11 +63,6 @@ func ReloadTemplates() {
|
|||
name: "user_login",
|
||||
file: "user/login.html",
|
||||
},
|
||||
templateLoader{
|
||||
templ: &viewProfileTemplate,
|
||||
name: "user_profile",
|
||||
file: "user/profile.html",
|
||||
},
|
||||
}
|
||||
for _, templ := range templs {
|
||||
t := template.Must(template.New(templ.name).Funcs(FuncMap).ParseFiles(filepath.Join(TemplateDir, "index.html"), filepath.Join(TemplateDir, templ.file)))
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"net/url"
|
||||
"strconv"
|
||||
"github.com/nicksnyder/go-i18n/i18n"
|
||||
"github.com/ewhal/nyaa/service/user/permission"
|
||||
)
|
||||
|
||||
var FuncMap = template.FuncMap{
|
||||
|
@ -60,9 +59,4 @@ var FuncMap = template.FuncMap{
|
|||
return template.HTML(ret)
|
||||
},
|
||||
"T": i18n.IdentityTfunc,
|
||||
"getAvatar": func (hash string, size int) string {
|
||||
return "https://www.gravatar.com/avatar/"+hash+"?s="+strconv.Itoa(size)
|
||||
},
|
||||
"CurrentOrAdmin": userPermission.CurrentOrAdmin,
|
||||
"CurrentUserIdentical": userPermission.CurrentUserIdentical,
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ import (
|
|||
type FaqTemplateVariables struct {
|
||||
Navigation Navigation
|
||||
Search SearchForm
|
||||
User *model.User
|
||||
User model.User
|
||||
URL *url.URL // For parsing Url in templates
|
||||
Route *mux.Route // For getting current route in templates
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ type FaqTemplateVariables struct {
|
|||
type NotFoundTemplateVariables struct {
|
||||
Navigation Navigation
|
||||
Search SearchForm
|
||||
User *model.User
|
||||
User model.User
|
||||
URL *url.URL // For parsing Url in templates
|
||||
Route *mux.Route // For getting current route in templates
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ type ViewTemplateVariables struct {
|
|||
Captcha captcha.Captcha
|
||||
Search SearchForm
|
||||
Navigation Navigation
|
||||
User *model.User
|
||||
User model.User
|
||||
URL *url.URL // For parsing Url in templates
|
||||
Route *mux.Route // For getting current route in templates
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ type UserRegisterTemplateVariables struct {
|
|||
FormErrors map[string][]string
|
||||
Search SearchForm
|
||||
Navigation Navigation
|
||||
User *model.User
|
||||
User model.User
|
||||
URL *url.URL // For parsing Url in templates
|
||||
Route *mux.Route // For getting current route in templates
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ type UserVerifyTemplateVariables struct {
|
|||
FormErrors map[string][]string
|
||||
Search SearchForm
|
||||
Navigation Navigation
|
||||
User *model.User
|
||||
User model.User
|
||||
URL *url.URL // For parsing Url in templates
|
||||
Route *mux.Route // For getting current route in templates
|
||||
}
|
||||
|
@ -67,17 +67,7 @@ type UserLoginFormVariables struct {
|
|||
FormErrors map[string][]string
|
||||
Search SearchForm
|
||||
Navigation Navigation
|
||||
User *model.User
|
||||
URL *url.URL // For parsing Url in templates
|
||||
Route *mux.Route // For getting current route in templates
|
||||
}
|
||||
|
||||
type UserProfileVariables struct {
|
||||
UserProfile *model.User
|
||||
FormErrors map[string][]string
|
||||
Search SearchForm
|
||||
Navigation Navigation
|
||||
User *model.User
|
||||
User model.User
|
||||
URL *url.URL // For parsing Url in templates
|
||||
Route *mux.Route // For getting current route in templates
|
||||
}
|
||||
|
@ -86,7 +76,7 @@ type HomeTemplateVariables struct {
|
|||
ListTorrents []model.TorrentsJson
|
||||
Search SearchForm
|
||||
Navigation Navigation
|
||||
User *model.User
|
||||
User model.User
|
||||
URL *url.URL // For parsing Url in templates
|
||||
Route *mux.Route // For getting current route in templates
|
||||
}
|
||||
|
@ -95,7 +85,7 @@ type UploadTemplateVariables struct {
|
|||
Upload UploadForm
|
||||
Search SearchForm
|
||||
Navigation Navigation
|
||||
User *model.User
|
||||
User model.User
|
||||
URL *url.URL
|
||||
Route *mux.Route
|
||||
}
|
||||
|
@ -146,7 +136,7 @@ func NewSearchForm(params ...string) (searchForm SearchForm) {
|
|||
return
|
||||
}
|
||||
|
||||
func GetUser(r *http.Request) *model.User {
|
||||
func GetUser(r *http.Request) model.User {
|
||||
user, _ , _ := userService.RetrieveCurrentUser(r)
|
||||
return &user
|
||||
return user
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
|
||||
"github.com/ewhal/nyaa/service/captcha"
|
||||
"github.com/ewhal/nyaa/service/user"
|
||||
"github.com/ewhal/nyaa/service/user/permission"
|
||||
"github.com/ewhal/nyaa/service/user/form"
|
||||
"github.com/ewhal/nyaa/util/languages"
|
||||
"github.com/ewhal/nyaa/util/modelHelper"
|
||||
|
@ -49,25 +48,7 @@ func UserLoginFormHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// Getting User Profile
|
||||
func UserProfileHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
id := vars["id"]
|
||||
userProfile, _, errorUser := userService.RetrieveUserForAdmin(id)
|
||||
currentUser := GetUser(r)
|
||||
view := r.URL.Query().Get("view")
|
||||
if (errorUser == nil) {
|
||||
if ((view == "edit")&&(userPermission.CurrentOrAdmin(currentUser, userProfile.Id))) {
|
||||
} else {
|
||||
languages.SetTranslationFromRequest(viewProfileTemplate, r, "en-us")
|
||||
htv := UserProfileVariables{&userProfile, form.NewErrors(), NewSearchForm(), Navigation{}, currentUser, r.URL, mux.CurrentRoute(r)}
|
||||
|
||||
err := viewProfileTemplate.ExecuteTemplate(w, "index.html", htv)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
NotFoundHandler(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// Getting View User Profile Update
|
||||
|
|
|
@ -102,25 +102,5 @@
|
|||
{
|
||||
"id":"signup_verification_email",
|
||||
"translation": "Now, you are one step left before being part of our community! Check your emails (inbox or spam folder) and click the link provided for activating your account!"
|
||||
},
|
||||
{
|
||||
"id":"settings",
|
||||
"translation": "Account Settings"
|
||||
},
|
||||
{
|
||||
"id":"torrents",
|
||||
"translation": "Torrents"
|
||||
},
|
||||
{
|
||||
"id":"follow",
|
||||
"translation": "Follow"
|
||||
},
|
||||
{
|
||||
"id":"profile_page",
|
||||
"translation": "%s Profile Page"
|
||||
},
|
||||
{
|
||||
"id":"see_more_torrents_from",
|
||||
"translation": "See more torrents from %s "
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
package userPermission
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"github.com/ewhal/nyaa/model"
|
||||
"github.com/ewhal/nyaa/service/user"
|
||||
"github.com/ewhal/nyaa/util/log"
|
||||
)
|
||||
|
||||
|
@ -24,10 +27,14 @@ func CurrentOrAdmin(user *model.User, userId uint) bool {
|
|||
}
|
||||
|
||||
// CurrentUserIdentical check that userId is same as current user's Id.
|
||||
func CurrentUserIdentical(user *model.User, userId uint) (bool) {
|
||||
if user.Id != userId {
|
||||
return false
|
||||
func CurrentUserIdentical(r *http.Request, userId uint) (bool, error) {
|
||||
currentUser, err := userService.CurrentUser(r)
|
||||
if err != nil {
|
||||
return false, errors.New("Auth failed.")
|
||||
}
|
||||
if currentUser.Id != userId {
|
||||
return false, errors.New("User is not identical.")
|
||||
}
|
||||
|
||||
return true
|
||||
return true, nil
|
||||
}
|
||||
|
|
|
@ -307,7 +307,7 @@ func RetrieveUserForAdmin(id string) (model.User, int, error) {
|
|||
if db.ORM.First(&user, id).RecordNotFound() {
|
||||
return user, http.StatusNotFound, errors.New("User is not found.")
|
||||
}
|
||||
db.ORM.Model(&user).Related("Torrents").Find(&model.Torrents{})
|
||||
db.ORM.Model(&user).Association("Languages").Find(&user.Languages)
|
||||
db.ORM.Model(&user).Association("Roles").Find(&user.Roles)
|
||||
return user, http.StatusOK, nil
|
||||
}
|
||||
|
@ -318,7 +318,7 @@ func RetrieveUsersForAdmin() []model.User {
|
|||
var userArr []model.User
|
||||
db.ORM.Find(&users)
|
||||
for _, user := range users {
|
||||
db.ORM.Model(&user).Related("Torrents").Find(&model.Torrents{})
|
||||
db.ORM.Model(&user).Association("Languages").Find(&user.Languages)
|
||||
db.ORM.Model(&user).Association("Roles").Find(&user.Roles)
|
||||
userArr = append(userArr, user)
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<li class="dropdown">
|
||||
{{if gt .Id 0}}
|
||||
<a href="{{ genRoute "user_profile" "id" (print .Id) "username" .Username }}" class="dropdown-toggle profile-image" data-toggle="dropdown">
|
||||
<img src="{{ getAvatar .Md5 50 }}" class="img-circle special-img"> {{ .Username }} <b class="caret"></b></a>
|
||||
<img src="https://www.gravatar.com/avatar/{{ .Md5 }}?s=50" class="img-circle special-img"> {{ .Username }} <b class="caret"></b></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{{ genRoute "user_profile" "id" (print .Id) "username" .Username }}"><i class="fa fa-cog"></i> Profile</a></li>
|
||||
<li class="divider"></li>
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
{{define "profile_content"}}
|
||||
{{with .UserProfile}}
|
||||
<div class="table-responsive">
|
||||
<table class="table custom-table-hover">
|
||||
<tr>
|
||||
<th>Category</th>
|
||||
<th>Name</th>
|
||||
<th>Date</th>
|
||||
<th>Size</th>
|
||||
<th>Links</th>
|
||||
</tr>
|
||||
{{ range .Torrents }}
|
||||
<tr class="torrent-info
|
||||
{{if eq .Status 2}}remake{{end}}
|
||||
{{if eq .Status 3}}trusted{{end}}
|
||||
{{if eq .Status 4}}aplus{{end}}">
|
||||
<!-- forced width because the <td> gets bigger randomly otherwise -->
|
||||
<td style="width:80px">
|
||||
<a href="{{$.URL.Parse (printf "/search?c=%s_%s" .Category .Sub_Category) }}">
|
||||
<img src="{{$.URL.Parse (printf "/img/torrents/%s.png" .Sub_Category) }}">
|
||||
</a>
|
||||
</td>
|
||||
<td class="name">
|
||||
<a href="{{genRoute "view_torrent" "id" .Id }}">
|
||||
{{.Name}}
|
||||
</a>
|
||||
</td>
|
||||
<td class="date date-short">{{.Date}}</td>
|
||||
<td class="filesize">{{.Filesize}}</td>
|
||||
<td>
|
||||
<a href="{{.Magnet}}" title="Magnet link">
|
||||
<span class="glyphicon glyphicon-magnet" aria-hidden="true"></span>
|
||||
</a>
|
||||
<a href="http://anicache.com/torrent/{{.Hash}}.torrent" title="Torrent file">
|
||||
<span class="glyphicon glyphicon-floppy-save" aria-hidden="true"></span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
<nav class="torrentNav" aria-label="Page navigation">
|
||||
<ul class="pagination">
|
||||
<li><a href="{{ genRoute "search" }}?userId={{ .Id }}" aria-label="Next"><span class="glyphicon glyphicon-add"></span> {{ T "see_more_torrents_from" .Username }}</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
|
@ -1,62 +0,0 @@
|
|||
{{define "title"}}{{ T "profile_page" .UserProfile.Username }}{{end}}
|
||||
{{define "contclass"}}cont-view{{end}}
|
||||
{{define "content"}}
|
||||
<div class="row profile">
|
||||
{{with .UserProfile}}
|
||||
<div class="col-md-3">
|
||||
<div class="profile-sidebar">
|
||||
<!-- SIDEBAR USERPIC -->
|
||||
<div class="profile-userpic">
|
||||
<img src="{{ getAvatar .Md5 130 }}" class="img-responsive" alt="{{.Username}}">
|
||||
</div>
|
||||
<!-- END SIDEBAR USERPIC -->
|
||||
<!-- SIDEBAR USER TITLE -->
|
||||
<div class="profile-usertitle">
|
||||
<div class="profile-usertitle-name">
|
||||
{{.Username}}
|
||||
</div>
|
||||
<div class="profile-usertitle-job">
|
||||
{{if .Roles }}{{index .Roles 0 }}{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<!-- END SIDEBAR USER TITLE -->
|
||||
<!-- SIDEBAR BUTTONS -->
|
||||
<div class="profile-userbuttons">
|
||||
{{if gt $.User.Id 0 }}
|
||||
{{if not (CurrentUserIdentical $.User .Id) }}
|
||||
<button type="button" class="btn btn-success btn-sm">{{ T "follow"}}</button>
|
||||
{{end}}
|
||||
{{end}}
|
||||
<!-- <button type="button" class="btn btn-danger btn-sm">Message</button> -->
|
||||
</div>
|
||||
<!-- END SIDEBAR BUTTONS -->
|
||||
<!-- SIDEBAR MENU -->
|
||||
<div class="profile-usermenu">
|
||||
<ul class="nav">
|
||||
<li class="active">
|
||||
<a href="#">
|
||||
<i class="glyphicon glyphicon-home"></i>
|
||||
{{T "torrents"}} </a>
|
||||
</li>
|
||||
{{if gt $.User.Id 0 }}
|
||||
{{if CurrentOrAdmin $.User .Id }}
|
||||
<li>
|
||||
<a href="#">
|
||||
<i class="glyphicon glyphicon-user"></i>
|
||||
{{T "settings"}} </a>
|
||||
</li>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</ul>
|
||||
</div>
|
||||
<!-- END MENU -->
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="col-md-9">
|
||||
<div class="profile-content">
|
||||
{{ block "profile_content" . }}{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
|
@ -3,15 +3,14 @@ package languages
|
|||
import (
|
||||
"github.com/nicksnyder/go-i18n/i18n"
|
||||
"html/template"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func SetTranslation(tmpl *template.Template, language string, languages ...string) {
|
||||
T, _ := i18n.Tfunc(language, languages...)
|
||||
tmpl.Funcs(map[string]interface{}{
|
||||
"T": func(str string, args ...interface{}) template.HTML {
|
||||
return template.HTML(fmt.Sprintf(T(str), args...))
|
||||
"T": func(str string) template.HTML {
|
||||
return template.HTML(T(str))
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
Référencer dans un nouveau ticket