pull/35/head
sunface 6 years ago
parent 90d1f4bb2e
commit 636082faab

1
.gitignore vendored

@ -0,0 +1 @@
im.dev

@ -3,29 +3,33 @@ package internal
import ( import (
"github.com/labstack/echo" "github.com/labstack/echo"
"github.com/thinkindev/im.dev/internal/post" "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) { func apiHandler(e *echo.Echo) {
// sign-in apis // sign-in apis
e.POST("/web/signIn", session.SignIn) e.POST("/web/signIn", user.SignIn)
e.POST("/web/signOut", session.SignOut) e.POST("/web/signOut", user.SignOut)
e.GET("/web/user/get", session.GetUser) e.GET("/web/user/card", user.Card)
// article apis // article apis
e.POST("/web/article/saveNew", post.NewArticle, session.CheckSignIn) e.POST("/web/article/saveNew", post.NewArticle, user.CheckSignIn)
e.POST("/web/post/preview", post.Preview, session.CheckSignIn) e.POST("/web/post/preview", post.Preview, user.CheckSignIn)
e.GET("/web/article/detail", post.GetArticleDetail) e.GET("/web/article/detail", post.GetArticleDetail)
e.GET("/web/article/beforeEdit", post.BeforeEditAr, session.CheckSignIn) e.GET("/web/article/beforeEdit", post.BeforeEditAr, user.CheckSignIn)
e.POST("/web/article/saveChanges", post.SaveArticleChanges, session.CheckSignIn) e.POST("/web/article/saveChanges", post.SaveArticleChanges, user.CheckSignIn)
// comment apis // comment apis
e.POST("/web/comment/create", post.Comment, session.CheckSignIn) e.POST("/web/comment/create", post.Comment, user.CheckSignIn)
e.POST("/web/comment/reply", post.CommentReply, session.CheckSignIn) e.POST("/web/comment/reply", post.CommentReply, user.CheckSignIn)
e.POST("/web/comment/edit", post.EditComment, session.CheckSignIn) e.POST("/web/comment/edit", post.EditComment, user.CheckSignIn)
e.POST("/web/comment/delete", post.DeleteComment, session.CheckSignIn) e.POST("/web/comment/delete", post.DeleteComment, user.CheckSignIn)
e.POST("/web/comment/revert", post.RevertComment, session.CheckSignIn) e.POST("/web/comment/revert", post.RevertComment, user.CheckSignIn)
e.GET("/web/comment/query", post.QueryComments) e.GET("/web/comment/query", post.QueryComments)
e.POST("/web/comment/like", post.CommentLike, session.CheckSignIn) e.POST("/web/comment/like", post.CommentLike, user.CheckSignIn)
e.POST("/web/comment/dislike", post.CommentDislike, session.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)
} }

@ -13,6 +13,9 @@ const (
NoPermission = 1004 NoPermission = 1004
NoPermissionMsg = "You don't have permission" NoPermissionMsg = "You don't have permission"
NotFound = 1005
NotFoundMsg = "404 not found"
) )
// article // article
@ -29,3 +32,6 @@ const (
CommentLiked = 1200 CommentLiked = 1200
CommentLikedMsg = "You have agreed this comment before" CommentLikedMsg = "You have agreed this comment before"
) )
// user
const ()

@ -9,7 +9,7 @@ import (
"github.com/labstack/echo" "github.com/labstack/echo"
"github.com/labstack/echo/middleware" "github.com/labstack/echo/middleware"
"github.com/thinkindev/im.dev/internal/misc" "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 // Start web server for im.dev ui
@ -29,7 +29,7 @@ func Start(confPath string) {
} }
} }
session.InitUser() user.InitUser()
e := echo.New() e := echo.New()
e.Pre(middleware.RemoveTrailingSlash()) e.Pre(middleware.RemoveTrailingSlash())

