diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..58e5577e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +im.dev \ No newline at end of file diff --git a/internal/api_handler.go b/internal/api_handler.go index 52648bf7..c0895913 100644 --- a/internal/api_handler.go +++ b/internal/api_handler.go @@ -3,29 +3,33 @@ package internal import ( "github.com/labstack/echo" "github.com/thinkindev/im.dev/internal/post" - "github.com/thinkindev/im.dev/internal/session" + "github.com/thinkindev/im.dev/internal/user" ) func apiHandler(e *echo.Echo) { // sign-in apis - e.POST("/web/signIn", session.SignIn) - e.POST("/web/signOut", session.SignOut) - e.GET("/web/user/get", session.GetUser) + e.POST("/web/signIn", user.SignIn) + e.POST("/web/signOut", user.SignOut) + e.GET("/web/user/card", user.Card) // article apis - e.POST("/web/article/saveNew", post.NewArticle, session.CheckSignIn) - e.POST("/web/post/preview", post.Preview, session.CheckSignIn) + e.POST("/web/article/saveNew", post.NewArticle, user.CheckSignIn) + e.POST("/web/post/preview", post.Preview, user.CheckSignIn) e.GET("/web/article/detail", post.GetArticleDetail) - e.GET("/web/article/beforeEdit", post.BeforeEditAr, session.CheckSignIn) - e.POST("/web/article/saveChanges", post.SaveArticleChanges, session.CheckSignIn) + e.GET("/web/article/beforeEdit", post.BeforeEditAr, user.CheckSignIn) + e.POST("/web/article/saveChanges", post.SaveArticleChanges, user.CheckSignIn) // comment apis - e.POST("/web/comment/create", post.Comment, session.CheckSignIn) - e.POST("/web/comment/reply", post.CommentReply, session.CheckSignIn) - e.POST("/web/comment/edit", post.EditComment, session.CheckSignIn) - e.POST("/web/comment/delete", post.DeleteComment, session.CheckSignIn) - e.POST("/web/comment/revert", post.RevertComment, session.CheckSignIn) + e.POST("/web/comment/create", post.Comment, user.CheckSignIn) + e.POST("/web/comment/reply", post.CommentReply, user.CheckSignIn) + e.POST("/web/comment/edit", post.EditComment, user.CheckSignIn) + e.POST("/web/comment/delete", post.DeleteComment, user.CheckSignIn) + e.POST("/web/comment/revert", post.RevertComment, user.CheckSignIn) e.GET("/web/comment/query", post.QueryComments) - e.POST("/web/comment/like", post.CommentLike, session.CheckSignIn) - e.POST("/web/comment/dislike", post.CommentDislike, session.CheckSignIn) + e.POST("/web/comment/like", post.CommentLike, user.CheckSignIn) + e.POST("/web/comment/dislike", post.CommentDislike, user.CheckSignIn) + + // user + e.GET("/user/profile", user.Profile, user.CheckSignIn) + e.POST("/user/profile/set", user.SetProfile, user.CheckSignIn) } diff --git a/internal/ecode/ecode.go b/internal/ecode/ecode.go index 43cd40e2..3c642097 100644 --- a/internal/ecode/ecode.go +++ b/internal/ecode/ecode.go @@ -13,6 +13,9 @@ const ( NoPermission = 1004 NoPermissionMsg = "You don't have permission" + + NotFound = 1005 + NotFoundMsg = "404 not found" ) // article @@ -29,3 +32,6 @@ const ( CommentLiked = 1200 CommentLikedMsg = "You have agreed this comment before" ) + +// user +const () diff --git a/internal/internal.go b/internal/internal.go index c1cba7bc..89b932a2 100644 --- a/internal/internal.go +++ b/internal/internal.go @@ -9,7 +9,7 @@ import ( "github.com/labstack/echo" "github.com/labstack/echo/middleware" "github.com/thinkindev/im.dev/internal/misc" - "github.com/thinkindev/im.dev/internal/session" + "github.com/thinkindev/im.dev/internal/user" ) // Start web server for im.dev ui @@ -29,7 +29,7 @@ func Start(confPath string) { } } - session.InitUser() + user.InitUser() e := echo.New() e.Pre(middleware.RemoveTrailingSlash()) diff --git a/internal/misc/var.go b/internal/misc/var.go index df70dc2b..66c6bad4 100644 --- a/internal/misc/var.go +++ b/internal/misc/var.go @@ -3,6 +3,7 @@ package misc import ( "encoding/base64" + "github.com/microcosm-cc/bluemonday" "go.uber.org/zap" ) @@ -14,3 +15,23 @@ var Log *zap.Logger // Base64 is the base64 handler var Base64 = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") + +// Sanitizer makes outside string clean +var Sanitizer *bluemonday.Policy + +func init() { + p := bluemonday.UGCPolicy() + p.AllowAttrs("class").Globally() + p.AllowAttrs("id").Globally() + p.AllowElements("input") + p.AllowAttrs("checked").OnElements("input") + p.AllowAttrs("disabled").OnElements("input") + p.AllowAttrs("type").OnElements("input") + p.AllowAttrs("style").OnElements("span") + p.AllowAttrs("style").OnElements("td") + p.AllowAttrs("style").OnElements("th") + + p.AllowDataURIImages() + + Sanitizer = p +} diff --git a/internal/post/article.go b/internal/post/article.go index adaaa022..0cc32d16 100644 --- a/internal/post/article.go +++ b/internal/post/article.go @@ -8,7 +8,7 @@ import ( "github.com/labstack/echo" "github.com/thinkindev/im.dev/internal/ecode" "github.com/thinkindev/im.dev/internal/misc" - "github.com/thinkindev/im.dev/internal/session" + "github.com/thinkindev/im.dev/internal/user" "github.com/thinkindev/im.dev/internal/utils" "go.uber.org/zap" ) @@ -44,7 +44,7 @@ func NewArticle(c echo.Context) error { }) } - sess := session.Get(c) + sess := user.GetSession(c) // generate id for article ar.ID = misc.GenID() @@ -152,7 +152,7 @@ func BeforeEditAr(c echo.Context) error { }) } - sess := session.Get(c) + sess := user.GetSession(c) // check whether user has permission to do so if uid != sess.ID { @@ -204,7 +204,7 @@ func SaveArticleChanges(c echo.Context) error { Message: ecode.CommonErrorMsg, }) } - sess := session.Get(c) + sess := user.GetSession(c) if sess.ID != uid { return c.JSON(http.StatusInternalServerError, misc.HTTPResp{ ErrCode: ecode.NoPermission, diff --git a/internal/post/comment.go b/internal/post/comment.go index 313bfbd8..acde341f 100644 --- a/internal/post/comment.go +++ b/internal/post/comment.go @@ -13,7 +13,7 @@ import ( "github.com/labstack/echo" "github.com/thinkindev/im.dev/internal/ecode" "github.com/thinkindev/im.dev/internal/misc" - "github.com/thinkindev/im.dev/internal/session" + "github.com/thinkindev/im.dev/internal/user" "github.com/thinkindev/im.dev/internal/utils" "go.uber.org/zap" ) @@ -96,7 +96,7 @@ func Comment(c echo.Context) error { }) } - sess := session.Get(c) + sess := user.GetSession(c) cc.UID = sess.ID // generate id for article @@ -176,7 +176,7 @@ func CommentReply(c echo.Context) error { }) } - sess := session.Get(c) + sess := user.GetSession(c) cc.UID = sess.ID // generate id for article @@ -224,7 +224,7 @@ func EditComment(c echo.Context) error { }) } - sess := session.Get(c) + sess := user.GetSession(c) // check permission q := misc.CQL.Query(`SELECT uid FROM comment WHERE id=?`, id) @@ -281,7 +281,7 @@ func DeleteComment(c echo.Context) error { }) } - sess := session.Get(c) + sess := user.GetSession(c) // check comment exists and this user has permission var uid string q := misc.CQL.Query(`SELECT uid FROM comment WHERE id=?`, id) @@ -326,7 +326,7 @@ func RevertComment(c echo.Context) error { }) } - sess := session.Get(c) + sess := user.GetSession(c) // check comment exists and this user has permission var uid, md, render string @@ -361,7 +361,7 @@ func RevertComment(c echo.Context) error { comment := &CommentContent{} comment.MD = md comment.Render = render - u := session.GetUserByID(uid) + u := user.GetUserByID(uid) if u == nil { comment.UName = "[404]" comment.UNickname = "[404]" @@ -409,7 +409,7 @@ func QueryComments(c echo.Context) error { editDate: edate, Status: status, } - u := session.GetUserByID(comment.UID) + u := user.GetUserByID(comment.UID) if u == nil { continue } @@ -470,7 +470,7 @@ func QueryComments(c echo.Context) error { b.WriteString(`SELECT id,likes FROM comment_counter WHERE id in (`) var b1 strings.Builder - sess := session.Get(c) + sess := user.GetSession(c) if sess != nil { b1.WriteString(`SELECT comment_id,type FROM comment_like WHERE uid=? and comment_id in (`) } @@ -563,7 +563,7 @@ func CommentLike(c echo.Context) error { }) } - sess := session.Get(c) + sess := user.GetSession(c) // check whether you already liked this comment status, err := commentLikeStatus(postID, sess.ID) @@ -613,7 +613,7 @@ func CommentDislike(c echo.Context) error { }) } - sess := session.Get(c) + sess := user.GetSession(c) // check whether you already liked this comment status, err := commentLikeStatus(postID, sess.ID) diff --git a/internal/post/modify.go b/internal/post/modify.go index aba4b32d..1ce3da37 100644 --- a/internal/post/modify.go +++ b/internal/post/modify.go @@ -5,9 +5,8 @@ import ( "net/http" "github.com/labstack/echo" - "github.com/microcosm-cc/bluemonday" "github.com/thinkindev/im.dev/internal/misc" - "github.com/thinkindev/im.dev/internal/session" + "github.com/thinkindev/im.dev/internal/user" "github.com/thinkindev/im.dev/internal/utils" ) @@ -27,19 +26,8 @@ func Preview(c echo.Context) error { // @user -> @user // remove js,iframe such html tags and attributes func modify(s string) string { - p := bluemonday.UGCPolicy() - p.AllowAttrs("class").Globally() - p.AllowAttrs("id").Globally() - p.AllowElements("input") - p.AllowAttrs("checked").OnElements("input") - p.AllowAttrs("disabled").OnElements("input") - p.AllowAttrs("type").OnElements("input") - p.AllowAttrs("style").OnElements("span") - p.AllowAttrs("style").OnElements("td") - p.AllowAttrs("style").OnElements("th") // The policy can then be used to sanitize lots of input and it is safe to use the policy in multiple goroutines - render := p.Sanitize(s) - + render := misc.Sanitizer.Sanitize(s) afterRender := make([]rune, 0, len(render)) idParseFlag := false tempName := make([]rune, 0) @@ -57,7 +45,7 @@ func modify(s string) string { idParseFlag = false // check name exist - if session.CheckUserExist(string(tempName)) { + if user.CheckUserExist(string(tempName)) { // converse @name -> @user afterRender = append(afterRender, []rune(fmt.Sprintf("%s", string(tempName), string(tempName)))...) } else { diff --git a/internal/session/user.go b/internal/session/user.go deleted file mode 100644 index 9e798ec7..00000000 --- a/internal/session/user.go +++ /dev/null @@ -1,66 +0,0 @@ -package session - -import ( - "net/http" - "time" - - "github.com/labstack/echo" - "go.uber.org/zap" - - "github.com/thinkindev/im.dev/internal/ecode" - "github.com/thinkindev/im.dev/internal/misc" -) - -// InitUser insert preserve users -func InitUser() { - err := misc.CQL.Query(`INSERT INTO user (id,name,nickname,avatar,create_date,edit_date) VALUES (?,?,?,?,?,?)`, - "13269", "sunface", "Sunface", "https://avatars2.githubusercontent.com/u/7036754?s=460&v=4", time.Now().Unix(), 0).Exec() - if err != nil { - misc.Log.Warn("access database error", zap.Error(err)) - } -} - -// CheckUserExist checks user exist by given user id -func CheckUserExist(n string) bool { - var name string - err := misc.CQL.Query(`SELECT name FROM user where name=?`, n).Scan(&name) - if err != nil && err.Error() != misc.CQLNotFound { - misc.Log.Warn("access database error", zap.Error(err)) - return false - } - - if name != "" { - return true - } - - return false -} - -// GetUser returns user detail info -func GetUser(c echo.Context) error { - uid := c.FormValue("uid") - sess := GetUserByID(uid) - if sess == nil { - return c.JSON(http.StatusInternalServerError, misc.HTTPResp{ - ErrCode: ecode.DatabaseError, - Message: ecode.CommonErrorMsg, - }) - } - - return c.JSON(http.StatusOK, misc.HTTPResp{ - Data: sess, - }) -} - -// GetUserByID return the user info by user id -func GetUserByID(uid string) *Session { - sess := &Session{ID: uid} - - err := misc.CQL.Query(`SELECT name,nickname,avatar FROM user WHERE id=?`, uid).Scan(&sess.Name, &sess.NickName, &sess.Avatar) - if err != nil && err.Error() != misc.CQLNotFound { - misc.Log.Warn("access database error", zap.Error(err)) - return nil - } - - return sess -} diff --git a/internal/session/session.go b/internal/user/session.go similarity index 93% rename from internal/session/session.go rename to internal/user/session.go index 3a797da4..c327b11c 100644 --- a/internal/session/session.go +++ b/internal/user/session.go @@ -1,4 +1,4 @@ -package session +package user import ( "net/http" @@ -55,8 +55,8 @@ func CheckSignIn(f echo.HandlerFunc) echo.HandlerFunc { } } -// Get return the session for given user -func Get(c echo.Context) *Session { +// GetSession return the session for given user +func GetSession(c echo.Context) *Session { token := c.Request().Header.Get("token") s, ok := sessions.Load(token) if !ok { diff --git a/internal/user/user.go b/internal/user/user.go new file mode 100644 index 00000000..6a163243 --- /dev/null +++ b/internal/user/user.go @@ -0,0 +1,260 @@ +package user + +import ( + "encoding/json" + "errors" + "net/http" + "strings" + "time" + "unicode/utf8" + + "github.com/labstack/echo" + "go.uber.org/zap" + + "github.com/thinkindev/im.dev/internal/ecode" + "github.com/thinkindev/im.dev/internal/misc" + "github.com/thinkindev/im.dev/internal/utils" + "github.com/thinkindev/im.dev/internal/utils/validate" +) + +// InitUser insert preserve users +func InitUser() { + err := misc.CQL.Query(`INSERT INTO user (id,name,nickname,avatar,create_date,edit_date) VALUES (?,?,?,?,?,?)`, + "13269", "sunface", "Sunface", "https://avatars2.githubusercontent.com/u/7036754?s=460&v=4", time.Now().Unix(), 0).Exec() + if err != nil { + misc.Log.Warn("access database error", zap.Error(err)) + } +} + +// CheckUserExist checks user exist by given user id +func CheckUserExist(n string) bool { + var name string + err := misc.CQL.Query(`SELECT name FROM user where name=?`, n).Scan(&name) + if err != nil && err.Error() != misc.CQLNotFound { + misc.Log.Warn("access database error", zap.Error(err)) + return false + } + + if name != "" { + return true + } + + return false +} + +// Card returns user detail card info +func Card(c echo.Context) error { + uid := c.FormValue("uid") + if uid == "" { + return c.JSON(http.StatusBadRequest, misc.HTTPResp{ + ErrCode: ecode.ParamInvalid, + Message: ecode.ParamInvalidMsg, + }) + } + + user := &User{} + q := misc.CQL.Query(`SELECT name,nickname,avatar,website,about,location,bg_color,text_color,looking_for_work,education,employer, + create_date FROM user WHERE id=?`, uid) + err := q.Scan(&user.Name, &user.NickName, + &user.Avatar, &user.Website, &user.About, &user.Location, &user.BgColor, &user.TextColor, &user.LookingForWork, + &user.Education, &user.Employer, &user.createDate) + + if err != nil { + if err.Error() == misc.CQLNotFound { + return c.JSON(http.StatusNotFound, misc.HTTPResp{ + ErrCode: ecode.NotFound, + Message: ecode.NotFoundMsg, + }) + } + misc.Log.Warn("access database error", zap.Error(err)) + return c.JSON(http.StatusInternalServerError, misc.HTTPResp{ + ErrCode: ecode.DatabaseError, + Message: ecode.CommonErrorMsg, + }) + } + + user.CreateDate = utils.Time2ReadableString(time.Unix(user.createDate, 0)) + if user.BgColor == "" { + user.BgColor = "#000" + } + if user.TextColor == "" { + user.TextColor = "#000" + } + return c.JSON(http.StatusOK, misc.HTTPResp{ + Data: user, + }) +} + +// GetUserByID return the user info by user id +func GetUserByID(uid string) *Session { + sess := &Session{ID: uid} + + err := misc.CQL.Query(`SELECT name,nickname,avatar FROM user WHERE id=?`, uid).Scan(&sess.Name, &sess.NickName, &sess.Avatar) + if err != nil && err.Error() != misc.CQLNotFound { + misc.Log.Warn("access database error", zap.Error(err)) + return nil + } + + return sess +} + +// User holds all info of one user +type User struct { + //----account----- + ID string `json:"id"` + Name string `json:"name"` + Email string `json:"email,omitempty"` + //----profile----- + NickName string `json:"nickname"` + Avatar string `json:"avatar"` + Website string `json:"website"` + About string `json:"about"` + Location string `json:"location"` + BgColor string `json:"bg_color"` + TextColor string `json:"text_color"` + LookingForWork bool `json:"lfw"` + Education string `json:"education"` + Employer string `json:"employer"` + Skills string `json:"skills"` + AvailableFor string `json:"available_for"` + WorkingExp string `json:"working_exp"` + + CreateDate string `json:"create_date"` + EditDate string `json:"edit_date"` + + createDate int64 + editDate int64 +} + +// Profile return user's detail profile +func Profile(c echo.Context) error { + sess := GetSession(c) + user := &User{} + q := misc.CQL.Query(`SELECT id,name,email,nickname,avatar,website,about,location,bg_color,text_color,looking_for_work,education,employer,skills, + available_for,working_exp,create_date,edit_date FROM user WHERE id=?`, sess.ID) + err := q.Scan(&user.ID, &user.Name, &user.Email, &user.NickName, + &user.Avatar, &user.Website, &user.About, &user.Location, &user.BgColor, &user.TextColor, &user.LookingForWork, + &user.Education, &user.Employer, &user.Skills, &user.AvailableFor, &user.WorkingExp, &user.createDate, &user.editDate) + + if err != nil { + if err.Error() == misc.CQLNotFound { + return c.JSON(http.StatusNotFound, misc.HTTPResp{ + ErrCode: ecode.NotFound, + Message: ecode.NotFoundMsg, + }) + } + misc.Log.Warn("access database error", zap.Error(err)) + return c.JSON(http.StatusInternalServerError, misc.HTTPResp{ + ErrCode: ecode.DatabaseError, + Message: ecode.CommonErrorMsg, + }) + } + + user.CreateDate = utils.Time2ReadableString(time.Unix(user.createDate, 0)) + if user.BgColor == "" { + user.BgColor = "#000" + } + if user.TextColor == "" { + user.TextColor = "#000" + } + return c.JSON(http.StatusOK, misc.HTTPResp{ + Data: user, + }) +} + +// SetProfile set user profile +func SetProfile(c echo.Context) error { + u := c.FormValue("user") + user := &User{} + err := json.Unmarshal([]byte(u), &user) + if err != nil { + return c.JSON(http.StatusBadRequest, misc.HTTPResp{ + ErrCode: ecode.ParamInvalid, + Message: ecode.ParamInvalidMsg, + }) + } + + err = validateProfile(user) + + sess := GetSession(c) + q := misc.CQL.Query(`UPDATE user SET nickname=?,avatar=?,website=?,about=?,location=?,bg_color=?, + text_color=?,looking_for_work=?,education=?,employer=?,skills=?,available_for=?,working_exp=?,edit_date=? WHERE id=?`, user.NickName, + user.Avatar, user.Website, user.About, user.Location, user.BgColor, user.TextColor, user.LookingForWork, user.Education, user.Employer, + user.Skills, user.AvailableFor, user.WorkingExp, time.Now().Unix(), sess.ID) + err = q.Exec() + if err != nil { + misc.Log.Warn("access database error", zap.Error(err)) + return c.JSON(http.StatusInternalServerError, misc.HTTPResp{ + ErrCode: ecode.DatabaseError, + Message: ecode.CommonErrorMsg, + }) + } + + if err != nil { + return c.JSON(http.StatusBadRequest, misc.HTTPResp{ + ErrCode: ecode.ParamInvalid, + Message: err.Error(), + }) + } + + return c.JSON(http.StatusOK, misc.HTTPResp{}) +} + +func validateProfile(user *User) error { + user.NickName = misc.Sanitizer.Sanitize(strings.TrimSpace(user.NickName)) + if utf8.RuneCountInString(user.NickName) > 30 { + return errors.New("Display name too long") + } + + user.About = misc.Sanitizer.Sanitize(strings.TrimSpace(user.About)) + if utf8.RuneCountInString(user.About) > 500 { + return errors.New("About too long") + } + + user.Website = misc.Sanitizer.Sanitize(strings.TrimSpace(user.Website)) + if utf8.RuneCountInString(user.Website) > 30 || !validate.IsURL(user.Website) { + return errors.New("Website invalid or too long") + } + + user.Location = misc.Sanitizer.Sanitize(strings.TrimSpace(user.Location)) + if utf8.RuneCountInString(user.Location) > 30 { + return errors.New("About too long") + } + + user.BgColor = strings.TrimSpace(user.BgColor) + if !validate.IsColor(user.BgColor) { + return errors.New("Invalid background color") + } + + user.TextColor = strings.TrimSpace(user.TextColor) + if !validate.IsColor(user.TextColor) { + return errors.New("Invalid text color") + } + + user.Education = misc.Sanitizer.Sanitize(strings.TrimSpace(user.Education)) + if utf8.RuneCountInString(user.Education) > 500 { + return errors.New("Education too long") + } + + user.Employer = misc.Sanitizer.Sanitize(strings.TrimSpace(user.Employer)) + if utf8.RuneCountInString(user.Employer) > 500 { + return errors.New("Employer too long") + } + + user.Skills = misc.Sanitizer.Sanitize(strings.TrimSpace(user.Skills)) + if utf8.RuneCountInString(user.Skills) > 500 { + return errors.New("Skills too long") + } + + user.AvailableFor = misc.Sanitizer.Sanitize(strings.TrimSpace(user.AvailableFor)) + if utf8.RuneCountInString(user.Employer) > 500 { + return errors.New("Available for too long") + } + + user.WorkingExp = misc.Sanitizer.Sanitize(strings.TrimSpace(user.WorkingExp)) + if utf8.RuneCountInString(user.Employer) > 500 { + return errors.New("Working experience too long") + } + + return nil +} diff --git a/internal/utils/string.go b/internal/utils/string.go index 0362ecb8..2ca1453d 100644 --- a/internal/utils/string.go +++ b/internal/utils/string.go @@ -1,6 +1,11 @@ package utils -import "unsafe" +import ( + "strings" + "unsafe" + + "github.com/labstack/echo" +) // Bytes2String see below func Bytes2String(b []byte) (s string) { @@ -33,3 +38,8 @@ func ValidNameRune(c rune) bool { } return false } + +// FormValue get value from http form +func FormValue(c echo.Context, k string) string { + return strings.TrimSpace(c.FormValue(k)) +} diff --git a/internal/utils/validate/validate.go b/internal/utils/validate/validate.go new file mode 100644 index 00000000..075ca13f --- /dev/null +++ b/internal/utils/validate/validate.go @@ -0,0 +1,149 @@ +package validate + +/*********************************************************** +*名字 golang 正则工具 +*功能 支持数字,字母,字符,常用信息(电话,邮箱)等的正则匹配 +************************************************************/ + +import ( + "regexp" + "strings" +) + +func IsColor(str ...string) bool { + var b bool + for _, s := range str { + b, _ = regexp.MatchString("^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$", s) + if false == b { + return b + } + } + return b +} + +func IsURL(s string) bool { + if strings.HasPrefix(s, "http://") { + return true + } + if strings.HasPrefix(s, "https://") { + return true + } + + return false +} + +/************************* 自定义类型 ************************/ +//数字+字母 不限制大小写 6~30位 +func IsID(str ...string) bool { + var b bool + for _, s := range str { + b, _ = regexp.MatchString("^[0-9a-zA-Z]{6,30}$", s) + if false == b { + return b + } + } + return b +} + +//数字+字母+符号 6~30位 +func IsPwd(str ...string) bool { + var b bool + for _, s := range str { + b, _ = regexp.MatchString("^[0-9a-zA-Z@.]{6,30}$", s) + if false == b { + return b + } + } + return b +} + +/************************* 数字类型 ************************/ +//纯整数 +func IsInteger(str ...string) bool { + var b bool + for _, s := range str { + b, _ = regexp.MatchString("^[0-9]+$", s) + if false == b { + return b + } + } + return b +} + +//纯小数 +func IsDecimals(str ...string) bool { + var b bool + for _, s := range str { + b, _ = regexp.MatchString("^\\d+\\.[0-9]+$", s) + if false == b { + return b + } + } + return b +} + +//手提电话(不带前缀)最高11位 +func IsCellphone(str ...string) bool { + var b bool + for _, s := range str { + b, _ = regexp.MatchString("^1[0-9]{10}$", s) + if false == b { + return b + } + } + return b +} + +//家用电话(不带前缀) 最高8位 +func IsTelephone(str ...string) bool { + var b bool + for _, s := range str { + b, _ = regexp.MatchString("^[0-9]{8}$", s) + if false == b { + return b + } + } + return b +} + +/************************* 英文类型 *************************/ +//仅小写 +func IsEngishLowCase(str ...string) bool { + var b bool + for _, s := range str { + b, _ = regexp.MatchString("^[a-z]+$", s) + if false == b { + return b + } + } + return b +} + +//仅大写 +func IsEnglishCap(str ...string) bool { + var b bool + for _, s := range str { + b, _ = regexp.MatchString("^[A-Z]+$", s) + if false == b { + return b + } + } + return b +} + +//大小写混合 +func IsEnglish(str ...string) bool { + var b bool + for _, s := range str { + b, _ = regexp.MatchString("^[A-Za-z]+$", s) + if false == b { + return b + } + } + return b +} + +//邮箱 最高30位 +func IsEmail(s string) (bool, error) { + return regexp.MatchString("^([a-z0-9_\\.-]+)@([\\da-z\\.-]+)\\.([a-z\\.]{2,6})$", s) +} diff --git a/quick-start/cql/start.cql b/quick-start/cql/start.cql index 382fb9ea..043a3593 100644 --- a/quick-start/cql/start.cql +++ b/quick-start/cql/start.cql @@ -4,23 +4,33 @@ CREATE KEYSPACE im_dev WITH replication = {'class': 'SimpleStrategy', 'replicati USE im_dev; CREATE TABLE IF NOT EXISTS user ( + --account id text, name text, + email text, + + --userprofile nickname text, avatar text, + website text, + about text, + location text, + bg_color text, + text_color text, + looking_for_work boolean, + education text, + employer text, + skills text, + available_for text, + working_exp text, + create_date bigint, edit_date bigint, PRIMARY KEY (id) ) WITH gc_grace_seconds = 10800; CREATE CUSTOM INDEX IF NOT EXISTS ON user (name) USING 'org.apache.cassandra.index.sasi.SASIIndex' ; - - -CREATE TABLE IF NOT EXISTS user_activity ( - id text, - PRIMARY KEY (id) -) WITH gc_grace_seconds = 10800; - + CREATE TABLE IF NOT EXISTS article ( id text, diff --git a/ui/src/main.js b/ui/src/main.js index 52c8c36f..304e63dd 100644 --- a/ui/src/main.js +++ b/ui/src/main.js @@ -8,7 +8,6 @@ import 'element-ui/lib/theme-chalk/index.css'; import mavonEditor from 'mavon-editor' import 'mavon-editor/dist/css/index.css' // import 'mavon-editor/dist/markdown/github-markdown.min.css' -import "./theme/editor.css"; Vue.use(mavonEditor) @@ -20,8 +19,7 @@ Vue.use(ElementUI); import router from './router' // 全局范围加载通用样式,每个vue page里无需重复引入 -import '!style-loader!css-loader!less-loader!./theme/common_layout.less' -import '!style-loader!css-loader!less-loader!./theme/layout.less' +import '!style-loader!css-loader!less-loader!./theme/eleui-style.less' import '!style-loader!css-loader!less-loader!./theme/style.less' Vue.config.productionTip = false diff --git a/ui/src/router/index.js b/ui/src/router/index.js index a58b4caa..0c5553d7 100644 --- a/ui/src/router/index.js +++ b/ui/src/router/index.js @@ -15,6 +15,16 @@ const router = new Router({ children: [ { path: '/', meta: {'title':'im.dev'}, component: () => import('@/views/home')}, { path: '/dev/article/new', meta: {'title':'Post - im.dev'},component: () => import('@/views/article/edit')}, + { + path: '/dev/setting', + component: () => import('@/views/setting/nav'), + redirect: '/dev/setting/account', + meta: '综合监控', + children: [ + { path: '/dev/setting/account',meta: {'title':'Settings - im.dev'}, component: () => import('@/views/setting/account')}, + { path: '/dev/setting/profile',meta: {'title':'Settings - im.dev'}, component: () => import('@/views/setting/profile')}, + ] + }, { path: '/:uname/:arID', meta: {'title':'Article - im.dev',},component: () => import('@/views/article/detail')}, { path: '/:uname/:arID/edit', meta: {'title':'Post - im.dev'},component: () => import('@/views/article/edit')}, ] diff --git a/ui/src/theme/common_layout.less b/ui/src/theme/common_layout.less deleted file mode 100644 index 8e250989..00000000 --- a/ui/src/theme/common_layout.less +++ /dev/null @@ -1,171 +0,0 @@ -// layout -.margin-left-5 { - margin-left: 5px -} -.margin-left-10 { - margin-left: 10px -} -.margin-left-15 { - margin-left: 15px -} -.margin-left-20 { - margin-left: 20px -} - -.margin-right-5 { - margin-right: 5px -} -.margin-right-10 { - margin-right: 10px -} -.margin-right-15 { - margin-right: 15px -} -.margin-right-20 { - margin-right: 20px -} -.margin-right-30 { - margin-right: 30px -} -.margin-right-40 { - margin-right: 40px -} - -.margin-top-5 { - margin-top: 5px -} - -.margin-top-10 { - margin-top: 10px -} - -.margin-top-15 { - margin-top: 15px -} - -.margin-top-20 { - margin-top: 20px !important -} -.margin-top-30 { - margin-top: 30px !important -} -.margin-top-40 { - margin-top: 40px !important -} - -.padding-5 { - padding: 5px 5px -} - -.padding-10 { - padding: 10px 10px -} - -.padding-20 { - padding: 20px 20px -} - -.padding-left-5 { - padding-left: 5px -} - -.padding-left-10 { - padding-left: 10px -} - -.padding-left-15 { - padding-left: 15px -} - - -.padding-left-20 { - padding-left: 20px -} - -.padding-right-5 { - padding-right: 5px -} - -.padding-right-10 { - padding-right: 10px -} - -.padding-right-15 { - padding-right: 15px -} - - -.padding-right-20 { - padding-right: 20px -} - -.padding-top-5 { - padding-top: 5px -} - -.padding-top-10 { - padding-top: 10px -} - -.padding-top-15 { - padding-top: 15px -} - -.padding-top-20 { - padding-top: 20px -} -.padding-top-40 { - padding-top: 40px -} - -.padding-bottom-5 { - padding-bottom: 5px -} - -.padding-bottom-10 { - padding-bottom: 10px -} - -.padding-bottom-15 { - padding-bottom: 15px -} - - -.padding-bottom-20 { - padding-bottom: 20px -} - -.padding-bottom-40 { - padding-bottom: 40px -} - -// 鼠标悬浮 -.hover-cursor:hover{ - cursor: pointer -} - -.float-right { - float: right -} - -.text-align-center { - text-align:center -} - -.middle-inline { - display: inline-block; - vertical-align: middle; -} - -.font-size-18 { - font-size:18px; -} -.font-size-20 { - font-size:18px; -} -.font-size-22 { - font-size:18px; -} -.font-size-24 { - font-size:18px; -} diff --git a/ui/src/theme/editor.css b/ui/src/theme/editor.css deleted file mode 100644 index ae763802..00000000 --- a/ui/src/theme/editor.css +++ /dev/null @@ -1,30 +0,0 @@ -.v-note-wrapper { - z-index: 10 !important; -} - -/* .v-note-wrapper .v-note-panel .v-note-edit.divarea-wrapper.scroll-style::-webkit-scrollbar { - width: 1px !important; - background: rgba(0, 0, 0, .1) !important; -} */ - -/* .v-note-wrapper .v-note-panel .v-note-edit.divarea-wrapper.scroll-style::-webkit-scrollbar{ -width: 2px !important; -background: rgba(0, 0, 0, .1) !important; -} -.v-note-wrapper .v-note-panel .v-note-edit.divarea-wrapper.scroll-style::-webkit-scrollbar-thumb{ -border-radius: 1em; -background-color: rgba(50,50,50,.5); -} -.v-note-wrapper .v-note-panel .v-note-edit.divarea-wrapper.scroll-style::-webkit-scrollbar-track{ -border-radius: 1em; -background-color: rgba(50,50,50,.1); -} */ - -.v-note-wrapper .v-note-panel { - box-shadow: none !important; -} - -.v-note-wrapper .v-note-op .v-right-item { - display:none; -} - diff --git a/ui/src/theme/eleui-style.less b/ui/src/theme/eleui-style.less new file mode 100644 index 00000000..0f2df11d --- /dev/null +++ b/ui/src/theme/eleui-style.less @@ -0,0 +1,79 @@ +// overide global focus style +h1 { + font-size: 36px; +} + +h3 { + font-size:19px; + font-weight:500; +} +h4 { + font-size:17px; + font-weight:500; + margin-block-start: .5em; + margin-block-end: .5em; +} + +:focus { + outline-color: transparent; + outline-width: 0; + outline-style: none; +} +input::-webkit-input-placeholder { + color: #aab2bd; + font-weight: bold; + font-size: 14px; +} + +::-webkit-scrollbar { + width: 2px !important; + height: 2px; + background-color: #e5e5e5 !important; +} +::-webkit-scrollbar-thumb { + background-color: #b7b7b7 !important; + border-radius: 3px !important; +} +::-webkit-scrollbar-track { + border-radius: 3px; + box-shadow: 0 0 0px #808080 inset !important; +} + +.no-border { + input { + border: none; + } + button { + border:none; + } +} + +.image-modal { + .el-dialog { + box-shadow: none !important; + position: absolute; + top: 45%; + left: 50%; + transform: translate(-50%, -50%); + cursor: zoom-out; + } + .el-dialog__header { + display: none; + } + .el-dialog__body { + padding: 0; + } +} + +.el-message.el-message--error.network-error { + top: 20px !important; +} + + +.el-message { + min-width: 0px; +} + +.el-divider { + margin: 13px 0; + } \ No newline at end of file diff --git a/ui/src/theme/layout.less b/ui/src/theme/layout.less deleted file mode 100644 index e69de29b..00000000 diff --git a/ui/src/theme/light/eleui-style.less b/ui/src/theme/light/eleui-style.less new file mode 100644 index 00000000..e7f24668 --- /dev/null +++ b/ui/src/theme/light/eleui-style.less @@ -0,0 +1,15 @@ +@import './var.less'; + +a { + color: @main-color; +} + +.el-button.el-button--info { + background-color: #f5f6f7; + color: @grey-color; +} + +.el-dialog__wrapper.white-bg-modal { + background-color: rgba(255,255,255,.95); +} + diff --git a/ui/src/theme/light/layout.less b/ui/src/theme/light/layout.less deleted file mode 100644 index e69de29b..00000000 diff --git a/ui/src/theme/light/style.less b/ui/src/theme/light/style.less index 64b2e272..bb477bf4 100644 --- a/ui/src/theme/light/style.less +++ b/ui/src/theme/light/style.less @@ -1,20 +1,642 @@ -.el-dialog__wrapper.white-bg-modal { - background-color: rgba(255,255,255,.95); +@import './var.less'; +.meta-word { + color:@grey-color; } +.bold-meta-word { + color:@light-grey-color; + font-weight:700; +} + .border-bottom { + border-bottom: 1px solid #eee; + } + .color-regular { + color: @regular-color; + } -.post-huge-title { - font-size: 36px; +.global-nav { + .write-post { + text-decoration:none;color:black;background:#66e2d5;padding:2px 12px;border:2px solid #0a0a0a;border-radius:3px;font-weight:bold;font-size:14px + } + .sign-in-modal { + .el-dialog__header { + display: none; + } + .el-dialog__body { + padding: 0 0; + padding-bottom: 40px; + } + .sign-in-panel { + height: 100%; + background: url(../../assets/login.png) no-repeat; + background-size: 100%; + } + } + .user-panel { + div { + cursor: pointer; + padding-left: 10px; + } + } + .fade-enter-active, + .fade-leave-active { + transition: all 0.6s; + } + .fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ { + // opacity: 0; + transform: translateY(-50px); + } + + .fade-leave-active, + .fade-enter-active { + transition: all 0.4s; + } + .nav { + top: 0; + width: 100%; + background-color: rgba(255, 255, 255, 0); + position: fixed; + + // box-shadow: rgba(0, 0, 0, 0.0470588) 0px 4px 12px 0px; + padding-top: 8px; + padding-bottom: 4px; + } + .nav.toTop { + position: fixed; + // box-shadow: rgba(0, 0, 0, 0.0470588) 0px 4px 12px 0px; + border-bottom: 1px solid #eee; + background-color: white; + z-index: 999; + // transition:transform 300ms ease; + // transform: translateY(100px) + } + .nav.inTop { + // box-shadow: rgba(0, 0, 0, 0.0470588) 0px 4px 12px 0px;; + z-index: 1; + } } -.meta-word { - font-size:12px; - color:rgb(124,124,124); +.article-detail { + .discuss { + background-color: #fafafa + } + .squares { + .square { + text-align: center; + font-size: 25px; + a { + color: rgba(0,0,0,.8) + } + } + } + + .clap { + position: relative; + outline: 1px solid transparent; + border-radius: 50%; + border: 1px solid #bdc3c7; + width: 60px; + height: 60px; + background: none; + } + .clap:after { + content: ""; + position: absolute; + top: 0; + left: 0; + display: block; + border-radius: 50%; + width: 59px; + height: 59px; + } + .clap:hover { + cursor: pointer; + border: 1px solid #333; + transition: border-color 0.3s ease-in; + } + .clap:hover:after { + animation: shockwave 1s ease-in infinite; + } + .clap svg { + width: 30px; + fill: none; + stroke: #333; + stroke-width: 1px; + margin-top: 5px; + } + .clap.liked { + border: 1px solid #333; + } + .clap.liked svg { + stroke: #333; + stroke-width: 2px; + } + .clap svg.checked { + fill: #333; + stroke: #fff; + stroke-width: 2px; + } + @keyframes shockwave { + 0% { + transform: scale(1); + box-shadow: 0 0 2px #333; + opacity: 1; + } + 100% { + transform: scale(1); + opacity: 0; + box-shadow: 0 0 50px #145b32, inset 0 0 10px #333 + } + } } -.bold-meta-word { - color:rgb(135, 138, 140); - font-weight:700; - font-size:12px; +.article-edit { + .render { + border:1px solid #eee;border-bottom:none;border-right:none; + } + .tags { + .el-input.el-input--suffix input{ + background-color: #f5f6f7; + border-radius: 100px + } + } } + +.component-render { + .content p { + margin: 0 0 8px + } + .content blockquote h1:last-child, + .content blockquote h2:last-child, + .content blockquote h3:last-child, + .content blockquote h4:last-child, + .content blockquote h5:last-child, + .content blockquote h6:last-child, + .content blockquote li:last-child, + .content blockquote ol:last-child, + .content blockquote p:last-child, + .content blockquote ul:last-child { + margin-bottom: 0 + } + + .content .video-package .video-description p { + margin: 0 + } + + .content li p { + overflow: visible + } + + .content a { + color: rgba(0, 0, 0, .84); + text-decoration: none; + background-image: linear-gradient(to bottom, rgba(0, 0, 0, .68) 50%, rgba(0, 0, 0, 0) 50%); + background-repeat: repeat-x; + background-size: 2px .2em; + background-position: 0 1.07em + } + + + /* .content a:hover { + color: #3194d0; + text-decoration: underline + } */ + + + /* .content a.active, + .content a:active, + .content a:focus { + color: #3194d0 + } */ + + .content a.disabled, + .content a.disabled.active, + .content a.disabled:active, + .content a.disabled:focus, + .content a.disabled:hover, + .content a[disabled], + .content a[disabled].active, + .content a[disabled]:active, + .content a[disabled]:focus, + .content a[disabled]:hover { + cursor: not-allowed; + color: #f5f5f5 + } + + .content ol, + .content p, + .content ul { + word-break: break-word!important; + word-break: break-all + } + + .content hr { + margin: 0 0 20px; + border-top: 1px solid #ddd + } + + .content h1, + .content h2, + .content h3, + .content h4, + .content h5, + .content h6 { + margin: 0 0 12px; + font-weight: 700; + color: #2f2f2f; + line-height: 1.6; + text-rendering: optimizelegibility + } + + .content h1 { + font-size: 26px + } + + .content h2 { + font-size: 24px + } + + .content h3 { + font-size: 22px + } + + .content h4 { + font-size: 20px + } + + .content h5 { + font-size: 18px + } + + .content h6 { + font-size: 16px + } + + .content img { + max-width: 100% + } + + .content blockquote { + font-style: italic; + font-size: 18px !important; + color: rgba(0, 0, 0, .68); + padding-left: 25px; + padding-top:4px; + padding-bottom: 4px; + box-shadow:rgba(0, 0, 0, 0.843137) 3px 0px 0px 0px inset; + margin-inline-start: 0px !important; + margin-inline-end: 0px !important; + margin-left:-20px !important; + } + + + + + + .content ol .image-package, + .content ul .image-package { + width: auto!important; + margin-left: 0!important + } + + .content code { + background: rgba(0, 0, 0, .05); + padding: 3px 4px; + margin: 0 2px; + font-size: 14px; + } + + .content pre { + font-size: 14px; + word-wrap: normal; + word-break: break-word!important; + word-break: break-all; + white-space: pre; + overflow: auto; + border-radius: 0; + /* border: .3px solid #ddd;*/ + page-break-inside: avoid; + display: block; + /* line-height: 1.42857; */ + color: #333; + background-color: #f8f8f8; + border-radius: 4px; + line-height: 27px; + padding: 15px; + } + + .content pre code { + padding: 0; + background-color: transparent; + /* white-space: pre */ + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border-radius: 0 + } + + .content table { + width: 100%; + margin-bottom: 20px; + border: 0px solid #ddd; + border-collapse: collapse; + border-left: none; + word-break: normal; + display: table; + overflow: auto; + } + + .content table tr:nth-of-type(2n) { + background-color: rgba(243, 243, 243, 0.3) + } + + .content table thead th { + vertical-align: middle; + text-align: left; + color: rgba(0, 0, 0, .64); + font-size: 14px; + } + + .content table tr { + border-top: .3px solid #ddd; + padding-top: 5px; + padding-bottom: 5px; + } + + .content table thead tr { + border-top: none; + } + + .content table td, + .content table th { + padding: 12px 8px; + border: 0px solid #ddd; + line-height: 20px; + vertical-align: middle + } + + .content table th { + font-weight: 500 + } + + .content table .image-package { + width: auto; + margin-left: 0 + } + + .content .image-package .image-container { + z-index: 100; + position: relative; + background-color: #eee; + transition: background-color .1s linear; + margin: 0 auto + } + + .content .image-package .image-container .image-container-fill { + z-index: 50 + } + + .content .image-package .image-container .image-view { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + overflow: hidden + } + + .content .image-package .image-container .image-view img.image-loading { + will-change: filter, opacity; + -webkit-filter: blur(4px); + filter: blur(4px); + opacity: .3 + } + + .content .image-package .image-container .image-view img { + transition: all .15s linear; + z-index: 100; + will-change: filter, opacity; + -webkit-filter: blur(0); + filter: blur(0); + opacity: 1 + } + + + + .content img { + max-width: 100%; + height: auto; + vertical-align: middle; + border: 0; + cursor: -webkit-zoom-in; + transition: all .25s ease-in-out + } + + + + .content .image-package .image-caption:empty { + display: none + } + + .content .video-package { + position: relative; + margin: -20px auto 20px; + text-align: center + } + + .content .video-package .video-placeholder-area { + position: relative; + display: inline-block; + height: 110px; + padding: 10px; + padding-left: 120px; + box-sizing: border-box; + border: 1px solid #d9d9d9; + background-color: hsla(0, 0%, 71%, .1); + text-align: left; + cursor: pointer + } + + .content .video-package .video-placeholder-area:after { + content: " "; + position: absolute; + top: -1px; + left: -1px; + display: block; + width: 110px; + height: 110px; + background-color: rgba(0, 0, 0, .3); + background-image: url(//cdn2.jianshu.io/assets/common/play-btn-c4bc06b9dfe063495b6b8277b14bc5c3.png); + background-position: 30px; + background-size: 50px; + background-repeat: no-repeat; + transition: all .1s linear + } + + .content .video-package .video-placeholder-area:hover:after { + background-color: transparent + } + + .content .video-package .video-placeholder-area .video-cover { + position: absolute; + top: -1px; + left: -1px; + display: block; + width: 110px; + height: 110px; + opacity: .8; + transition: opacity .1s linear + } + + .content .video-package .video-description { + min-width: 20%; + min-height: 22px; + display: none; + padding: 10px; + margin: 0 auto; + border-bottom: 1px solid #d9d9d9; + font-size: 13px; + color: #999; + line-height: 1.7 + } + + .content .video-package .video-description:empty { + display: none + } + + .content .video-package .video-close-button, + .content .video-package .video-provider-button { + text-align: left; + font-size: 14px; + padding: 0; + line-height: 14px; + cursor: pointer; + transition: opacity .1s linear + } + + .content .video-package .video-close-button i, + .content .video-package .video-provider-button i { + position: relative; + top: 1px + } + + .content .video-package .video-provider-button { + float: right + } + + .content .hljs { + background-color: transparent; + } + + .content .hljs-center { + text-align: center + } + + .content .hljs-right { + text-align: right + } + + .content .hljs-left { + text-align: left +} +} + +.component-editor { + .v-note-wrapper { + z-index: 10 !important; + } + + .v-note-wrapper .v-note-panel { + box-shadow: none !important; + } + + .v-note-wrapper .v-note-op .v-right-item { + display:none; + } +} + + + +.setting-nav { + .header { + + } + .item { + padding: 15px 12px 8px; + } + .item.selected { + border-bottom: solid 3px #cfd7ff; + } + } + +.discuss { + .write-comment { + .header-buttons { + margin-top:-190px + } + } + .comments { + padding-bottom:30px + } + .no-comments { + .iconfont { + color:rgba(0, 121, 211, 0.4);font-size: 30px + } + } + .sorter { + color:@grey-color; + + } + .comments { + background:white; + padding:5px 25px; + margin-top:0px; + .comment { + i.vote-highlighted { + color: rgb(255, 69, 0); + } + .upvote { + position:absolute; + margin-left:-21px; + margin-top:-2px; + color: @light-grey-color; + font-size:10px + } + .downvote { + position:absolute; + margin-left:-21px; + margin-top:9px; + color:@light-grey-color; + font-size:10px + } + .header { + color:rgb(124,124,124); + .uname { + color:rgb(0, 121, 211); + } + } + .footer { + color:@light-grey-color; + + } + } + } + + .v-note-wrapper { + min-height: 150px !important; + .v-note-op.shadow { + box-shadow: none; + border-bottom: 1px solid #efefef + } + textarea { + font-size:13px !important; + } + } + .reply-comment { + .v-note-wrapper { + border:1px solid #eee + } + } +} \ No newline at end of file diff --git a/ui/src/theme/light/var.less b/ui/src/theme/light/var.less index 081d2429..c2473fdc 100644 --- a/ui/src/theme/light/var.less +++ b/ui/src/theme/light/var.less @@ -1,8 +1,5 @@ -@text-default-color: rgba(0, 0, 0, .84); -@text-second-color: rgba(0, 0, 0, .54); -@nav-color: rgba(0,0,0,.70); -@text-enter-more-color: rgb(150, 150, 150); -@text-annotation-color: rgb(180, 180, 180); -@topic-text-color: white; -@topic-annotation-color: rgba(255, 255, 255, .7); -@menu-item-active-color: #f0f0f0; \ No newline at end of file +@grey-color: rgb(124,124,124); +@light-grey-color: rgb(135, 138, 140); + +@main-color: #303133; +@regular-color: #606266; \ No newline at end of file diff --git a/ui/src/theme/md_render.css b/ui/src/theme/md_render.css deleted file mode 100644 index e5c9448e..00000000 --- a/ui/src/theme/md_render.css +++ /dev/null @@ -1,385 +0,0 @@ -.render .content p { - margin: 0 0 8px -} - - - -.render .content blockquote h1:last-child, -.render .content blockquote h2:last-child, -.render .content blockquote h3:last-child, -.render .content blockquote h4:last-child, -.render .content blockquote h5:last-child, -.render .content blockquote h6:last-child, -.render .content blockquote li:last-child, -.render .content blockquote ol:last-child, -.render .content blockquote p:last-child, -.render .content blockquote ul:last-child { - margin-bottom: 0 -} - -.render .content .video-package .video-description p { - margin: 0 -} - -.render .content li p { - overflow: visible -} - -.render .content a { - color: rgba(0, 0, 0, .84); - text-decoration: none; - background-image: linear-gradient(to bottom, rgba(0, 0, 0, .68) 50%, rgba(0, 0, 0, 0) 50%); - background-repeat: repeat-x; - background-size: 2px .2em; - background-position: 0 1.07em -} - - -/* .render .content a:hover { - color: #3194d0; - text-decoration: underline -} */ - - -/* .render .content a.active, -.render .content a:active, -.render .content a:focus { - color: #3194d0 -} */ - -.render .content a.disabled, -.render .content a.disabled.active, -.render .content a.disabled:active, -.render .content a.disabled:focus, -.render .content a.disabled:hover, -.render .content a[disabled], -.render .content a[disabled].active, -.render .content a[disabled]:active, -.render .content a[disabled]:focus, -.render .content a[disabled]:hover { - cursor: not-allowed; - color: #f5f5f5 -} - -.render .content ol, -.render .content p, -.render .content ul { - word-break: break-word!important; - word-break: break-all -} - -.render .content hr { - margin: 0 0 20px; - border-top: 1px solid #ddd -} - -.render .content h1, -.render .content h2, -.render .content h3, -.render .content h4, -.render .content h5, -.render .content h6 { - margin: 0 0 12px; - font-weight: 700; - color: #2f2f2f; - line-height: 1.6; - text-rendering: optimizelegibility -} - -.render .content h1 { - font-size: 26px -} - -.render .content h2 { - font-size: 24px -} - -.render .content h3 { - font-size: 22px -} - -.render .content h4 { - font-size: 20px -} - -.render .content h5 { - font-size: 18px -} - -.render .content h6 { - font-size: 16px -} - -.render .content img { - max-width: 100% -} - -.render .content blockquote { - font-style: italic; - font-size: 18px !important; - color: rgba(0, 0, 0, .68); - padding-left: 25px; - padding-top:4px; - padding-bottom: 4px; - box-shadow:rgba(0, 0, 0, 0.843137) 3px 0px 0px 0px inset; - margin-inline-start: 0px !important; - margin-inline-end: 0px !important; - margin-left:-20px !important; -} - - - - - -.render .content ol .image-package, -.render .content ul .image-package { - width: auto!important; - margin-left: 0!important -} - -.render .content code { - background: rgba(0, 0, 0, .05); - padding: 3px 4px; - margin: 0 2px; - font-size: 14px; -} - -.render .content pre { - font-size: 14px; - word-wrap: normal; - word-break: break-word!important; - word-break: break-all; - white-space: pre; - overflow: auto; - border-radius: 0; - /* border: .3px solid #ddd;*/ - page-break-inside: avoid; - display: block; - /* line-height: 1.42857; */ - color: #333; - background-color: #f8f8f8; - border-radius: 4px; - line-height: 27px; - padding: 15px; -} - -.render .content pre code { - padding: 0; - background-color: transparent; - /* white-space: pre */ - font-size: inherit; - color: inherit; - white-space: pre-wrap; - background-color: transparent; - border-radius: 0 -} - -.render .content table { - width: 100%; - margin-bottom: 20px; - border: 0px solid #ddd; - border-collapse: collapse; - border-left: none; - word-break: normal; - display: table; - overflow: auto; -} - -.render .content table tr:nth-of-type(2n) { - background-color: rgba(243, 243, 243, 0.3) -} - -.render .content table thead th { - vertical-align: middle; - text-align: left; - color: rgba(0, 0, 0, .64); - font-size: 14px; -} - -.render .content table tr { - border-top: .3px solid #ddd; - padding-top: 5px; - padding-bottom: 5px; -} - -.render .content table thead tr { - border-top: none; -} - -.render .content table td, -.render .content table th { - padding: 12px 8px; - border: 0px solid #ddd; - line-height: 20px; - vertical-align: middle -} - -.render .content table th { - font-weight: 500 -} - -.render .content table .image-package { - width: auto; - margin-left: 0 -} - -.render .content .image-package .image-container { - z-index: 100; - position: relative; - background-color: #eee; - transition: background-color .1s linear; - margin: 0 auto -} - -.render .content .image-package .image-container .image-container-fill { - z-index: 50 -} - -.render .content .image-package .image-container .image-view { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - overflow: hidden -} - -.render .content .image-package .image-container .image-view img.image-loading { - will-change: filter, opacity; - -webkit-filter: blur(4px); - filter: blur(4px); - opacity: .3 -} - -.render .content .image-package .image-container .image-view img { - transition: all .15s linear; - z-index: 100; - will-change: filter, opacity; - -webkit-filter: blur(0); - filter: blur(0); - opacity: 1 -} - - - -.render .content img { - max-width: 100%; - height: auto; - vertical-align: middle; - border: 0; - cursor: -webkit-zoom-in; - transition: all .25s ease-in-out -} - - - -.render .content .image-package .image-caption:empty { - display: none -} - -.render .content .video-package { - position: relative; - margin: -20px auto 20px; - text-align: center -} - -.render .content .video-package .video-placeholder-area { - position: relative; - display: inline-block; - height: 110px; - padding: 10px; - padding-left: 120px; - box-sizing: border-box; - border: 1px solid #d9d9d9; - background-color: hsla(0, 0%, 71%, .1); - text-align: left; - cursor: pointer -} - -.render .content .video-package .video-placeholder-area:after { - content: " "; - position: absolute; - top: -1px; - left: -1px; - display: block; - width: 110px; - height: 110px; - background-color: rgba(0, 0, 0, .3); - background-image: url(//cdn2.jianshu.io/assets/common/play-btn-c4bc06b9dfe063495b6b8277b14bc5c3.png); - background-position: 30px; - background-size: 50px; - background-repeat: no-repeat; - transition: all .1s linear -} - -.render .content .video-package .video-placeholder-area:hover:after { - background-color: transparent -} - -.render .content .video-package .video-placeholder-area .video-cover { - position: absolute; - top: -1px; - left: -1px; - display: block; - width: 110px; - height: 110px; - opacity: .8; - transition: opacity .1s linear -} - -.render .content .video-package .video-description { - min-width: 20%; - min-height: 22px; - display: none; - padding: 10px; - margin: 0 auto; - border-bottom: 1px solid #d9d9d9; - font-size: 13px; - color: #999; - line-height: 1.7 -} - -.render .content .video-package .video-description:empty { - display: none -} - -.render .content .video-package .video-close-button, -.render .content .video-package .video-provider-button { - text-align: left; - font-size: 14px; - padding: 0; - line-height: 14px; - cursor: pointer; - transition: opacity .1s linear -} - -.render .content .video-package .video-close-button i, -.render .content .video-package .video-provider-button i { - position: relative; - top: 1px -} - -.render .content .video-package .video-provider-button { - float: right -} - -.render .content .hljs { - background-color: transparent; -} - -.render .content .hljs-center { - text-align: center -} - -.render .content .hljs-right { - text-align: right -} - -.render .content .hljs-left { - text-align: left -} - - - - - diff --git a/ui/src/theme/style.less b/ui/src/theme/style.less index 806877fa..e185a63e 100644 --- a/ui/src/theme/style.less +++ b/ui/src/theme/style.less @@ -1,67 +1,75 @@ -// overide global focus style -:focus { - outline-color: transparent; - outline-width: 0; - outline-style: none; -} -input::-webkit-input-placeholder { - color: #aab2bd; - font-weight: bold; - font-size: 14px; -} +/* ----------------------------margin------------------------ */ +.margin-0-auto { margin: 0 auto} -::-webkit-scrollbar { - width: 2px !important; - height: 2px; - background-color: #e5e5e5 !important; -} -::-webkit-scrollbar-thumb { - background-color: #b7b7b7 !important; - border-radius: 3px !important; -} -::-webkit-scrollbar-track { - border-radius: 3px; - box-shadow: 0 0 0px #808080 inset !important; -} +.margin-left-5 {margin-left: 5px};.margin-left-10 {margin-left: 10px};.margin-left-15 {margin-left: 15px};.margin-left-20 {margin-left: 20px};.margin-left-30 {margin-left: 30px};.margin-left-40 {margin-left: 40px};.margin-left-50 {margin-left: 50px};.margin-left-60 {margin-left: 60px}; +.margin-left--20 {margin-left: -20px}; -.no-border { - input { - border: none; - } - button { - border:none; - } -} +.margin-right-5 {margin-right: 5px};.margin-right-10 {margin-right: 10px};.margin-right-15 {margin-right: 15px};.margin-right-20 {margin-right: 20px};.margin-right-30 {margin-right: 30px};.margin-right-40 {margin-right: 40px}; + +.margin-top-2 {margin-top: 2px};.margin-top-5 {margin-top: 5px};.margin-top-8 {margin-top: 8px};.margin-top-10 {margin-top: 10px};.margin-top-15 {margin-top: 15px};.margin-top-20 {margin-top: 20px};.margin-top-25 {margin-top: 25px};.margin-top-30 {margin-top: 30px};.margin-top-40 {margin-top: 40px};.margin-top-60 {margin-top: 60px};.margin-top-80 {margin-top: 80px};.margin-top-100 {margin-top: 100px}; +.margin-top--35 {margin-top: -35px};.margin-top--40 {margin-top: -40px};.margin-top--50 {margin-top: -50px}; + +.margin-bottom-5 {margin-bottom: 5px};.margin-bottom-10 {margin-bottom: 10px};.margin-bottom-15 {margin-bottom: 15px};.margin-bottom-20 { margin-bottom: 20px}.margin-bottom-30 {margin-bottom: 30px };.margin-bottom-40 {margin-bottom: 40px };.margin-bottom-50 {margin-bottom: 50px }; + +/* ----------------------------padding------------------------ */ +.padding-5 {padding: 5px 5px};.padding-10 { padding: 10px 10px};.padding-20 {padding: 20px 20px}; + +.padding-left-5 {padding-left: 5px};.padding-left-10 {padding-left: 10px};.padding-left-15 {padding-left: 15px};.padding-left-20 {padding-left: 20px}; + +.padding-right-5 {padding-right: 5px}.padding-right-10 {padding-right: 10px};.padding-right-15 {padding-right: 15px};.padding-right-20 {padding-right: 20px}; + +.padding-top-5 {padding-top: 5px};.padding-top-10 {padding-top: 10px};.padding-top-15 {padding-top: 15px};.padding-top-20 {padding-top: 20px};.padding-top-40 {padding-top: 40px};.padding-top-60 {padding-top: 60px}; + +.padding-bottom-5 { padding-bottom: 5px};.padding-bottom-10 {padding-bottom: 10px};.padding-bottom-15 {padding-bottom: 15px};.padding-bottom-20 {padding-bottom: 20px};.padding-bottom-30 {padding-bottom: 30px};.padding-bottom-40 {padding-bottom: 40px}; -.border-ellipse{ - border-radius: 100px; +/* ----------------------------height/width------------------------ */ +.height-45{height:45px};.height-50{height:50px};.height-100{height:100px};.height-150{height:150px};.height-200{height:200px}; + +.width-50{width:50px};.width-100{width:100px};.width-150{width:150px};.width-200{width:200px};.width-300{width:300px}; + +.width-100p {width: 100%}; + +.max-width-300 {width: 300px} + +/* ----------------------------font------------------------ */ +.font-size-12 {font-size:12px;};.font-size-13 {font-size:13px;};.font-size-14 {font-size:14px;};.font-size-15 {font-size:15px;};.font-size-16 {font-size:16px;};.font-size-18 {font-size:18px;};.font-size-20 {font-size:20px;};.font-size-22 {font-size:22px;};.font-size-24 {font-size:24px;}; + +.font-weight-500 {font-weight: 500;};.font-weight-bold {font-weight: bold;} + +/* ----------------------------position------------------------ */ +.position-fixed {position: fixed};.position-absolute {position: absolute};.position-relative{position: relative}; + +/* ----------------------------z-index------------------------ */ +.z-index-1 {z-index: 1};.z-index-100 {z-index: 100};.z-index-1000 {z-index: 1000}; + +/* ----------------------------border------------------------ */ +.border-none {border:noen};.border-top-none{border-top: none};.border-bottom-none{border-bottom: none};.border-left-none{border-left: none};.border-right-none{border-right: none}; + +.border-radius-3{border-radius: 3px};.border-radius-100{border-radius: 100px}; + +// 鼠标悬浮 +.cursor-pointer:hover { + cursor: pointer } -.background-grey { - background-color: #f5f6f7 +.float-right { + float: right } -.image-modal { - .el-dialog { - box-shadow: none !important; - position: absolute; - top: 45%; - left: 50%; - transform: translate(-50%, -50%); - cursor: zoom-out; - } - .el-dialog__header { - display: none; - } - .el-dialog__body { - padding: 0; - } +.text-align-center { + text-align:center +} +.vertical-align-middle { + vertical-align: middle; +} +.display-inline-block { + display: inline-block; } -.background-light-grey { - background-color: #fafafa!important +.overflow-y-auto{ + overflow-y: auto } -.el-message.el-message--error.network-error { - top: 20px !important; +.text-decoration-none { + text-decoration: none; } \ No newline at end of file diff --git a/ui/src/views/article/detail.vue b/ui/src/views/article/detail.vue index 6d406926..63eae382 100644 --- a/ui/src/views/article/detail.vue +++ b/ui/src/views/article/detail.vue @@ -1,14 +1,14 @@