@@ -1133,6 +1133,9 @@ func IsUsableRepoAlias(name string) error { | |||
// CreateRepository creates a repository for the user/organization. | |||
func CreateRepository(ctx DBContext, doer, u *User, repo *Repository, opts ...CreateRepoOptions) (err error) { | |||
if repo.Alias == "" { | |||
repo.Alias = repo.Name | |||
} | |||
repo.LowerAlias = strings.ToLower(repo.Alias) | |||
if err = IsUsableRepoName(repo.Name); err != nil { | |||
return err | |||
@@ -64,6 +64,11 @@ func Toggle(options *ToggleOptions) macaron.Handler { | |||
ctx.Redirect(setting.AppSubURL + "/") | |||
return | |||
} | |||
if ctx.QueryBool("course") { | |||
ctx.Redirect(setting.AppSubURL + "/" + setting.Course.OrgName) | |||
return | |||
} | |||
} | |||
// Redirect to dashboard if user tries to visit any non-login page. | |||
@@ -45,8 +45,8 @@ type Context struct { | |||
IsSigned bool | |||
IsBasicAuth bool | |||
Repo *Repository | |||
Org *Organization | |||
Repo *Repository | |||
Org *Organization | |||
Cloudbrain *models.Cloudbrain | |||
} | |||
@@ -347,9 +347,9 @@ func Contexter() macaron.Handler { | |||
ctx.Data["EnableSwagger"] = setting.API.EnableSwagger | |||
ctx.Data["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn | |||
notice, _ := notice.GetNewestNotice() | |||
if notice != nil { | |||
ctx.Data["notice"] = *notice | |||
notices, _ := notice.GetNewestNotice() | |||
if notices != nil { | |||
ctx.Data["notices"] = notices | |||
} | |||
c.Map(ctx) | |||
} | |||
@@ -57,8 +57,8 @@ func ObsHasObject(path string) (bool, error) { | |||
return hasObject, nil | |||
} | |||
func GetObsPartInfos(uuid string, uploadID string) (string, error) { | |||
key := strings.TrimPrefix(path.Join(setting.BasePath, path.Join(uuid[0:1], uuid[1:2], uuid, uuid)), "/") | |||
func GetObsPartInfos(uuid, uploadID, fileName string) (string, error) { | |||
key := strings.TrimPrefix(path.Join(setting.BasePath, path.Join(uuid[0:1], uuid[1:2], uuid, fileName)), "/") | |||
output, err := ObsCli.ListParts(&obs.ListPartsInput{ | |||
Bucket: setting.Bucket, | |||
@@ -100,6 +100,10 @@ type CreateRepoOption struct { | |||
// required: true | |||
// unique: true | |||
Name string `json:"name" binding:"Required;AlphaDashDot;MaxSize(100)"` | |||
// Alias of the repository to create | |||
// required: false | |||
// unique: true | |||
Alias string `json:"alias" binding:"AlphaDashDotChinese;MaxSize(100)"` | |||
// Description of the repository to create | |||
Description string `json:"description" binding:"MaxSize(255)"` | |||
// Whether the repository is private | |||
@@ -128,6 +128,9 @@ func addAlphaDashDotChineseRule() { | |||
return strings.HasPrefix(rule, "AlphaDashDotChinese") | |||
}, | |||
IsValid: func(errs binding.Errors, name string, val interface{}) (bool, binding.Errors) { | |||
if val == "" { | |||
return true, errs | |||
} | |||
if !ValidAlphaDashDotChinese(fmt.Sprintf("%v", val)) { | |||
errs.Add([]string{name}, ErrAlphaDashDotChinese, "ErrAlphaDashDotChinese") | |||
return false, errs | |||
@@ -232,6 +232,7 @@ func CreateUserRepo(ctx *context.APIContext, owner *models.User, opt api.CreateR | |||
} | |||
repo, err := repo_service.CreateRepository(ctx.User, owner, models.CreateRepoOptions{ | |||
Name: opt.Name, | |||
Alias: opt.Alias, | |||
Description: opt.Description, | |||
IssueLabels: opt.IssueLabels, | |||
Gitignores: opt.Gitignores, | |||
@@ -16,27 +16,31 @@ const ( | |||
) | |||
type Notice struct { | |||
Title string | |||
Link string | |||
Visible int //0 invisible, 1 visible | |||
Title string | |||
Link string | |||
Visible int //0 invisible, 1 visible | |||
} | |||
type NoticeResponse struct { | |||
Notices []*Notice | |||
CommitId string | |||
} | |||
var lock int32 = 0 | |||
func GetNewestNotice() (*Notice, error) { | |||
func GetNewestNotice() (*NoticeResponse, error) { | |||
defer func() { | |||
if err := recover(); err != nil { | |||
log.Error("recover error", err) | |||
} | |||
}() | |||
var notice *Notice | |||
var notice *NoticeResponse | |||
var err error | |||
if setting.CacheOn { | |||
notice, err = getNewestNoticeFromCacheAndDisk() | |||
notice, err = getNewestNoticesFromCacheAndDisk() | |||
} else { | |||
notice, err = getNewestNoticeFromDisk() | |||
notice, err = getNewestNoticesFromDisk() | |||
} | |||
if err != nil { | |||
@@ -49,34 +53,39 @@ func getNoticeTimeout() time.Duration { | |||
return time.Duration(setting.CacheTimeOutSecond) * time.Second | |||
} | |||
func getNewestNoticeFromDisk() (*Notice, error) { | |||
func getNewestNoticesFromDisk() (*NoticeResponse, error) { | |||
log.Debug("Get notice from disk") | |||
repoFile, err := models.ReadLatestFileInRepo(setting.UserNameOfNoticeRepo, setting.RepoNameOfNoticeRepo, setting.RefNameOfNoticeRepo, setting.TreePathOfNoticeRepo) | |||
repo, err := models.GetRepositoryByOwnerAndAlias(setting.UserNameOfNoticeRepo, setting.RepoNameOfNoticeRepo) | |||
if err != nil { | |||
log.Error("get notice repo failed, error=%v", err) | |||
return nil, err | |||
} | |||
repoFile, err := models.ReadLatestFileInRepo(repo.OwnerName, repo.Name, setting.RefNameOfNoticeRepo, setting.TreePathOfNoticeRepo) | |||
if err != nil { | |||
log.Error("GetNewestNotice failed, error=%v", err) | |||
return nil, err | |||
} | |||
notice := &Notice{} | |||
json.Unmarshal(repoFile.Content, notice) | |||
if notice.Title == "" { | |||
res := &NoticeResponse{} | |||
json.Unmarshal(repoFile.Content, res) | |||
if res == nil || len(res.Notices) == 0 { | |||
return nil, err | |||
} | |||
notice.CommitId = repoFile.CommitId | |||
return notice, nil | |||
res.CommitId = repoFile.CommitId | |||
return res, nil | |||
} | |||
func getNewestNoticeFromCacheAndDisk() (*Notice, error) { | |||
func getNewestNoticesFromCacheAndDisk() (*NoticeResponse, error) { | |||
v, success := noticeCache.Get(NOTICE_CACHE_KEY) | |||
if success { | |||
log.Debug("Get notice from cache,value = %v", v) | |||
if v == nil { | |||
return nil, nil | |||
} | |||
n := v.(*Notice) | |||
n := v.(*NoticeResponse) | |||
return n, nil | |||
} | |||
notice, err := getNewestNoticeFromDisk() | |||
notice, err := getNewestNoticesFromDisk() | |||
if err != nil { | |||
log.Error("GetNewestNotice failed, error=%v", err) | |||
noticeCache.Set(NOTICE_CACHE_KEY, nil, 30*time.Second) | |||
@@ -542,7 +542,7 @@ func GetSuccessChunks(ctx *context.Context) { | |||
log.Error("GetPartInfos failed:%v", err.Error()) | |||
} | |||
} else { | |||
chunks, err = storage.GetObsPartInfos(fileChunk.UUID, fileChunk.UploadID) | |||
chunks, err = storage.GetObsPartInfos(fileChunk.UUID, fileChunk.UploadID, fileName) | |||
if err != nil { | |||
log.Error("GetObsPartInfos failed:%v", err.Error()) | |||
} | |||
@@ -116,8 +116,16 @@ func checkAutoLogin(ctx *context.Context) bool { | |||
} | |||
if isSucceed { | |||
isCourse := ctx.QueryBool("course") | |||
ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true) | |||
ctx.RedirectToFirst(redirectTo, setting.AppSubURL+string(setting.LandingPageURL)) | |||
if redirectTo == "" && isCourse { | |||
redirectToCourse := setting.AppSubURL + "/" + setting.Course.OrgName | |||
ctx.RedirectToFirst(redirectToCourse) | |||
} else { | |||
ctx.RedirectToFirst(redirectTo, setting.AppSubURL+string(setting.LandingPageURL)) | |||
} | |||
return true | |||
} | |||
@@ -143,6 +151,7 @@ func SignIn(ctx *context.Context) { | |||
ctx.Data["Title"] = ctx.Tr("sign_in") | |||
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" | |||
ctx.Data["PageIsSignIn"] = true | |||
ctx.Data["IsCourse"] = ctx.QueryBool("course") | |||
ctx.Data["PageIsLogin"] = true | |||
ctx.Data["EnableSSPI"] = models.IsSSPIEnabled() | |||
ctx.Data["EnableCloudBrain"] = true | |||
@@ -182,6 +191,7 @@ func SignInPost(ctx *context.Context, form auth.SignInForm) { | |||
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" | |||
ctx.Data["PageIsSignIn"] = true | |||
ctx.Data["PageIsLogin"] = true | |||
ctx.Data["IsCourse"] = ctx.QueryBool("course") | |||
ctx.Data["EnableSSPI"] = models.IsSSPIEnabled() | |||
if ctx.HasError() { | |||
@@ -565,6 +575,12 @@ func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyR | |||
return setting.AppSubURL + "/dashboard" | |||
} | |||
isCourse := ctx.QueryBool("course") | |||
if isCourse { | |||
redirectToCourse := setting.AppSubURL + "/" + setting.Course.OrgName | |||
ctx.RedirectToFirst(redirectToCourse) | |||
return redirectToCourse | |||
} | |||
if redirectTo := ctx.GetCookie("redirect_to"); len(redirectTo) > 0 && !util.IsExternalURL(redirectTo) { | |||
ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true) | |||
if obeyRedirect { | |||
@@ -200,16 +200,7 @@ var _hmt = _hmt || []; | |||
<div class="ui top secondary stackable main menu following bar dark"> | |||
{{template "base/head_navbar" .}} | |||
</div><!-- end bar --> | |||
<!-- {{if not .IsCourse}} --> | |||
<div class="notic_content" id ="notic_content" style="display: none;"> | |||
<a href={{.notice.Link}} class="a_width"> | |||
<marquee behavior="scroll" direction="left"> | |||
{{.notice.Title}} | |||
</marquee> | |||
</a> | |||
<i class="ri-close-fill x_icon" onclick="closeNoice()"></i> | |||
</div> | |||
<!-- {{end}} --> | |||
{{template "base/head_notice" .}} | |||
{{end}} | |||
{{/* | |||
</div> | |||
@@ -233,7 +224,15 @@ var _hmt = _hmt || []; | |||
}else{ | |||
isNewNotice=false; | |||
} | |||
if (JSON.parse("{{.notice.Visible}}")){ | |||
let isShowNoticeTag = false; | |||
let notices= {{.notices.Notices}} | |||
for (i =0;i<notices.length;i++){ | |||
if (notices[i].Visible==1){ | |||
isShowNoticeTag =true; | |||
break; | |||
} | |||
} | |||
if (isShowNoticeTag){ | |||
if(isNewNotice){ | |||
document.getElementById("notic_content").style.display='block' | |||
}else{ | |||
@@ -249,6 +248,7 @@ var _hmt = _hmt || []; | |||
document.getElementById("notic_content").style.display='none' | |||
} | |||
} | |||
if(!("{{.IsCourse}}" == true || "{{.IsCourse}}" =='true')) { | |||
isShowNotice(); | |||
} | |||
@@ -201,14 +201,7 @@ var _hmt = _hmt || []; | |||
<div class="ui top secondary stackable main menu following bar dark"> | |||
{{template "base/head_navbar_fluid" .}} | |||
</div><!-- end bar --> | |||
<div class="notic_content" id ="notic_content" style="display: none;"> | |||
<a href={{.notice.Link}} class="a_width"> | |||
<marquee behavior="scroll" direction="left"> | |||
{{.notice.Title}} | |||
</marquee> | |||
</a> | |||
<i class="ri-close-fill x_icon" onclick="closeNoice()"></i> | |||
</div> | |||
{{template "base/head_notice" .}} | |||
{{end}} | |||
{{/* | |||
</div> | |||
@@ -232,7 +225,15 @@ var _hmt = _hmt || []; | |||
}else{ | |||
isNewNotice=false; | |||
} | |||
if (JSON.parse("{{.notice.Visible}}")){ | |||
let isShowNoticeTag = false; | |||
let notices= {{.notices.Notices}} | |||
for (i =0;i<notices.length;i++){ | |||
if (notices[i].Visible==1){ | |||
isShowNoticeTag =true; | |||
break; | |||
} | |||
} | |||
if (isShowNoticeTag){ | |||
if(isNewNotice){ | |||
document.getElementById("notic_content").style.display='block' | |||
}else{ | |||
@@ -248,6 +249,7 @@ var _hmt = _hmt || []; | |||
document.getElementById("notic_content").style.display='none' | |||
} | |||
} | |||
if(!("{{.IsCourse}}" == true || "{{.IsCourse}}" =='true')) { | |||
isShowNotice(); | |||
} |
@@ -205,14 +205,7 @@ var _hmt = _hmt || []; | |||
<div class="ui top secondary stackable main menu following bar dark"> | |||
{{template "base/head_navbar" .}} | |||
</div><!-- end bar --> | |||
<div class="notic_content" id ="notic_content" style="display: none;" > | |||
<a href={{.notice.Link}} class="a_width"> | |||
<marquee behavior="scroll" direction="left"> | |||
{{.notice.Title}} | |||
</marquee> | |||
</a> | |||
<i class="ri-close-fill x_icon" onclick="closeNoice()"></i> | |||
</div> | |||
{{template "base/head_notice" .}} | |||
{{end}} | |||
{{/* | |||
</div> | |||
@@ -236,7 +229,15 @@ var _hmt = _hmt || []; | |||
}else{ | |||
isNewNotice=false; | |||
} | |||
if (JSON.parse("{{.notice.Visible}}")){ | |||
let isShowNoticeTag = false; | |||
let notices= {{.notices.Notices}} | |||
for (i =0;i<notices.length;i++){ | |||
if (notices[i].Visible==1){ | |||
isShowNoticeTag =true; | |||
break; | |||
} | |||
} | |||
if (isShowNoticeTag){ | |||
if(isNewNotice){ | |||
document.getElementById("notic_content").style.display='block' | |||
}else{ | |||
@@ -252,6 +253,7 @@ var _hmt = _hmt || []; | |||
document.getElementById("notic_content").style.display='none' | |||
} | |||
} | |||
if(!("{{.IsCourse}}" == true || "{{.IsCourse}}" =='true')) { | |||
isShowNotice(); | |||
} |
@@ -211,13 +211,25 @@ | |||
</div> | |||
</form> | |||
{{if .ShowRegistrationButton}} | |||
<a class="item{{if .PageIsSignUp}} active{{end}}" href="{{AppSubUrl}}/user/sign_up"> | |||
{{svg "octicon-person" 16}} {{.i18n.Tr "register"}} | |||
{{if .IsCourse}} | |||
<a class="item{{if .PageIsSignUp}} active{{end}}" href="https://git.openi.org.cn/user/sign_up" target="_blank"> | |||
{{svg "octicon-person" 16}} {{.i18n.Tr "register"}} | |||
</a> | |||
{{else}} | |||
<a class="item{{if .PageIsSignUp}} active{{end}}" href="{{AppSubUrl}}/user/sign_up"> | |||
{{svg "octicon-person" 16}} {{.i18n.Tr "register"}} | |||
</a> | |||
{{end}} | |||
{{end}} | |||
{{if .IsCourse}} | |||
<a class="item{{if .PageIsSignIn}} active{{end}}" rel="nofollow" href="https://git.openi.org.cn/user/login?course=true" target="_blank"> | |||
{{svg "octicon-sign-in" 16}} {{.i18n.Tr "sign_in"}} | |||
</a> | |||
{{else}} | |||
<a class="item{{if .PageIsSignIn}} active{{end}}" rel="nofollow" href="{{AppSubUrl}}/user/login"> | |||
{{svg "octicon-sign-in" 16}} {{.i18n.Tr "sign_in"}} | |||
</a> | |||
{{end}} | |||
<a class="item{{if .PageIsSignIn}} active{{end}}" rel="nofollow" href="{{AppSubUrl}}/user/login"> | |||
{{svg "octicon-sign-in" 16}} {{.i18n.Tr "sign_in"}} | |||
</a> | |||
</div><!-- end anonymous right menu --> | |||
{{end}} | |||
@@ -0,0 +1,32 @@ | |||
{{if not .IsCourse}} | |||
{{ if .notices}} | |||
<div class="notic_content" id ="notic_content" style="display: block; position: relative"> | |||
<diV class="ui container"> | |||
<marquee behavior="scroll" direction="left"> | |||
{{ $firstTag := true }} | |||
{{range .notices.Notices}} | |||
{{if eq .Visible 1}} | |||
{{if $firstTag}} | |||
<a href={{.Link}} class="a_width" style = 'margin-left: 0px !important;' target="_blank"> | |||
<i class="ri-arrow-right-s-line"></i> | |||
{{.Title}} | |||
</a> | |||
{{else}} | |||
<a href={{.Link}} class="a_width" target="_blank"> | |||
<i class="ri-arrow-right-s-line"></i> | |||
{{.Title}} | |||
</a> | |||
{{end}} | |||
{{ $firstTag = false }} | |||
{{end}} | |||
{{end}} | |||
</marquee> | |||
<div class="item right" style="position:absolute;right: 1px;top:0px;"> | |||
<i class="ri-close-fill x_icon" onclick="closeNoice()"></i> | |||
</div> | |||
</diV> | |||
</div> | |||
{{end}} | |||
{{end}} |
@@ -201,14 +201,7 @@ var _hmt = _hmt || []; | |||
<div class="ui top secondary stackable main menu following bar dark"> | |||
{{template "base/head_navbar_pro" .}} | |||
</div><!-- end bar --> | |||
<div class="notic_content" id ="notic_content" style="display: none;" > | |||
<a href={{.notice.Link}} class="a_width"> | |||
<marquee behavior="scroll" direction="left"> | |||
{{.notice.Title}} | |||
</marquee> | |||
</a> | |||
<i class="icon icon-octicon x_icon" onclick="closeNoice()">{{svg "octicon-x" 16}}</i> | |||
</div> | |||
{{template "base/head_notice" .}} | |||
{{end}} | |||
{{/* | |||
</div> | |||
@@ -233,7 +226,15 @@ var _hmt = _hmt || []; | |||
}else{ | |||
isNewNotice=false; | |||
} | |||
if (JSON.parse("{{.notice.Visible}}")){ | |||
let isShowNoticeTag = false; | |||
let notices= {{.notices.Notices}} | |||
for (i =0;i<notices.length;i++){ | |||
if (notices[i].Visible==1){ | |||
isShowNoticeTag =true; | |||
break; | |||
} | |||
} | |||
if (isShowNoticeTag){ | |||
if(isNewNotice){ | |||
document.getElementById("notic_content").style.display='block' | |||
}else{ | |||
@@ -249,6 +250,7 @@ var _hmt = _hmt || []; | |||
document.getElementById("notic_content").style.display='none' | |||
} | |||
} | |||
if(!("{{.IsCourse}}" == true || "{{.IsCourse}}" =='true')) { | |||
isShowNotice(); | |||
} |
@@ -96,7 +96,7 @@ | |||
{{if .Topics }} | |||
<div class="omit tags " style="position: relative;"> | |||
{{range .Topics}} | |||
{{if ne . "" }}<a style="max-width:100%;margin: 5px 0;display:inline-flex;" ><span class="ui small label topic course_topic" >{{.}}</span></a>{{end}} | |||
{{if ne . "" }}<a style="max-width:100%;margin: 5px 0;display:inline-flex;cursor:default" ><span class="ui small label topic course_topic" >{{.}}</span></a>{{end}} | |||
{{end}} | |||
</div> | |||
@@ -136,11 +136,7 @@ | |||
<div class="ui sixteen wide mobile six wide tablet four wide computer column"> | |||
<div class=" ui bottom attached segment text center noborder text center" > | |||
{{if .IsSigned}} | |||
<a style="width: 80%;" class="ui green button bpadding" href="{{AppSubUrl}}/course/create"><i class="ri-folder-add-line" style="vertical-align: middle;"></i> {{.i18n.Tr "org.release_course"}} </a> | |||
{{else}} | |||
<a style="width: 80%;" class="ui green button bpadding" href="{{AppSubUrl}}/user/login"><i class="ri-folder-add-line" style="vertical-align: middle;"></i> {{.i18n.Tr "org.release_course"}} </a> | |||
{{end}} | |||
<a style="width: 80%;" class="ui green button bpadding" href="https://git.openi.org.cn/course/create" target="_blank"><i class="ri-folder-add-line" style="vertical-align: middle;"></i> {{.i18n.Tr "org.release_course"}} </a> | |||
</div> | |||
</div> | |||
</div> | |||
@@ -209,7 +205,7 @@ | |||
{{if .IsSigned}} | |||
<a class="ui blue basic button" onclick="jion_course_team()" style="width: 80%;"> <i class="ri-user-add-line"></i> {{.i18n.Tr "org.teams.join_teams"}}</a> | |||
{{else}} | |||
<a class="ui blue basic button" href="{{AppSubUrl}}/user/login" style="width: 80%;"> <i class="ri-user-add-line"></i> {{.i18n.Tr "org.teams.join_teams"}}</a> | |||
<a class="ui blue basic button" href="https://git.openi.org.cn/user/login?course=true" style="width: 80%;" target="_blank"> <i class="ri-user-add-line"></i> {{.i18n.Tr "org.teams.join_teams"}}</a> | |||
{{end}} | |||
</div> | |||
</div> | |||
@@ -29,7 +29,11 @@ | |||
<div class="ui grid"> | |||
<div class="column"> | |||
<form class="ui form" action="{{.SignInLink}}" method="post"> | |||
{{if .IsCourse}} | |||
<form class="ui form" action="{{.SignInLink}}?course=true" method="post"> | |||
{{else}} | |||
<form class="ui form" action="{{.SignInLink}}" method="post"> | |||
{{end}} | |||
{{.CsrfTokenHtml}} | |||
<div class="field"> | |||
<div class="ui left icon input {{if and (.Err_UserName) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn))}}error{{end}}"> | |||
@@ -350,6 +350,16 @@ export default { | |||
etags[currentChunk] = res.headers.etag; | |||
} | |||
async function uploadMinioNewMethod(url,e){ | |||
var xhr = new XMLHttpRequest(); | |||
xhr.open('PUT', url, false); | |||
xhr.setRequestHeader('Content-Type', '') | |||
xhr.send(e.target.result); | |||
var etagValue = xhr.getResponseHeader('ETag'); | |||
//console.log(etagValue); | |||
etags[currentChunk] = etagValue; | |||
} | |||
async function updateChunk(currentChunk) { | |||
await axios.post( | |||
'/attachments/update_chunk', | |||
@@ -372,7 +382,7 @@ export default { | |||
await getUploadChunkUrl(currentChunk, partSize); | |||
if (urls[currentChunk] != '') { | |||
// 上传到minio | |||
await uploadMinio(urls[currentChunk], e); | |||
await uploadMinioNewMethod(urls[currentChunk], e); | |||
if (etags[currentChunk] != '') { | |||
// 更新数据库:分片上传结果 | |||
//await updateChunk(currentChunk); | |||
@@ -604,10 +604,13 @@ display: block; | |||
font-weight: bold !important; | |||
} | |||
.a_width{ | |||
width: 50% !important; | |||
display:inline-block !important; | |||
// width: 50% !important; | |||
margin-left: 20em; | |||
// display:inline-block !important; | |||
} | |||
.a_width i{ | |||
vertical-align:middle!important; | |||
} | |||
.footer_icon{ | |||
display: inline-block !important; | |||
vertical-align: middle !important; | |||