@ -3,6 +3,7 @@ package misc
import ( import (
"encoding/base64" "encoding/base64"
"github.com/microcosm-cc/bluemonday"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -14,3 +15,23 @@ var Log *zap.Logger
// Base64 is the base64 handler // Base64 is the base64 handler
var Base64 = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") 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
}

@ -8,7 +8,7 @@ import (
"github.com/labstack/echo" "github.com/labstack/echo"
"github.com/thinkindev/im.dev/internal/ecode" "github.com/thinkindev/im.dev/internal/ecode"
"github.com/thinkindev/im.dev/internal/misc" "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" "github.com/thinkindev/im.dev/internal/utils"
"go.uber.org/zap" "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 // generate id for article
ar.ID = misc.GenID() 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 // check whether user has permission to do so
if uid != sess.ID { if uid != sess.ID {
@ -204,7 +204,7 @@ func SaveArticleChanges(c echo.Context) error {
Message: ecode.CommonErrorMsg, Message: ecode.CommonErrorMsg,
}) })
} }
sess := session.Get(c) sess := user.GetSession(c)
if sess.ID != uid { if sess.ID != uid {
return c.JSON(http.StatusInternalServerError, misc.HTTPResp{ return c.JSON(http.StatusInternalServerError, misc.HTTPResp{
ErrCode: ecode.NoPermission, ErrCode: ecode.NoPermission,

@ -13,7 +13,7 @@ import (
"github.com/labstack/echo" "github.com/labstack/echo"
"github.com/thinkindev/im.dev/internal/ecode" "github.com/thinkindev/im.dev/internal/ecode"
"github.com/thinkindev/im.dev/internal/misc" "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" "github.com/thinkindev/im.dev/internal/utils"
"go.uber.org/zap" "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 cc.UID = sess.ID
// generate id for article // 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 cc.UID = sess.ID
// generate id for article // generate id for article
@ -224,7 +224,7 @@ func EditComment(c echo.Context) error {
}) })
} }
sess := session.Get(c) sess := user.GetSession(c)
// check permission // check permission
q := misc.CQL.Query(`SELECT uid FROM comment WHERE id=?`, id) 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 // check comment exists and this user has permission
var uid string var uid string
q := misc.CQL.Query(`SELECT uid FROM comment WHERE id=?`, id) 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 // check comment exists and this user has permission
var uid, md, render string var uid, md, render string
@ -361,7 +361,7 @@ func RevertComment(c echo.Context) error {
comment := &CommentContent{} comment := &CommentContent{}
comment.MD = md comment.MD = md
comment.Render = render comment.Render = render
u := session.GetUserByID(uid) u := user.GetUserByID(uid)
if u == nil { if u == nil {
comment.UName = "[404]" comment.UName = "[404]"
comment.UNickname = "[404]" comment.UNickname = "[404]"
@ -409,7 +409,7 @@ func QueryComments(c echo.Context) error {
editDate: edate, editDate: edate,
Status: status, Status: status,
} }
u := session.GetUserByID(comment.UID) u := user.GetUserByID(comment.UID)
if u == nil { if u == nil {
continue continue
} }
@ -470,7 +470,7 @@ func QueryComments(c echo.Context) error {
b.WriteString(`SELECT id,likes FROM comment_counter WHERE id in (`) b.WriteString(`SELECT id,likes FROM comment_counter WHERE id in (`)
var b1 strings.Builder var b1 strings.Builder
sess := session.Get(c) sess := user.GetSession(c)
if sess != nil { if sess != nil {
b1.WriteString(`SELECT comment_id,type FROM comment_like WHERE uid=? and comment_id in (`) 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 // check whether you already liked this comment
status, err := commentLikeStatus(postID, sess.ID) 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 // check whether you already liked this comment
status, err := commentLikeStatus(postID, sess.ID) status, err := commentLikeStatus(postID, sess.ID)

@ -5,9 +5,8 @@ import (
"net/http" "net/http"
"github.com/labstack/echo" "github.com/labstack/echo"
"github.com/microcosm-cc/bluemonday"
"github.com/thinkindev/im.dev/internal/misc" "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" "github.com/thinkindev/im.dev/internal/utils"
) )
@ -27,19 +26,8 @@ func Preview(c echo.Context) error {
// @user -> <a href="UserPage">@user</a> // @user -> <a href="UserPage">@user</a>
// remove js,iframe such html tags and attributes // remove js,iframe such html tags and attributes
func modify(s string) string { 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 // 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)) afterRender := make([]rune, 0, len(render))
idParseFlag := false idParseFlag := false
tempName := make([]rune, 0) tempName := make([]rune, 0)
@ -57,7 +45,7 @@ func modify(s string) string {
idParseFlag = false idParseFlag = false
// check name exist // check name exist
if session.CheckUserExist(string(tempName)) { if user.CheckUserExist(string(tempName)) {
// converse @name -> <a href="UserPage">@user</a> // converse @name -> <a href="UserPage">@user</a>
afterRender = append(afterRender, []rune(fmt.Sprintf("<a href='http://localhost:9532/%s'>%s</a>", string(tempName), string(tempName)))...) afterRender = append(afterRender, []rune(fmt.Sprintf("<a href='http://localhost:9532/%s'>%s</a>", string(tempName), string(tempName)))...)
} else { } else {

@ -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
}

@ -1,4 +1,4 @@
package session package user
import ( import (
"net/http" "net/http"
@ -55,8 +55,8 @@ func CheckSignIn(f echo.HandlerFunc) echo.HandlerFunc {
} }
} }
// Get return the session for given user // GetSession return the session for given user
func Get(c echo.Context) *Session { func GetSession(c echo.Context) *Session {
token := c.Request().Header.Get("token") token := c.Request().Header.Get("token")
s, ok := sessions.Load(token) s, ok := sessions.Load(token)
if !ok { if !ok {

@ -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
}

@ -1,6 +1,11 @@
package utils package utils
import "unsafe" import (
"strings"
"unsafe"
"github.com/labstack/echo"
)
// Bytes2String see below // Bytes2String see below
func Bytes2String(b []byte) (s string) { func Bytes2String(b []byte) (s string) {
@ -33,3 +38,8 @@ func ValidNameRune(c rune) bool {
} }
return false return false
} }
// FormValue get value from http form
func FormValue(c echo.Context, k string) string {
return strings.TrimSpace(c.FormValue(k))
}

@ -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)
}

@ -4,10 +4,26 @@ CREATE KEYSPACE im_dev WITH replication = {'class': 'SimpleStrategy', 'replicati
USE im_dev; USE im_dev;
CREATE TABLE IF NOT EXISTS user ( CREATE TABLE IF NOT EXISTS user (
--account
id text, id text,
name text, name text,
email text,
--userprofile
nickname text, nickname text,
avatar 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, create_date bigint,
edit_date bigint, edit_date bigint,
PRIMARY KEY (id) PRIMARY KEY (id)
@ -16,12 +32,6 @@ CREATE CUSTOM INDEX IF NOT EXISTS ON user (name)
USING 'org.apache.cassandra.index.sasi.SASIIndex' ; 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 ( CREATE TABLE IF NOT EXISTS article (
id text, id text,
uid text, -- author user id uid text, -- author user id

@ -8,7 +8,6 @@ import 'element-ui/lib/theme-chalk/index.css';
import mavonEditor from 'mavon-editor' import mavonEditor from 'mavon-editor'
import 'mavon-editor/dist/css/index.css' import 'mavon-editor/dist/css/index.css'
// import 'mavon-editor/dist/markdown/github-markdown.min.css' // import 'mavon-editor/dist/markdown/github-markdown.min.css'
import "./theme/editor.css";
Vue.use(mavonEditor) Vue.use(mavonEditor)
@ -20,8 +19,7 @@ Vue.use(ElementUI);
import router from './router' import router from './router'
// 全局范围加载通用样式每个vue page里无需重复引入 // 全局范围加载通用样式每个vue page里无需重复引入
import '!style-loader!css-loader!less-loader!./theme/common_layout.less' import '!style-loader!css-loader!less-loader!./theme/eleui-style.less'
import '!style-loader!css-loader!less-loader!./theme/layout.less'
import '!style-loader!css-loader!less-loader!./theme/style.less' import '!style-loader!css-loader!less-loader!./theme/style.less'
Vue.config.productionTip = false Vue.config.productionTip = false

@ -15,6 +15,16 @@ const router = new Router({
children: [ children: [
{ path: '/', meta: {'title':'im.dev'}, component: () => import('@/views/home')}, { 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/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', meta: {'title':'Article - im.dev',},component: () => import('@/views/article/detail')},
{ path: '/:uname/:arID/edit', meta: {'title':'Post - im.dev'},component: () => import('@/views/article/edit')}, { path: '/:uname/:arID/edit', meta: {'title':'Post - im.dev'},component: () => import('@/views/article/edit')},
] ]

@ -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;
}

@ -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;
}

@ -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;
}

@ -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);
}

@ -1,20 +1,642 @@
.el-dialog__wrapper.white-bg-modal { @import './var.less';
background-color: rgba(255,255,255,.95); .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;
}
.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;
.post-huge-title { // box-shadow: rgba(0, 0, 0, 0.0470588) 0px 4px 12px 0px;
font-size: 36px; 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 { .article-detail {
font-size:12px; .discuss {
color:rgb(124,124,124); 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 { .article-edit {
color:rgb(135, 138, 140); .render {
font-weight:700; border:1px solid #eee;border-bottom:none;border-right:none;
font-size:12px; }
.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
}
}
}

@ -1,8 +1,5 @@
@text-default-color: rgba(0, 0, 0, .84); @grey-color: rgb(124,124,124);
@text-second-color: rgba(0, 0, 0, .54); @light-grey-color: rgb(135, 138, 140);
@nav-color: rgba(0,0,0,.70);
@text-enter-more-color: rgb(150, 150, 150); @main-color: #303133;
@text-annotation-color: rgb(180, 180, 180); @regular-color: #606266;
@topic-text-color: white;
@topic-annotation-color: rgba(255, 255, 255, .7);
@menu-item-active-color: #f0f0f0;

@ -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
}

@ -1,67 +1,75 @@
// overide global focus style /* ----------------------------margin------------------------ */
:focus { .margin-0-auto { margin: 0 auto}
outline-color: transparent;
outline-width: 0;
outline-style: none;
}
input::-webkit-input-placeholder {
color: #aab2bd;
font-weight: bold;
font-size: 14px;
}
::-webkit-scrollbar { .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};
width: 2px !important; .margin-left--20 {margin-left: -20px};
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 { .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};
input {
border: none; .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};
button {
border:none; .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{ /* ----------------------------height/width------------------------ */
border-radius: 100px; .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 { .float-right {
background-color: #f5f6f7 float: right
} }
.image-modal { .text-align-center {
.el-dialog { text-align:center
box-shadow: none !important; }
position: absolute; .vertical-align-middle {
top: 45%; vertical-align: middle;
left: 50%; }
transform: translate(-50%, -50%); .display-inline-block {
cursor: zoom-out; display: inline-block;
}
.el-dialog__header {
display: none;
}
.el-dialog__body {
padding: 0;
}
} }
.background-light-grey { .overflow-y-auto{
background-color: #fafafa!important overflow-y: auto
} }
.el-message.el-message--error.network-error { .text-decoration-none {
top: 20px !important; text-decoration: none;
} }

@ -1,14 +1,14 @@
<template> <template>
<div class="article-detail"> <div class="article-detail">
<el-row> <el-row :gutter="20">
<el-col <el-col
:xs="{span:1,offset:0}" :xs="{span:1,offset:0}"
:sm="{span:1,offset:0}" :sm="{span:1,offset:0}"
:md="{span: 1,offset:3}" :md="{span: 1,offset:2}"
:lg="{ span: 1, offset: 3 }" :lg="{ span: 1, offset: 2 }"
> >
<div class="squares"> <div class="squares position-fixed z-index-100 margin-top-100">
<div class="square font-hover-primary" style="margin-top:2px;"> <div class="square font-hover-primary">
<button id="clap" :class="{clap:true, liked: arliked}" @click="arLike"> <button id="clap" :class="{clap:true, liked: arliked}" @click="arLike">
<span> <span>
<svg <svg
@ -26,23 +26,23 @@
</span> </span>
</button> </button>
</div> </div>
<div v-show="arDetail.uid==this.$store.state.user.id" class="square hover-cursor margin-top-20"> <div v-show="arDetail.uid==this.$store.state.user.id" class="square cursor-pointer margin-top-20">
<a> <a>
<el-tooltip content="编辑文章" placement="right"> <el-tooltip content="编辑文章" placement="right">
<router-link :to="'/'+this.authorInfo.name + '/' + this.arID + '/edit'"><i class="el-icon-edit hover-cursor"></i></router-link> <router-link :to="'/'+this.authorInfo.name + '/' + this.arID + '/edit'"><i class="el-icon-edit cursor-pointer"></i></router-link>
</el-tooltip> </el-tooltip>
</a> </a>
</div> </div>
<div class="square font-hover-primary" style="margin-top:10px;"> <div class="square font-hover-primary padding-top-10">
<el-tooltip content="加入书签" placement="right"> <el-tooltip content="加入书签" placement="right">
<i class="el-icon-star-off hover-cursor"></i> <i class="el-icon-star-off cursor-pointer"></i>
</el-tooltip> </el-tooltip>
</div> </div>
<div class="square font-hover-primary" style="margin-top:10px;"> <div class="square font-hover-primary padding-top-10" >
<el-tooltip content="Discuss" placement="right"> <el-tooltip content="Discuss" placement="right">
<a href="#discuss"><i class="el-icon-s-comment hover-cursor"></i></a> <a href="#discuss"><i class="el-icon-s-comment cursor-pointer"></i></a>
</el-tooltip> </el-tooltip>
</div> </div>
</div> </div>
@ -50,20 +50,23 @@
<el-col <el-col
:xs="{span:22,offset:2}" :xs="{span:22,offset:2}"
:sm="{span:17,offset:3}" :sm="{span:17,offset:3}"
:md="{span: 13,offset:6}" :md="{span: 13,offset:5}"
:lg="{ span: 13, offset: 6 }" :lg="{ span: 13, offset: 5 }"
> >
<div class="post-huge-title margin-top-30">{{arDetail.title}}</div> <h1 class=" margin-top-30">{{arDetail.title}}</h1>
<render :content="arDetail.render"></render> <render :content="arDetail.render" style="min-height:400px"></render>
</el-col>
<el-col :span="1" >
<UserCard class="user-card z-index-100 position-fixed margin-top-40 max-width-300" :user="authorInfo"></UserCard>
</el-col> </el-col>
</el-row> </el-row>
<el-row class="background-light-grey"> <el-row class="discuss margin-top-40">
<el-col <el-col
:xs="{span:22,offset:2}" :xs="{span:22,offset:2}"
:sm="{span:17,offset:3}" :sm="{span:17,offset:3}"
:md="{span: 13,offset:6}" :md="{span: 13,offset:5}"
:lg="{ span: 13, offset: 6 }" :lg="{ span: 13, offset: 5 }"
> >
<discuss id="discuss" :postID="this.arID" postType="1" :postAuthorID="authorInfo.id"></discuss> <discuss id="discuss" :postID="this.arID" postType="1" :postAuthorID="authorInfo.id"></discuss>
@ -76,8 +79,10 @@
import request from "@/utils/request"; import request from "@/utils/request";
import render from "../components/render"; import render from "../components/render";
import discuss from "../components/discuss"; import discuss from "../components/discuss";
import UserCard from "../components/user-card";
export default { export default {
components: { render,discuss}, components: { render,discuss,UserCard},
data() { data() {
return { return {
arID: "", // unique article id arID: "", // unique article id
@ -112,7 +117,7 @@ export default {
} }
}).then(res0 => { }).then(res0 => {
request({ request({
url: "/web/user/get", url: "/web/user/card",
method: "GET", method: "GET",
params: { params: {
uid: res0.data.data.uid uid: res0.data.data.uid
@ -123,97 +128,7 @@ export default {
}); });
}); });
}, },
mounted() {}, mounted() {
},
}; };
</script> </script>
<style lang="less" scoped>
.squares {
margin-top: 8.8rem;
position: fixed;
z-index:1;
.square {
margin-top: 3px;
line-height: 36px;
text-align: center;
z-index: 100;
font-size: 25px;
a {
color: rgba(0,0,0,.8)
}
}
.like-out {
padding: 14px 14px 10px 14px;
border: 1px solid rgb(180, 180, 180);
border-radius: 50%;
}
.like-out:hover,
.like-out.active {
border: 1px solid #bbb;
}
.like-count {
text-align: center;
}
}
.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
}
}
</style>

@ -1,10 +1,10 @@
<template> <template>
<div class="home markdown"> <div class="home markdown article-edit">
<el-row> <el-row>
<el-col :span="24" :offset="0"> <el-col :span="24" :offset="0">
<div class="tags no-border"> <div class="tags no-border">
<el-input size="small" v-model="tempArticle.title" class="inline-input" placeholder="Title" style="width:200px" @blur="setTitle"></el-input> <el-input size="small" v-model="tempArticle.title" class="inline-input width-200" placeholder="Title" @blur="setTitle"></el-input>
<el-select class="margin-left-10" size="small" v-model="tempArticle.tags" multiple filterable remote placeholder="Tags" @change="setTags" :remote-method="queryTags" :loading="tagsLoading" allow-create default-first-option style="width:300px"> <el-select class="margin-left-10 width-300" size="small" v-model="tempArticle.tags" multiple filterable remote placeholder="Tags" @change="setTags" :remote-method="queryTags" :loading="tagsLoading" allow-create default-first-option>
<el-option v-for="item in tags" :key="item" :label="item" :value="item"></el-option> <el-option v-for="item in tags" :key="item" :label="item" :value="item"></el-option>
</el-select> </el-select>
@ -12,28 +12,28 @@
v-model="tempArticle.lang" v-model="tempArticle.lang"
placeholder="lang for article" placeholder="lang for article"
size="small" size="small"
style="width:150px" class="width-150"
> >
<el-option v-for="o in langOptions" :key="o.label" :label="o.label" :value="o.value"></el-option> <el-option v-for="o in langOptions" :key="o.label" :label="o.label" :value="o.value"></el-option>
</el-select> </el-select>
<span> <span>
<el-button size="medium" class="border-ellipse background-grey" @click="preview">PREVIEW</el-button> <el-button size="medium" type="info" class="border-radius-100" @click="preview">PREVIEW</el-button>
<el-button size="medium" class="border-ellipse background-grey" @click="saveNew(1)" v-show="tempArticle.status == 1 || mode=='new'">SAVE DRAFT</el-button> <el-button size="medium" type="info" class="border-radius-100" @click="saveNew(1)" v-show="tempArticle.status == 1 || mode=='new'">SAVE DRAFT</el-button>
<el-button size="medium" class="border-ellipse background-grey" @click="saveChanges" v-show="tempArticle.status == 2">SAVE CHANGES</el-button> <el-button size="medium" type="info" class="border-radius-100" @click="saveChanges" v-show="tempArticle.status == 2">SAVE CHANGES</el-button>
<el-button size="medium" type="primary" class="border-ellipse" @click="saveNew(2)" v-show="tempArticle.status != 2">PUBLISH</el-button> <el-button size="medium" type="primary" class="border-radius-100" @click="saveNew(2)" v-show="tempArticle.status != 2">PUBLISH</el-button>
</span> </span>
<span class="float-right margin-top-5 margin-right-10" style="font-size:18px"> <span class="float-right margin-top-5 margin-right-10 font-size-18">
<el-tooltip content="Revert to the previous save" class="margin-right-15" v-show="mode=='edit'"><i class="el-icon-back hover-cursor" @click="clearChanges" ></i></el-tooltip> <el-tooltip content="Revert to the previous save" class="margin-right-15" v-show="mode=='edit'"><i class="el-icon-back cursor-pointer" @click="clearChanges" ></i></el-tooltip>
<el-tooltip content="Delete this post" v-show="mode=='edit'"><i class="el-icon-delete hover-cursor"></i></el-tooltip> <el-tooltip content="Delete this post" v-show="mode=='edit'"><i class="el-icon-delete cursor-pointer"></i></el-tooltip>
</span> </span>
</div> </div>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<editor :editorHeight="editorHeight" class="margin-top-5" parent="article" :md="tempArticle.md" @articleSetMD="articleSetMD"></editor> <editor :editorHeight="editorHeight" class="margin-top-5" parent="article" :md="tempArticle.md" @articleSetMD="articleSetMD"></editor>
</el-col> </el-col>
<el-col :span="12" v-if="previewReset" class="margin-top-5" style="border:1px solid #eee;border-bottom:none;border-right:none;"> <el-col :span="12" v-if="previewReset" class="margin-top-5 render">
<render id ="render-content" :content="tempArticle.render" :style="{'height':editorHeight,'overflow-y':'scroll'}" style="padding:10px"></render> <render id ="render-content" :content="tempArticle.render" :style="{'height':editorHeight}" class="padding-10 overflow-y-auto"></render>
</el-col> </el-col>
</el-row> </el-row>
@ -219,12 +219,3 @@ export default {
}, },
} }
</script> </script>
<style lang="less">
.tags {
.el-input.el-input--suffix input{
background-color: #f5f6f7;
border-radius: 100px
}
}
</style>

@ -1,26 +1,26 @@
<template> <template>
<div class="discuss" style="padding:20px"> <div class="discuss padding-20" >
<!-- comment editor --> <!-- comment editor -->
<div class="write-comment" style="border-bottom:1px solid #eee"> <div class="write-comment">
<editor placeholder="Add to the discussion" editorHeight="200px" parent="discuss" :md="tempComment.md" @discussSetMD="discussSetMD" v-if="!commentPreviewd"></editor> <editor placeholder="Add to the discussion" editorHeight="200px" parent="discuss" :md="tempComment.md" @discussSetMD="discussSetMD" v-if="!commentPreviewd"></editor>
<render paddingTop="46px" paddingLeft="20px" :content="tempComment.render" style="height:200px;overflow-y:auto;background:white;" v-else></render> <render paddingTop="46px" paddingLeft="20px" :content="tempComment.render" class="height-200" v-else></render>
<span style="position:relative;float:right;margin-top:-190px;z-index:1000;margin-right:20px"> <span class="position-relative float-right z-index-1000 margin-right-20 header-buttons">
<span class="bold-meta-word hover-cursor" @click="previewComment" v-if="!commentPreviewd">PREVIEW</span> <span class="bold-meta-word font-size-12 cursor-pointer" @click="previewComment" v-if="!commentPreviewd">PREVIEW</span>
<span class="bold-meta-word hover-cursor" @click="commentPreviewd=false" v-else>MARKDOWN</span> <span class="bold-meta-word font-size-12 cursor-pointer" @click="commentPreviewd=false" v-else>MARKDOWN</span>
<span class="bold-meta-word hover-cursor margin-left-10" @click="publishComment">PUBLISH</span> <span class="bold-meta-word font-size-12 cursor-pointer margin-left-10" @click="publishComment">PUBLISH</span>
</span> </span>
</div> </div>
<div class="sorter">SORT BY <span>BEST</span></div> <div class="sorter font-weight-bold font-size-12 margin-top-30 padding-bottom-5">SORT BY <span>BEST</span></div>
<div class="comments" v-if="comments.length>0" style="padding-bottom:30px;"> <div class="comments" v-if="comments.length>0">
<div class="comment margin-top-30" v-for="c in comments" :key="c.id" :style="{'margin-left':c.depth * 23 + 'px'}"> <div class="comment margin-top-30" v-for="c in comments" :key="c.id" :style="{'margin-left':c.depth * 23 + 'px'}">
<i class="iconfont icon-jiantou_shang hover-cursor upvote" :class="{'vote-highlighted':c.liked==1}" @click="upvoteComment(c)"></i> <i class="iconfont icon-jiantou_shang cursor-pointer upvote" :class="{'vote-highlighted':c.liked==1}" @click="upvoteComment(c)"></i>
<i class="iconfont icon-jiantou_xia hover-cursor downvote" :class="{'vote-highlighted':c.liked==2}" @click="downvoteComment(c)"></i> <i class="iconfont icon-jiantou_xia cursor-pointer downvote" :class="{'vote-highlighted':c.liked==2}" @click="downvoteComment(c)"></i>
<div class="header"> <div class="header font-size-12">
<router-link class="uname" :to="`/${c.uname}`" v-if="c.status==0">{{c.unickname}}</router-link> <router-link class="uname text-decoration-none" :to="`/${c.uname}`" v-if="c.status==0">{{c.unickname}}</router-link>
<span v-else>[deleted]</span> <span v-else>[deleted]</span>
<span class="margin-left-5 date-agree">{{c.likes}} agreed &nbsp;·&nbsp; {{c.date}} &nbsp; <i v-if="c.edit_date!=undefined">·&nbsp;edited {{c.edit_date}}</i></span> <span class="margin-left-5">{{c.likes}} agreed &nbsp;·&nbsp; {{c.date}} &nbsp; <i v-if="c.edit_date!=undefined">·&nbsp;edited {{c.edit_date}}</i></span>
</div> </div>
<!-- edit reply editor --> <!-- edit reply editor -->
@ -28,14 +28,14 @@
<editor editorHeight="150px" parent="discuss" :toolbarsShow="false" :md="tempEditReply.md" @discussSetMD="editReplySetMD"></editor> <editor editorHeight="150px" parent="discuss" :toolbarsShow="false" :md="tempEditReply.md" @discussSetMD="editReplySetMD"></editor>
</div> </div>
<!-- body and footer, hide when editing --> <!-- body and footer, hide when editing -->
<render :content="c.render" class="body" v-else></render> <render :content="c.render" class="body font-size-14 margin-top-8" v-else></render>
<div class="footer"> <div class="footer font-weight-bold font-size-12 margin-top-10">
<span v-if="currentEditCommentID != c.id"> <span v-if="currentEditCommentID != c.id">
<span class="hover-cursor" @click="reply(c.id)" v-if="c.status!=1">Reply</span> <span class="cursor-pointer" @click="reply(c.id)" v-if="c.status!=1">Reply</span>
<span class="hover-cursor margin-left-5 margin-right-10" v-if="$store.state.user.id==c.uid && c.status!=1" @click="editReply(c)">Edit</span> <span class="cursor-pointer margin-left-5 margin-right-10" v-if="$store.state.user.id==c.uid && c.status!=1" @click="editReply(c)">Edit</span>
<el-dropdown placement="bottom-start" v-if="$store.state.user.id==c.uid"> <el-dropdown placement="bottom-start" v-if="$store.state.user.id==c.uid">
<span class="el-dropdown-link"> <span class="el-dropdown-link">
<i class="el-icon-more hover-cursor"></i> <i class="el-icon-more cursor-pointer"></i>
</span> </span>
<el-dropdown-menu slot="dropdown"> <el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="deleteComment(c.id)" v-show="c.status!=1"><i class="el-icon-delete" ></i>Delete comment</el-dropdown-item> <el-dropdown-item @click.native="deleteComment(c.id)" v-show="c.status!=1"><i class="el-icon-delete" ></i>Delete comment</el-dropdown-item>
@ -45,28 +45,28 @@
<span v-if="currentCommentID == c.id" class="float-right"> <span v-if="currentCommentID == c.id" class="float-right">
<span class="hover-cursor" @click="previewReply" v-if="!replyPreviewd">Preview</span> <span class="cursor-pointer" @click="previewReply" v-if="!replyPreviewd">Preview</span>
<span class="hover-cursor" @click="replyPreviewd=false" v-else>Markdown</span> <span class="cursor-pointer" @click="replyPreviewd=false" v-else>Markdown</span>
<span class=" hover-cursor margin-left-5" @click="publishReply(c.id)">Publish</span> <span class=" cursor-pointer margin-left-5" @click="publishReply(c.id)">Publish</span>
</span> </span>
</span> </span>
<span v-else class="float-right"> <span v-else class="float-right">
<span class=" hover-cursor margin-left-5" @click="cancelEditReply">Cancel</span> <span class=" cursor-pointer margin-left-5" @click="cancelEditReply">Cancel</span>
<span class=" hover-cursor margin-left-5" @click="publishEditReply(c.id)">Publish</span> <span class=" cursor-pointer margin-left-5" @click="publishEditReply(c.id)">Publish</span>
</span> </span>
</div> </div>
<!-- reply editor --> <!-- reply editor -->
<div class="write-comment reply-comment margin-top-10" v-if="currentCommentID == c.id"> <div class="write-comment reply-comment margin-top-10" v-if="currentCommentID == c.id">
<editor placeholder="Add to the discussion" editorHeight="150px" parent="discuss" :toolbarsShow="false" :md="tempReply.md" @discussSetMD="replySetMD" v-if="!replyPreviewd"></editor> <editor placeholder="Add to the discussion" editorHeight="150px" parent="discuss" :toolbarsShow="false" :md="tempReply.md" @discussSetMD="replySetMD" v-if="!replyPreviewd"></editor>
<render :content="tempReply.render" style="height:150px;overflow-y:auto;background:white;" v-else></render> <render :content="tempReply.render" class="height-150" v-else></render>
</div> </div>
</div> </div>
</div> </div>
<div v-else style="text-align:center;padding-top:40px;padding-bottom:40px;"> <div v-else class="text-align-center padding-top-40 padding-bottom-40 no-comments">
<i class="iconfont icon-comments-alt" style="color:rgba(0, 121, 211, 0.4);font-size: 30px" /> <i class="iconfont icon-comments-alt"/>
<div class="meta-word margin-top-20" style="font-size:18px">No Comments Yet</div> <div class="meta-word font-size-18 margin-top-20" >No Comments Yet</div>
<div class="meta-word margin-top-15" style="font-size:15px">Be the first to share what you think!</div> <div class="meta-word font-size-16 margin-top-15">Be the first to share what you think!</div>
</div> </div>
</div> </div>
</template> </template>
@ -107,7 +107,7 @@ export default {
watch: { watch: {
"$store.state.user.id"() { "$store.state.user.id"() {
if (this.$store.state.user.id != '') { if (this.$store.state.user.id != '') {
this.initComments() this.init()
} }
}, },
}, },
@ -342,7 +342,7 @@ export default {
this.tempComment.md = md this.tempComment.md = md
this.tempCommentRender = render this.tempCommentRender = render
}, },
initComments() { init() {
request({ request({
url: "/web/comment/query", url: "/web/comment/query",
method: "GET", method: "GET",
@ -355,80 +355,7 @@ export default {
} }
}, },
mounted() { mounted() {
this.initComments() this.init()
} }
}; };
</script> </script>
<style lang="less">
.discuss {
.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
}
}
}
</style>
<style lang="less" scoped>
.sorter {
color:rgb(124,124,124);
font-weight:700;
font-size:12px;
margin-top:30px;
padding-bottom:5px
}
.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: rgb(135, 138, 140);
font-size:10px;
}
.downvote {
position:absolute;
margin-left:-21px;
margin-top:9px;
color:rgb(135, 138, 140);
font-size:10px;
}
.header {
font-size:12px;
color:rgb(124,124,124);
.uname {
color:rgb(0, 121, 211);
text-decoration:none;
}
}
.body {
font-size:14px;
margin-top:7px
}
.footer {
color:rgb(135, 138, 140);
font-weight:700;
font-size:12px;
margin-top:10px
}
}
}
</style>

@ -1,5 +1,5 @@
<template> <template>
<div class="editor"> <div class="component-editor">
<mavon-editor ref="areditor" :style="{height:editorHeight}" :language="$store.state.misc.lang" :value="md" :ishljs = "true" :toolbars="toolbars" :toolbarsFlag="toolbarsShow" :tabSize="2" @change="setMD" :subfield="false" @imgAdd="imgAdd" :placeholder="placeholder"></mavon-editor> <mavon-editor ref="areditor" :style="{height:editorHeight}" :language="$store.state.misc.lang" :value="md" :ishljs = "true" :toolbars="toolbars" :toolbarsFlag="toolbarsShow" :tabSize="2" @change="setMD" :subfield="false" @imgAdd="imgAdd" :placeholder="placeholder"></mavon-editor>
</div> </div>
</template> </template>
@ -49,14 +49,10 @@ export default {
} }
}, },
imgAdd: function(fn, _) { imgAdd: function(fn, _) {
this.$refs.areditor.$img2Url(fn, "http://test.ccd"); // this.$refs.areditor.$img2Url(fn, "http://test.ccd");
}, },
}, },
mounted() { mounted() {
} }
}; };
</script> </script>
<style lang="less">
@import "../../theme/md_render.css";
</style>

@ -1,13 +1,13 @@
<template> <template>
<div class="render"> <div class="component-render overflow-y-auto">
<div class="content markdown-body" id="render-content" v-html="content" @click="viewImg" :style="{'padding-top':paddingTop,'padding-left':paddingLeft}"></div> <div class="content markdown-body" id="render-content" v-html="content" @click="viewImg" :style="{'padding-top':paddingTop,'padding-left':paddingLeft}"></div>
<!-- <el-dialog class="white-bg-modal image-modal" :visible.sync="imageModalVisible" top="5vh" width="100%"> <el-dialog class="white-bg-modal image-modal" :visible.sync="imageModalVisible" top="5vh">
<el-row align="middle" justify="center" @click.native="cancelViewImage"> <el-row align="middle" justify="center" @click.native="cancelViewImage">
<el-col :xs="{span:24,offset:0}" :sm="{span:24,offset:0}" :md="{span: 24,offset:0}" :lg="{ span: 24, offset: 0 }"> <el-col :xs="{span:24,offset:0}" :sm="{span:24,offset:0}" :md="{span: 24,offset:0}" :lg="{ span: 24, offset: 0 }">
<img :src="currentImg" alt="" width="100%" /> <img :src="currentImg" alt="" class="width-100p"/>
</el-col> </el-col>
</el-row> </el-row>
</el-dialog> --> </el-dialog>
</div> </div>
</template> </template>
@ -37,7 +37,3 @@ export default {
} }
}; };
</script> </script>
<style lang="less">
@import "../../theme/md_render.css";
</style>

@ -0,0 +1,63 @@
<template>
<div class="user-card padding-10" :style="{'border': '1px solid ' + user.text_color,'box-shadow':'1px 1px 0px ' +user.text_color }">
<div>
<el-avatar
:src="user.avatar"
slot="reference"
class="display-inline-block vertical-align-middle cursor-pointer"
@click.native="userHome"
></el-avatar>
<router-link :to="'/' + user.name" class="position-absolute margin-left-10 font-weight-500 cursor-pointer text-decoration-none">{{user.nickname}}</router-link>
<router-link :to="'/' + user.name" class="position-absolute margin-top-25 margin-left-10 font-size-14 cursor-pointer text-decoration-none">@{{user.name}}</router-link >
</div>
<div class="margin-top-15 margin-left-10" v-html="user.about" v-if="user.about!='' && user.about!=undefined">
</div>
<div class="text-align-center padding-top-10 padding-bottom-10 font-weight-bold border-radius-3 cursor-pointer margin-top-10" :style="{'background-color':user.bg_color,'color':user.text_color}">FOLLOW</div>
<div class="margin-top-10 margin-left-10" v-if="user.website!='' && user.website!=undefined">
<div class="color-regular font-weight-500">website</div>
<div>{{user.website}}</div>
</div>
<div class="margin-top-10 margin-left-10" v-if="user.employer!='' && user.employer!=undefined">
<div class="color-regular font-weight-500">work</div>
<div v-html="user.employer"></div>
</div>
<div class="margin-top-10 margin-left-10" v-if="user.education!='' && user.education!=undefined">
<div class="color-regular font-weight-500">education</div>
<div v-html="user.education"></div>
</div>
<el-divider></el-divider>
<el-row class="margin-top-10 padding-left-10 padding-right-10">
<el-col :span="12">
<div class="color-regular font-weight-500">location</div>
<div><i class="el-icon-location" :style="{color:user.text_color}"></i><span class="font-size-14">{{user.location}}</span></div>
</el-col>
<el-col :span="12">
<div class="color-regular font-weight-500">joined</div>
<div><i class="el-icon-date margin-right-5" :style="{color:user.text_color}"></i><span class="font-size-14">{{user.create_date}}</span></div>
</el-col>
</el-row>
</div>
</template>
<script>
import request from "@/utils/request";
export default {
props: ['user'],
data() {
return {
};
},
watch: {
},
methods: {
userHome() {
this.$router.push('/' + this.user.name)
}
},
mounted() {
}
};
</script>

@ -1,106 +1,116 @@
<template> <template>
<div> <div class="global-nav">
<transition name="fade" > <transition name="fade">
<el-row v-show="toTop" :class="{'nav':'true','inTop':inTop,'toTop': toTop}" type="flex" align="middle"> <el-row
<el-col v-show="toTop"
:xs="{span:4}" :class="{'nav':'true','inTop':inTop,'toTop': toTop}"
:sm="{span:7,offset:1}" type="flex"
:md="{span: 2,offset:1}" align="middle"
:lg="{ span: 2, offset: 2 }"
> >
<router-link to="/"><img src="../assets/logo.png" class="hover-cursor" style="height:45px" /></router-link> <el-col
</el-col> :xs="{span:4}"
<el-col :sm="{span:7,offset:1}"
:xs="{span:10,offset:4}" :md="{span: 2,offset:1}"
:sm="{span:8}" :lg="{ span: 2, offset: 1 }"
:md="{span: 4}" >
:lg="{ span: 6,offset:2}" <router-link to="/">
class <img src="../assets/logo.png" class="cursor-pointer height-45" />
> </router-link>
<el-input </el-col>
v-model="searchContent" <el-col
prefix-icon="el-icon-search" :xs="{span:10,offset:4}"
:placeholder="$t('nav.navSearchHolder')" :sm="{span:8}"
size="medium" :md="{span: 4}"
></el-input> :lg="{ span: 7,offset:2}"
</el-col> class
>
<el-input
v-model="searchContent"
prefix-icon="el-icon-search"
:placeholder="$t('nav.navSearchHolder')"
size="medium"
></el-input>
</el-col>
<el-col :span="10"> <el-col :span="10">
<span class="float-right"> <span class="float-right">
<el-popover placement="bottom" trigger="hover" class="margin-right-20"> <el-popover placement="bottom" trigger="hover" class="margin-right-20">
<el-form label-width="100px"> <el-form label-width="110px">
<el-form-item :label="$t('nav.setLang')"> <el-form-item :label="$t('nav.setLang')">
<el-radio-group v-model="currentLang" size="medium" @change="changeLang"> <el-radio-group v-model="currentLang" size="medium" @change="changeLang">
<el-radio-button label="en">English</el-radio-button> <el-radio-button label="en">English</el-radio-button>
<el-radio-button label="zh">Chinese</el-radio-button> <el-radio-button label="zh">Chinese</el-radio-button>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item :label="$t('nav.setTheme')"> <el-form-item :label="$t('nav.setTheme')">
<el-select <el-select
v-model="currentTheme" v-model="currentTheme"
placeholder="theme.." placeholder="theme.."
size="medium" size="medium"
style="width:150px" class="width-150"
:popper-append-to-body="false" :popper-append-to-body="false"
@change="changeTheme" @change="changeTheme"
> >
<el-option label="Light" value="light"></el-option> <el-option label="Light" value="light"></el-option>
<el-option label="Dark" value="dark"></el-option> <el-option label="Dark" value="dark"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item :label="$t('nav.readingLang')"> <el-form-item :label="$t('nav.readingLang')">
<el-select <el-select
v-model="currentReadingLang" v-model="currentReadingLang"
placeholder="Reading Lang.." placeholder="Reading Lang.."
size="medium" size="medium"
style="width:200px" class="width-200"
multiple multiple
:popper-append-to-body="false" :popper-append-to-body="false"
@change="changeReadingLang" @change="changeReadingLang"
> >
<el-option v-for="o in langOptions" :key="o.label" :label="o.label" :value="o.value"></el-option> <el-option
</el-select> v-for="o in langOptions"
</el-form-item> :key="o.label"
</el-form> :label="o.label"
<i class="el-icon-more-outline hover-cursor" slot="reference"></i> :value="o.value"
</el-popover> ></el-option>
<router-link </el-select>
v-if="this.$store.state.user.token!=''" </el-form-item>
to="/dev/article/new" </el-form>
class="margin-right-20" <i class="el-icon-more-outline cursor-pointer" slot="reference"></i>
style="text-decoration:none;color:black;background:#66e2d5;padding:2px 12px;border:2px solid #0a0a0a;border-radius:3px;font-weight:bold;font-size:14px" </el-popover>
>WRITE A POST</router-link> <router-link
<el-button v-if="this.$store.state.user.token!=''"
v-if="this.$store.state.user.token==''" to="/dev/article/new"
type="primary" class="margin-right-20 write-post"
@click="signInModalVisible=true" >WRITE A POST</router-link>
>{{$t('nav.signIn')}}</el-button> <el-button
<!-- <img v-else :src="this.$store.state.user.avatar" style="height:45px;display:inline-block;vertical-align:middle" /> --> v-if="this.$store.state.user.token==''"
<el-popover v-else placement="bottom" trigger="hover" > type="primary"
<div class="user-panel font-size-18"> @click="signInModalVisible=true"
<div>@{{this.$store.state.user.name}}</div> >{{$t('nav.signIn')}}</el-button>
<el-popover v-else placement="bottom" trigger="hover" class="user-menu">
<div class="user-panel font-size-18">
<div><router-link class="margin-top-5 text-decoration-none color-regular" to="/dev/setting">@{{this.$store.state.user.name}}</router-link></div>
<el-divider></el-divider> <el-divider></el-divider>
<div>Dashboard</div> <div class="margin-top-5"><router-link class=" text-decoration-none color-regular" to="/dev/setting">Dashboard</router-link></div>
<div class="margin-top-5">Write a Post</div> <div class="margin-top-5"><router-link class=" text-decoration-none color-regular" to="/dev/setting">Write A Post</router-link></div>
<div class="margin-top-5">Reading List</div> <div class="margin-top-5"><router-link class=" text-decoration-none color-regular" to="/dev/setting">Reading List</router-link></div>
<div class="margin-top-5">Settings</div> <div class="margin-top-5"><router-link class=" text-decoration-none color-regular" to="/dev/setting">Settings</router-link></div>
<el-divider></el-divider> <el-divider></el-divider>
<div>About im.dev</div> <div><router-link class="text-decoration-none color-regular" to="/dev/setting">About im.dev</router-link></div>
<el-divider></el-divider> <el-divider></el-divider>
<div @click.stop="signOut">Sign Out</div> <div class="cursor-pointer" @click.stop="signOut">Sign Out</div>
</div> </div>
<el-avatar <el-avatar
:src="this.$store.state.user.avatar" :src="this.$store.state.user.avatar"
slot="reference" slot="reference"
class="middle-inline hover-cursor" class="display-inline-block vertical-align-middle cursor-pointer"
></el-avatar> ></el-avatar>
</el-popover> </el-popover>
</span> </span>
</el-col> </el-col>
</el-row> </el-row>
</transition> </transition>
<router-view class="main-view" style="padding-top:60px"></router-view> <router-view class="main-view padding-top-60"></router-view>
<el-dialog class="white-bg-modal sign-in-modal" :visible.sync="signInModalVisible"> <el-dialog class="white-bg-modal sign-in-modal" :visible.sync="signInModalVisible">
<el-row class="sign-in-panel text-align-center padding-top-20 padding-bottom-20"> <el-row class="sign-in-panel text-align-center padding-top-20 padding-bottom-20">
@ -119,7 +129,7 @@
<script> <script>
import request from "@/utils/request"; import request from "@/utils/request";
import langOptions from "@/utils/data" import langOptions from "@/utils/data";
export default { export default {
name: "Nav", name: "Nav",
data() { data() {
@ -135,30 +145,30 @@ export default {
// nav fixed to top // nav fixed to top
scrollTop: 0, scrollTop: 0,
toTop : true, toTop: true,
topCount : 0, topCount: 0,
inTop : true, inTop: true,
langOptions: langOptions langOptions: langOptions
}; };
}, },
watch: { watch: {
"$store.state.misc.needSignin"() { "$store.state.misc.needSignin"() {
this.signInModalVisible = true this.signInModalVisible = true;
}, },
"$store.state.misc.navFixed"() { "$store.state.misc.navFixed"() {
if (this.$store.state.misc.navFixed) { if (this.$store.state.misc.navFixed) {
window.removeEventListener('scroll', this.handleScroll); window.removeEventListener("scroll", this.handleScroll);
this.inTop = true this.inTop = true;
this.toTop = true this.toTop = true;
} else { } else {
window.addEventListener('scroll', this.handleScroll); window.addEventListener("scroll", this.handleScroll);
} }
}, }
}, },
computed: {}, computed: {},
methods: { methods: {
signOut() { signOut() {
this.$store.dispatch('SignOut') this.$store.dispatch("SignOut");
}, },
signIn() { signIn() {
request({ request({
@ -172,13 +182,13 @@ export default {
}); });
}, },
changeReadingLang(val) { changeReadingLang(val) {
this.$store.dispatch("setReadingLang", val); this.$store.dispatch("setReadingLang", val);
window.location.reload(); window.location.reload();
}, },
changeLang(val) { changeLang(val) {
this.$store.dispatch("setLang", val); this.$store.dispatch("setLang", val);
this.$i18n.locale = val; this.$i18n.locale = val;
window.location.reload(); window.location.reload();
}, },
changeTheme(val) { changeTheme(val) {
this.$store.dispatch("setTheme", val); this.$store.dispatch("setTheme", val);
@ -187,44 +197,40 @@ export default {
loadTheme() { loadTheme() {
// vue page // vue page
if (this.theme == "light") { if (this.theme == "light") {
require("!style-loader!css-loader!less-loader!../theme/light/layout.less");
require("!style-loader!css-loader!less-loader!../theme/light/style.less"); require("!style-loader!css-loader!less-loader!../theme/light/style.less");
require("!style-loader!css-loader!less-loader!../theme/light/eleui-style.less");
} else { } else {
require("!style-loader!css-loader!less-loader!../theme/dark/layout.less");
require("!style-loader!css-loader!less-loader!../theme/dark/style.less"); require("!style-loader!css-loader!less-loader!../theme/dark/style.less");
} }
}, },
handleScroll: function(e) { handleScroll: function(e) {
var y = window.scrollY var y = window.scrollY;
if (y <1) { if (y < 1) {
if (!this.inTop){ if (!this.inTop) {
this.inTop = true this.inTop = true;
}
} else {
if (this.inTop) {
this.inTop = false
}
} }
if (y- this.scrollTop < 0) { } else {
if (!this.toTop) { if (this.inTop) {
// \ this.inTop = false;
this.topCount = this.topCount + 1
if (this.topCount >= 9) {
this.toTop = true
this.topCount = 0
}
}
} else {
if (window.scrollY < 30) {
return
}
this.toTop = false
this.topCount = 0
} }
this.scrollTop = y }
if (y - this.scrollTop < 0) {
if (!this.toTop) {
// \
this.topCount = this.topCount + 1;
if (this.topCount >= 9) {
this.toTop = true;
this.topCount = 0;
}
}
} else {
if (window.scrollY < 30) {
return;
}
this.toTop = false;
this.topCount = 0;
}
this.scrollTop = y;
} }
}, },
mounted() { mounted() {
@ -234,66 +240,3 @@ export default {
}; };
</script> </script>
<style lang="less">
@import "../theme/light/var.less";
.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 {
.el-divider {
margin: 13px 0;
}
div {
cursor:pointer;
padding-left:10px
}
}
.fade-enter-active, .fade-leave-active {
transition: all .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 .4s;
}
</style>
<style lang="less" scoped>
.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;
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;
}
</style>

@ -0,0 +1,24 @@
<template>
<div class="setting-account margin-top-20">
<el-card shadow="hover">
<div>aaaa</div>
</el-card>
</div>
</template>
<script>
import request from "@/utils/request";
export default {
data() {
return {
};
},
watch: {
},
methods: {
},
mounted() {
}
};
</script>

@ -0,0 +1,55 @@
<template>
<div class="setting-nav">
<el-row>
<el-col :span="20" :offset="2">
<div class="header padding-bottom-10 border-bottom" >
<h3 class="big-title margin-bottom-30"><i class="el-icon-s-tools margin-right-10"></i>User Settings</h3>
<span v-for="i in items" :key="i" :class="{'selected': selItem==i,'meta-word':selItem!=i}" class="cursor-pointer item font-weight-500" @click="selectItem(i)">{{names[i]}}</span>
</div>
<router-view></router-view>
</el-col>
</el-row>
</div>
</template>
<script>
export default {
data () {
return {
items: [],
level: {},
path : '',
selItem : '',
appNames: []
}
},
watch: {
$route() {
this.initItem()
}
},
computed: {
},
methods: {
selectItem(i) {
this.$router.push('/dev/setting/' + i)
},
initItem() {
this.path = window.location.pathname
this.items = ['account','profile']
this.level = {account:2,profile:2}
this.names = {account: 'Account',profile:'Profile'}
this.selItem = this.path.split('/')[3]
}
},
mounted() {
this.initItem()
}
}
</script>

@ -0,0 +1,141 @@
<template>
<div class="setting-profile margin-top-20">
<el-row v-if="user.id!=undefined">
<el-col :span="13" class="padding-left-20 padding-right-20">
<div>
<h4>Display name</h4>
<div class="meta-word font-size-12">Set a display name. This does not change your username.</div>
<el-input size="medium" class="margin-top-5 margin-bottom-5" v-model="user.nickname" :maxlength="30"></el-input>
<div class="meta-word font-size-12">{{30-user.nickname.length}} Characters</div>
</div>
<div class="margin-top-20">
<h4>About</h4>
<div class="meta-word font-size-12">A brief description of yourself shown on your profile.</div>
<el-input type="textarea" class="margin-top-5 margin-bottom-5" v-model="user.about" :maxlength="500"></el-input>
<div class="meta-word font-size-12">{{500-user.about.length}} Characters</div>
</div>
<div class="margin-top-20">
<h4>Website url</h4>
<el-input size="medium" placeholder="https://yoursite.com" v-model="user.website" :maxlength="30"></el-input>
<div class="meta-word font-size-12 margin-top-5">{{30-user.website.length}} Characters</div>
</div>
<div class="margin-top-20">
<h4>Location </h4>
<el-input size="medium" placeholder="Your location" v-model="user.location" :maxlength="30"></el-input>
<div class="meta-word font-size-12 margin-top-5">{{30-user.location.length}} Characters</div>
</div>
<div class="margin-top-30">
<h4>Background color</h4>
<el-color-picker class="position-absolute" size="small" v-model="user.bg_color"></el-color-picker>
<span class="meta-word font-size-12 margin-left-40">The background color used in your home page</span>
</div>
<div class="margin-top-30 margin-bottom-50">
<h4>Text color </h4>
<el-color-picker class="position-absolute" size="small" v-model="user.text_color"></el-color-picker>
<span class="meta-word font-size-12 margin-left-40">The text color used in your home page</span>
</div>
<el-divider content-position="left" >Job/Working</el-divider>
<div class="margin-top-40">
<h4>Education</h4>
<div class="meta-word font-size-12">A brief description of your education.</div>
<el-input type="textarea" class="margin-top-5 margin-bottom-5" v-model="user.education" :maxlength="500"></el-input>
<div class="meta-word font-size-12">{{500-user.education.length}} Characters</div>
</div>
<div class="margin-top-20">
<h4>Employer</h4>
<div class="meta-word font-size-12">e.g : Development Team Leader at &lt;a href="https://google.com"&gt;Google&lt;/a&gt;</div>
<el-input type="textarea" class="margin-top-5 margin-bottom-5" v-model="user.employer" :maxlength="500"></el-input>
<div class="meta-word font-size-12">{{500-user.employer.length}} Characters</div>
</div>
<div class="margin-top-20">
<h4>Skills </h4>
<div class="meta-word font-size-12">What programming skills do you have? Are your a expert?</div>
<el-input type="textarea" class="margin-top-5 margin-bottom-5" v-model="user.skills" :maxlength="500"></el-input>
<div class="meta-word font-size-12">{{500-user.skills.length}} Characters</div>
</div>
<div class="margin-top-20">
<h4>Working Experience</h4>
<div class="meta-word font-size-12">Your working experience and the projects you participated</div>
<el-input type="textarea" class="margin-top-5 margin-bottom-5" v-model="user.working_exp" :maxlength="500"></el-input>
<div class="meta-word font-size-12">{{500-user.working_exp.length}} Characters</div>
</div>
<div class="margin-top-20">
<h4>Available for</h4>
<div class="meta-word font-size-12">What kinds of collaborations or works are you available for?</div>
<el-input type="textarea" class="margin-top-5 margin-bottom-5" v-model="user.available_for" :maxlength="500"></el-input>
<div class="meta-word font-size-12">{{500-user.available_for.length}} Characters</div>
</div>
<div class="margin-top-20">
<h4>Looking for work</h4>
<el-switch
class="position-absolute"
v-model="user.lfw"
active-color="#13ce66">
</el-switch>
<span class="meta-word font-size-12 margin-left-50">Are you looking for a work now?</span>
</div>
<div class="margin-top-20">
<el-button type="primary" @click="setProfile">SUBMIT</el-button>
</div>
</el-col>
<el-col :span="6" :offset="1">
<UserCard :user="user"></UserCard>
</el-col>
</el-row>
</div>
</template>
<script>
import request from "@/utils/request";
import UserCard from "../components/user-card";
export default {
components: {UserCard},
data() {
return {
user: {}
};
},
watch: {
"$store.state.user.id"() {
if (this.$store.state.user.id != '') {
this.init()
}
},
},
methods: {
setProfile() {
request({
url: "/user/profile/set",
method: "POST",
params: {
user: JSON.stringify(this.user)
}
}).then(res => {
this.$message.success("Saved")
});
},
init() {
request({
url: "/user/profile",
method: "GET",
params: {
}
}).then(res => {
this.user = res.data.data
if (this.user.lfw ==0) {
this.user.lfw = false
} else {
this.user.lfw = true
}
});
}
},
mounted() {
this.init()
}
};
</script>
Loading…
Cancel
Save