v1.0 beta init

This commit is contained in:
life
2014-10-22 16:20:45 +08:00
parent 8ae438272b
commit 593d2c2965
225 changed files with 27217 additions and 3675 deletions

View File

@@ -4,6 +4,7 @@ import (
"github.com/revel/revel"
"github.com/leanote/leanote/app/info"
. "github.com/leanote/leanote/app/lea"
// "strconv"
)
// 用户登录/注销/找回密码
@@ -14,48 +15,88 @@ type Auth struct {
//--------
// 登录
func (c Auth) Login(email string) revel.Result {
func (c Auth) Login(email, from string) revel.Result {
c.RenderArgs["title"] = c.Message("login")
c.RenderArgs["subTitle"] = c.Message("login")
c.RenderArgs["email"] = email
c.RenderArgs["from"] = from
c.RenderArgs["openRegister"] = openRegister
sessionId := c.Session.Id()
if sessionService.LoginTimesIsOver(sessionId) {
c.RenderArgs["needCaptcha"] = true
}
c.SetLocale()
if c.Has("demo") {
c.RenderArgs["demo"] = true
c.RenderArgs["email"] = "demo@leanote.com"
}
return c.RenderTemplate("home/login.html")
}
func (c Auth) DoLogin(email, pwd string) revel.Result {
// 为了demo和register
func (c Auth) doLogin(email, pwd string) revel.Result {
sessionId := c.Session.Id()
var msg = ""
userInfo := authService.Login(email, pwd)
if userInfo.Email != "" {
c.SetSession(userInfo)
// 必须要redirect, 不然用户刷新会重复提交登录信息
// return c.Redirect("/")
configService.InitUserConfigs(userInfo.UserId.Hex())
sessionService.ClearLoginTimes(sessionId)
return c.RenderJson(info.Re{Ok: true})
} else {
// 登录错误, 则错误次数++
msg = "wrongUsernameOrPassword"
}
// return c.RenderTemplate("login.html")
return c.RenderJson(info.Re{Ok: false, Msg: c.Message("wrongUsernameOrPassword")})
return c.RenderJson(info.Re{Ok: false, Item: sessionService.LoginTimesIsOver(sessionId) , Msg: c.Message(msg)})
}
func (c Auth) DoLogin(email, pwd string, captcha string) revel.Result {
sessionId := c.Session.Id()
var msg = ""
// > 5次需要验证码, 直到登录成功
if sessionService.LoginTimesIsOver(sessionId) && sessionService.GetCaptcha(sessionId) != captcha {
msg = "captchaError"
} else {
userInfo := authService.Login(email, pwd)
if userInfo.Email != "" {
c.SetSession(userInfo)
sessionService.ClearLoginTimes(sessionId)
return c.RenderJson(info.Re{Ok: true})
} else {
// 登录错误, 则错误次数++
msg = "wrongUsernameOrPassword"
sessionService.IncrLoginTimes(sessionId)
}
}
return c.RenderJson(info.Re{Ok: false, Item: sessionService.LoginTimesIsOver(sessionId) , Msg: c.Message(msg)})
}
// 注销
func (c Auth) Logout() revel.Result {
sessionId := c.Session.Id()
sessionService.Clear(sessionId)
c.ClearSession()
return c.Redirect("/login")
}
// 体验一下
func (c Auth) Demo() revel.Result {
c.DoLogin("demo@leanote.com", "demo@leanote.com")
c.doLogin(configService.GetGlobalStringConfig("demoUsername"), configService.GetGlobalStringConfig("demoPassword"))
return c.Redirect("/note")
}
//--------
// 注册
func (c Auth) Register() revel.Result {
func (c Auth) Register(from string) revel.Result {
if !openRegister {
return c.Redirect("/index")
}
c.SetLocale()
c.RenderArgs["from"] = from
c.RenderArgs["title"] = c.Message("register")
c.RenderArgs["subTitle"] = c.Message("register")
@@ -68,21 +109,11 @@ func (c Auth) DoRegister(email, pwd string) revel.Result {
re := info.NewRe();
if email == "" {
re.Msg = c.Message("inputEmail")
return c.RenderJson(re)
} else if !IsEmail(email) {
re.Msg = c.Message("wrongEmail")
return c.RenderJson(re)
if re.Ok, re.Msg = Vd("email", email); !re.Ok {
return c.RenderRe(re);
}
// 密码
if pwd == "" {
re.Msg = c.Message("inputPassword")
return c.RenderJson(re)
} else if len(pwd) < 6 {
re.Msg = c.Message("wrongPassword")
return c.RenderJson(re)
if re.Ok, re.Msg = Vd("password", pwd); !re.Ok {
return c.RenderRe(re);
}
// 注册
@@ -90,10 +121,10 @@ func (c Auth) DoRegister(email, pwd string) revel.Result {
// 注册成功, 则立即登录之
if re.Ok {
c.DoLogin(email, pwd)
c.doLogin(email, pwd)
}
return c.RenderJson(re)
return c.RenderRe(re)
}
//--------
@@ -131,12 +162,11 @@ func (c Auth) FindPassword2(token string) revel.Result {
func (c Auth) FindPasswordUpdate(token, pwd string) revel.Result {
re := info.NewRe();
re.Ok, re.Msg = IsGoodPwd(pwd)
if !re.Ok {
return c.RenderJson(re)
if re.Ok, re.Msg = Vd("password", pwd); !re.Ok {
return c.RenderRe(re);
}
// 修改之
re.Ok, re.Msg = pwdService.UpdatePwd(token, pwd)
return c.RenderJson(re)
return c.RenderRe(re)
}

View File

@@ -5,11 +5,13 @@ import (
"gopkg.in/mgo.v2/bson"
"encoding/json"
"github.com/leanote/leanote/app/info"
// . "github.com/leanote/leanote/app/lea"
// "io/ioutil"
// "fmt"
"math"
"strconv"
"strings"
"bytes"
)
// 公用Controller, 其它Controller继承它
@@ -54,15 +56,21 @@ func (c BaseController) GetUsername() string {
// 得到用户信息
func (c BaseController) GetUserInfo() info.User {
if userId, ok := c.Session["UserId"]; ok && userId != "" {
return userService.GetUserInfo(userId);
/*
notebookWidth, _ := strconv.Atoi(c.Session["NotebookWidth"])
noteListWidth, _ := strconv.Atoi(c.Session["NoteListWidth"])
mdEditorWidth, _ := strconv.Atoi(c.Session["MdEditorWidth"])
LogJ(c.Session)
user := info.User{UserId: bson.ObjectIdHex(userId),
Email: c.Session["Email"],
Logo: c.Session["Logo"],
Username: c.Session["Username"],
UsernameRaw: c.Session["UsernameRaw"],
Theme: c.Session["Theme"],
NotebookWidth: notebookWidth,
NoteListWidth: noteListWidth,
MdEditorWidth: mdEditorWidth,
}
if c.Session["Verified"] == "1" {
user.Verified = true
@@ -71,10 +79,19 @@ func (c BaseController) GetUserInfo() info.User {
user.LeftIsMin = true
}
return user
*/
}
return info.User{}
}
// 这里的session都是cookie中的, 与数据库session无关
func (c BaseController) GetSession(key string) string {
v, ok := c.Session[key]
if !ok {
v = ""
}
return v
}
func (c BaseController) SetSession(userInfo info.User) {
if userInfo.UserId.Hex() != "" {
c.Session["UserId"] = userInfo.UserId.Hex()
@@ -82,6 +99,7 @@ func (c BaseController) SetSession(userInfo info.User) {
c.Session["Username"] = userInfo.Username
c.Session["UsernameRaw"] = userInfo.UsernameRaw
c.Session["Theme"] = userInfo.Theme
c.Session["Logo"] = userInfo.Logo
c.Session["NotebookWidth"] = strconv.Itoa(userInfo.NotebookWidth)
c.Session["NoteListWidth"] = strconv.Itoa(userInfo.NoteListWidth)
@@ -165,6 +183,12 @@ func (c BaseController) SetLocale() string {
lang = "en";
}
c.RenderArgs["locale"] = lang;
c.RenderArgs["siteUrl"] = siteUrl;
c.RenderArgs["blogUrl"] = configService.GetBlogUrl()
c.RenderArgs["leaUrl"] = configService.GetLeaUrl()
c.RenderArgs["noteUrl"] = configService.GetNoteUrl()
return lang
}
@@ -173,3 +197,46 @@ func (c BaseController) SetUserInfo() {
userInfo := c.GetUserInfo()
c.RenderArgs["userInfo"] = userInfo
}
// life
// 返回解析后的字符串, 只是为了解析模板得到字符串
func (c BaseController) RenderTemplateStr(templatePath string) string {
// Get the Template.
// 返回 GoTemplate{tmpl, loader}
template, err := revel.MainTemplateLoader.Template(templatePath)
if err != nil {
}
tpl := &revel.RenderTemplateResult{
Template: template,
RenderArgs: c.RenderArgs, // 把args给它
}
var buffer bytes.Buffer
tpl.Template.Render(&buffer, c.RenderArgs)
return buffer.String();
}
// json, result
// 为了msg
// msg-v1-v2-v3
func (c BaseController) RenderRe(re info.Re) revel.Result {
if re.Msg != "" {
if(strings.Contains(re.Msg, "-")) {
msgAndValues := strings.Split(re.Msg, "-")
if len(msgAndValues) == 2 {
re.Msg = c.Message(msgAndValues[0], msgAndValues[1])
} else {
others := msgAndValues[0:]
a := make([]interface{}, len(others))
for i, v := range others {
a[i] = v
}
re.Msg = c.Message(msgAndValues[0], a...)
}
} else {
re.Msg = c.Message(re.Msg)
}
}
return c.RenderJson(re)
}

View File

@@ -1,6 +1,8 @@
package controllers
import (
"strings"
"time"
"github.com/revel/revel"
// "encoding/json"
"gopkg.in/mgo.v2/bson"
@@ -17,142 +19,283 @@ type Blog struct {
BaseController
}
//---------------------------
// 后台 note<->blog
// 设置/取消Blog; 置顶
func (c Blog) SetNote2Blog(noteId string, isBlog, isTop bool) revel.Result {
if isTop {
isBlog = true
}
if !isBlog {
isTop = false
}
noteUpdate := bson.M{"IsBlog": isBlog, "IsTop": isTop}
re := noteService.UpdateNote(c.GetUserId(), c.GetUserId(),
noteId, noteUpdate)
return c.RenderJson(re)
}
// 设置notebook <-> blog
func (c Blog) SetNotebook2Blog(notebookId string, isBlog bool) revel.Result {
noteUpdate := bson.M{"IsBlog": isBlog}
re := notebookService.UpdateNotebook(c.GetUserId(),
notebookId, noteUpdate)
return c.RenderJson(re)
}
//-----------------------------
// 前台
// 域名, 没用
func (c Blog) domain() (ok bool, userBlog info.UserBlog) {
return
}
// 各种地址设置
func (c Blog) setUrl(userBlog info.UserBlog, userInfo info.User) {
// 主页 http://leanote.com/blog/life or http://blog.leanote.com/life or http:// xxxx.leanote.com or aa.com
var indexUrl, viewUrl, searchUrl, cateUrl, aboutMeUrl, staticUrl string
host := c.Request.Request.Host
staticUrl = configService.GetUserUrl(strings.Split(host, ":")[0])
// staticUrl == host, 为保证同源!!! 只有host, http://leanote.com, http://blog/leanote.com
// life.leanote.com, lealife.com
if userBlog.Domain != "" && configService.AllowCustomDomain() {
// ok
indexUrl = configService.GetUserUrl(userBlog.Domain)
cateUrl = indexUrl + "/cate" // /xxxxx
viewUrl = indexUrl + "/view" // /xxxxx
searchUrl = indexUrl + "/search" // /xxxxx
aboutMeUrl = indexUrl + "/aboutMe"
} else if userBlog.SubDomain != "" {
indexUrl = configService.GetUserSubUrl(userBlog.SubDomain)
cateUrl = indexUrl + "/cate" // /xxxxx
viewUrl = indexUrl + "/view" // /xxxxx
searchUrl = indexUrl + "/search" // /xxxxx
aboutMeUrl = indexUrl + "/aboutMe"
} else {
// ok
blogUrl := configService.GetBlogUrl()
userIdOrEmail := ""
if userInfo.Username != "" {
userIdOrEmail = userInfo.Username
} else if userInfo.Email != "" {
userIdOrEmail = userInfo.Email
} else {
userIdOrEmail = userInfo.UserId.Hex()
}
indexUrl = blogUrl + "/" + userIdOrEmail
cateUrl = blogUrl + "/cate" // /notebookId
viewUrl = blogUrl + "/view" // /xxxxx
searchUrl = blogUrl + "/search/" + userIdOrEmail // /xxxxx
aboutMeUrl = blogUrl + "/aboutMe/" + userIdOrEmail
}
// 分类
// 搜索
// 查看
c.RenderArgs["indexUrl"] = indexUrl
c.RenderArgs["cateUrl"] = cateUrl
c.RenderArgs["viewUrl"] = viewUrl
c.RenderArgs["searchUrl"] = searchUrl
c.RenderArgs["aboutMeUrl"] = aboutMeUrl
c.RenderArgs["staticUrl"] = staticUrl
}
// 公共
func (c Blog) blogCommon(userId string, userBlog info.UserBlog, userInfo info.User) (ok bool) {
if userInfo.UserId == "" {
userInfo = userService.GetUserInfoByAny(userId)
if userInfo.UserId == "" {
return
}
}
c.RenderArgs["userInfo"] = userInfo
// 分类导航
c.RenderArgs["notebooks"] = blogService.ListBlogNotebooks(userId)
// 最新笔记
c.getRecentBlogs(userId)
// 语言, url地址
c.SetLocale();
c.RenderArgs["isMe"] = userId == c.GetUserId()
// 得到博客设置信息
if userBlog.UserId == "" {
userBlog = blogService.GetUserBlog(userId)
}
c.RenderArgs["userBlog"] = userBlog
c.setUrl(userBlog, userInfo)
return true
}
// 跨域判断是否是我的博客
func (c Blog) IsMe(userId string) revel.Result {
var js = ""
if c.GetUserId() == userId {
js = "$('.is-me').removeClass('hide');"
}
return c.RenderText(js);
}
// 进入某个用户的博客
var blogPageSize = 5
var searchBlogPageSize = 30
func (c Blog) Index(userId string, notebookId string) revel.Result {
// 用户id为空, 转至博客平台
if userId == "" {
userId = leanoteUserId;
}
// userId可能是 username, email
userInfo := userService.GetUserInfoByAny(userId)
if userInfo.UserId == "" {
// 分类 /cate/xxxxxxxx?notebookId=1212
func (c Blog) Cate(notebookId string) revel.Result {
if notebookId == "" {
return c.E404()
}
userId = userInfo.UserId.Hex()
c.isMe(userId)
c.RenderArgs["userInfo"] = userInfo
// 得到博客设置信息
userBlog := blogService.GetUserBlog(userId)
c.RenderArgs["userBlog"] = userBlog
// 自定义域名
hasDomain, userBlog := c.domain()
userId := ""
if hasDomain {
userId = userBlog.UserId.Hex()
}
var notebook info.Notebook
if notebookId != "" {
notebook = notebookService.GetNotebook(notebookId, userId)
notebook = notebookService.GetNotebookById(notebookId)
if !notebook.IsBlog {
return c.E404()
}
c.RenderArgs["title"] = userBlog.Title + " - 分类: " + notebook.Title
} else {
c.RenderArgs["title"] = userBlog.Title
if userId != "" && userId != notebook.UserId.Hex() {
return c.E404()
}
userId = notebook.UserId.Hex()
if !c.blogCommon(userId, userBlog, info.User{}) {
return c.E404()
}
// 分页的话, 需要分页信息, totalPage, curPage
page := c.GetPage()
count, blogs := blogService.ListBlogs(userId, notebookId, page, blogPageSize, "UpdatedTime", false)
count, blogs := blogService.ListBlogs(userId, notebookId, page, blogPageSize, "PublicTime", false)
c.RenderArgs["notebookId"] = notebookId
c.RenderArgs["notebook"] = notebook
c.RenderArgs["title"] = c.Message("blogClass") + " - " + notebook.Title
c.RenderArgs["blogs"] = blogs
c.RenderArgs["page"] = page
c.RenderArgs["pageSize"] = blogPageSize
c.RenderArgs["count"] = count
// 当前notebook
c.RenderArgs["notebookId"] = notebookId
c.RenderArgs["notebook"] = notebook
c.RenderArgs["notebooks"] = blogService.ListBlogNotebooks(userId)
if notebookId == "" {
c.RenderArgs["index"] = true
return c.RenderTemplate("blog/index.html")
}
c.getRecentBlogs(userId)
// 显示分类的最近博客, json
func (c Blog) ListCateLatest(notebookId string) revel.Result {
if notebookId == "" {
return c.E404()
}
// 自定义域名
hasDomain, userBlog := c.domain()
userId := ""
if hasDomain {
userId = userBlog.UserId.Hex()
}
var notebook info.Notebook
notebook = notebookService.GetNotebookById(notebookId)
if !notebook.IsBlog {
return c.E404()
}
if userId != "" && userId != notebook.UserId.Hex() {
return c.E404()
}
userId = notebook.UserId.Hex()
if !c.blogCommon(userId, userBlog, info.User{}) {
return c.E404()
}
// 分页的话, 需要分页信息, totalPage, curPage
page := 1
_, blogs := blogService.ListBlogs(userId, notebookId, page, 5, "PublicTime", false)
re := info.NewRe()
re.Ok = true
re.List = blogs
return c.RenderJson(re)
}
func (c Blog) Index(userIdOrEmail string) revel.Result {
// 自定义域名
hasDomain, userBlog := c.domain()
userId := ""
if hasDomain {
userId = userBlog.UserId.Hex()
}
// 用户id为空, 转至博客平台
if userIdOrEmail == "" {
userIdOrEmail = leanoteUserId;
}
var userInfo info.User
if userId != "" {
userInfo = userService.GetUserInfoByAny(userId)
} else {
userInfo = userService.GetUserInfoByAny(userIdOrEmail)
}
userId = userInfo.UserId.Hex()
if !c.blogCommon(userId, userBlog, userInfo) {
return c.E404()
}
// 分页的话, 需要分页信息, totalPage, curPage
page := c.GetPage()
count, blogs := blogService.ListBlogs(userId, "", page, blogPageSize, "PublicTime", false)
c.RenderArgs["blogs"] = blogs
c.RenderArgs["page"] = page
c.RenderArgs["pageSize"] = blogPageSize
c.RenderArgs["count"] = count
c.RenderArgs["index"] = true
c.RenderArgs["notebookId"] = ""
c.RenderArgs["title"] = userBlog.Title
return c.RenderTemplate("blog/index.html")
}
// 详情
func (c Blog) View(noteId string) revel.Result {
// 自定义域名
hasDomain, userBlog := c.domain()
userId := ""
if hasDomain {
userId = userBlog.UserId.Hex()
}
blog := blogService.GetBlog(noteId)
c.RenderArgs["blog"] = blog
userInfo := userService.GetUserInfo(blog.UserId.Hex())
if userId != "" && userInfo.UserId.Hex() != userId {
return c.E404()
}
c.RenderArgs["blog"] = blog
c.RenderArgs["userInfo"] = userInfo
c.RenderArgs["title"] = blog.Title + " - " + userInfo.Username
c.RenderArgs["title"] = blog.Title + " - " + userInfo.Email
userId = userInfo.UserId.Hex()
c.blogCommon(userId, userBlog, info.User{})
userId := userInfo.UserId.Hex()
c.isMe(userId)
c.RenderArgs["notebooks"] = blogService.ListBlogNotebooks(userId)
// 得到博客设置信息
c.RenderArgs["userBlog"] = blogService.GetUserBlog(userId)
c.getRecentBlogs(userId)
// 得到访问者id
visitUserId := c.GetUserId()
if(visitUserId != "") {
visitUserInfo := userService.GetUserInfo(visitUserId)
c.RenderArgs["visitUserInfoJson"] = c.Json(visitUserInfo)
c.RenderArgs["visitUserInfo"] = visitUserInfo
} else {
c.RenderArgs["visitUserInfoJson"] = "{}";
}
return c.RenderTemplate("blog/view.html")
}
// 搜索
func (c Blog) SearchBlog(userId, key string) revel.Result {
c.RenderArgs["title"] = "搜索 " + key
func (c Blog) Search(userIdOrEmail, key string) revel.Result {
// 自定义域名
hasDomain, userBlog := c.domain()
userId := ""
if hasDomain {
userId = userBlog.UserId.Hex()
}
c.RenderArgs["title"] = c.Message("search") + " - " + key
c.RenderArgs["key"] = key
userInfo := userService.GetUserInfoByAny(userId)
var userInfo info.User
if userId != "" {
userInfo = userService.GetUserInfoByAny(userId)
} else {
userInfo = userService.GetUserInfoByAny(userIdOrEmail)
}
c.RenderArgs["userInfo"] = userInfo
userId = userInfo.UserId.Hex()
c.blogCommon(userId, userBlog, userInfo)
page := c.GetPage()
_, blogs := blogService.SearchBlog(key, userId, page, searchBlogPageSize, "UpdatedTime", false)
_, blogs := blogService.SearchBlog(key, userId, page, searchBlogPageSize, "PublicTime", false)
c.RenderArgs["blogs"] = blogs
c.RenderArgs["key"] = key
c.RenderArgs["notebooks"] = blogService.ListBlogNotebooks(userId)
// 得到博客设置信息
c.RenderArgs["userBlog"] = blogService.GetUserBlog(userId)
c.getRecentBlogs(userId)
c.isMe(userId)
return c.RenderTemplate("blog/search.html")
}
@@ -162,17 +305,15 @@ func (c Blog) Set() revel.Result {
userInfo := userService.GetUserInfo(userId)
c.RenderArgs["userInfo"] = userInfo
c.RenderArgs["notebooks"] = blogService.ListBlogNotebooks(userId)
// 得到博客设置信息
c.RenderArgs["userBlog"] = blogService.GetUserBlog(userId)
c.RenderArgs["title"] = "博客设置"
c.RenderArgs["title"] = c.Message("blogSet")
c.RenderArgs["isMe"] = true
c.RenderArgs["set"] = true
c.getRecentBlogs(userId)
c.RenderArgs["allowCustomDomain"] = configService.GetGlobalStringConfig("allowCustomDomain")
c.SetLocale();
userBlog := blogService.GetUserBlog(userId)
c.blogCommon(userId, userBlog, info.User{})
return c.RenderTemplate("blog/set.html")
}
@@ -194,37 +335,38 @@ func (c Blog) SetUserBlogStyle(userBlog info.UserBlogStyle) revel.Result {
}
// userId可能是其它的
func (c Blog) AboutMe(userId string) revel.Result {
userInfo := userService.GetUserInfoByAny(userId)
func (c Blog) AboutMe(userIdOrEmail string) revel.Result {
// 自定义域名
hasDomain, userBlog := c.domain()
userId := ""
if hasDomain {
userId = userBlog.UserId.Hex()
}
var userInfo info.User
if userId != "" {
userInfo = userService.GetUserInfoByAny(userId)
} else {
userInfo = userService.GetUserInfoByAny(userIdOrEmail)
}
if userInfo.UserId == "" {
return c.E404()
}
userId = userInfo.UserId.Hex()
c.RenderArgs["userInfo"] = userInfo
c.RenderArgs["notebooks"] = blogService.ListBlogNotebooks(userId)
c.RenderArgs["userBlog"] = blogService.GetUserBlog(userId)
c.RenderArgs["aboutMe"] = true
c.RenderArgs["title"] = "关于我"
c.isMe(userId)
c.getRecentBlogs(userId)
c.RenderArgs["title"] = c.Message("aboutMe")
c.blogCommon(userId, userBlog, info.User{})
return c.RenderTemplate("blog/about_me.html")
}
// 当前的博客是否是我的
func (c Blog) isMe(userId string) {
c.RenderArgs["isMe"] = userId == c.GetUserId()
}
// 优化, 这里不要得到count
func (c Blog) getRecentBlogs(userId string) {
_, c.RenderArgs["recentBlogs"] = blogService.ListBlogs(userId, "", 1, 5, "UpdatedTime", false)
_, c.RenderArgs["recentBlogs"] = blogService.ListBlogs(userId, "", 1, 5, "PublicTime", false)
}
// 可以不要, 因为注册的时候已经把username设为email了
@@ -234,3 +376,129 @@ func (c Blog) setRenderUserInfo(userInfo info.User) {
}
c.RenderArgs["userInfo"] = userInfo
}
//---------------------------
// 后台 note<->blog
// 设置/取消Blog; 置顶
func (c Blog) SetNote2Blog(noteId string, isBlog, isTop bool) revel.Result {
noteUpdate := bson.M{}
if isTop {
isBlog = true
}
if !isBlog {
isTop = false
}
noteUpdate["IsBlog"] = isBlog
noteUpdate["IsTop"] = isTop
if isBlog {
noteUpdate["PublicTime"] = time.Now()
}
re := noteService.UpdateNote(c.GetUserId(), c.GetUserId(),
noteId, noteUpdate)
return c.RenderJson(re)
}
// 设置notebook <-> blog
func (c Blog) SetNotebook2Blog(notebookId string, isBlog bool) revel.Result {
noteUpdate := bson.M{"IsBlog": isBlog}
re := notebookService.UpdateNotebook(c.GetUserId(),
notebookId, noteUpdate)
return c.RenderJson(re)
}
//----------------
// 社交, 点赞, 评论
// 我是否点过赞?
// 所有点赞的用户列表
// 各个评论中是否我也点过赞?
func (c Blog) GetLike(noteId string) revel.Result {
userId := c.GetUserId()
// 我也点过?
isILikeIt := blogService.IsILikeIt(noteId, userId)
// 点赞用户列表
likedUsers, hasMoreLikedUser := blogService.ListLikedUsers(noteId, false)
result := map[string]interface{}{}
result["isILikeIt"] = isILikeIt
result["likedUsers"] = likedUsers
result["hasMoreLikedUser"] = hasMoreLikedUser
return c.RenderJson(result)
}
func (c Blog) GetLikeAndComments(noteId string) revel.Result {
userId := c.GetUserId()
// 我也点过?
isILikeIt := blogService.IsILikeIt(noteId, userId)
// 点赞用户列表
likedUsers, hasMoreLikedUser := blogService.ListLikedUsers(noteId, false)
// 评论
page := c.GetPage()
pageInfo, comments, commentUserInfo := blogService.ListComments(userId, noteId, page, 15)
result := map[string]interface{}{}
result["isILikeIt"] = isILikeIt
result["likedUsers"] = likedUsers
result["hasMoreLikedUser"] = hasMoreLikedUser
result["pageInfo"] = pageInfo
result["comments"] = comments
result["commentUserInfo"] = commentUserInfo
return c.RenderJson(result)
}
func (c Blog) IncReadNum(noteId string) revel.Result {
blogService.IncReadNum(noteId)
return nil
}
// 点赞
func (c Blog) LikeBlog(noteId string) revel.Result {
userId := c.GetUserId()
re := info.NewRe()
re.Ok, re.Item = blogService.LikeBlog(noteId, userId)
return c.RenderJson(re)
}
func (c Blog) ListLikes(noteId string) revel.Result {
return nil
}
func (c Blog) ListComments(noteId string) revel.Result {
// 评论
userId := c.GetUserId()
page := c.GetPage()
pageInfo, comments, commentUserInfo := blogService.ListComments(userId, noteId, page, 15)
result := map[string]interface{}{}
result["pageInfo"] = pageInfo
result["comments"] = comments
result["commentUserInfo"] = commentUserInfo
return c.RenderJson(result)
}
func (c Blog) DeleteComment(noteId, commentId string) revel.Result {
re := info.NewRe()
re.Ok = blogService.DeleteComment(noteId, commentId, c.GetUserId())
return c.RenderJson(re)
}
func (c Blog) Comment(noteId, content, toCommentId string) revel.Result {
re := info.NewRe()
re.Ok, re.Item = blogService.Comment(noteId, toCommentId, c.GetUserId(), content);
return c.RenderJson(re)
}
func (c Blog) LikeComment(commentId string) revel.Result {
re := info.NewRe()
ok, isILikeIt, num := blogService.LikeComment(commentId, c.GetUserId())
re.Ok = ok
re.Item = bson.M{"IsILikeIt": isILikeIt, "Num": num}
return c.RenderJson(re)
}
func (c Blog) Report(noteId, commentId, reason string) revel.Result {
re := info.NewRe()
re.Ok = blogService.Report(noteId, commentId, reason, c.GetUserId());
return c.RenderJson(re)
}

View File

@@ -0,0 +1,43 @@
package controllers
import (
"github.com/revel/revel"
// "encoding/json"
// "gopkg.in/mgo.v2/bson"
. "github.com/leanote/leanote/app/lea"
"github.com/leanote/leanote/app/lea/captcha"
// "github.com/leanote/leanote/app/types"
// "io/ioutil"
// "fmt"
// "math"
// "os"
// "path"
// "strconv"
"net/http"
)
// 验证码服务
type Captcha struct {
BaseController
}
type Ca string
func (r Ca) Apply(req *revel.Request, resp *revel.Response) {
resp.WriteHeader(http.StatusOK, "image/png")
}
func (c Captcha) Get() revel.Result {
c.Response.ContentType = "image/png"
image, str := captcha.Fetch()
image.WriteTo(c.Response.Out)
sessionId := c.Session["_ID"]
// LogJ(c.Session)
// Log("------")
// Log(str)
// Log(sessionId)
Log("..")
sessionService.SetCaptcha(sessionId, str)
return c.Render()
}

View File

@@ -5,6 +5,7 @@ import (
// "encoding/json"
"gopkg.in/mgo.v2/bson"
. "github.com/leanote/leanote/app/lea"
"github.com/leanote/leanote/app/lea/netutil"
"github.com/leanote/leanote/app/info"
"io/ioutil"
"os"
@@ -22,7 +23,7 @@ type File struct {
func (c File) UploadBlogLogo() revel.Result {
re := c.uploadImage("logo", "");
c.RenderArgs["fileUrlPath"] = siteUrl + "/" + re.Id
c.RenderArgs["fileUrlPath"] = re.Id
c.RenderArgs["resultCode"] = re.Code
c.RenderArgs["resultMsg"] = re.Msg
@@ -53,6 +54,24 @@ func (c File) PasteImage(noteId string) revel.Result {
return c.RenderJson(re)
}
// 头像设置
func (c File) UploadAvatar() revel.Result {
re := c.uploadImage("logo", "");
c.RenderArgs["fileUrlPath"] = re.Id
c.RenderArgs["resultCode"] = re.Code
c.RenderArgs["resultMsg"] = re.Msg
if re.Ok {
re.Ok = userService.UpdateAvatar(c.GetUserId(), re.Id)
if re.Ok {
c.UpdateSession("Logo", re.Id);
}
}
return c.RenderJson(re)
}
// leaui image plugin upload image
func (c File) UploadImageLeaui(albumId string) revel.Result {
re := c.uploadImage("", albumId);
@@ -243,6 +262,38 @@ func (c File) CopyImage(userId, fileId, toUserId string) revel.Result {
return c.RenderJson(re)
}
// 复制外网的图片, 成公共图片 放在/upload下
func (c File) CopyHttpImage(src string) revel.Result {
re := info.NewRe()
fileUrlPath := "upload/" + c.GetUserId() + "/images"
dir := revel.BasePath + "/public/" + fileUrlPath
err := os.MkdirAll(dir, 0755)
if err != nil {
return c.RenderJson(re)
}
filesize, filename, _, ok := netutil.WriteUrl(src, dir)
if !ok {
re.Msg = "copy error"
return c.RenderJson(re)
}
// File
fileInfo := info.File{Name: filename,
Title: filename,
Path: fileUrlPath + "/" + filename,
Size: filesize}
id := bson.NewObjectId();
fileInfo.FileId = id
re.Id = id.Hex()
re.Item = fileInfo.Path
re.Ok = fileService.AddImage(fileInfo, "", c.GetUserId())
return c.RenderJson(re)
}
//------------
// 过时 已弃用!
func (c File) UploadImage(renderHtml string) revel.Result {

View File

@@ -3,7 +3,7 @@ package controllers
import (
"github.com/revel/revel"
"github.com/leanote/leanote/app/info"
. "github.com/leanote/leanote/app/lea"
// . "github.com/leanote/leanote/app/lea"
)
// 首页
@@ -29,7 +29,7 @@ func (c Index) Suggestion(addr, suggestion string) revel.Result {
// 发给我
go func() {
SendToLeanote("建议", "建议", "UserId: " + c.GetUserId() + " <br /> Suggestions: " + suggestion)
emailService.SendEmail("leanote@leanote.com", "建议", "UserId: " + c.GetUserId() + " <br /> Suggestions: " + suggestion)
}();
return c.RenderJson(re)

View File

@@ -5,12 +5,13 @@ import (
// "encoding/json"
"gopkg.in/mgo.v2/bson"
. "github.com/leanote/leanote/app/lea"
"github.com/leanote/leanote/app/lea/html2image"
"github.com/leanote/leanote/app/info"
// "os"
"os/exec"
// "github.com/leanote/leanote/app/types"
// "io/ioutil"
// "fmt"
// "bytes"
// "os"
)
type Note struct {
@@ -52,6 +53,8 @@ func (c Note) Index() revel.Result {
// 当然, 还需要得到第一个notes的content
//...
adminUsername, _ := revel.Config.String("adminUsername")
c.RenderArgs["isAdmin"] = adminUsername == userInfo.Username
c.RenderArgs["userInfo"] = userInfo
c.RenderArgs["userInfoJson"] = c.Json(userInfo)
c.RenderArgs["notebooks"] = c.Json(notebooks)
@@ -221,31 +224,131 @@ func (c Note) SearchNoteByTags(tags []string) revel.Result {
return c.RenderJson(blogs)
}
//-----------------
//------------------
// html2image
// 判断是否有权限生成
// 博客也可以调用
// 这是脚本调用, 没有cookie, 不执行权限控制, 通过传来的appKey判断
func (c Note) ToImage(noteId, appKey string) revel.Result {
// 虽然传了cookie但是这里还是不能得到userId, 所以还是通过appKey来验证之
appKeyTrue, _ := revel.Config.String("app.secret")
if appKeyTrue != appKey {
return c.RenderText("")
}
note := noteService.GetNoteById(noteId)
if note.NoteId == "" {
return c.RenderText("")
}
c.SetLocale()
noteUserId := note.UserId.Hex()
content := noteService.GetNoteContent(noteId, noteUserId)
userInfo := userService.GetUserInfo(noteUserId);
c.RenderArgs["blog"] = note
c.RenderArgs["content"] = content.Content
c.RenderArgs["userInfo"] = userInfo
userBlog := blogService.GetUserBlog(noteUserId)
c.RenderArgs["userBlog"] = userBlog
return c.RenderTemplate("html2Image/index.html")
}
func (c Note) Html2Image(noteId string) revel.Result {
re := info.NewRe()
userId := c.GetUserId()
note := noteService.GetNote(noteId, userId)
note := noteService.GetNoteById(noteId)
if note.NoteId == "" {
re.Msg = "No Note"
return c.RenderJson(re)
}
content := noteService.GetNoteContent(noteId, userId)
noteUserId := note.UserId.Hex()
// 是否有权限
if noteUserId != userId {
// 是否是有权限协作的
if !note.IsBlog && !shareService.HasReadPerm(noteUserId, userId, noteId) {
re.Msg = "No Perm"
return c.RenderJson(re)
}
}
// path 判断是否需要重新生成之
fileUrlPath := "/upload/" + userId + "/images/weibo"
fileUrlPath := "/upload/" + noteUserId + "/images/weibo"
dir := revel.BasePath + "/public/" + fileUrlPath
if !ClearDir(dir) {
re.Msg = "No Dir"
return c.RenderJson(re)
}
filename := note.NoteId.Hex() + ".png";
path := dir + "/" + filename
// 生成之
html2image.ToImage(userId, c.GetUsername(), noteId, note.Title, content.Content, path)
// cookie
cookieName := revel.CookiePrefix + "_SESSION"
cookie, err := c.Request.Cookie(cookieName)
cookieStr := cookie.String()
cookieValue := ""
if err == nil && len(cookieStr) > len(cookieName) {
cookieValue = cookieStr[len(cookieName)+1:]
}
appKey, _ := revel.Config.String("app.secret")
cookieDomain, _ := revel.Config.String("cookie.domain")
// 生成之
url := siteUrl + "/note/toImage?noteId=" + noteId + "&appKey=" + appKey;
// /Users/life/Documents/bin/phantomjs/bin/phantomjs /Users/life/Desktop/test/b.js
binPath := configService.GetGlobalStringConfig("toImageBinPath")
if binPath == "" {
return c.RenderJson(re);
}
cc := binPath + " \"" + url + "\" \"" + path + "\" \"" + cookieDomain + "\" \"" + cookieName + "\" \"" + cookieValue + "\""
cmd := exec.Command("/bin/sh", "-c", cc)
Log(cc);
b, err := cmd.Output()
if err == nil {
re.Ok = true
re.Id = fileUrlPath + "/" + filename
} else {
re.Msg = string(b)
Log("error:......")
Log(string(b))
}
return c.RenderJson(re)
/*
// 这里速度慢, 生成不完全(图片和内容都不全)
content := noteService.GetNoteContent(noteId, noteUserId)
userInfo := userService.GetUserInfo(noteUserId);
c.SetLocale()
c.RenderArgs["blog"] = note
c.RenderArgs["content"] = content.Content
c.RenderArgs["userInfo"] = userInfo
userBlog := blogService.GetUserBlog(noteUserId)
c.RenderArgs["userBlog"] = userBlog
html := c.RenderTemplateStr("html2Image/index.html") // Result类型的
contentFile := dir + "/html";
fout, err := os.Create(contentFile)
if err != nil {
return c.RenderJson(re)
}
fout.WriteString(html);
fout.Close()
cc := "/Users/life/Documents/bin/phantomjs/bin/phantomjs /Users/life/Desktop/test/c.js \"" + contentFile + "\" \"" + path + "\""
cmd := exec.Command("/bin/sh", "-c", cc)
b, err := cmd.Output()
if err == nil {
re.Ok = true
re.Id = fileUrlPath + "/" + filename
} else {
Log(string(b))
}
*/
}

View File

@@ -8,7 +8,7 @@ import (
"github.com/leanote/leanote/app/info"
// "github.com/leanote/leanote/app/types"
// "io/ioutil"
"fmt"
// "fmt"
// "math"
// "os"
// "path"
@@ -19,43 +19,48 @@ type User struct {
BaseController
}
func (c User) Account(tab int) revel.Result {
userInfo := c.GetUserInfo()
c.RenderArgs["userInfo"] = userInfo
c.RenderArgs["tab"] = tab
c.SetLocale()
return c.RenderTemplate("user/account.html")
}
// 修改用户名, 需要重置session
func (c User) UpdateUsername(username string) revel.Result {
re := info.NewRe();
// 判断是否满足最基本的, 4位, 不含特殊字符, 大小写无关. email大小写无关
if len(username) < 4 {
re.Ok = false
re.Msg = "至少4位"
return c.RenderJson(re);
if(c.GetUsername() == "demo") {
re.Msg = "cannotUpdateDemo"
return c.RenderRe(re);
}
if !IsUsername(username) {
re.Ok = false
re.Msg = "不能包含特殊字符"
return c.RenderJson(re);
if re.Ok, re.Msg = Vd("username", username); !re.Ok {
return c.RenderRe(re);
}
re.Ok, re.Msg = userService.UpdateUsername(c.GetUserId(), username)
if(re.Ok) {
c.UpdateSession("Username", username)
}
return c.RenderJson(re);
return c.RenderRe(re);
}
// 修改密码
func (c User) UpdatePwd(oldPwd, pwd string) revel.Result {
re := info.NewRe();
if oldPwd == "" {
re.Msg = "旧密码错误"
return c.RenderJson(re);
if(c.GetUsername() == "demo") {
re.Msg = "cannotUpdateDemo"
return c.RenderRe(re);
}
re.Ok, re.Msg = IsGoodPwd(pwd)
if !re.Ok {
return c.RenderJson(re);
if re.Ok, re.Msg = Vd("password", oldPwd); !re.Ok {
return c.RenderRe(re);
}
if re.Ok, re.Msg = Vd("password", pwd); !re.Ok {
return c.RenderRe(re);
}
re.Ok, re.Msg = userService.UpdatePwd(c.GetUserId(), oldPwd, pwd)
return c.RenderJson(re);
return c.RenderRe(re);
}
// 更新主题
@@ -75,14 +80,7 @@ func (c User) SendRegisterEmail(content, toEmail string) revel.Result {
return c.RenderJson(re);
}
// 发送邮件
var userInfo = c.GetUserInfo();
siteUrl, _ := revel.Config.String("site.url")
url := siteUrl + "/register?from=" + userInfo.Username
body := fmt.Sprintf("点击链接注册leanote: <a href='%v'>%v</a>. ", url, url);
body = content + "<br />" + body
re.Ok = SendEmail(toEmail, userInfo.Username + "邀请您注册leanote", "邀请注册", body)
re.Ok = emailService.SendInviteEmail(c.GetUserInfo(), toEmail, content)
return c.RenderJson(re);
}
@@ -91,15 +89,23 @@ func (c User) SendRegisterEmail(content, toEmail string) revel.Result {
// 重新发送激活邮件
func (c User) ReSendActiveEmail() revel.Result {
re := info.NewRe()
re.Ok = userService.RegisterSendActiveEmail(c.GetUserId(), c.GetEmail())
re.Ok = emailService.RegisterSendActiveEmail(c.GetUserInfo(), c.GetEmail())
return c.RenderJson(re)
}
// 修改Email发送激活邮箱
func (c User) UpdateEmailSendActiveEmail(email string) revel.Result {
re := info.NewRe()
re.Ok, re.Msg = userService.UpdateEmailSendActiveEmail(c.GetUserId(), email)
return c.RenderJson(re)
if(c.GetUsername() == "demo") {
re.Msg = "cannotUpdateDemo"
return c.RenderJson(re);
}
if re.Ok, re.Msg = Vd("email", email); !re.Ok {
return c.RenderRe(re);
}
re.Ok, re.Msg = emailService.UpdateEmailSendActiveEmail(c.GetUserInfo(), email)
return c.RenderRe(re)
}
// 通过点击链接
@@ -146,21 +152,11 @@ func (c User) ActiveEmail(token string) revel.Result {
func (c User) AddAccount(email, pwd string) revel.Result {
re := info.NewRe()
if email == "" {
re.Msg = "请输入邮箱"
return c.RenderJson(re)
} else if !IsEmail(email) {
re.Msg = "请输入正确的邮箱"
return c.RenderJson(re)
if re.Ok, re.Msg = Vd("email", email); !re.Ok {
return c.RenderRe(re);
}
// 密码
if pwd == "" {
re.Msg = "请输入密码"
return c.RenderJson(re)
} else if len(pwd) < 6 {
re.Msg = "密码长度至少6位"
return c.RenderJson(re)
if re.Ok, re.Msg = Vd("password", pwd); !re.Ok {
return c.RenderRe(re);
}
re.Ok, re.Msg = userService.ThirdAddUser(c.GetUserId(), email, pwd)
@@ -169,17 +165,20 @@ func (c User) AddAccount(email, pwd string) revel.Result {
c.UpdateSession("Email", email);
}
return c.RenderJson(re)
return c.RenderRe(re)
}
//-----------------
// 用户偏爱
func (c User) UpdateColumnWidth(notebookWidth, noteListWidth int) revel.Result {
func (c User) UpdateColumnWidth(notebookWidth, noteListWidth, mdEditorWidth int) revel.Result {
re := info.NewRe()
re.Ok = userService.UpdateColumnWidth(c.GetUserId(), notebookWidth, noteListWidth)
re.Ok = userService.UpdateColumnWidth(c.GetUserId(), notebookWidth, noteListWidth, mdEditorWidth)
if re.Ok {
c.UpdateSession("NotebookWidth", strconv.Itoa(notebookWidth));
c.UpdateSession("NoteListWidth", strconv.Itoa(noteListWidth));
c.UpdateSession("MdEditorWidth", strconv.Itoa(mdEditorWidth));
LogJ(c.Session)
}
return c.RenderJson(re)
}

View File

@@ -49,3 +49,11 @@ func (c AdminBaseController) getSorter(sorterField string, isAsc bool, okSorter
c.RenderArgs["sorter"] = sorter
return sorterField, isAsc;
}
func (c AdminBaseController) updateConfig(keys []string) {
userId := c.GetUserId()
for _, key := range keys {
v := c.Params.Values.Get(key)
configService.UpdateGlobalStringConfig(userId, key, v)
}
}

View File

@@ -17,9 +17,22 @@ func (c Admin) Index() revel.Result {
c.RenderArgs["title"] = "leanote"
c.SetLocale()
c.RenderArgs["countUser"] = userService.CountUser()
c.RenderArgs["countNote"] = noteService.CountNote()
c.RenderArgs["countBlog"] = noteService.CountBlog()
return c.RenderTemplate("admin/index.html");
}
// 模板
func (c Admin) T(t string) revel.Result {
c.RenderArgs["str"] = configService.GlobalStringConfigs
c.RenderArgs["arr"] = configService.GlobalArrayConfigs
c.RenderArgs["map"] = configService.GlobalMapConfigs
c.RenderArgs["arrMap"] = configService.GlobalArrMapConfigs
return c.RenderTemplate("admin/" + t + ".html")
}
func (c Admin) GetView(view string) revel.Result {
return c.RenderTemplate("admin/" + view);
}

View File

@@ -0,0 +1,114 @@
package admin
import (
"github.com/revel/revel"
. "github.com/leanote/leanote/app/lea"
"github.com/leanote/leanote/app/info"
"archive/tar"
"compress/gzip"
"os"
"io"
"time"
)
// 数据管理, 备份和恢复
type AdminData struct {
AdminBaseController
}
func (c AdminData) Index() revel.Result {
backups := configService.GetGlobalArrMapConfig("backups")
// 逆序之
backups2 := make([]map[string]string, len(backups))
j := 0
for i := len(backups)-1; i >= 0; i-- {
backups2[j] = backups[i]
j++
}
c.RenderArgs["backups"] = backups2
return c.RenderTemplate("admin/data/index.html");
}
func (c AdminData) Backup() revel.Result {
re := info.NewRe()
re.Ok, re.Msg = configService.Backup("")
return c.RenderJson(re)
}
// 还原
func (c AdminData) Restore(createdTime string) revel.Result {
re := info.Re{}
re.Ok, re.Msg = configService.Restore(createdTime)
return c.RenderJson(re)
}
func (c AdminData) Delete(createdTime string) revel.Result {
re := info.Re{}
re.Ok, re.Msg = configService.DeleteBackup(createdTime)
return c.RenderJson(re)
}
func (c AdminData) UpdateRemark(createdTime, remark string) revel.Result {
re := info.Re{}
re.Ok, re.Msg = configService.UpdateBackupRemark(createdTime, remark)
return c.RenderJson(re)
}
func (c AdminData) Download(createdTime string) revel.Result {
backup, ok := configService.GetBackup(createdTime)
if !ok {
return c.RenderText("")
}
dbname, _ := revel.Config.String("db.dbname")
path := backup["path"] + "/" + dbname
allFiles := ListDir(path)
filename := "backup_" + dbname + "_" + backup["createdTime"] + ".tar.gz"
// file write
fw, err := os.Create(revel.BasePath + "/files/" + filename)
if err != nil {
return c.RenderText("")
}
// defer fw.Close() // 不需要关闭, 还要读取给用户下载
// gzip write
gw := gzip.NewWriter(fw)
defer gw.Close()
// tar write
tw := tar.NewWriter(gw)
defer tw.Close()
// 遍历文件列表
for _, file := range allFiles {
fn := path + "/" + file
fr, err := os.Open(fn)
fileInfo, _ := fr.Stat()
if err != nil {
return c.RenderText("")
}
defer fr.Close()
// 信息头
h := new(tar.Header)
h.Name = file
h.Size = fileInfo.Size()
h.Mode = int64(fileInfo.Mode())
h.ModTime = fileInfo.ModTime()
// 写信息头
err = tw.WriteHeader(h)
if err != nil {
panic(err)
}
// 写文件
_, err = io.Copy(tw, fr)
if err != nil {
panic(err)
}
} // for
return c.RenderBinary(fw, filename, revel.Attachment, time.Now()) // revel.Attachm
}

View File

@@ -0,0 +1,233 @@
package admin
import (
"github.com/revel/revel"
. "github.com/leanote/leanote/app/lea"
"github.com/leanote/leanote/app/info"
"strings"
"strconv"
)
// admin 首页
type AdminEmail struct {
AdminBaseController
}
// email配置
func (c AdminEmail) Email() revel.Result {
return nil
}
// blog标签设置
func (c AdminEmail) Blog() revel.Result {
recommendTags := configService.GetGlobalArrayConfig("recommendTags")
newTags := configService.GetGlobalArrayConfig("newTags")
c.RenderArgs["recommendTags"] = strings.Join(recommendTags, ",")
c.RenderArgs["newTags"] = strings.Join(newTags, ",")
return c.RenderTemplate("admin/setting/blog.html");
}
func (c AdminEmail) DoBlogTag(recommendTags, newTags string) revel.Result {
re := info.NewRe()
re.Ok = configService.UpdateGlobalArrayConfig(c.GetUserId(), "recommendTags", strings.Split(recommendTags, ","))
re.Ok = configService.UpdateGlobalArrayConfig(c.GetUserId(), "newTags", strings.Split(newTags, ","))
return c.RenderJson(re)
}
// demo
// blog标签设置
func (c AdminEmail) Demo() revel.Result {
c.RenderArgs["demoUsername"] = configService.GetGlobalStringConfig("demoUsername")
c.RenderArgs["demoPassword"] = configService.GetGlobalStringConfig("demoPassword")
return c.RenderTemplate("admin/setting/demo.html");
}
func (c AdminEmail) DoDemo(demoUsername, demoPassword string) revel.Result {
re := info.NewRe()
userInfo := authService.Login(demoUsername, demoPassword)
if userInfo.UserId == "" {
re.Msg = "The User is Not Exists";
return c.RenderJson(re)
}
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "demoUserId", userInfo.UserId.Hex())
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "demoUsername", demoUsername)
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "demoPassword", demoPassword)
return c.RenderJson(re)
}
// ToImage
// 长微博的bin路径phantomJs
func (c AdminEmail) ToImage() revel.Result {
c.RenderArgs["toImageBinPath"] = configService.GetGlobalStringConfig("toImageBinPath")
return c.RenderTemplate("admin/setting/toImage.html");
}
func (c AdminEmail) DoToImage(toImageBinPath string) revel.Result {
re := info.NewRe()
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "toImageBinPath", toImageBinPath)
return c.RenderJson(re)
}
func (c AdminEmail) Set(emailHost, emailPort, emailUsername, emailPassword string) revel.Result {
re := info.NewRe()
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "emailHost", emailHost)
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "emailPort", emailPort)
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "emailUsername", emailUsername)
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "emailPassword", emailPassword)
return c.RenderJson(re)
}
func (c AdminEmail) Template() revel.Result {
re := info.NewRe()
keys := []string{"emailTemplateHeader", "emailTemplateFooter",
"emailTemplateRegisterSubject",
"emailTemplateRegister",
"emailTemplateFindPasswordSubject",
"emailTemplateFindPassword",
"emailTemplateUpdateEmailSubject",
"emailTemplateUpdateEmail",
"emailTemplateInviteSubject",
"emailTemplateInvite",
"emailTemplateCommentSubject",
"emailTemplateComment",
}
userId := c.GetUserId()
for _, key := range keys {
v := c.Params.Values.Get(key)
if v != "" {
ok, msg := emailService.ValidTpl(v)
if !ok {
re.Ok = false
re.Msg = "Error key: " + key + "<br />" + msg
return c.RenderJson(re)
} else {
configService.UpdateGlobalStringConfig(userId, key, v)
}
}
}
re.Ok = true
return c.RenderJson(re)
}
// 发送Email
func (c AdminEmail) SendEmailToEmails(sendEmails, latestEmailSubject, latestEmailBody string, verified, saveAsOldEmail bool) revel.Result {
re := info.NewRe()
c.updateConfig([]string{"sendEmails", "latestEmailSubject", "latestEmailBody"})
if latestEmailSubject == "" || latestEmailBody == "" {
re.Msg = "subject or body is blank"
return c.RenderJson(re)
}
if saveAsOldEmail {
oldEmails := configService.GetGlobalMapConfig("oldEmails")
oldEmails[latestEmailSubject] = latestEmailBody
configService.UpdateGlobalMapConfig(c.GetUserId(), "oldEmails", oldEmails);
}
sendEmails = strings.Replace(sendEmails, "\r", "", -1)
emails := strings.Split(sendEmails, "\n")
re.Ok, re.Msg = emailService.SendEmailToEmails(emails, latestEmailSubject, latestEmailBody);
return c.RenderJson(re)
}
// 发送Email
func (c AdminEmail) SendToUsers2(emails, latestEmailSubject, latestEmailBody string, verified, saveAsOldEmail bool) revel.Result {
re := info.NewRe()
c.updateConfig([]string{"sendEmails", "latestEmailSubject", "latestEmailBody"})
if latestEmailSubject == "" || latestEmailBody == "" {
re.Msg = "subject or body is blank"
return c.RenderJson(re)
}
if saveAsOldEmail {
oldEmails := configService.GetGlobalMapConfig("oldEmails")
oldEmails[latestEmailSubject] = latestEmailBody
configService.UpdateGlobalMapConfig(c.GetUserId(), "oldEmails", oldEmails);
}
emails = strings.Replace(emails, "\r", "", -1)
emailsArr := strings.Split(emails, "\n")
users := userService.ListUserInfosByEmails(emailsArr)
LogJ(emailsArr)
re.Ok, re.Msg = emailService.SendEmailToUsers(users, latestEmailSubject, latestEmailBody);
return c.RenderJson(re)
}
// send Email dialog
func (c AdminEmail) SendEmailDialog(emails string) revel.Result{
emailsArr := strings.Split(emails, ",")
emailsNl := strings.Join(emailsArr, "\n")
c.RenderArgs["emailsNl"] = emailsNl
c.RenderArgs["str"] = configService.GlobalStringConfigs
c.RenderArgs["map"] = configService.GlobalMapConfigs
return c.RenderTemplate("admin/email/emailDialog.html");
}
func (c AdminEmail) SendToUsers(userFilterEmail, userFilterWhiteList, userFilterBlackList, latestEmailSubject, latestEmailBody string, verified, saveAsOldEmail bool) revel.Result {
re := info.NewRe()
c.updateConfig([]string{"userFilterEmail", "userFilterWhiteList", "userFilterBlackList", "latestEmailSubject", "latestEmailBody"})
if latestEmailSubject == "" || latestEmailBody == "" {
re.Msg = "subject or body is blank"
return c.RenderJson(re)
}
if saveAsOldEmail {
oldEmails := configService.GetGlobalMapConfig("oldEmails")
oldEmails[latestEmailSubject] = latestEmailBody
configService.UpdateGlobalMapConfig(c.GetUserId(), "oldEmails", oldEmails);
}
users := userService.GetAllUserByFilter(userFilterEmail, userFilterWhiteList, userFilterBlackList, verified)
if(users == nil || len(users) == 0) {
re.Ok = false
re.Msg = "no users"
return c.RenderJson(re)
}
re.Ok, re.Msg = emailService.SendEmailToUsers(users, latestEmailSubject, latestEmailBody);
if(!re.Ok) {
return c.RenderJson(re)
}
re.Ok = true
re.Msg = "users:" + strconv.Itoa(len(users))
return c.RenderJson(re)
}
// 删除emails
func (c AdminEmail) DeleteEmails(ids string) revel.Result {
re := info.NewRe()
re.Ok = emailService.DeleteEmails(strings.Split(ids, ","))
return c.RenderJson(re)
}
func (c AdminEmail) List(sorter, keywords string) revel.Result {
pageNumber := c.GetPage()
sorterField, isAsc := c.getSorter("CreatedTime", false, []string{"email", "ok", "subject", "createdTime"});
pageInfo, emails := emailService.ListEmailLogs(pageNumber, userPageSize, sorterField, isAsc, keywords);
c.RenderArgs["pageInfo"] = pageInfo
c.RenderArgs["emails"] = emails
c.RenderArgs["keywords"] = keywords
return c.RenderTemplate("admin/email/list.html");
}

View File

@@ -29,12 +29,22 @@ func (c AdminSetting) Blog() revel.Result {
func (c AdminSetting) DoBlogTag(recommendTags, newTags string) revel.Result {
re := info.NewRe()
re.Ok = configService.UpdateUserArrayConfig(c.GetUserId(), "recommendTags", strings.Split(recommendTags, ","))
re.Ok = configService.UpdateUserArrayConfig(c.GetUserId(), "newTags", strings.Split(newTags, ","))
re.Ok = configService.UpdateGlobalArrayConfig(c.GetUserId(), "recommendTags", strings.Split(recommendTags, ","))
re.Ok = configService.UpdateGlobalArrayConfig(c.GetUserId(), "newTags", strings.Split(newTags, ","))
return c.RenderJson(re)
}
// 共享设置
func (c AdminSetting) ShareNote(registerSharedUserId string,
registerSharedNotebookPerms, registerSharedNotePerms []int,
registerSharedNotebookIds, registerSharedNoteIds, registerCopyNoteIds []string) revel.Result {
re := info.NewRe()
re.Ok, re.Msg = configService.UpdateShareNoteConfig(registerSharedUserId, registerSharedNotebookPerms, registerSharedNotePerms, registerSharedNotebookIds, registerSharedNoteIds, registerCopyNoteIds);
return c.RenderJson(re)
}
// demo
// blog标签设置
func (c AdminSetting) Demo() revel.Result {
@@ -51,12 +61,53 @@ func (c AdminSetting) DoDemo(demoUsername, demoPassword string) revel.Result {
return c.RenderJson(re)
}
re.Ok = configService.UpdateUserStringConfig(c.GetUserId(), "demoUserId", userInfo.UserId.Hex())
re.Ok = configService.UpdateUserStringConfig(c.GetUserId(), "demoUsername", demoUsername)
re.Ok = configService.UpdateUserStringConfig(c.GetUserId(), "demoPassword", demoPassword)
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "demoUserId", userInfo.UserId.Hex())
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "demoUsername", demoUsername)
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "demoPassword", demoPassword)
return c.RenderJson(re)
}
// ToImage
// 长微博的bin路径phantomJs
func (c AdminSetting) ToImage() revel.Result {
c.RenderArgs["toImageBinPath"] = configService.GetGlobalStringConfig("toImageBinPath")
return c.RenderTemplate("admin/setting/toImage.html");
}
func (c AdminSetting) DoToImage(toImageBinPath string) revel.Result {
re := info.NewRe()
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "toImageBinPath", toImageBinPath)
return c.RenderJson(re)
}
// SubDomain
func (c AdminSetting) SubDomain() revel.Result {
c.RenderArgs["str"] = configService.GlobalStringConfigs
c.RenderArgs["arr"] = configService.GlobalArrayConfigs
c.RenderArgs["noteSubDomain"] = configService.GetGlobalStringConfig("noteSubDomain")
c.RenderArgs["blogSubDomain"] = configService.GetGlobalStringConfig("blogSubDomain")
c.RenderArgs["leaSubDomain"] = configService.GetGlobalStringConfig("leaSubDomain")
return c.RenderTemplate("admin/setting/subDomain.html");
}
func (c AdminSetting) DoSubDomain(noteSubDomain, blogSubDomain, leaSubDomain, blackSubDomains, allowCustomDomain, blackCustomDomains string) revel.Result {
re := info.NewRe()
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "noteSubDomain", noteSubDomain)
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "blogSubDomain", blogSubDomain)
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "leaSubDomain", leaSubDomain)
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "allowCustomDomain", allowCustomDomain)
re.Ok = configService.UpdateGlobalArrayConfig(c.GetUserId(), "blackSubDomains", strings.Split(blackSubDomains, ","))
re.Ok = configService.UpdateGlobalArrayConfig(c.GetUserId(), "blackCustomDomains", strings.Split(blackCustomDomains, ","))
return c.RenderJson(re)
}
func (c AdminSetting) Mongodb(mongodumpPath, mongorestorePath string) revel.Result {
re := info.NewRe()
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "mongodumpPath", mongodumpPath)
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "mongorestorePath", mongorestorePath)
return c.RenderJson(re)
}

View File

@@ -0,0 +1,18 @@
package admin
import (
"github.com/revel/revel"
// "encoding/json"
// . "github.com/leanote/leanote/app/lea"
// "io/ioutil"
)
// Upgrade controller
type AdminUpgrade struct {
AdminBaseController
}
func (c AdminUpgrade) UpgradeBlog() revel.Result {
upgradeService.UpgradeBlog()
return nil;
}

View File

@@ -25,6 +25,8 @@ var noteImageService *service.NoteImageService
var fileService *service.FileService
var attachService *service.AttachService
var configService *service.ConfigService
var emailService *service.EmailService
var upgradeService *service.UpgradeService
var adminUsername = "admin"
// 拦截器
@@ -115,12 +117,18 @@ func InitService() {
suggestionService = service.SuggestionS
authService = service.AuthS
configService = service.ConfigS
emailService = service.EmailS
upgradeService = service.UpgradeS
}
func init() {
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &Admin{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &AdminSetting{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &AdminUser{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &AdminBlog{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &AdminEmail{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &AdminUpgrade{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &AdminData{})
revel.OnAppStart(func() {
adminUsername, _ = revel.Config.String("adminUsername")
})

View File

@@ -25,6 +25,8 @@ var noteImageService *service.NoteImageService
var fileService *service.FileService
var attachService *service.AttachService
var configService *service.ConfigService
var emailService *service.EmailService
var sessionService *service.SessionService
var pageSize = 1000
var defaultSortField = "UpdatedTime"
@@ -47,10 +49,15 @@ var commonUrl = map[string]map[string]bool{"Index": map[string]bool{"Index": tru
"FindPasswordUpdate": true,
"Suggestion": true,
},
"Note": map[string]bool{"ToImage": true},
"Blog": map[string]bool{"Index": true,
"View": true,
"AboutMe": true,
"SearchBlog": true,
"Cate": true,
"Search": true,
"GetLikeAndComments": true,
"IncReadNum": true,
"ListComments": true,
},
// 用户的激活与修改邮箱都不需要登录, 通过链接地址
"User": map[string]bool{"UpdateEmail": true,
@@ -118,6 +125,8 @@ func InitService() {
suggestionService = service.SuggestionS
authService = service.AuthS
configService = service.ConfigS
emailService = service.EmailS
sessionService = service.SessionS
}
func init() {

View File

@@ -40,6 +40,15 @@ var Attachs *mgo.Collection
var NoteImages *mgo.Collection
var Configs *mgo.Collection
var EmailLogs *mgo.Collection
// blog
var BlogLikes *mgo.Collection
var BlogComments *mgo.Collection
var Reports *mgo.Collection
// session
var Sessions *mgo.Collection
// 初始化时连接数据库
func Init() {
@@ -113,12 +122,17 @@ func Init() {
NoteImages = Session.DB(dbname).C("note_images")
Configs = Session.DB(dbname).C("configs")
}
EmailLogs = Session.DB(dbname).C("email_logs")
func init() {
revel.OnAppStart(func() {
Init()
})
// 社交
BlogLikes = Session.DB(dbname).C("blog_likes")
BlogComments = Session.DB(dbname).C("blog_comments")
// 举报
Reports = Session.DB(dbname).C("reports")
// session
Sessions = Session.DB(dbname).C("sessions")
}
func close() {

View File

@@ -10,8 +10,8 @@ import (
// convert revel msg to js msg
var msgBasePath = "/Users/life/Documents/Go/package/src/github.com/leanote/leanote/messages/"
var targetBasePath = "/Users/life/Documents/Go/package/src/github.com/leanote/leanote/public/js/i18n/"
var msgBasePath = "/Users/life/Documents/Go/package1/src/github.com/leanote/leanote/messages/"
var targetBasePath = "/Users/life/Documents/Go/package1/src/github.com/leanote/leanote/public/js/i18n/"
func parse(filename string) {
file, err := os.Open(msgBasePath + filename)
reader := bufio.NewReader(file)
@@ -62,11 +62,28 @@ func parse(filename string) {
if err2 != nil {
file2, err2 = os.Create(targetName)
}
file2.WriteString("var MSG = " + str + ";")
file2.WriteString("var MSG = " + str + ";" + `
function getMsg(key, data) {
var msg = MSG[key]
if(msg) {
if(data) {
if(!isArray(data)) {
data = [data];
}
for(var i = 0; i < data.length; ++i) {
msg = msg.replace("%s", data[i]);
}
}
return msg;
}
return key;
}`)
}
// 生成js的i18n文件
func main() {
parse("msg.en")
parse("msg.zh")
parse("blog.zh")
parse("blog.en")
}

View File

@@ -2,6 +2,7 @@ package info
import (
"gopkg.in/mgo.v2/bson"
"time"
)
// 只为blog, 不为note
@@ -22,11 +23,13 @@ type UserBlogBase struct {
type UserBlogComment struct {
CanComment bool `CanComment` // 是否可以评论
CommentType string `CommentType` // default 或 disqus
DisqusId string `DisqusId`
}
type UserBlogStyle struct {
Style string `Style` // 风格
Css string `Css` // 自定义css
}
// 每个用户一份博客设置信息
@@ -38,7 +41,46 @@ type UserBlog struct {
AboutMe string `AboutMe` // 关于我
CanComment bool `CanComment` // 是否可以评论
CommentType string `CommentType` // default 或 disqus
DisqusId string `DisqusId`
Style string `Style` // 风格
Css string `Css` // 自定义css
SubDomain string `SubDomain` // 二级域名
Domain string `Domain` // 自定义域名
}
//------------------------
// 社交功能, 点赞, 分享, 评论
// 点赞记录
type BlogLike struct {
LikeId bson.ObjectId `bson:"_id"`
NoteId bson.ObjectId `NoteId`
UserId bson.ObjectId `UserId`
CreatedTime time.Time `CreatedTime`
}
// 评论
type BlogComment struct {
CommentId bson.ObjectId `bson:"_id"`
NoteId bson.ObjectId `NoteId`
UserId bson.ObjectId `UserId` // UserId回复ToUserId
Content string `Content` // 评论内容
ToCommentId bson.ObjectId `ToCommendId,omitempty` // 对某条评论进行回复
ToUserId bson.ObjectId `ToUserId,omitempty` // 为空表示直接评论, 不回空表示回复某人
LikeNum int `LikeNum` // 点赞次数, 评论也可以点赞
LikeUserIds []string `LikeUserIds` // 点赞的用户ids
CreatedTime time.Time `CreatedTime`
}
type BlogCommentPublic struct {
BlogComment
IsILikeIt bool
}

View File

@@ -5,11 +5,21 @@ import (
"time"
)
// 配置
// 用户配置高于全局配置
// 配置, 每一个配置一行记录
type Config struct {
UserId bson.ObjectId `bson:"_id"`
StringConfigs map[string]string `StringConfigs` // key => value
ArrayConfigs map[string][]string `ArrayConfigs` // key => []value
ConfigId bson.ObjectId `bson:"_id"`
UserId bson.ObjectId `UserId`
Key string `Key`
ValueStr string `ValueStr,omitempty` // "1"
ValueArr []string `ValueArr,omitempty` // ["1","b","c"]
ValueMap map[string]string `ValueMap,omitempty` // {"a":"bb", "CC":"xx"}
ValueArrMap []map[string]string `ValueArrMap,omitempty` // [{"a":"B"}, {}, {}]
IsArr bool `IsArr` // 是否是数组
IsMap bool `IsMap` // 是否是Map
IsArrMap bool `IsArrMap` // 是否是数组Map
// StringConfigs map[string]string `StringConfigs` // key => value
// ArrayConfigs map[string][]string `ArrayConfigs` // key => []value
UpdatedTime time.Time `UpdatedTime`
}

19
app/info/EmailLogInfo.go Normal file
View File

@@ -0,0 +1,19 @@
package info
import (
"gopkg.in/mgo.v2/bson"
"time"
)
// 发送邮件
type EmailLog struct {
LogId bson.ObjectId `bson:"_id"`
Email string `Email` // 发送者
Subject string `Subject` // 主题
Body string `Body` // 内容
Msg string `Msg` // 发送失败信息
Ok bool `Ok` // 发送是否成功
CreatedTime time.Time `CreatedTime`
}

View File

@@ -24,12 +24,19 @@ type Note struct {
IsRecommend bool `IsRecommend,omitempty` // 是否为推荐博客 2014/9/24新加
IsTop bool `IsTop,omitempty` // blog是否置顶
// 2014/9/28 添加评论社交功能
ReadNum int `ReadNum,omitempty` // 阅读次数 2014/9/28
LikeNum int `LikeNum,omitempty` // 点赞次数 2014/9/28
CommentNum int `CommentNum,omitempty` // 评论次数 2014/9/28
IsMarkdown bool `IsMarkdown` // 是否是markdown笔记, 默认是false
AttachNum int `AttachNum` // 2014/9/21, attachments num
CreatedTime time.Time `CreatedTime`
UpdatedTime time.Time `UpdatedTime`
RecommendTime time.Time `RecommendTime,omitempty` // 推荐时间
PublicTime time.Time `PublicTime,omitempty` // 发表时间, 公开为博客则设置
UpdatedUserId bson.ObjectId `bson:"UpdatedUserId"` // 如果共享了, 并可写, 那么可能是其它他修改了
}

19
app/info/ReportInfo.go Normal file
View File

@@ -0,0 +1,19 @@
package info
import (
"gopkg.in/mgo.v2/bson"
"time"
)
// 举报
type Report struct {
ReportId bson.ObjectId `bson:"_id"`
NoteId bson.ObjectId `NoteId`
UserId bson.ObjectId `UserId` // UserId回复ToUserId
Reason string `Reason` // 评论内容
CommentId bson.ObjectId `CommendId,omitempty` // 对某条评论进行回复
CreatedTime time.Time `CreatedTime`
}

19
app/info/SessionInfo.go Normal file
View File

@@ -0,0 +1,19 @@
package info
import (
"gopkg.in/mgo.v2/bson"
"time"
)
// http://docs.mongodb.org/manual/tutorial/expire-data/
type Session struct {
Id bson.ObjectId `bson:"_id,omitempty"` // 没有意义
SessionId string `bson:"SessionId"` // SessionId
LoginTimes int `LoginTimes` // 登录错误时间
Captcha string `Captcha` // 验证码
CreatedTime time.Time `CreatedTime`
UpdatedTime time.Time `UpdatedTime` // 更新时间, expire这个时间会自动清空
}

View File

@@ -27,6 +27,7 @@ type User struct {
// 用户配置
NotebookWidth int `NotebookWidth` // 笔记本宽度
NoteListWidth int `NoteListWidth` // 笔记列表宽度
MdEditorWidth int `MdEditorWidth` // markdown 左侧编辑器宽度
LeftIsMin bool `LeftIsMin` // 左侧是否是隐藏的, 默认是打开的
// 这里 第三方登录

View File

@@ -4,9 +4,13 @@ import (
"github.com/revel/revel"
. "github.com/leanote/leanote/app/lea"
"github.com/leanote/leanote/app/service"
"github.com/leanote/leanote/app/db"
"github.com/leanote/leanote/app/controllers"
"github.com/leanote/leanote/app/controllers/admin"
_ "github.com/leanote/leanote/app/lea/binder"
"github.com/leanote/leanote/app/lea/session"
"github.com/leanote/leanote/app/lea/memcache"
"github.com/leanote/leanote/app/lea/route"
"reflect"
"fmt"
"html/template"
@@ -14,20 +18,24 @@ import (
"strings"
"strconv"
"time"
"encoding/json"
)
func init() {
// Filters is the default set of global filters.
revel.Filters = []revel.Filter{
revel.PanicFilter, // Recover from panics and display an error page instead.
RouterFilter,
route.RouterFilter,
// revel.RouterFilter, // Use the routing table to select the right Action
// AuthFilter, // Invoke the action.
revel.FilterConfiguringFilter, // A hook for adding or removing per-Action filters.
revel.ParamsFilter, // Parse parameters into Controller.Params.
revel.SessionFilter, // Restore and write the session cookie.
// revel.SessionFilter, // Restore and write the session cookie.
// session.SessionFilter, // leanote memcache session life
// 使用SessionFilter标准版从cookie中得到sessionID, 然后通过MssessionFilter从Memcache中得到
// session, 之后MSessionFilter将session只存sessionID然后返回给SessionFilter返回到web
session.SessionFilter, // leanote session
// session.MSessionFilter, // leanote memcache session
revel.FlashFilter, // Restore and write the flash cookie.
revel.ValidationFilter, // Restore kept validation errors and save new ones from cookie.
@@ -48,12 +56,34 @@ func init() {
i = i - 1;
return i
}
revel.TemplateFuncs["join"] = func(arr []string) template.HTML {
if arr == nil {
return template.HTML("")
}
return template.HTML(strings.Join(arr, ","))
}
revel.TemplateFuncs["concat"] = func(s1, s2 string) template.HTML {
return template.HTML(s1 + s2)
}
revel.TemplateFuncs["concatStr"] = func(strs ...string) string {
str := ""
for _, s := range strs {
str += s
}
return str
}
revel.TemplateFuncs["json"] = func(i interface{}) string {
b, _ := json.Marshal(i)
return string(b)
}
revel.TemplateFuncs["datetime"] = func(t time.Time) template.HTML {
return template.HTML(t.Format("2006-01-02 15:04:05"))
}
revel.TemplateFuncs["unixDatetime"] = func(unixSec string) template.HTML {
sec, _ := strconv.Atoi(unixSec)
t := time.Unix(int64(sec), 0)
return template.HTML(t.Format("2006-01-02 15:04:05"))
}
// interface是否有该字段
revel.TemplateFuncs["has"] = func(i interface{}, key string) bool {
@@ -63,6 +93,26 @@ func init() {
}
// tags
revel.TemplateFuncs["blogTags"] = func(renderArgs map[string]interface{}, tags []string) template.HTML {
if tags == nil || len(tags) == 0 {
return ""
}
locale, _ := renderArgs[revel.CurrentLocaleRenderArg].(string)
tagStr := ""
lenTags := len(tags)
for i, tag := range tags {
str := revel.Message(locale, tag)
if strings.HasPrefix(str, "???") {
str = tag
}
tagStr += str
if i != lenTags - 1 {
tagStr += ","
}
}
return template.HTML(tagStr)
}
/*
revel.TemplateFuncs["blogTags"] = func(tags []string) template.HTML {
if tags == nil || len(tags) == 0 {
return ""
@@ -83,7 +133,7 @@ func init() {
}
return template.HTML(tagStr)
}
*/
revel.TemplateFuncs["li"] = func(a string) string {
Log(a)
Log("life==")
@@ -130,6 +180,15 @@ func init() {
return ""
}
// http://stackoverflow.com/questions/14226416/go-lang-templates-always-quotes-a-string-and-removes-comments
revel.TemplateFuncs["rawMsg"] = func(renderArgs map[string]interface{}, message string, args ...interface{}) template.JS {
str, ok := renderArgs[revel.CurrentLocaleRenderArg].(string)
if !ok {
return ""
}
return template.JS(revel.Message(str, message, args...))
}
// 为后台管理sorter th使用
// 必须要返回HTMLAttr, 返回html, golang 会执行安全检查返回ZgotmplZ
// sorterI 可能是nil, 所以用interfalce{}来接收
@@ -155,7 +214,7 @@ func init() {
}
// pagination
revel.TemplateFuncs["page"] = func(userId, notebookId string, page, pageSize, count int) template.HTML {
revel.TemplateFuncs["page"] = func(urlBase string, page, pageSize, count int) template.HTML {
if count == 0 {
return "";
}
@@ -170,11 +229,6 @@ func init() {
nextPage := page + 1
var preUrl, nextUrl string
urlBase := "/blog/" + userId
if notebookId != "" {
urlBase += "/" + notebookId
}
preUrl = urlBase + "?page=" + strconv.Itoa(prePage)
nextUrl = urlBase + "?page=" + strconv.Itoa(nextPage)
@@ -238,8 +292,13 @@ func init() {
// init Email
revel.OnAppStart(func() {
// 数据库
db.Init()
// email配置
InitEmail()
InitVd()
memcache.InitMemcache() // session服务
// 其它service
service.InitService()
controllers.InitService()
admin.InitService()

View File

@@ -22,7 +22,7 @@ func InitEmail() {
var bodyTpl = `
<html>
<body>
<div style="width: 800px; margin:auto; border-radius:5px; border: 1px solid #ccc; padding: 20px;">
<div style="width: 600px; margin:auto; border-radius:5px; border: 1px solid #ccc; padding: 20px;">
<div>
<div>
<div style="float:left; height: 40px;">
@@ -56,7 +56,7 @@ var bodyTpl = `
</body>
</html>
`
func SendEmail(to, subject, title, body string) bool {
func SendEmailOld(to, subject, body string) bool {
hp := strings.Split(host, ":")
auth := smtp.PlainAuth("", username, password, hp[0])
@@ -69,9 +69,8 @@ func SendEmail(to, subject, title, body string) bool {
content_type = "Content-Type: text/plain" + "; charset=UTF-8"
}
// 登录之
body = strings.Replace(bodyTpl, "$body", body, 1)
body = strings.Replace(body, "$title", title, 1)
//body = strings.Replace(bodyTpl, "$body", body, 1)
//body = strings.Replace(body, "$title", title, 1)
msg := []byte("To: " + to + "\r\nFrom: " + username + "<"+ username +">\r\nSubject: " + subject + "\r\n" + content_type + "\r\n\r\n" + body)
send_to := strings.Split(to, ";")
@@ -84,7 +83,7 @@ func SendEmail(to, subject, title, body string) bool {
return true
}
func SendToLeanote(subject, title, body string) {
func SendToLeanoteOld(subject, title, body string) {
to := "leanote@leanote.com"
SendEmail(to, subject, title, body);
SendEmailOld(to, subject, body);
}

View File

@@ -78,3 +78,13 @@ func CopyFile(srcName, dstName string) (written int64, err error) {
defer dst.Close()
return io.Copy(dst, src)
}
func IsDirExists(path string) bool {
fi, err := os.Stat(path)
if err != nil {
return os.IsExist(err)
}else{
return fi.IsDir()
}
return false
}

145
app/lea/Vd.go Normal file
View File

@@ -0,0 +1,145 @@
package lea
import (
"encoding/json"
"strconv"
"regexp"
)
// 验证
var rulesStr = `{
"username": [
{"rule": "required", "msg": "inputUsername"},
{"rule": "noSpecialChars", "msg": "noSpecialChars"},
{"rule": "minLength", "data": "4", "msg": "minLength", "msgData": "4"}
],
"email": [
{"rule": "required", "msg": "inputEmail"},
{"rule": "email", "msg": "errorEmail"}
],
"password": [
{"rule": "required", "msg": "inputPassword"},
{"rule": "password", "msg": "errorPassword"}
],
"subDomain": [
{"rule": "subDomain", "msg": "errorSubDomain"}
],
"domain": [
{"rule": "domain", "msg": "errorDomain"}
]
}
`
var rulesMap map[string][]map[string]string
var rules = map[string]func(string, map[string]string)(bool, string) {
"required": func(value string, rule map[string]string)(ok bool, msg string) {
if value == "" {
return
}
ok = true
return
},
"minLength": func(value string, rule map[string]string)(ok bool, msg string) {
if value == "" {
return
}
data := rule["data"]
dataI, _ := strconv.Atoi(data)
ok = len(value) >= dataI
return
},
"password": func(value string, rule map[string]string)(ok bool, msg string) {
if value == "" {
return
}
ok = len(value) >= 6
return
},
"email": func(value string, rule map[string]string)(ok bool, msg string) {
if value == "" {
return
}
ok = IsEmail(value)
return
},
"noSpecialChars": func(value string, rule map[string]string)(ok bool, msg string) {
if value == "" {
return
}
ok = IsUsername(value)
return
},
// www.baidu.com
//
"domain": func(value string, rule map[string]string)(ok bool, msg string) {
if value == "" {
ok = true
return // 可为空
}
ok2, _ := regexp.MatchString(`[^0-9a-zA-Z_\.\-]`, value)
ok = !ok2
if !ok {
return
}
ok = true
return
},
// abcd
"subDomain": func(value string, rule map[string]string)(ok bool, msg string) {
if value == "" {
ok = true
return // 可为空
}
if len(value) < 4 {
ok = false
return
}
ok2, _ := regexp.MatchString(`[^0-9a-zA-Z_\-]`, value)
ok = !ok2
return
},
}
func InitVd() {
json.Unmarshal([]byte(rulesStr), &rulesMap)
LogJ(rulesMap)
}
// 验证
// Vd("username", "life")
func Vd(name, value string) (ok bool, msg string) {
rs, _ := rulesMap[name]
for _, rule := range rs {
ruleFunc, _ := rules[rule["rule"]]
if ok2, msg2 := ruleFunc(value, rule); !ok2 {
ok = false
if msg2 != "" {
msg = msg2
} else {
msg = rule["msg"]
}
msgData := rule["msgData"]
if msgData != "" {
msg += "-" + msgData
}
return
}
}
ok = true
return
}
func Vds(m map[string]string) (ok bool, msg string) {
for name, value := range m {
ok, msg = Vd(name, value)
if !ok {
return
}
}
ok = true
return
}

399
app/lea/captcha/Captcha.go Normal file
View File

@@ -0,0 +1,399 @@
package captcha
import (
"image"
"image/color"
"image/png"
"io"
"math/rand"
crand "crypto/rand"
"time"
"strconv"
)
const (
stdWidth = 100
stdHeight = 40
maxSkew = 2
)
const (
fontWidth = 5
fontHeight = 8
blackChar = 1
)
var font = [][]byte{
{ // 0
0, 1, 1, 1, 0,
1, 0, 0, 0, 1,
1, 0, 0, 0, 1,
1, 0, 0, 0, 1,
1, 0, 0, 0, 1,
1, 0, 0, 0, 1,
1, 0, 0, 0, 1,
0, 1, 1, 1, 0,
},
{ // 1
0, 0, 1, 0, 0,
0, 1, 1, 0, 0,
1, 0, 1, 0, 0,
0, 0, 1, 0, 0,
0, 0, 1, 0, 0,
0, 0, 1, 0, 0,
0, 0, 1, 0, 0,
1, 1, 1, 1, 1,
},
{ // 2
0, 1, 1, 1, 0,
1, 0, 0, 0, 1,
0, 0, 0, 0, 1,
0, 0, 0, 1, 1,
0, 1, 1, 0, 0,
1, 0, 0, 0, 0,
1, 0, 0, 0, 0,
1, 1, 1, 1, 1,
},
{ // 3
1, 1, 1, 1, 0,
0, 0, 0, 0, 1,
0, 0, 0, 1, 0,
0, 1, 1, 1, 0,
0, 0, 0, 1, 0,
0, 0, 0, 0, 1,
0, 0, 0, 0, 1,
1, 1, 1, 1, 0,
},
{ // 4
1, 0, 0, 1, 0,
1, 0, 0, 1, 0,
1, 0, 0, 1, 0,
1, 0, 0, 1, 0,
1, 1, 1, 1, 1,
0, 0, 0, 1, 0,
0, 0, 0, 1, 0,
0, 0, 0, 1, 0,
},
{ // 5
1, 1, 1, 1, 1,
1, 0, 0, 0, 0,
1, 0, 0, 0, 0,
1, 1, 1, 1, 0,
0, 0, 0, 0, 1,
0, 0, 0, 0, 1,
0, 0, 0, 0, 1,
1, 1, 1, 1, 0,
},
{ // 6
0, 0, 1, 1, 1,
0, 1, 0, 0, 0,
1, 0, 0, 0, 0,
1, 1, 1, 1, 0,
1, 0, 0, 0, 1,
1, 0, 0, 0, 1,
1, 0, 0, 0, 1,
0, 1, 1, 1, 0,
},
{ // 7
1, 1, 1, 1, 1,
0, 0, 0, 0, 1,
0, 0, 0, 0, 1,
0, 0, 0, 1, 0,
0, 0, 1, 0, 0,
0, 1, 0, 0, 0,
0, 1, 0, 0, 0,
0, 1, 0, 0, 0,
},
{ // 8
0, 1, 1, 1, 0,
1, 0, 0, 0, 1,
1, 0, 0, 0, 1,
0, 1, 1, 1, 0,
1, 0, 0, 0, 1,
1, 0, 0, 0, 1,
1, 0, 0, 0, 1,
0, 1, 1, 1, 0,
},
{ // 9
0, 1, 1, 1, 0,
1, 0, 0, 0, 1,
1, 0, 0, 0, 1,
1, 1, 0, 0, 1,
0, 1, 1, 1, 1,
0, 0, 0, 0, 1,
0, 0, 0, 0, 1,
1, 1, 1, 1, 0,
},
}
type Image struct {
*image.NRGBA
color *color.NRGBA
width int //a digit width
height int //a digit height
dotsize int
}
func init(){
rand.Seed(int64(time.Second))
}
func NewImage(digits []byte, width, height int) *Image {
img := new(Image)
r := image.Rect(img.width, img.height, stdWidth, stdHeight)
img.NRGBA = image.NewNRGBA(r)
img.color = &color.NRGBA{
uint8(rand.Intn(129)),
uint8(rand.Intn(129)),
uint8(rand.Intn(129)),
0xFF,
}
// Draw background (10 random circles of random brightness)
img.calculateSizes(width, height, len(digits))
img.fillWithCircles(10, img.dotsize)
maxx := width - (img.width+img.dotsize)*len(digits) - img.dotsize
maxy := height - img.height - img.dotsize*2
x := rnd(img.dotsize*2, maxx)
y := rnd(img.dotsize*2, maxy)
// Draw digits.
for _, n := range digits {
img.drawDigit(font[n], x, y)
x += img.width + img.dotsize
}
// Draw strike-through line.
// 中间线不要
//img.strikeThrough()
return img
}
func (img *Image) WriteTo(w io.Writer) (int64, error) {
return 0, png.Encode(w, img)
}
func (img *Image) calculateSizes(width, height, ncount int) {
// Goal: fit all digits inside the image.
var border int
if width > height {
border = height / 5
} else {
border = width / 5
}
// Convert everything to floats for calculations.
w := float64(width - border*2) //268
h := float64(height - border*2) //48
// fw takes into account 1-dot spacing between digits.
fw := float64(fontWidth) + 1 //6
fh := float64(fontHeight) //8
nc := float64(ncount) //7
// Calculate the width of a single digit taking into account only the
// width of the image.
nw := w / nc //38
// Calculate the height of a digit from this width.
nh := nw * fh / fw //51
// Digit too high?
if nh > h {
// Fit digits based on height.
nh = h //nh = 44
nw = fw / fh * nh
}
// Calculate dot size.
img.dotsize = int(nh / fh)
// Save everything, making the actual width smaller by 1 dot to account
// for spacing between digits.
img.width = int(nw)
img.height = int(nh) - img.dotsize
}
func (img *Image) fillWithCircles(n, maxradius int) {
color := img.color
maxx := img.Bounds().Max.X
maxy := img.Bounds().Max.Y
for i := 0; i < n; i++ {
setRandomBrightness(color, 255)
r := rnd(1, maxradius)
img.drawCircle(color, rnd(r, maxx-r), rnd(r, maxy-r), r)
}
}
func (img *Image) drawHorizLine(color color.Color, fromX, toX, y int) {
for x := fromX; x <= toX; x++ {
img.Set(x, y, color)
}
}
func (img *Image) drawCircle(color color.Color, x, y, radius int) {
f := 1 - radius
dfx := 1
dfy := -2 * radius
xx := 0
yy := radius
img.Set(x, y+radius, color)
img.Set(x, y-radius, color)
img.drawHorizLine(color, x-radius, x+radius, y)
for xx < yy {
if f >= 0 {
yy--
dfy += 2
f += dfy
}
xx++
dfx += 2
f += dfx
img.drawHorizLine(color, x-xx, x+xx, y+yy)
img.drawHorizLine(color, x-xx, x+xx, y-yy)
img.drawHorizLine(color, x-yy, x+yy, y+xx)
img.drawHorizLine(color, x-yy, x+yy, y-xx)
}
}
func (img *Image) strikeThrough() {
r := 0
maxx := img.Bounds().Max.X
maxy := img.Bounds().Max.Y
y := rnd(maxy/3, maxy-maxy/3)
for x := 0; x < maxx; x += r {
r = rnd(1, img.dotsize/3)
y += rnd(-img.dotsize/2, img.dotsize/2)
if y <= 0 || y >= maxy {
y = rnd(maxy/3, maxy-maxy/3)
}
img.drawCircle(img.color, x, y, r)
}
}
func (img *Image) drawDigit(digit []byte, x, y int) {
skf := rand.Float64() * float64(rnd(-maxSkew, maxSkew))
xs := float64(x)
minr := img.dotsize / 2 // minumum radius
maxr := img.dotsize/2 + img.dotsize/4 // maximum radius
y += rnd(-minr, minr)
for yy := 0; yy < fontHeight; yy++ {
for xx := 0; xx < fontWidth; xx++ {
if digit[yy*fontWidth+xx] != blackChar {
continue
}
// Introduce random variations.
or := rnd(minr, maxr)
ox := x + (xx * img.dotsize) + rnd(0, or/2)
oy := y + (yy * img.dotsize) + rnd(0, or/2)
img.drawCircle(img.color, ox, oy, or)
}
xs += skf
x = int(xs)
}
}
func setRandomBrightness(c *color.NRGBA, max uint8) {
minc := min3(c.R, c.G, c.B)
maxc := max3(c.R, c.G, c.B)
if maxc > max {
return
}
n := rand.Intn(int(max-maxc)) - int(minc)
c.R = uint8(int(c.R) + n)
c.G = uint8(int(c.G) + n)
c.B = uint8(int(c.B) + n)
}
func min3(x, y, z uint8) (o uint8) {
o = x
if y < o {
o = y
}
if z < o {
o = z
}
return
}
func max3(x, y, z uint8) (o uint8) {
o = x
if y > o {
o = y
}
if z > o {
o = z
}
return
}
// rnd returns a random number in range [from, to].
func rnd(from, to int) int {
//println(to+1-from)
return rand.Intn(to+1-from) + from
}
const (
// Standard length of uniuri string to achive ~95 bits of entropy.
StdLen = 16
// Length of uniurl string to achive ~119 bits of entropy, closest
// to what can be losslessly converted to UUIDv4 (122 bits).
UUIDLen = 20
)
// Standard characters allowed in uniuri string.
var StdChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
// New returns a new random string of the standard length, consisting of
// standard characters.
func New() string {
return NewLenChars(StdLen, StdChars)
}
// NewLen returns a new random string of the provided length, consisting of
// standard characters.
func NewLen(length int) string {
return NewLenChars(length, StdChars)
}
// NewLenChars returns a new random string of the provided length, consisting
// of the provided byte slice of allowed characters (maximum 256).
func NewLenChars(length int, chars []byte) string {
b := make([]byte, length)
r := make([]byte, length+(length/4)) // storage for random bytes.
clen := byte(len(chars))
maxrb := byte(256 - (256 % len(chars)))
i := 0
for {
if _, err := io.ReadFull(crand.Reader, r); err != nil {
panic("error reading from random source: " + err.Error())
}
for _, c := range r {
if c >= maxrb {
// Skip this number to avoid modulo bias.
continue
}
b[i] = chars[c%clen]
i++
if i == length {
return string(b)
}
}
}
panic("unreachable")
}
func Fetch() (*Image, string) {
d := make([]byte, 4)
s := NewLen(4)
ss := ""
d = []byte(s)
for v := range d {
d[v] %= 10
ss += strconv.FormatInt(int64(d[v]), 32)
}
return NewImage(d, 100, 40), ss
}

View File

@@ -0,0 +1,10 @@
package html2image
import (
"github.com/leanote/leanote/app/info"
)
func Html2Image(userInfo info.User, note info.Note, content, toPath string) bool {
return true
}

View File

@@ -3,9 +3,20 @@ package memcache
import (
"github.com/robfig/gomemcache/memcache"
"encoding/json"
"strconv"
)
func Set(key string, value map[string]string, expiration int32) {
var client *memcache.Client
// onAppStart后调用
func InitMemcache() {
client = memcache.New("localhost:11211")
}
//------------
// map
func SetMap(key string, value map[string]string, expiration int32) {
// 把value转成byte
bytes, _ := json.Marshal(value)
if expiration == -1 {
@@ -14,7 +25,7 @@ func Set(key string, value map[string]string, expiration int32) {
client.Set(&memcache.Item{Key: key, Value: bytes, Expiration: expiration})
}
func Get(key string) map[string]string {
func GetMap(key string) map[string]string {
item, err := client.Get(key)
if err != nil {
return nil
@@ -24,3 +35,32 @@ func Get(key string) map[string]string {
json.Unmarshal(item.Value, &m)
return m
}
//------------
// string
func GetString(key string) string {
item, err := client.Get(key)
if err != nil {
return ""
}
return string(item.Value)
}
func SetString(key string, value string, expiration int32) {
if expiration == -1 {
expiration = 30 * 24 * 60 * 60 // 30天
}
client.Set(&memcache.Item{Key: key, Value: []byte(value), Expiration: expiration})
}
//-------------------------
// int, 是通过转成string来存的
func GetInt(key string) int {
str := GetString(key)
i, _ := strconv.Atoi(str)
return i
}
func SetInt(key string, value int, expiration int32) {
str := strconv.Itoa(value)
SetString(key, str, expiration)
}

View File

@@ -1,11 +0,0 @@
package memcache
import (
"github.com/robfig/gomemcache/memcache"
)
var client *memcache.Client
func init() {
// client = memcache.New("localhost:11211")
}

View File

@@ -13,7 +13,7 @@ import (
// toPath 文件保存的目录
// 默认是/tmp
// 返回文件的完整目录
func WriteUrl(url string, toPath string) (path string, ok bool) {
func WriteUrl(url string, toPath string) (length int64, newFilename, path string, ok bool) {
if url == "" {
return;
}
@@ -22,6 +22,8 @@ func WriteUrl(url string, toPath string) (path string, ok bool) {
return;
}
length = int64(len(content))
// a.html?a=a11&xxx
url = trimQueryParams(url)
_, ext := SplitFilename(url)
@@ -29,13 +31,8 @@ func WriteUrl(url string, toPath string) (path string, ok bool) {
toPath = "/tmp"
}
// dir := filepath.Dir(toPath)
newFilename := NewGuid() + ext
newFilename = NewGuid() + ext
fullPath := toPath + "/" + newFilename
/*
if err := os.MkdirAll(dir, 0777); err != nil {
return
}
*/
// 写到文件中
file, err := os.Create(fullPath)
@@ -54,6 +51,7 @@ func WriteUrl(url string, toPath string) (path string, ok bool) {
func GetContent(url string) (content []byte, err error) {
var resp *http.Response
resp, err = http.Get(url)
Log(err)
if(resp != nil && resp.Body != nil) {
defer resp.Body.Close()
} else {
@@ -65,6 +63,7 @@ func GetContent(url string) (content []byte, err error) {
var buf []byte
buf, err = ioutil.ReadAll(resp.Body)
if(err != nil) {
Log(err)
return
}

View File

@@ -1,14 +1,20 @@
package lea
package route
import (
"github.com/revel/revel"
// "github.com/leanote/leanote/app/service"
// . "github.com/leanote/leanote/app/lea"
"net/url"
"strings"
)
// overwite revel RouterFilter
// /api/user/Info => ApiUser.Info()
var staticPrefix = []string{"/public", "/favicon.ico", "/css", "/js", "/images", "/tinymce", "/upload", "/fonts"}
func RouterFilter(c *revel.Controller, fc []revel.Filter) {
// 补全controller部分
path := c.Request.Request.URL.Path
// Figure out the Controller/Action
var route *revel.RouteMatch = revel.MainRouter.Route(c.Request.Request)
if route == nil {
@@ -24,12 +30,25 @@ func RouterFilter(c *revel.Controller, fc []revel.Filter) {
//----------
// life start
path := c.Request.Request.URL.Path
// Log(c.Request.Request.URL.Host)
if strings.HasPrefix(path, "/api") || strings.HasPrefix(path, "api") {
/*
type URL struct {
Scheme string
Opaque string // encoded opaque data
User *Userinfo // username and password information
Host string // host or host:port
Path string
RawQuery string // encoded query values, without '?'
Fragment string // fragment for references, without '#'
}
*/
if route.ControllerName != "Static" {
// api设置
// leanote.com/api/user/get => ApiUser::Get
if strings.HasPrefix(path, "/api/") || strings.HasPrefix(path, "api/") {
route.ControllerName = "Api" + route.ControllerName
}
// end
}
// Set the action.
if err := c.SetAction(route.ControllerName, route.MethodName); err != nil {

View File

@@ -0,0 +1,38 @@
package session
import (
"github.com/revel/revel"
"github.com/leanote/leanote/app/lea/memcache"
. "github.com/leanote/leanote/app/lea"
)
// 使用filter
// 很巧妙就使用了memcache来处理session
// revel的session(cookie)只存sessionId, 其它信息存在memcache中
func MSessionFilter(c *revel.Controller, fc []revel.Filter) {
sessionId := c.Session.Id()
// 从memcache中得到cache, 赋给session
cache := revel.Session(memcache.GetMap(sessionId))
Log("memcache")
LogJ(cache)
if cache == nil {
cache = revel.Session{}
cache.Id()
}
c.Session = cache
// Make session vars available in templates as {{.session.xyz}}
c.RenderArgs["session"] = c.Session
fc[0](c, fc[1:])
// 再把session保存之
LogJ(c.Session)
memcache.SetMap(sessionId, c.Session, -1)
// 只留下sessionId
c.Session = revel.Session{revel.SESSION_ID_KEY: sessionId}
}

View File

@@ -1,31 +1,208 @@
package session
import (
"github.com/robfig/revel"
"github.com/leanote/leanote/app/lea/memcache"
// . "leanote/app/lea"
"github.com/revel/revel"
// . "github.com/leanote/leanote/app/lea"
"crypto/rand"
"encoding/hex"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
)
// 使用filter
// 很巧妙就使用了memcache来处理session
// revel的session(cookie)只存sessionId, 其它信息存在memcache中
// 主要修改revel的cookie, 设置Domain
// 为了使sub domain共享cookie
// cookie.domain = leanote.com
func SessionFilter(c *revel.Controller, fc []revel.Filter) {
sessionId := c.Session.Id()
// A signed cookie (and thus limited to 4kb in size).
// Restriction: Keys may not have a colon in them.
type Session map[string]string
// 从memcache中得到cache, 赋给session
cache := revel.Session(memcache.Get(sessionId))
if cache == nil {
cache = revel.Session{}
cache.Id()
const (
SESSION_ID_KEY = "_ID"
TIMESTAMP_KEY = "_TS"
)
// expireAfterDuration is the time to live, in seconds, of a session cookie.
// It may be specified in config as "session.expires". Values greater than 0
// set a persistent cookie with a time to live as specified, and the value 0
// sets a session cookie.
var expireAfterDuration time.Duration
var cookieDomain = "" // life
func init() {
// Set expireAfterDuration, default to 30 days if no value in config
revel.OnAppStart(func() {
var err error
if expiresString, ok := revel.Config.String("session.expires"); !ok {
expireAfterDuration = 30 * 24 * time.Hour
} else if expiresString == "session" {
expireAfterDuration = 0
} else if expireAfterDuration, err = time.ParseDuration(expiresString); err != nil {
panic(fmt.Errorf("session.expires invalid: %s", err))
}
c.Session = cache
cookieDomain, _ = revel.Config.String("cookie.domain")
})
}
// Id retrieves from the cookie or creates a time-based UUID identifying this
// session.
func (s Session) Id() string {
if sessionIdStr, ok := s[SESSION_ID_KEY]; ok {
return sessionIdStr
}
buffer := make([]byte, 32)
if _, err := rand.Read(buffer); err != nil {
panic(err)
}
s[SESSION_ID_KEY] = hex.EncodeToString(buffer)
return s[SESSION_ID_KEY]
}
// getExpiration return a time.Time with the session's expiration date.
// If previous session has set to "session", remain it
func (s Session) getExpiration() time.Time {
if expireAfterDuration == 0 || s[TIMESTAMP_KEY] == "session" {
// Expire after closing browser
return time.Time{}
}
return time.Now().Add(expireAfterDuration)
}
// cookie returns an http.Cookie containing the signed session.
func (s Session) cookie() *http.Cookie {
var sessionValue string
ts := s.getExpiration()
s[TIMESTAMP_KEY] = getSessionExpirationCookie(ts)
for key, value := range s {
if strings.ContainsAny(key, ":\x00") {
panic("Session keys may not have colons or null bytes")
}
if strings.Contains(value, "\x00") {
panic("Session values may not have null bytes")
}
sessionValue += "\x00" + key + ":" + value + "\x00"
}
sessionData := url.QueryEscape(sessionValue)
cookie := http.Cookie{
Name: revel.CookiePrefix + "_SESSION",
Value: revel.Sign(sessionData) + "-" + sessionData,
Path: "/",
HttpOnly: revel.CookieHttpOnly,
Secure: revel.CookieSecure,
Expires: ts.UTC(),
}
if cookieDomain != "" {
cookie.Domain = cookieDomain
}
return &cookie
}
// sessionTimeoutExpiredOrMissing returns a boolean of whether the session
// cookie is either not present or present but beyond its time to live; i.e.,
// whether there is not a valid session.
func sessionTimeoutExpiredOrMissing(session Session) bool {
if exp, present := session[TIMESTAMP_KEY]; !present {
return true
} else if exp == "session" {
return false
} else if expInt, _ := strconv.Atoi(exp); int64(expInt) < time.Now().Unix() {
return true
}
return false
}
// getSessionFromCookie returns a Session struct pulled from the signed
// session cookie.
func getSessionFromCookie(cookie *http.Cookie) Session {
session := make(Session)
// Separate the data from the signature.
hyphen := strings.Index(cookie.Value, "-")
if hyphen == -1 || hyphen >= len(cookie.Value)-1 {
return session
}
sig, data := cookie.Value[:hyphen], cookie.Value[hyphen+1:]
// Verify the signature.
if !revel.Verify(data, sig) {
revel.INFO.Println("Session cookie signature failed")
return session
}
revel.ParseKeyValueCookie(data, func(key, val string) {
session[key] = val
})
if sessionTimeoutExpiredOrMissing(session) {
session = make(Session)
}
return session
}
// SessionFilter is a Revel Filter that retrieves and sets the session cookie.
// Within Revel, it is available as a Session attribute on Controller instances.
// The name of the Session cookie is set as CookiePrefix + "_SESSION".
func SessionFilter(c *revel.Controller, fc []revel.Filter) {
session := restoreSession(c.Request.Request)
// c.Session, 重新生成一个revel.Session给controller!!!
// Log("sessoin--------")
// LogJ(session)
revelSession := revel.Session(session) // 强制转换 还是同一个对象, 但有个问题, 这样Session.Id()方法是用revel的了
c.Session = revelSession
// 生成sessionId
c.Session.Id()
sessionWasEmpty := len(c.Session) == 0
// Make session vars available in templates as {{.session.xyz}}
c.RenderArgs["session"] = c.Session
fc[0](c, fc[1:])
// 再把session保存之
memcache.Set(sessionId, c.Session, -1)
// 只留下sessionId
c.Session = revel.Session{revel.SESSION_ID_KEY: sessionId}
// Store the signed session if it could have changed.
if len(c.Session) > 0 || !sessionWasEmpty {
// 转换成lea.Session
session = Session(c.Session)
c.SetCookie(session.cookie())
}
}
// restoreSession returns either the current session, retrieved from the
// session cookie, or a new session.
func restoreSession(req *http.Request) Session {
cookie, err := req.Cookie(revel.CookiePrefix + "_SESSION")
if err != nil {
return make(Session)
} else {
return getSessionFromCookie(cookie)
}
}
// getSessionExpirationCookie retrieves the cookie's time to live as a
// string of either the number of seconds, for a persistent cookie, or
// "session".
func getSessionExpirationCookie(t time.Time) string {
if t.IsZero() {
return "session"
}
return strconv.FormatInt(t.Unix(), 10)
}
// SetNoExpiration sets session to expire when browser session ends
func (s Session) SetNoExpiration() {
s[TIMESTAMP_KEY] = "session"
}
// SetDefaultExpiration sets session to expire after default duration
func (s Session) SetDefaultExpiration() {
delete(s, TIMESTAMP_KEY)
}

View File

@@ -39,6 +39,7 @@ var cmdPath = "/usr/local/bin/uglifyjs"
func cmdError(err error) {
if err != nil {
fmt.Println(err)
fmt.Fprintf(os.Stderr, "The command failed to perform: %s (Command: %s, Arguments: %s)", err, "", "")
} else {
fmt.Println("OK")
@@ -63,6 +64,7 @@ func combineJs() {
for _, js := range jss {
to := base + js + "-min.js"
fmt.Println(to)
compressJs(js)
// 每个压缩后的文件放入之
@@ -85,6 +87,7 @@ func dev() {
"notebook.js": "notebook-min.js",
"share.js": "share-min.js",
"tag.js": "tag-min.js",
"main.js": "main-min.js",
"jquery.contextmenu.js": "jquery.contextmenu-min.js",
"editor/editor.js": "editor/editor-min.js",
"/public/mdeditor/editor/scrollLink.js": "/public/mdeditor/editor/scrollLink-min.js",
@@ -108,7 +111,8 @@ func tinymce() {
// cmd := exec.Command("/Users/life/Documents/eclipse-workspace/go/leanote_release/tinymce-master/node_modules/jake/bin/cli.js", "minify", "bundle[themes:modern,plugins:table,paste,advlist,autolink,link,image,lists,charmap,hr,searchreplace,visualblocks,visualchars,code,nav,tabfocus,contextmenu,directionality,codemirror,codesyntax,textcolor,fullpage]")
cmd := exec.Command("/Users/life/Documents/eclipse-workspace/go/leanote_release/tinymce-master/node_modules/jake/bin/cli.js", "minify")
cmd.Dir = "/Users/life/Documents/eclipse-workspace/go/leanote_release/tinymce-master"
_, err := cmd.CombinedOutput()
c, err := cmd.CombinedOutput()
fmt.Println(string(c))
cmdError(err)
}
@@ -116,11 +120,12 @@ func main() {
dev();
// 其它零散的需要压缩的js
otherJss := []string{"tinymce/tinymce", "js/app/page", "js/contextmenu/jquery.contextmenu",
otherJss := []string{"tinymce/tinymce", "js/main", "js/app/page", "js/contextmenu/jquery.contextmenu",
"mdeditor/editor/scrollLink",
"mdeditor/editor/editor",
"mdeditor/editor/jquery.waitforimages",
"mdeditor/editor/pagedown/local/Markdown.local.zh",
"mdeditor/editor/pagedown/local/Markdown.local.en",
"mdeditor/editor/pagedown/Markdown.Editor",
"mdeditor/editor/pagedown/Markdown.Sanitizer",
"mdeditor/editor/pagedown/Markdown.Converter",

View File

@@ -4,9 +4,10 @@ import (
"gopkg.in/mgo.v2/bson"
// "github.com/leanote/leanote/app/db"
"github.com/leanote/leanote/app/info"
"github.com/revel/revel"
// "github.com/revel/revel"
. "github.com/leanote/leanote/app/lea"
"fmt"
"strconv"
)
// 登录与权限
@@ -16,7 +17,8 @@ type AuthService struct {
// pwd已md5了
func (this *AuthService) Login(emailOrUsername, pwd string) info.User {
return userService.LoginGetUserInfo(emailOrUsername, Md5(pwd))
userInfo := userService.LoginGetUserInfo(emailOrUsername, Md5(pwd))
return userInfo
}
// 注册
@@ -56,21 +58,31 @@ func (this *AuthService) register(user info.User) (bool, string) {
email := user.Email
// 添加leanote -> 该用户的共享
leanoteUserId, _ := revel.Config.String("register.sharedUserId"); // "5368c1aa99c37b029d000001";
nk1, _ := revel.Config.String("register.sharedUserShareNotebookId"); // 5368c1aa99c37b029d000002" // leanote
welcomeNoteId, _ := revel.Config.String("register.welcomeNoteId") // "5368c1b919807a6f95000000" // 欢迎来到leanote
registerSharedUserId := configService.GetGlobalStringConfig("registerSharedUserId")
if(registerSharedUserId != "") {
registerSharedNotebooks := configService.GetGlobalArrMapConfig("registerSharedNotebooks")
registerSharedNotes := configService.GetGlobalArrMapConfig("registerSharedNotes")
registerCopyNoteIds := configService.GetGlobalArrayConfig("registerCopyNoteIds")
if leanoteUserId != "" && nk1 != "" && welcomeNoteId != "" {
shareService.AddShareNotebook(nk1, 0, leanoteUserId, email);
shareService.AddShareNote(welcomeNoteId, 0, leanoteUserId, email);
// 添加共享笔记本
for _, notebook := range registerSharedNotebooks {
perm, _ := strconv.Atoi(notebook["perm"])
shareService.AddShareNotebook(notebook["notebookId"], perm, registerSharedUserId, email);
}
// 将welcome copy给我
note := noteService.CopySharedNote(welcomeNoteId, title2Id["life"].Hex(), leanoteUserId, user.UserId.Hex());
// 添加共享笔记
for _, note := range registerSharedNotes {
perm, _ := strconv.Atoi(note["perm"])
shareService.AddShareNote(note["noteId"], perm, registerSharedUserId, email);
}
// 公开为博客
// 复制笔记
for _, noteId := range registerCopyNoteIds {
note := noteService.CopySharedNote(noteId, title2Id["life"].Hex(), registerSharedUserId, user.UserId.Hex());
noteUpdate := bson.M{"IsBlog": true}
noteService.UpdateNote(user.UserId.Hex(), user.UserId.Hex(), note.NoteId.Hex(), noteUpdate)
}
}
//---------------
// 添加一条userBlog

View File

@@ -3,10 +3,12 @@ package service
import (
"github.com/leanote/leanote/app/info"
"github.com/leanote/leanote/app/db"
// . "github.com/leanote/leanote/app/lea"
. "github.com/leanote/leanote/app/lea"
"gopkg.in/mgo.v2/bson"
// "time"
// "sort"
"strings"
"time"
)
// blog
@@ -207,15 +209,25 @@ func (this *BlogService) ListAllBlogs(tag string, keywords string, isRecommend b
//------------------------
// 博客设置
func (this *BlogService) fixUserBlog(userBlog *info.UserBlog) {
/*
if userBlog.Title == "" {
userInfo := userService.GetUserInfo(userBlog)
userBlog.Title = userInfo.Username + " 's Blog"
}
*/
// Logo路径问题, 有些有http: 有些没有
Log(userBlog.Logo)
if userBlog.Logo != "" && !strings.HasPrefix(userBlog.Logo, "http") {
userBlog.Logo = strings.Trim(userBlog.Logo, "/")
userBlog.Logo = siteUrl + "/" + userBlog.Logo
}
}
func (this *BlogService) GetUserBlog(userId string) info.UserBlog {
userBlog := info.UserBlog{}
db.Get(db.UserBlogs, userId, &userBlog)
if userBlog.Title == "" {
userInfo := userService.GetUserInfo(userId)
userBlog.Title = userInfo.Username + " 的博客"
}
this.fixUserBlog(&userBlog)
return userBlog
}
@@ -225,7 +237,8 @@ func (this *BlogService) UpdateUserBlog(userBlog info.UserBlog) bool {
}
// 修改之UserBlogBase
func (this *BlogService) UpdateUserBlogBase(userId string, userBlog info.UserBlogBase) bool {
return db.UpdateByQMap(db.UserBlogs, bson.M{"_id": bson.ObjectIdHex(userId)}, userBlog)
ok := db.UpdateByQMap(db.UserBlogs, bson.M{"_id": bson.ObjectIdHex(userId)}, userBlog)
return ok
}
func (this *BlogService) UpdateUserBlogComment(userId string, userBlog info.UserBlogComment) bool {
return db.UpdateByQMap(db.UserBlogs, bson.M{"_id": bson.ObjectIdHex(userId)}, userBlog)
@@ -234,10 +247,325 @@ func (this *BlogService) UpdateUserBlogStyle(userId string, userBlog info.UserBl
return db.UpdateByQMap(db.UserBlogs, bson.M{"_id": bson.ObjectIdHex(userId)}, userBlog)
}
//------------
//---------------------
// 后台管理
// 推荐博客
func (this *BlogService) SetRecommend(noteId string, isRecommend bool) bool {
return db.UpdateByQField(db.Notes, bson.M{"_id": bson.ObjectIdHex(noteId), "IsBlog": true}, "IsRecommend", isRecommend)
data := bson.M{"IsRecommend": isRecommend}
if isRecommend {
data["RecommendTime"] = time.Now()
}
return db.UpdateByQMap(db.Notes, bson.M{"_id": bson.ObjectIdHex(noteId), "IsBlog": true}, data)
}
//----------------------
// 博客社交, 评论
// 返回所有liked用户, bool是否还有
func (this *BlogService) ListLikedUsers(noteId string, isAll bool) ([]info.User, bool) {
// 默认前5
pageSize := 5
skipNum, sortFieldR := parsePageAndSort(1, pageSize, "CreatedTime", false)
likes := []info.BlogLike{}
query := bson.M{"NoteId": bson.ObjectIdHex(noteId)}
q := db.BlogLikes.Find(query);
// 总记录数
count, _ := q.Count()
if count == 0 {
return nil, false
}
if isAll {
q.Sort(sortFieldR).Skip(skipNum).Limit(pageSize).All(&likes)
} else {
q.Sort(sortFieldR).All(&likes)
}
// 得到所有userIds
userIds := make([]bson.ObjectId, len(likes))
for i, like := range likes {
userIds[i] = like.UserId
}
// 得到用户信息
userMap := userService.MapUserInfoAndBlogInfosByUserIds(userIds)
users := make([]info.User, len(likes));
for i, like := range likes {
users[i] = userMap[like.UserId]
}
return users, count > pageSize
}
func (this *BlogService) IsILikeIt(noteId, userId string) bool {
if userId == "" {
return false
}
if db.Has(db.BlogLikes, bson.M{"NoteId": bson.ObjectIdHex(noteId), "UserId": bson.ObjectIdHex(userId)}) {
return true
}
return false
}
// 阅读次数统计+1
func (this *BlogService) IncReadNum(noteId string) bool {
note := noteService.GetNoteById(noteId)
if note.IsBlog {
return db.Update(db.Notes, bson.M{"_id": bson.ObjectIdHex(noteId)}, bson.M{"$inc": bson.M{"ReadNum": 1}})
}
return false
}
// 点赞
// retun ok , isLike
func (this *BlogService) LikeBlog(noteId, userId string) (ok bool, isLike bool) {
ok = false
isLike = false
if noteId == "" || userId == "" {
return
}
// 判断是否点过赞, 如果点过那么取消点赞
note := noteService.GetNoteById(noteId)
if !note.IsBlog /*|| note.UserId.Hex() == userId */{
return
}
noteIdO := bson.ObjectIdHex(noteId)
userIdO := bson.ObjectIdHex(userId)
var n int
if !db.Has(db.BlogLikes, bson.M{"NoteId": noteIdO, "UserId": userIdO}) {
n = 1
// 添加之
db.Insert(db.BlogLikes, info.BlogLike{LikeId: bson.NewObjectId(), NoteId: noteIdO, UserId: userIdO, CreatedTime: time.Now()})
isLike = true
} else {
// 已点过, 那么删除之
n = -1
db.Delete(db.BlogLikes, bson.M{"NoteId": noteIdO, "UserId": userIdO})
isLike = false
}
ok = db.Update(db.Notes, bson.M{"_id": noteIdO}, bson.M{"$inc": bson.M{"LikeNum": n}})
return
}
// 评论
// 在noteId博客下userId 给toUserId评论content
// commentId可为空(针对某条评论评论)
func (this *BlogService) Comment(noteId, toCommentId, userId, content string) (bool, info.BlogComment) {
var comment info.BlogComment
if content == "" {
return false, comment
}
note := noteService.GetNoteById(noteId)
if !note.IsBlog {
return false, comment
}
comment = info.BlogComment{CommentId: bson.NewObjectId(),
NoteId: bson.ObjectIdHex(noteId),
UserId: bson.ObjectIdHex(userId),
Content: content,
CreatedTime: time.Now(),
}
var comment2 = info.BlogComment{}
if toCommentId != "" {
comment2 = info.BlogComment{}
db.Get(db.BlogComments, toCommentId, &comment2)
if comment2.CommentId != "" {
comment.ToCommentId = comment2.CommentId
comment.ToUserId = comment2.UserId
}
} else {
// comment.ToUserId = note.UserId
}
ok := db.Insert(db.BlogComments, comment)
if ok {
// 评论+1
db.Update(db.Notes, bson.M{"_id": bson.ObjectIdHex(noteId)}, bson.M{"$inc": bson.M{"CommentNum": 1}})
}
if userId != note.UserId.Hex() || toCommentId != "" {
go func() {
this.sendEmail(note, comment2, userId, content);
}()
}
return ok, comment
}
// 发送email
func (this *BlogService) sendEmail(note info.Note, comment info.BlogComment, userId, content string) {
emailService.SendCommentEmail(note, comment, userId, content);
/*
toUserId := note.UserId.Hex()
// title := "评论提醒"
// 表示回复回复的内容, 那么发送给之前回复的
if comment.CommentId != "" {
toUserId = comment.UserId.Hex()
}
toUserInfo := userService.GetUserInfo(toUserId)
sendUserInfo := userService.GetUserInfo(userId)
subject := note.Title + " 收到 " + sendUserInfo.Username + " 的评论";
if comment.CommentId != "" {
subject = "您在 " + note.Title + " 发表的评论收到 " + sendUserInfo.Username;
if userId == note.UserId.Hex() {
subject += "(作者)";
}
subject += " 的评论";
}
body := "{header}<b>评论内容</b>: <br /><blockquote>" + content + "</blockquote>";
href := "http://"+ configService.GetBlogDomain() + "/view/" + note.NoteId.Hex()
body += "<br /><b>博客链接</b>: <a href='" + href + "'>" + href + "</a>{footer}";
emailService.SendEmail(toUserInfo.Email, subject, body)
*/
}
// 作者(或管理员)可以删除所有评论
// 自己可以删除评论
func (this *BlogService) DeleteComment(noteId, commentId, userId string) bool {
note := noteService.GetNoteById(noteId)
if !note.IsBlog {
return false
}
comment := info.BlogComment{}
db.Get(db.BlogComments, commentId, &comment)
if comment.CommentId == "" {
return false
}
if userId == adminUserId || note.UserId.Hex() == userId || comment.UserId.Hex() == userId {
if db.Delete(db.BlogComments, bson.M{"_id": bson.ObjectIdHex(commentId)}) {
// 评论-1
db.Update(db.Notes, bson.M{"_id": bson.ObjectIdHex(noteId)}, bson.M{"$inc": bson.M{"CommentNum": -1}})
return true
}
}
return false
}
// 点赞/取消赞
func (this *BlogService) LikeComment(commentId, userId string) (ok bool, isILike bool, num int) {
ok = false
isILike = false
num = 0
comment := info.BlogComment{}
db.Get(db.BlogComments, commentId, &comment)
var n int
if comment.LikeUserIds != nil && len(comment.LikeUserIds) > 0 && InArray(comment.LikeUserIds, userId) {
n = -1
// 从点赞名单删除
db.Update(db.BlogComments, bson.M{"_id": bson.ObjectIdHex(commentId)},
bson.M{"$pull": bson.M{"LikeUserIds": userId}})
isILike = false
} else {
n = 1
// 添加之
db.Update(db.BlogComments, bson.M{"_id": bson.ObjectIdHex(commentId)},
bson.M{"$push": bson.M{"LikeUserIds": userId}})
isILike = true
}
if comment.LikeUserIds == nil {
num = 0
} else {
num = len(comment.LikeUserIds) + n
}
ok = db.Update(db.BlogComments, bson.M{"_id": bson.ObjectIdHex(commentId)},
bson.M{"$set": bson.M{"LikeNum": num}})
return
}
// 评论列表
// userId主要是显示userId是否点过某评论的赞
// 还要获取用户信息
func (this *BlogService) ListComments(userId, noteId string, page, pageSize int) (info.Page, []info.BlogCommentPublic, map[string]info.User) {
pageInfo := info.Page{CurPage: page}
comments2 := []info.BlogComment{}
skipNum, sortFieldR := parsePageAndSort(page, pageSize, "CreatedTime", false)
query := bson.M{"NoteId": bson.ObjectIdHex(noteId)}
q := db.BlogComments.Find(query);
// 总记录数
count, _ := q.Count()
q.Sort(sortFieldR).Skip(skipNum).Limit(pageSize).All(&comments2)
if(len(comments2) == 0) {
return pageInfo, nil, nil
}
comments := make([]info.BlogCommentPublic, len(comments2))
// 我是否点过赞呢?
for i, comment := range comments2 {
comments[i].BlogComment = comment
if comment.LikeNum > 0 && comment.LikeUserIds != nil && len(comment.LikeUserIds) > 0 && InArray(comment.LikeUserIds, userId) {
comments[i].IsILikeIt = true
}
}
note := noteService.GetNoteById(noteId);
// 得到用户信息
userIdsMap := map[bson.ObjectId]bool{note.UserId: true}
for _, comment := range comments {
userIdsMap[comment.UserId] = true
if comment.ToUserId != "" { // 可能为空
userIdsMap[comment.ToUserId] = true
}
}
userIds := make([]bson.ObjectId, len(userIdsMap))
i := 0
for userId, _ := range userIdsMap {
userIds[i] = userId
i++
}
// 得到用户信息
userMap := userService.MapUserInfoByUserIds(userIds)
userMap2 := make(map[string]info.User, len(userMap))
for userId, v := range userMap {
userMap2[userId.Hex()] = v
}
pageInfo = info.NewPage(page, pageSize, count, nil)
return pageInfo, comments, userMap2
}
// 举报
func (this *BlogService) Report(noteId, commentId, reason, userId string) (bool) {
note := noteService.GetNoteById(noteId)
if !note.IsBlog {
return false
}
report := info.Report{ReportId: bson.NewObjectId(),
NoteId: bson.ObjectIdHex(noteId),
UserId: bson.ObjectIdHex(userId),
Reason: reason,
CreatedTime: time.Now(),
}
if commentId != "" {
report.CommentId = bson.ObjectIdHex(commentId)
}
return db.Insert(db.Reports, report)
}

View File

@@ -2,40 +2,38 @@ package service
import (
"github.com/leanote/leanote/app/info"
// . "github.com/leanote/leanote/app/lea"
. "github.com/leanote/leanote/app/lea"
"github.com/leanote/leanote/app/db"
"gopkg.in/mgo.v2/bson"
"github.com/revel/revel"
"time"
"os"
"os/exec"
"fmt"
"strings"
"strconv"
)
// 配置服务
// 只是全局的, 用户的配置没有
type ConfigService struct {
// 全局的
GlobalAllConfigs map[string]interface{}
GlobalStringConfigs map[string]string
GlobalArrayConfigs map[string][]string
// 两种配置, 用户自己的
UserStringConfigs map[string]string
UserArrayConfigs map[string][]string
// 合并之后的
AllStringConfigs map[string]string
AllArrayConfigs map[string][]string
GlobalMapConfigs map[string]map[string]string
GlobalArrMapConfigs map[string][]map[string]string
}
var adminUserId = ""
// appStart时 将全局的配置从数据库中得到作为全局
func (this *ConfigService) InitGlobalConfigs() bool {
this.GlobalAllConfigs = map[string]interface{}{}
this.GlobalStringConfigs = map[string]string{}
this.GlobalArrayConfigs = map[string][]string{}
this.UserStringConfigs = map[string]string{}
this.UserArrayConfigs = map[string][]string{}
this.AllStringConfigs = map[string]string{}
this.AllArrayConfigs = map[string][]string{}
this.GlobalMapConfigs = map[string]map[string]string{}
this.GlobalArrMapConfigs = map[string][]map[string]string{}
adminUsername, _ := revel.Config.String("adminUsername")
if adminUsername == "" {
@@ -48,84 +46,95 @@ func (this *ConfigService) InitGlobalConfigs() bool {
}
adminUserId = userInfo.UserId.Hex()
configs := info.Config{}
db.Get2(db.Configs, userInfo.UserId, &configs)
configs := []info.Config{}
db.ListByQ(db.Configs, bson.M{"UserId": userInfo.UserId}, &configs)
if configs.UserId == "" {
db.Insert(db.Configs, info.Config{UserId: userInfo.UserId, StringConfigs: map[string]string{}, ArrayConfigs: map[string][]string{}})
for _, config := range configs {
if config.IsArr {
this.GlobalArrayConfigs[config.Key] = config.ValueArr
this.GlobalAllConfigs[config.Key] = config.ValueArr
} else if config.IsMap {
this.GlobalMapConfigs[config.Key] = config.ValueMap
this.GlobalAllConfigs[config.Key] = config.ValueMap
} else if config.IsArrMap {
this.GlobalArrMapConfigs[config.Key] = config.ValueArrMap
this.GlobalAllConfigs[config.Key] = config.ValueArrMap
} else {
this.GlobalStringConfigs[config.Key] = config.ValueStr
this.GlobalAllConfigs[config.Key] = config.ValueStr
}
this.GlobalStringConfigs = configs.StringConfigs;
this.GlobalArrayConfigs = configs.ArrayConfigs;
// 复制到所有配置上
for key, value := range this.GlobalStringConfigs {
this.AllStringConfigs[key] = value
}
for key, value := range this.GlobalArrayConfigs {
this.AllArrayConfigs[key] = value
}
return true
}
// 用户登录后获取用户自定义的配置, 并将所有的配置都用上
func (this *ConfigService) InitUserConfigs(userId string) bool {
configs := info.Config{}
db.Get(db.Configs, userId, &configs)
if configs.UserId == "" {
db.Insert(db.Configs, info.Config{UserId: bson.ObjectIdHex(userId), StringConfigs: map[string]string{}, ArrayConfigs: map[string][]string{}})
// 通用方法
func (this *ConfigService) updateGlobalConfig(userId, key string, value interface{}, isArr, isMap, isArrMap bool) bool {
// 判断是否存在
if _, ok := this.GlobalAllConfigs[key]; !ok {
// 需要添加
config := info.Config{ConfigId: bson.NewObjectId(),
UserId: bson.ObjectIdHex(userId),
Key: key,
IsArr: isArr,
IsMap: isMap,
IsArrMap: isArrMap,
UpdatedTime: time.Now(),
}
this.UserStringConfigs = configs.StringConfigs;
this.UserArrayConfigs = configs.ArrayConfigs;
// 合并配置
for key, value := range this.UserStringConfigs {
this.AllStringConfigs[key] = value
if(isArr) {
v, _ := value.([]string)
config.ValueArr = v
this.GlobalArrayConfigs[key] = v
} else if isMap {
v, _ := value.(map[string]string)
config.ValueMap = v
this.GlobalMapConfigs[key] = v
} else if isArrMap {
v, _ := value.([]map[string]string)
config.ValueArrMap = v
this.GlobalArrMapConfigs[key] = v
} else {
v, _ := value.(string)
config.ValueStr = v
this.GlobalStringConfigs[key] = v
}
for key, value := range this.UserArrayConfigs {
this.AllArrayConfigs[key] = value
return db.Insert(db.Configs, config)
} else {
i := bson.M{"UpdatedTime": time.Now()}
this.GlobalAllConfigs[key] = value
if(isArr) {
v, _ := value.([]string)
i["ValueArr"] = v
this.GlobalArrayConfigs[key] = v
} else if isMap {
v, _ := value.(map[string]string)
i["ValueMap"] = v
this.GlobalMapConfigs[key] = v
} else if isArrMap {
v, _ := value.([]map[string]string)
i["ValueArrMap"] = v
this.GlobalArrMapConfigs[key] = v
} else {
v, _ := value.(string)
i["ValueStr"] = v
this.GlobalStringConfigs[key] = v
}
return true
return db.UpdateByQMap(db.Configs, bson.M{"UserId": bson.ObjectIdHex(userId), "Key": key}, i)
}
// 获取配置
func (this *ConfigService) GetStringConfig(key string) string {
return this.AllStringConfigs[key]
}
func (this *ConfigService) GetArrayConfig(key string) []string {
arr := this.AllArrayConfigs[key]
if arr == nil {
return []string{}
}
return arr
}
// 更新用户配置
func (this *ConfigService) UpdateUserStringConfig(userId, key string, value string) bool {
this.UserStringConfigs[key] = value
this.AllStringConfigs[key] = value
if userId == adminUserId {
this.GlobalStringConfigs[key] = value
func (this *ConfigService) UpdateGlobalStringConfig(userId, key string, value string) bool {
return this.updateGlobalConfig(userId, key, value, false, false, false)
}
// 保存到数据库中
return db.UpdateByQMap(db.Configs, bson.M{"_id": bson.ObjectIdHex(userId)},
bson.M{"StringConfigs": this.UserStringConfigs, "UpdatedTime": time.Now()})
func (this *ConfigService) UpdateGlobalArrayConfig(userId, key string, value []string) bool {
return this.updateGlobalConfig(userId, key, value, true, false, false)
}
func (this *ConfigService) UpdateUserArrayConfig(userId, key string, value []string) bool {
this.UserArrayConfigs[key] = value
this.AllArrayConfigs[key] = value
if userId == adminUserId {
this.GlobalArrayConfigs[key] = value
func (this *ConfigService) UpdateGlobalMapConfig(userId, key string, value map[string]string) bool {
return this.updateGlobalConfig(userId, key, value, false, true, false)
}
// 保存到数据库中
return db.UpdateByQMap(db.Configs, bson.M{"_id": bson.ObjectIdHex(userId)},
bson.M{"ArrayConfigs": this.UserArrayConfigs, "UpdatedTime": time.Now()})
func (this *ConfigService) UpdateGlobalArrMapConfig(userId, key string, value []map[string]string) bool {
return this.updateGlobalConfig(userId, key, value, false, false, true)
}
// 获取全局配置, 博客平台使用
@@ -139,3 +148,390 @@ func (this *ConfigService) GetGlobalArrayConfig(key string) []string {
}
return arr
}
func (this *ConfigService) GetGlobalMapConfig(key string) map[string]string {
m := this.GlobalMapConfigs[key]
if m == nil {
return map[string]string{}
}
return m
}
func (this *ConfigService) GetGlobalArrMapConfig(key string) []map[string]string {
m := this.GlobalArrMapConfigs[key]
if m == nil {
return []map[string]string{}
}
return m
}
//-------
// 修改共享笔记的配置
func (this *ConfigService) UpdateShareNoteConfig(registerSharedUserId string,
registerSharedNotebookPerms, registerSharedNotePerms []int,
registerSharedNotebookIds, registerSharedNoteIds, registerCopyNoteIds []string) (ok bool, msg string) {
defer func() {
if err := recover(); err != nil {
ok = false
msg = fmt.Sprint(err)
}
}();
// 用户是否存在?
if registerSharedUserId == "" {
ok = true
msg = "share userId is blank, So it share nothing to register"
this.UpdateGlobalStringConfig(adminUserId, "registerSharedUserId", "")
return
} else {
user := userService.GetUserInfo(registerSharedUserId)
if user.UserId == "" {
ok = false
msg = "no such user: " + registerSharedUserId
return
} else {
this.UpdateGlobalStringConfig(adminUserId, "registerSharedUserId", registerSharedUserId)
}
}
notebooks := []map[string]string{}
// 共享笔记本
if len(registerSharedNotebookIds) > 0 {
for i := 0; i < len(registerSharedNotebookIds); i++ {
// 判断笔记本是否存在
notebookId := registerSharedNotebookIds[i]
if notebookId == "" {
continue
}
notebook := notebookService.GetNotebook(notebookId, registerSharedUserId)
if notebook.NotebookId == "" {
ok = false
msg = "The user has no such notebook: " + notebookId
return
} else {
perm := "0";
if registerSharedNotebookPerms[i] == 1 {
perm = "1"
}
notebooks = append(notebooks, map[string]string{"notebookId": notebookId, "perm": perm})
}
}
}
this.UpdateGlobalArrMapConfig(adminUserId, "registerSharedNotebooks", notebooks)
notes := []map[string]string{}
// 共享笔记
if len(registerSharedNoteIds) > 0 {
for i := 0; i < len(registerSharedNoteIds); i++ {
// 判断笔记本是否存在
noteId := registerSharedNoteIds[i]
if noteId == "" {
continue
}
note := noteService.GetNote(noteId, registerSharedUserId)
if note.NoteId == "" {
ok = false
msg = "The user has no such note: " + noteId
return
} else {
perm := "0";
if registerSharedNotePerms[i] == 1 {
perm = "1"
}
notes = append(notes, map[string]string{"noteId": noteId, "perm": perm})
}
}
}
this.UpdateGlobalArrMapConfig(adminUserId, "registerSharedNotes", notes)
// 复制
noteIds := []string{}
if len(registerCopyNoteIds) > 0 {
for i := 0; i < len(registerCopyNoteIds); i++ {
// 判断笔记本是否存在
noteId := registerCopyNoteIds[i]
if noteId == "" {
continue
}
note := noteService.GetNote(noteId, registerSharedUserId)
if note.NoteId == "" {
ok = false
msg = "The user has no such note: " + noteId
return
} else {
noteIds = append(noteIds, noteId)
}
}
}
this.UpdateGlobalArrayConfig(adminUserId, "registerCopyNoteIds", noteIds)
ok = true
return
}
// 添加备份
func (this *ConfigService) AddBackup(path, remark string) bool {
backups := this.GetGlobalArrMapConfig("backups") // [{}, {}]
n := time.Now().Unix()
nstr := fmt.Sprintf("%v", n)
backups = append(backups, map[string]string{"createdTime": nstr, "path": path, "remark": remark})
return this.UpdateGlobalArrMapConfig(adminUserId, "backups", backups)
}
func (this *ConfigService) getBackupDirname() string {
n := time.Now()
y, m, d := n.Date()
return strconv.Itoa(y) + "_" + m.String() + "_" + strconv.Itoa(d) + "_" + fmt.Sprintf("%v", n.Unix())
}
func (this *ConfigService) Backup(remark string) (ok bool, msg string) {
binPath := configService.GetGlobalStringConfig("mongodumpPath")
config := revel.Config;
dbname, _ := config.String("db.dbname")
host, _ := revel.Config.String("db.host")
port, _ := revel.Config.String("db.port")
username, _ := revel.Config.String("db.username")
password, _ := revel.Config.String("db.password")
// mongodump -h localhost -d leanote -o /root/mongodb_backup/leanote-9-22/ -u leanote -p nKFAkxKnWkEQy8Vv2LlM
binPath = binPath + " -h " + host + " -d " + dbname + " -port " + port
if username != "" {
binPath += " -u " + username + " -p " + password
}
// 保存的路径
dir := revel.BasePath + "/backup/" + this.getBackupDirname()
binPath += " -o " + dir
err := os.MkdirAll(dir, 0755)
if err != nil {
ok = false
msg = fmt.Sprintf("%v", err)
return
}
cmd := exec.Command("/bin/sh", "-c", binPath)
Log(binPath);
b, err := cmd.Output()
if err != nil {
msg = fmt.Sprintf("%v", err)
ok = false
Log("error:......")
Log(string(b))
return
}
ok = configService.AddBackup(dir, remark)
return ok, msg
}
// 还原
func (this *ConfigService) Restore(createdTime string) (ok bool, msg string) {
backups := this.GetGlobalArrMapConfig("backups") // [{}, {}]
var i int
var backup map[string]string
for i, backup = range backups {
if backup["createdTime"] == createdTime {
break;
}
}
if i == len(backups) {
return false, "Backup Not Found"
}
// 先备份当前
ok, msg = this.Backup("Auto backup when restore from " + backup["createdTime"] )
if !ok {
return
}
// mongorestore -h localhost -d leanote --directoryperdb /home/user1/gopackage/src/github.com/leanote/leanote/mongodb_backup/leanote_install_data/
binPath := configService.GetGlobalStringConfig("mongorestorePath")
config := revel.Config;
dbname, _ := config.String("db.dbname")
host, _ := revel.Config.String("db.host")
port, _ := revel.Config.String("db.port")
username, _ := revel.Config.String("db.username")
password, _ := revel.Config.String("db.password")
// mongorestore -h localhost -d leanote -o /root/mongodb_backup/leanote-9-22/ -u leanote -p nKFAkxKnWkEQy8Vv2LlM
binPath = binPath + " --drop -h " + host + " -d " + dbname + " -port " + port
if username != "" {
binPath += " -u " + username + " -p " + password
}
path := backup["path"] + "/" + dbname
// 判断路径是否存在
if !IsDirExists(path) {
return false, path + " Is Not Exists"
}
binPath += " --directoryperdb " + path
cmd := exec.Command("/bin/sh", "-c", binPath)
Log(binPath);
b, err := cmd.Output()
if err != nil {
msg = fmt.Sprintf("%v", err)
ok = false
Log("error:......")
Log(string(b))
return
}
return true, ""
}
func (this *ConfigService) DeleteBackup(createdTime string) (bool, string) {
backups := this.GetGlobalArrMapConfig("backups") // [{}, {}]
var i int
var backup map[string]string
for i, backup = range backups {
if backup["createdTime"] == createdTime {
break;
}
}
if i == len(backups) {
return false, "Backup Not Found"
}
// 删除文件夹之
err := os.RemoveAll(backups[i]["path"])
if err != nil {
return false, fmt.Sprintf("%v", err)
}
// 删除之
backups = append(backups[0:i], backups[i+1:]...)
ok := this.UpdateGlobalArrMapConfig(adminUserId, "backups", backups)
return ok, ""
}
func (this *ConfigService) UpdateBackupRemark(createdTime, remark string) (bool, string) {
backups := this.GetGlobalArrMapConfig("backups") // [{}, {}]
var i int
var backup map[string]string
for i, backup = range backups {
if backup["createdTime"] == createdTime {
break;
}
}
if i == len(backups) {
return false, "Backup Not Found"
}
backup["remark"] = remark;
ok := this.UpdateGlobalArrMapConfig(adminUserId, "backups", backups)
return ok, ""
}
// 得到备份
func (this *ConfigService) GetBackup(createdTime string) (map[string]string, bool) {
backups := this.GetGlobalArrMapConfig("backups") // [{}, {}]
var i int
var backup map[string]string
for i, backup = range backups {
if backup["createdTime"] == createdTime {
break;
}
}
if i == len(backups) {
return map[string]string{}, false
}
return backup, true
}
//--------------
// sub domain
var defaultDomain string
var schema = "http://"
var port string
func init() {
revel.OnAppStart(func() {
port = strconv.Itoa(revel.HttpPort)
if port != "80" {
port = ":" + port
} else {
port = "";
}
siteUrl, _ = revel.Config.String("site.url") // 已包含:9000, http, 去掉成 leanote.com
if strings.HasPrefix(siteUrl, "http://") {
defaultDomain = siteUrl[len("http://"):]
} else if strings.HasPrefix(siteUrl, "https://") {
defaultDomain = siteUrl[len("https://"):]
schema = "https://"
}
})
}
func (this *ConfigService) GetSchema() string {
return schema;
}
// 默认
func (this *ConfigService) GetDefaultDomain() string {
return defaultDomain
}
// 包含http://
func (this *ConfigService) GetDefaultUrl() string {
return schema + defaultDomain
}
// note
func (this *ConfigService) GetNoteDomain() string {
subDomain := this.GetGlobalStringConfig("noteSubDomain");
if subDomain != "" {
return subDomain + port
}
return this.GetDefaultDomain() + "/note"
}
func (this *ConfigService) GetNoteUrl() string {
return schema + this.GetNoteDomain();
}
// blog
func (this *ConfigService) GetBlogDomain() string {
subDomain := this.GetGlobalStringConfig("blogSubDomain");
if subDomain != "" {
return subDomain + port
}
return this.GetDefaultDomain() + "/blog"
}
func (this *ConfigService) GetBlogUrl() string {
return schema + this.GetBlogDomain();
}
// lea
func (this *ConfigService) GetLeaDomain() string {
subDomain := this.GetGlobalStringConfig("leaSubDomain");
if subDomain != "" {
return subDomain + port
}
return this.GetDefaultDomain() + "/lea"
}
func (this *ConfigService) GetLeaUrl() string {
return schema + this.GetLeaDomain();
}
func (this *ConfigService) GetUserUrl(domain string) string {
return schema + domain + port
}
func (this *ConfigService) GetUserSubUrl(subDomain string) string {
return schema + subDomain + "." + this.GetDefaultDomain()
}
// 是否允许自定义域名
func (this *ConfigService) AllowCustomDomain() bool {
return configService.GetGlobalStringConfig("allowCustomDomain") != ""
}
// 是否是好的自定义域名
func (this *ConfigService) IsGoodCustomDomain(domain string) bool {
blacks := this.GetGlobalArrayConfig("blackCustomDomains")
for _, black := range blacks {
if strings.Contains(domain, black) {
return false
}
}
return true
}
func (this *ConfigService) IsGoodSubDomain(domain string) bool {
blacks := this.GetGlobalArrayConfig("blackSubDomains")
LogJ(blacks)
for _, black := range blacks {
if domain == black {
return false
}
}
return true
}

474
app/service/EmailService.go Normal file
View File

@@ -0,0 +1,474 @@
package service
import (
"github.com/leanote/leanote/app/info"
"github.com/leanote/leanote/app/db"
. "github.com/leanote/leanote/app/lea"
"gopkg.in/mgo.v2/bson"
"time"
"strings"
"net/smtp"
"strconv"
"fmt"
"html/template"
"bytes"
)
// 发送邮件
type EmailService struct {
tpls map[string]*template.Template
}
func NewEmailService() (*EmailService) {
return &EmailService{tpls: map[string]*template.Template{}}
}
// 发送邮件
var host = ""
var emailPort = ""
var username = ""
var password = ""
func InitEmailFromDb() {
host = configService.GetGlobalStringConfig("emailHost")
emailPort = configService.GetGlobalStringConfig("emailPort")
username = configService.GetGlobalStringConfig("emailUsername")
password = configService.GetGlobalStringConfig("emailPassword")
}
func (this *EmailService) SendEmail(to, subject, body string) (ok bool, e string) {
InitEmailFromDb()
if host == "" || emailPort == "" || username == "" || password == "" {
return
}
hp := strings.Split(host, ":")
auth := smtp.PlainAuth("", username, password, hp[0])
var content_type string
mailtype := "html"
if mailtype == "html" {
content_type = "Content-Type: text/"+ mailtype + "; charset=UTF-8"
} else{
content_type = "Content-Type: text/plain" + "; charset=UTF-8"
}
msg := []byte("To: " + to + "\r\nFrom: " + username + "<"+ username +">\r\nSubject: " + subject + "\r\n" + content_type + "\r\n\r\n" + body)
send_to := strings.Split(to, ";")
err := smtp.SendMail(host+":"+emailPort, auth, username, send_to, msg)
if err != nil {
e = fmt.Sprint(err)
return
}
ok = true
return
}
// AddUser调用
// 可以使用一个goroutine
func (this *EmailService) RegisterSendActiveEmail(userInfo info.User, email string) bool {
token := tokenService.NewToken(userInfo.UserId.Hex(), email, info.TokenActiveEmail)
if token == "" {
return false
}
subject := configService.GetGlobalStringConfig("emailTemplateRegisterSubject");
tpl := configService.GetGlobalStringConfig("emailTemplateRegister");
if(tpl == "") {
return false
}
tokenUrl := siteUrl + "/user/activeEmail?token=" + token
// {siteUrl} {tokenUrl} {token} {tokenTimeout} {user.id} {user.email} {user.username}
token2Value := map[string]interface{}{"siteUrl": siteUrl, "tokenUrl": tokenUrl, "token": token, "tokenTimeout": strconv.Itoa(int(tokenService.GetOverHours(info.TokenActiveEmail))),
"user": map[string]interface{}{
"userId": userInfo.UserId.Hex(),
"email": userInfo.Email,
"username": userInfo.Username,
},
}
var ok bool
ok, _, subject, tpl = this.renderEmail(subject, tpl, token2Value)
if !ok {
return false
}
// 发送邮件
ok, _ = this.SendEmail(email, subject, tpl)
return ok
}
// 修改邮箱
func (this *EmailService) UpdateEmailSendActiveEmail(userInfo info.User, email string) (ok bool, msg string) {
// 先验证该email是否被注册了
if userService.IsExistsUser(email) {
ok = false
msg = "该邮箱已注册"
return
}
token := tokenService.NewToken(userInfo.UserId.Hex(), email, info.TokenUpdateEmail)
if token == "" {
return
}
subject := configService.GetGlobalStringConfig("emailTemplateUpdateEmailSubject");
tpl := configService.GetGlobalStringConfig("emailTemplateUpdateEmail");
// 发送邮件
tokenUrl := siteUrl + "/user/updateEmail?token=" + token
// {siteUrl} {tokenUrl} {token} {tokenTimeout} {user.userId} {user.email} {user.username}
token2Value := map[string]interface{}{"siteUrl": siteUrl, "tokenUrl": tokenUrl, "token": token, "tokenTimeout": strconv.Itoa(int(tokenService.GetOverHours(info.TokenActiveEmail))),
"newEmail": email,
"user": map[string]interface{}{
"userId": userInfo.UserId.Hex(),
"email": userInfo.Email,
"username": userInfo.Username,
},
}
ok, msg, subject, tpl = this.renderEmail(subject, tpl, token2Value)
if !ok {
return
}
// 发送邮件
ok, msg = this.SendEmail(email, subject, tpl)
return
}
func (this *EmailService) FindPwdSendEmail(token, email string) (ok bool, msg string) {
subject := configService.GetGlobalStringConfig("emailTemplateFindPasswordSubject");
tpl := configService.GetGlobalStringConfig("emailTemplateFindPassword");
// 发送邮件
tokenUrl := siteUrl + "/findPassword/" + token
// {siteUrl} {tokenUrl} {token} {tokenTimeout} {user.id} {user.email} {user.username}
token2Value := map[string]interface{}{"siteUrl": siteUrl, "tokenUrl": tokenUrl,
"token": token, "tokenTimeout": strconv.Itoa(int(tokenService.GetOverHours(info.TokenActiveEmail)))}
ok, msg, subject, tpl = this.renderEmail(subject, tpl, token2Value)
if !ok {
return
}
// 发送邮件
ok, msg = this.SendEmail(email, subject, tpl)
return
}
// 发送邀请链接
func (this *EmailService) SendInviteEmail(userInfo info.User, email, content string) bool {
subject := configService.GetGlobalStringConfig("emailTemplateInviteSubject");
tpl := configService.GetGlobalStringConfig("emailTemplateInvite");
token2Value := map[string]interface{}{"siteUrl": siteUrl,
"registerUrl": siteUrl + "/register?from=" + userInfo.Username,
"content": content,
"user": map[string]interface{}{
"username": userInfo.Username,
"email": userInfo.Email,
},
}
var ok bool
ok, _, subject, tpl = this.renderEmail(subject, tpl, token2Value)
if !ok {
return false
}
// 发送邮件
ok, _ = this.SendEmail(email, subject, tpl)
return ok
}
// 发送评论
func (this *EmailService) SendCommentEmail(note info.Note, comment info.BlogComment, userId, content string) bool {
subject := configService.GetGlobalStringConfig("emailTemplateCommentSubject");
tpl := configService.GetGlobalStringConfig("emailTemplateComment");
// title := "评论提醒"
/*
toUserId := note.UserId.Hex()
// title := "评论提醒"
// 表示回复回复的内容, 那么发送给之前回复的
if comment.CommentId != "" {
toUserId = comment.UserId.Hex()
}
toUserInfo := userService.GetUserInfo(toUserId)
sendUserInfo := userService.GetUserInfo(userId)
subject := note.Title + " 收到 " + sendUserInfo.Username + " 的评论";
if comment.CommentId != "" {
subject = "您在 " + note.Title + " 发表的评论收到 " + sendUserInfo.Username;
if userId == note.UserId.Hex() {
subject += "(作者)";
}
subject += " 的评论";
}
*/
toUserId := note.UserId.Hex()
// 表示回复回复的内容, 那么发送给之前回复的
if comment.CommentId != "" {
toUserId = comment.UserId.Hex()
}
toUserInfo := userService.GetUserInfo(toUserId) // 被评论者
sendUserInfo := userService.GetUserInfo(userId) // 评论者
// {siteUrl} {blogUrl}
// {blog.id} {blog.title} {blog.url}
// {commentUser.userId} {commentUser.username} {commentUser.email}
// {commentedUser.userId} {commentedUser.username} {commentedUser.email}
token2Value := map[string]interface{}{"siteUrl": siteUrl, "blogUrl": configService.GetBlogUrl(),
"blog": map[string]string{
"id": note.NoteId.Hex(),
"title": note.Title,
"url": configService.GetBlogUrl() + "/view/" + note.NoteId.Hex(),
},
"commentContent": content,
// 评论者信息
"commentUser": map[string]interface{}{"userId": sendUserInfo.UserId.Hex(),
"username": sendUserInfo.Username,
"email": sendUserInfo.Email,
"isBlogAuthor": userId == note.UserId.Hex(),
},
// 被评论者信息
"commentedUser": map[string]interface{}{"userId": toUserId,
"username": toUserInfo.Username,
"email": toUserInfo.Email,
"isBlogAuthor": toUserId == note.UserId.Hex(),
},
}
ok := false
ok, _, subject, tpl = this.renderEmail(subject, tpl, token2Value)
if !ok {
return false
}
// 发送邮件
ok, _ = this.SendEmail(toUserInfo.Email, subject, tpl)
return ok
}
// 验证模板是否正确
func (this *EmailService) ValidTpl(str string) (ok bool, msg string){
defer func() {
if err := recover(); err != nil {
ok = false
msg = fmt.Sprint(err)
}
}();
header := configService.GetGlobalStringConfig("emailTemplateHeader");
footer := configService.GetGlobalStringConfig("emailTemplateFooter");
str = strings.Replace(str, "{{header}}", header, -1)
str = strings.Replace(str, "{{footer}}", footer, -1)
_, err := template.New("tpl name").Parse(str)
if err != nil {
msg = fmt.Sprint(err)
return
}
ok = true
return
}
// ok, msg, subject, tpl
func (this *EmailService) getTpl(str string) (ok bool, msg string, tpl *template.Template){
defer func() {
if err := recover(); err != nil {
ok = false
msg = fmt.Sprint(err)
}
}();
var err error
var has bool
if tpl, has = this.tpls[str]; !has {
tpl, err = template.New("tpl name").Parse(str)
if err != nil {
msg = fmt.Sprint(err)
return
}
this.tpls[str] = tpl
}
ok = true
return
}
// 通过subject, body和值得到内容
func (this *EmailService) renderEmail(subject, body string, values map[string]interface{}) (ok bool, msg string, o string, b string) {
ok = false
msg = ""
defer func() { // 必须要先声明defer否则不能捕获到panic异常
if err := recover(); err != nil {
ok = false
msg = fmt.Sprint(err) // 这里的err其实就是panic传入的内容
}
}();
var tpl *template.Template
values["siteUrl"] = siteUrl;
// subject
if subject != "" {
ok, msg, tpl = this.getTpl(subject)
if(!ok) {
return
}
var buffer bytes.Buffer
err := tpl.Execute(&buffer, values)
if err != nil {
msg = fmt.Sprint(err)
return
}
o = buffer.String()
} else {
o = ""
}
// content
header := configService.GetGlobalStringConfig("emailTemplateHeader");
footer := configService.GetGlobalStringConfig("emailTemplateFooter");
body = strings.Replace(body, "{{header}}", header, -1)
body = strings.Replace(body, "{{footer}}", footer, -1)
values["subject"] = o
ok, msg, tpl = this.getTpl(body)
if(!ok) {
return
}
var buffer2 bytes.Buffer
err := tpl.Execute(&buffer2, values)
if err != nil {
msg = fmt.Sprint(err)
return
}
b = buffer2.String()
return
}
// 发送email给用户
// 需要记录
func (this *EmailService) SendEmailToUsers(users []info.User, subject, body string) (ok bool, msg string) {
if(users == nil || len(users) == 0) {
msg = "no users"
return
}
// 尝试renderHtml
ok, msg, _, _ = this.renderEmail(subject, body, map[string]interface{}{})
if(!ok) {
Log(msg)
return
}
go func() {
for _, user := range users {
LogJ(user)
m := map[string]interface{}{}
m["userId"] = user.UserId.Hex()
m["username"] = user.Username
m["email"] = user.Email
ok2, msg2, subject2, body2 := this.renderEmail(subject, body, m)
ok = ok2
msg = msg2
if(ok2) {
sendOk, msg := this.SendEmail(user.Email, subject2, body2);
this.AddEmailLog(user.Email, subject, body, sendOk, msg) // 把模板记录下
// 记录到Email Log
if sendOk {
// Log("ok " + user.Email)
} else {
// Log("no " + user.Email)
}
} else {
// Log(msg);
}
}
}()
return
}
func (this *EmailService) SendEmailToEmails(emails []string, subject, body string) (ok bool, msg string) {
if(emails == nil || len(emails) == 0) {
msg = "no emails"
return
}
// 尝试renderHtml
ok, msg, _, _ = this.renderEmail(subject, body, map[string]interface{}{})
if(!ok) {
Log(msg)
return
}
// go func() {
for _, email := range emails {
if email == "" {
continue
}
m := map[string]interface{}{}
m["email"] = email
ok, msg, subject, body = this.renderEmail(subject, body, m)
if(ok) {
sendOk, msg := this.SendEmail(email, subject, body);
this.AddEmailLog(email, subject, body, sendOk, msg)
// 记录到Email Log
if sendOk {
Log("ok " + email)
} else {
Log("no " + email)
}
} else {
Log(msg);
}
}
// }()
return
}
// 添加邮件日志
func (this *EmailService) AddEmailLog(email, subject, body string, ok bool, msg string) {
log := info.EmailLog{LogId: bson.NewObjectId(), Email: email, Subject: subject, Body: body, Ok: ok, Msg: msg, CreatedTime: time.Now()}
db.Insert(db.EmailLogs, log)
}
// 展示邮件日志
func (this *EmailService) DeleteEmails(ids []string) bool {
idsO := make([]bson.ObjectId, len(ids))
for i, id := range ids {
idsO[i] = bson.ObjectIdHex(id)
}
db.DeleteAll(db.EmailLogs, bson.M{"_id": bson.M{"$in": idsO}})
return true
}
func (this *EmailService) ListEmailLogs(pageNumber, pageSize int, sortField string, isAsc bool, email string) (page info.Page, emailLogs []info.EmailLog) {
emailLogs = []info.EmailLog{}
skipNum, sortFieldR := parsePageAndSort(pageNumber, pageSize, sortField, isAsc)
query := bson.M{}
if email != "" {
query["Email"] = bson.M{"$regex": bson.RegEx{".*?" + email + ".*", "i"}}
}
q := db.EmailLogs.Find(query);
// 总记录数
count, _ := q.Count()
// 列表
q.Sort(sortFieldR).
Skip(skipNum).
Limit(pageSize).
All(&emailLogs)
page = info.NewPage(pageNumber, pageSize, count, nil)
return
}

View File

@@ -126,13 +126,21 @@ func (this *NoteService) AddNote(note info.Note) info.Note {
note.UpdatedUserId = note.UserId
// 设为blog
note.IsBlog = notebookService.IsBlog(note.NotebookId.Hex())
notebookId := note.NotebookId.Hex()
note.IsBlog = notebookService.IsBlog(notebookId)
if note.IsBlog {
note.PublicTime = note.UpdatedTime
}
db.Insert(db.Notes, note)
// tag1
tagService.AddTags(note.UserId.Hex(), note.Tags)
// recount notebooks' notes number
notebookService.ReCountNotebookNumberNotes(notebookId)
return note
}
@@ -276,6 +284,9 @@ func (this *NoteService) UpdateTags(noteId string, userId string, tags []string)
// 2. 要判断之前是否是blog, 如果不是, 那么notebook是否是blog?
func (this *NoteService) MoveNote(noteId, notebookId, userId string) info.Note {
if notebookService.IsMyNotebook(notebookId, userId) {
note := this.GetNote(noteId, userId)
preNotebookId := note.NotebookId.Hex()
re := db.UpdateByIdAndUserId(db.Notes, noteId, userId,
bson.M{"$set": bson.M{"IsTrash": false,
"NotebookId": bson.ObjectIdHex(notebookId)}})
@@ -283,6 +294,13 @@ func (this *NoteService) MoveNote(noteId, notebookId, userId string) info.Note {
if re {
// 更新blog状态
this.updateToNotebookBlog(noteId, notebookId, userId)
// recount notebooks' notes number
notebookService.ReCountNotebookNumberNotes(notebookId)
// 之前不是trash才统计, trash本不在统计中的
if !note.IsTrash && preNotebookId != notebookId {
notebookService.ReCountNotebookNumberNotes(preNotebookId)
}
}
return this.GetNote(noteId, userId);
@@ -330,7 +348,11 @@ func (this *NoteService) CopyNote(noteId, notebookId, userId string) info.Note {
// 更新blog状态
isBlog := this.updateToNotebookBlog(note.NoteId.Hex(), notebookId, userId)
// recount
notebookService.ReCountNotebookNumberNotes(notebookId)
note.IsBlog = isBlog
return note
}
@@ -340,7 +362,7 @@ func (this *NoteService) CopyNote(noteId, notebookId, userId string) info.Note {
// 复制别人的共享笔记给我
// 将别人可用的图片转为我的图片, 复制图片
func (this *NoteService) CopySharedNote(noteId, notebookId, fromUserId, myUserId string) info.Note {
Log(shareService.HasSharedNote(noteId, myUserId) || shareService.HasSharedNotebook(noteId, myUserId, fromUserId))
// Log(shareService.HasSharedNote(noteId, myUserId) || shareService.HasSharedNotebook(noteId, myUserId, fromUserId))
// 判断是否共享了给我
if notebookService.IsMyNotebook(notebookId, myUserId) &&
(shareService.HasSharedNote(noteId, myUserId) || shareService.HasSharedNotebook(noteId, myUserId, fromUserId)) {
@@ -375,6 +397,9 @@ func (this *NoteService) CopySharedNote(noteId, notebookId, fromUserId, myUserId
// 更新blog状态
isBlog := this.updateToNotebookBlog(note.NoteId.Hex(), notebookId, myUserId)
// recount
notebookService.ReCountNotebookNumberNotes(notebookId)
note.IsBlog = isBlog
return note
}
@@ -483,3 +508,12 @@ func (this *NoteService) SearchNoteByTags(tags []string, userId string, pageNumb
All(&notes)
return
}
//------------
// 统计
func (this *NoteService) CountNote() int {
return db.Count(db.Notes, bson.M{"IsTrash": false})
}
func (this *NoteService) CountBlog() int {
return db.Count(db.Notes, bson.M{"IsBlog": true, "IsTrash": false})
}

View File

@@ -5,7 +5,7 @@ import (
"gopkg.in/mgo.v2/bson"
"github.com/leanote/leanote/app/db"
"github.com/leanote/leanote/app/info"
// . "github.com/leanote/leanote/app/lea"
. "github.com/leanote/leanote/app/lea"
"sort"
"time"
)
@@ -96,6 +96,11 @@ func (this *NotebookService) GetNotebook(notebookId, userId string) info.Noteboo
db.GetByIdAndUserId(db.Notebooks, notebookId, userId, &notebook)
return notebook
}
func (this *NotebookService) GetNotebookById(notebookId string) info.Notebook {
notebook := info.Notebook{}
db.Get(db.Notebooks, notebookId, &notebook)
return notebook
}
// 得到用户下所有的notebook
// 排序好之后返回
@@ -168,11 +173,15 @@ func (this *NotebookService) UpdateNotebook(userId, notebookId string, needUpdat
// 如果有IsBlog之类的, 需要特殊处理
if isBlog, ok := needUpdate["IsBlog"]; ok {
// 设为blog/取消
// 设为blog/取消, 把它下面所有的note都设为isBlog
if is, ok2 := isBlog.(bool); ok2 {
q := bson.M{"UserId": bson.ObjectIdHex(userId),
"NotebookId": bson.ObjectIdHex(notebookId)}
db.UpdateByQMap(db.Notes, q, bson.M{"IsBlog": is})
data := bson.M{"IsBlog": is}
if is {
data["PublicTime"] = time.Now()
}
db.UpdateByQMap(db.Notes, q, data)
// noteContents也更新, 这个就麻烦了, noteContents表没有NotebookId
// 先查该notebook下所有notes, 得到id
@@ -249,3 +258,26 @@ func (this *NotebookService) DragNotebooks(userId string, curNotebookId string,
return true
}
// 重新统计笔记本下的笔记数目
// noteSevice: AddNote, CopyNote, CopySharedNote, MoveNote
// trashService: DeleteNote (recove不用, 都统一在MoveNote里了)
func (this *NotebookService) ReCountNotebookNumberNotes(notebookId string) bool {
notebookIdO := bson.ObjectIdHex(notebookId)
count := db.Count(db.Notes, bson.M{"NotebookId": notebookIdO, "IsTrash": false})
Log(count)
Log(notebookId)
return db.UpdateByQField(db.Notebooks, bson.M{"_id": notebookIdO}, "NumberNotes", count)
}
func (this *NotebookService) ReCountAll() {
/*
// 得到所有笔记本
notebooks := []info.Notebook{}
db.ListByQWithFields(db.Notebooks, bson.M{}, []string{"NotebookId"}, &notebooks)
for _, each := range notebooks {
this.ReCountNotebookNumberNotes(each.NotebookId.Hex())
}
*/
}

View File

@@ -2,11 +2,9 @@ package service
import (
"gopkg.in/mgo.v2/bson"
"github.com/revel/revel"
"github.com/leanote/leanote/app/db"
"github.com/leanote/leanote/app/info"
. "github.com/leanote/leanote/app/lea"
"fmt"
)
// 找回密码
@@ -32,14 +30,7 @@ func (this *PwdService) FindPwd(email string) (ok bool, msg string) {
}
// 发送邮件
siteUrl, _ := revel.Config.String("site.url")
url := siteUrl + "/findPassword/" + token
body := fmt.Sprintf("请点击链接修改密码: <a href='%v'>%v</a>. %v小时后过期.", url, url, int(overHours));
if !SendEmail(email, "leanote-找回密码", "找回密码", body) {
return false, "邮箱发送失败"
}
ok = true
ok, msg = emailService.FindPwdSendEmail(token, email)
return
}

View File

@@ -0,0 +1,71 @@
package service
import (
"github.com/leanote/leanote/app/info"
"github.com/leanote/leanote/app/db"
. "github.com/leanote/leanote/app/lea"
"gopkg.in/mgo.v2/bson"
"time"
// "strings"
)
// Session存储到mongodb中
type SessionService struct {
}
func (this *SessionService) Update(sessionId, key string, value interface{}) bool {
return db.UpdateByQMap(db.Sessions, bson.M{"SessionId": sessionId},
bson.M{key: value, "UpdatedTime": time.Now()})
}
// 注销时清空session
func (this *SessionService) Clear(sessionId string) bool {
return db.Delete(db.Sessions, bson.M{"SessionId": sessionId})
}
func (this *SessionService) Get(sessionId string) info.Session {
session := info.Session{}
db.GetByQ(db.Sessions, bson.M{"SessionId": sessionId}, &session)
// 如果没有session, 那么插入一条之
if session.Id == "" {
session.Id = bson.NewObjectId()
session.SessionId = sessionId
session.CreatedTime = time.Now()
session.UpdatedTime = session.CreatedTime
db.Insert(db.Sessions, session)
}
return session
}
//------------------
// 错误次数处理
// 登录错误时间是否已超过了
func (this *SessionService) LoginTimesIsOver(sessionId string) bool {
session := this.Get(sessionId)
return session.LoginTimes > 5
}
// 登录成功后清空错误次数
func (this *SessionService) ClearLoginTimes(sessionId string) bool {
return this.Update(sessionId, "LoginTimes", 0)
}
// 增加错误次数
func (this *SessionService) IncrLoginTimes(sessionId string) bool {
session := this.Get(sessionId)
return this.Update(sessionId, "LoginTimes", session.LoginTimes + 1)
}
//----------
// 验证码
func (this *SessionService) GetCaptcha(sessionId string) string {
session := this.Get(sessionId)
return session.Captcha
}
func (this *SessionService) SetCaptcha(sessionId, captcha string) bool {
this.Get(sessionId)
Log(sessionId)
Log(captcha)
ok := this.Update(sessionId, "Captcha", captcha)
Log(ok)
return ok
}

View File

@@ -28,7 +28,13 @@ func (this *TrashService) DeleteNote(noteId, userId string) bool {
// 首先删除其共享
if shareService.DeleteShareNoteAll(noteId, userId) {
// 更新note isTrash = true
return db.UpdateByIdAndUserId(db.Notes, noteId, userId, bson.M{"$set": bson.M{"IsTrash": true}})
if db.UpdateByIdAndUserId(db.Notes, noteId, userId, bson.M{"$set": bson.M{"IsTrash": true}}) {
// recount notebooks' notes number
notebookIdO := noteService.GetNotebookId(noteId)
notebookId := notebookIdO.Hex()
notebookService.ReCountNotebookNumberNotes(notebookId)
return true
}
}
return false
}

View File

@@ -0,0 +1,27 @@
package service
import (
"github.com/leanote/leanote/app/info"
. "github.com/leanote/leanote/app/lea"
"github.com/leanote/leanote/app/db"
"gopkg.in/mgo.v2/bson"
// "time"
)
type UpgradeService struct {
}
// 添加了PublicTime, RecommendTime
func (this *UpgradeService) UpgradeBlog() bool {
notes := []info.Note{}
db.ListByQ(db.Notes, bson.M{"IsBlog": true}, &notes)
// PublicTime, RecommendTime = UpdatedTime
for _, note := range notes {
db.UpdateByIdAndUserIdMap2(db.Notes, note.NoteId, note.UserId, bson.M{"PublicTime": note.UpdatedTime, "RecommendTime": note.UpdatedTime})
Log(note.NoteId.Hex())
}
return true
}

View File

@@ -1,20 +1,17 @@
package service
import (
"github.com/revel/revel"
"github.com/leanote/leanote/app/info"
"github.com/leanote/leanote/app/db"
. "github.com/leanote/leanote/app/lea"
"gopkg.in/mgo.v2/bson"
"time"
"strings"
"fmt"
)
type UserService struct {
}
// 添加用户
func (this *UserService) AddUser(user info.User) bool {
if user.UserId == "" {
@@ -27,7 +24,9 @@ func (this *UserService) AddUser(user info.User) bool {
// 发送验证邮箱
go func() {
this.RegisterSendActiveEmail(user.UserId.Hex(), user.Email)
emailService.RegisterSendActiveEmail(user, user.Email)
// 发送给我 life@leanote.com
emailService.SendEmail("life@leanote.com", "新增用户", "{header}用户名" + user.Email + "{footer}");
}();
}
@@ -69,10 +68,23 @@ func (this *UserService) GetUserInfoByAny(idEmailUsername string) info.User {
return this.GetUserInfoByUsername(idEmailUsername)
}
func (this *UserService) setUserLogo(user *info.User) {
// Logo路径问题, 有些有http: 有些没有
if user.Logo == "" {
user.Logo = "images/blog/default_avatar.png"
}
if user.Logo != "" && !strings.HasPrefix(user.Logo, "http") {
user.Logo = strings.Trim(user.Logo, "/")
user.Logo = siteUrl + "/" + user.Logo
}
}
// 得到用户信息 userId
func (this *UserService) GetUserInfo(userId string) info.User {
user := info.User{}
db.Get(db.Users, userId, &user)
// Logo路径问题, 有些有http: 有些没有
this.setUserLogo(&user)
return user
}
// 得到用户信息 email
@@ -99,29 +111,27 @@ func (this *UserService) ListUserInfosByUserIds(userIds []bson.ObjectId) []info.
db.ListByQ(db.Users, bson.M{"_id": bson.M{"$in": userIds}}, &users)
return users
}
// 用户信息和博客设置信息
func (this *UserService) MapUserInfoAndBlogInfosByUserIds(userIds []bson.ObjectId) map[bson.ObjectId]info.User {
func (this *UserService) ListUserInfosByEmails(emails []string) []info.User {
users := []info.User{}
db.ListByQ(db.Users, bson.M{"Email": bson.M{"$in": emails}}, &users)
return users
}
// 用户信息即可
func (this *UserService) MapUserInfoByUserIds(userIds []bson.ObjectId) map[bson.ObjectId]info.User {
users := []info.User{}
db.ListByQ(db.Users, bson.M{"_id": bson.M{"$in": userIds}}, &users)
userBlogs := []info.UserBlog{}
db.ListByQWithFields(db.UserBlogs, bson.M{"_id": bson.M{"$in": userIds}}, []string{"Logo"}, &userBlogs)
userBlogMap := make(map[bson.ObjectId]info.UserBlog, len(userBlogs))
for _, user := range userBlogs {
userBlogMap[user.UserId] = user
}
userMap := make(map[bson.ObjectId]info.User, len(users))
for _, user := range users {
if userBlog, ok := userBlogMap[user.UserId]; ok {
user.Logo = userBlog.Logo
}
this.setUserLogo(&user)
userMap[user.UserId] = user
}
return userMap
}
// 用户信息和博客设置信息
func (this *UserService) MapUserInfoAndBlogInfosByUserIds(userIds []bson.ObjectId) map[bson.ObjectId]info.User {
return this.MapUserInfoByUserIds(userIds)
}
// 通过ids得到users, 按id的顺序组织users
func (this *UserService) GetUserInfosOrderBySeq(userIds []bson.ObjectId) []info.User {
@@ -174,6 +184,12 @@ func (this *UserService) UpdateUsername(userId, username string) (bool, string)
return ok, ""
}
// 修改头像
func (this *UserService) UpdateAvatar(userId, avatarPath string) (bool) {
userIdO := bson.ObjectIdHex(userId)
return db.UpdateByQField(db.Users, bson.M{"_id": userIdO}, "Logo", avatarPath)
}
//----------------------
// 已经登录了的用户修改密码
func (this *UserService) UpdatePwd(userId, oldPwd, pwd string) (bool, string) {
@@ -194,59 +210,6 @@ func (this *UserService) UpdateTheme(userId, theme string) (bool) {
//---------------
// 修改email
// 发送激活邮件
// AddUser调用
// 可以使用一个goroutine
func (this *UserService) RegisterSendActiveEmail(userId string, email string) bool {
token := tokenService.NewToken(userId, email, info.TokenActiveEmail)
if token == "" {
return false
}
// 发送邮件
siteUrl, _ := revel.Config.String("site.url")
url := siteUrl + "/user/activeEmail?token=" + token
body := fmt.Sprintf("请点击链接验证邮箱: <a href='%v'>%v</a>. %v小时后过期.", url, url, tokenService.GetOverHours(info.TokenActiveEmail));
if !SendEmail(email, "leanote-验证邮箱", "验证邮箱", body) {
return false
}
// 发送给我 life@leanote.com
SendEmail("life@leanote.com", "新增用户", "新增用户", "用户名" + email);
return true
}
// 修改邮箱
func (this *UserService) UpdateEmailSendActiveEmail(userId, email string) (ok bool, msg string) {
// 先验证该email是否被注册了
if userService.IsExistsUser(email) {
ok = false
msg = "该邮箱已注册"
return
}
token := tokenService.NewToken(userId, email, info.TokenUpdateEmail)
if token == "" {
return
}
// 发送邮件
siteUrl, _ := revel.Config.String("site.url")
url := siteUrl + "/user/updateEmail?token=" + token
body := "邮箱验证后您的登录邮箱为: <b>" + email + "</b><br />";
body += fmt.Sprintf("请点击链接验证邮箱: <a href='%v'>%v</a>. %v小时后过期.", url, url, tokenService.GetOverHours(info.TokenUpdateEmail));
if !SendEmail(email, "leanote-验证邮箱", "验证邮箱", body) {
msg = "发送失败, 该邮箱存在?"
return
}
ok = true
return
}
// 注册后验证邮箱
func (this *UserService) ActiveEmail(token string) (ok bool, msg, email string) {
tokenInfo := info.Token{}
@@ -308,13 +271,13 @@ func (this *UserService) ThirdAddUser(userId, email, pwd string) (ok bool, msg s
return
}
//------------
// 偏好设置
// 宽度
func (this *UserService)UpdateColumnWidth(userId string, notebookWidth, noteListWidth int) bool {
return db.UpdateByQMap(db.Users, bson.M{"_id": bson.ObjectIdHex(userId)}, bson.M{"NotebookWidth": notebookWidth, "NoteListWidth": noteListWidth})
func (this *UserService)UpdateColumnWidth(userId string, notebookWidth, noteListWidth, mdEditorWidth int) bool {
return db.UpdateByQMap(db.Users, bson.M{"_id": bson.ObjectIdHex(userId)},
bson.M{"NotebookWidth": notebookWidth, "NoteListWidth": noteListWidth, "mdEditorWidth": mdEditorWidth})
}
// 左侧是否隐藏
func (this *UserService)UpdateLeftIsMin(userId string, leftIsMin bool) bool {
@@ -341,3 +304,47 @@ func (this *UserService) ListUsers(pageNumber, pageSize int, sortField string, i
page = info.NewPage(pageNumber, pageSize, count, nil)
return
}
func (this *UserService) GetAllUserByFilter(userFilterEmail, userFilterWhiteList, userFilterBlackList string, verified bool) []info.User {
query := bson.M{}
if verified {
query["Verified"] = true
}
orQ := []bson.M{}
if userFilterEmail != "" {
orQ = append(orQ, bson.M{"Email": bson.M{"$regex": bson.RegEx{".*?" + userFilterEmail + ".*", "i"}}},
bson.M{"Username": bson.M{"$regex": bson.RegEx{".*?" + userFilterEmail + ".*", "i"}}},
)
}
if(userFilterWhiteList != "") {
userFilterWhiteList = strings.Replace(userFilterWhiteList, "\r", "", -1)
emails := strings.Split(userFilterWhiteList, "\n");
orQ = append(orQ, bson.M{"Email": bson.M{"$in": emails}})
}
if len(orQ) > 0 {
query["$or"] = orQ
}
emailQ := bson.M{}
if(userFilterBlackList != "") {
userFilterWhiteList = strings.Replace(userFilterBlackList, "\r", "", -1)
bEmails := strings.Split(userFilterBlackList, "\n");
emailQ["$nin"] = bEmails
query["Email"] = emailQ
}
LogJ(query)
users := []info.User{}
q := db.Users.Find(query);
q.All(&users)
Log(len(users))
return users
}
// 统计
func (this *UserService) CountUser() int {
return db.Count(db.Users, bson.M{})
}

View File

@@ -1,7 +1,7 @@
package service
import (
"github.com/revel/revel"
)
// init service, for share service bettween services
@@ -24,7 +24,12 @@ var attachService, AttachS *AttachService
var configService, ConfigS *ConfigService
var PwdS *PwdService
var SuggestionS *SuggestionService
var emailService, EmailS *EmailService
var AuthS *AuthService
var UpgradeS *UpgradeService
var SessionS, sessionService *SessionService
var siteUrl string
// onAppStart调用
func InitService() {
@@ -45,6 +50,9 @@ func InitService() {
PwdS = &PwdService{}
SuggestionS = &SuggestionService{}
AuthS = &AuthService{}
EmailS = NewEmailService()
UpgradeS = &UpgradeService{}
SessionS = &SessionService{}
notebookService = NotebookS
noteService = NoteS
@@ -60,4 +68,9 @@ func InitService() {
albumService = AlbumS
attachService = AttachS
configService = ConfigS
emailService = EmailS
sessionService = SessionS
//
siteUrl, _ = revel.Config.String("site.url")
}

View File

@@ -189,7 +189,8 @@ func testLea() {
func main() {
revel.BasePath = "/Users/life/Documents/Go/package/src/leanote"
testLea();
// testLea();
// a, b := SplitFilename("http://ab/c/a.gif#??")
// println(a)
// println(b)

View File

@@ -4,22 +4,11 @@
<section class="panel panel-default">
<div class="row wrapper">
<div class="col-sm-5 m-b-xs">
<select class="input-sm form-control input-s-sm inline v-middle">
<option value="0">
Bulk action
</option>
<option value="1">
Delete selected
</option>
<option value="2">
Bulk edit
</option>
<option value="3">
Export
</option>
</select>
<button class="btn btn-sm btn-default">
Apply
Action1
</button>
<button class="btn btn-sm btn-default">
Action2
</button>
</div>
<div class="col-sm-4 m-b-xs">
@@ -62,16 +51,7 @@
<i class="fa fa-sort"></i>
</span>
</th>
<th
{{sorterTh $url "isRecommend" .sorter}}
>
isRecommend
<span class="th-sort">
<i class="fa fa-sort-down"></i>
<i class="fa fa-sort-up"></i>
<i class="fa fa-sort"></i>
</span>
</th>
<th
{{sorterTh $url "createdTime" .sorter}}
>
@@ -82,8 +62,6 @@
<i class="fa fa-sort"></i>
</span>
</th>
<th width="30">
</th>
</tr>
</thead>
<tbody>
@@ -100,21 +78,9 @@
{{.User.Username}}
</a>
</td>
<td>
<button data-loading-text="..." class="btn btn-default change-recommend" data-id="{{.NoteId.Hex}}" data-recommend="{{if .IsRecommend}}1{{else}}0{{end}}">
{{if .IsRecommend}}
Y
{{else}}
N
{{end}}
</button>
</td>
<td>
{{.CreatedTime|datetime}}
</td>
<td>
<a href="#" class="btn btn-default">Send Email</a>
</td>
</tr>
{{end}}
</tbody>
@@ -123,22 +89,11 @@
<footer class="panel-footer">
<div class="row">
<div class="col-sm-4 hidden-xs">
<select class="input-sm form-control input-s-sm inline v-middle">
<option value="0">
Bulk action
</option>
<option value="1">
Delete selected
</option>
<option value="2">
Bulk edit
</option>
<option value="3">
Export
</option>
</select>
<button class="btn btn-sm btn-default">
Apply
Action1
</button>
<button class="btn btn-sm btn-default">
Action2
</button>
</div>
<div class="col-sm-4 text-center">

View File

@@ -1,21 +1,21 @@
{{template "admin/top.html" .}}
<div class="m-b-md"> <h3 class="m-b-none">Blog</h3></div>
<div class="m-b-md"> <h3 class="m-b-none">Mongodb Tool Configuration</h3></div>
<div class="row">
<div class="col-sm-6">
<form id="add_user_form">
<form id="data_form">
<section class="panel panel-default">
<div class="panel-body">
<div class="form-group">
<label>Recommend Tags</label>
<input type="text" class="form-control" name="recommendTags" value="{{.recommendTags}}">
Split by ','
<label>mongodump path</label>
<input type="text" class="form-control" name="mongodumpPath" value="{{.str.mongodumpPath}}" placeholder="">
Please input the bin mongodump's absolute path
</div>
<div class="form-group">
<label>New Tags</label>
<input type="text" class="form-control" name="newTags" value="{{.newTags}}">
Split by ','
<label>mongorestore path</label>
<input type="text" class="form-control" name="mongorestorePath" value="{{.str.mongorestorePath}}" placeholder="">
Please input the bin mongorestore's absolute path
</div>
</div>
@@ -32,14 +32,14 @@
<script src="/public/admin/js/jquery-validation-1.13.0/jquery.validate.js"></script>
<script>
$(function() {
init_validator("#add_user_form");
init_validator("#data_form");
$("#submit").click(function(e){
e.preventDefault();
var t = this;
if($("#add_user_form").valid()) {
if($("#data_form").valid()) {
$(t).button('loading');
ajaxPost("/adminSetting/doBlogTag", getFormJsonData("add_user_form"), function(ret){
ajaxPost("/adminSetting/mongodb", getFormJsonData("data_form"), function(ret){
$(t).button('reset')
if(!ret.Ok) {
art.alert(ret.Msg)

View File

@@ -0,0 +1,115 @@
{{template "admin/top.html" .}}
<div class="m-b-md"> <h3 class="m-b-none">Backup & Restore</h3></div>
<style>
.break-all {
word-break:break-all; /*支持IEchromeFF不支持*/
word-wrap:break-word;/*支持IEchromeFF*/
}
</style>
<section class="panel panel-default">
<div class="row wrapper">
<div class="col-sm-5 m-b-xs">
<button class="btn btn-primary backup-btn">Backup</button>
</div>
</div>
<div class="table-responsive">
<table class="table table-striped b-t b-light">
<thead>
<tr>
<th width="136px">
Date
</th>
<th width="">
Remark
</th>
<th>
Path
</th>
<th width="170px">
</th>
</tr>
</thead>
<tbody>
{{range $each := .backups}}
<tr>
<td>
{{$each.createdTime|unixDatetime}}
</td>
<td>
<textarea class="remark" data-id="{{$each.createdTime}}">{{$each.remark}}</textarea>
</td>
<td class="break-all">
{{$each.path}}
</td>
<td>
<button href="#" class="btn btn-sm btn-danger restore-btn" data-id="{{$each.createdTime}}">Restore</button>
<a class="btn btn-sm btn-default download-attach" href="/adminData/download?createdTime={{$each.createdTime}}" target="_blank" title="Download" data-id=""><i class="fa fa-download"></i></a>
<button class="btn btn-sm btn-warning delete-btn" title="Delete" data-id="{{$each.createdTime}}"><i class="fa fa-trash-o"></i></button>
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
</section>
{{template "admin/footer.html" .}}
<script>
$(function() {
$(".backup-btn").click(function(){
ajaxGet("/adminData/backup", {}, function(ret) {
if(ret.Ok) {
art.tips("Success");
location.reload();
} else {
art.alert(ret.Msg);
}
});
});
// 还原
$(".restore-btn").click(function() {
var createdTime = $(this).data("id");
art.confirm("Are you sure? <br />Note. Leanote will do the following steps: <br />1)Backup the current database first. <br />2) And then delete the database. <br />3) Restore database from the selected version.", function() {
ajaxGet("/adminData/restore", {createdTime: createdTime}, function(ret) {
if(ret.Ok) {
art.tips("Success");
location.reload();
} else {
art.alert(ret.Msg);
}
});
});
});
$(".delete-btn").click(function() {
var createdTime = $(this).data("id");
art.confirm("Are you sure?", function() {
ajaxGet("/adminData/delete", {createdTime: createdTime}, function(ret) {
if(ret.Ok) {
art.tips("Success");
location.reload();
} else {
art.alert(ret.Msg);
}
});
});
});
$(".remark").change(function() {
var createdTime = $(this).data("id");
var remark = $(this).val();
ajaxPost("/adminData/updateRemark", {createdTime: createdTime, remark: remark}, function(ret) {
if(ret.Ok) {
art.tips("Update Remark Success");
} else {
art.alert(ret.Msg);
}
});
});
});
</script>
{{template "admin/end.html" .}}

View File

@@ -0,0 +1,76 @@
<div class="row" style="width: 500px;
height: 500px;
overflow-y: scroll;">
<div class="col-sm-12">
<form id="sendEmailForm">
<section class="panel panel-default">
<header class="panel-heading font-bold">Email</header>
<div class="panel-body">
<div class="form-group">
<label>Email List</label>
<textarea type="text" rows="10" class="form-control" name="emails">{{.emailsNl}}</textarea>
input email line by line
</div>
<div class="form-group">
<label>Select Old Email</label>
<select class="form-control old-emails">
<option value="">---Select---</option>
{{range $subject, $body := .map.oldEmails}}
<option>
{{$subject}}
</option>
{{end}}
</select>
</div>
<div class="form-group">
<label>Subject</label>
<input type="text" class="form-control" id="latestEmailSubject" name="latestEmailSubject" value="{{$.str.latestEmailSubject}}">
</div>
<div class="form-group">
<label>Body</label>
<textarea type="text" rows="10" id="latestEmailBody" class="form-control" name="latestEmailBody">{{$.str.latestEmailBody}}</textarea>
</div>
<label class="checkbox-inline"> <input type="checkbox" id="saveAsOldEmail" name="saveAsOldEmail" value="1"> Save As Old Email </label>
</div>
<footer class="panel-footer text-right bg-light lter">
<button type="submit" id="submitEmail" class="btn btn-success btn-s-xs">Submit</button>
</footer>
</section>
</form>
</div>
</div>
<script>
var oldEmails = eval("(" + {{json .map.oldEmails}} + ")");
$(function() {
$(".old-emails").change(function() {
var subject = $(this).val();
var body = oldEmails[subject];
if(subject) {
$("#latestEmailSubject").val(subject);
$("#latestEmailBody").val(body);
$("#saveAsOldEmail").prop("checked", false);
}
});
$("#submitEmail").click(function(e){
e.preventDefault();
var t = this;
$(t).button('loading');
ajaxPost("/adminEmail/sendToUsers2", getFormJsonData("sendEmailForm"), function(ret){
$(t).button('reset')
if(!ret.Ok) {
art.alert(ret.Msg)
} else {
art.tips("Success");
}
});
});
});
</script>

View File

@@ -0,0 +1,191 @@
{{template "admin/top.html" .}}
<div class="m-b-md"> <h3 class="m-b-none">Email Logs</h3></div>
<section class="panel panel-default">
<div class="row wrapper">
<div class="col-sm-5 m-b-xs">
<button class="btn btn-sm btn-default bulk-send">
Send
</button>
<button class="btn btn-sm btn-default bulk-delete">
Delete
</button>
</div>
<div class="col-sm-4 m-b-xs">
</div>
<div class="col-sm-3">
<div class="input-group search-group">
<input type="text" class="input-sm form-control" placeholder="Email" id="keywords" value="{{.keywords}}" />
<span class="input-group-btn">
<button class="btn btn-sm btn-default" type="button" data-url="/adminEmail/list">Search</button>
</span>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-striped b-t b-light">
<thead>
<tr>
<th width="20">
<input type="checkbox">
</th>
{{$url := urlConcat "/adminEmail/list" "keywords" .keywords}}
<th
{{sorterTh $url "email" .sorter}}
>
Email
<span class="th-sort">
<i class="fa fa-sort-down"></i>
<i class="fa fa-sort-up"></i>
<i class="fa fa-sort"></i>
</span>
</th>
<th
{{sorterTh $url "subject" .sorter}}
>
Subject
<span class="th-sort">
<i class="fa fa-sort-down"></i>
<i class="fa fa-sort-up"></i>
<i class="fa fa-sort"></i>
</span>
</th>
<th
{{sorterTh $url "ok" .sorter}}
>
Ok
<span class="th-sort">
<i class="fa fa-sort-down"></i>
<i class="fa fa-sort-up"></i>
<i class="fa fa-sort"></i>
</span>
</th>
<th
{{sorterTh $url "msg" .sorter}}
>
Msg
<span class="th-sort">
<i class="fa fa-sort-down"></i>
<i class="fa fa-sort-up"></i>
<i class="fa fa-sort"></i>
</span>
</th>
<th
{{sorterTh $url "createdTime" .sorter}}
>
Date
<span class="th-sort">
<i class="fa fa-sort-down"></i>
<i class="fa fa-sort-up"></i>
<i class="fa fa-sort"></i>
</span>
</th>
<th>
</th>
</tr>
</thead>
<tbody>
{{range .emails}}
<tr id="tr_{{.LogId.Hex}}">
<td>
<input type="checkbox" class="ck" data-email="{{.Email}}" data-id="{{.LogId.Hex}}">
</td>
<td>
{{.Email}}
</td>
<td>
{{.Subject}}
</td>
<td>
{{.Ok}}
</td>
<td>
{{.Msg}}
</td>
<td>
{{.CreatedTime|datetime}}
</td>
<td>
<a href="#" class="btn btn-default send-email" data-email="{{.Email}}">Send</a>
<a href="#" class="btn btn-default delete-email" data-id="{{.LogId.Hex}}">Delete</a>
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
<footer class="panel-footer">
<div class="row">
<div class="col-sm-4 hidden-xs">
<button class="btn btn-sm btn-default bulk-send">
Send
</button>
<button class="btn btn-sm btn-default bulk-delete">
Delete
</button>
</div>
<div class="col-sm-4 text-right text-center-xs">
{{set . "url" (urlConcat "/adminEmail/list" "sorter" .sorter "keywords" .keywords)}}
{{template "admin/user/page.html" .}}
</div>
</div>
</footer>
</section>
{{template "admin/footer.html" .}}
<script>
$(function() {
$(".send-email").click(function() {
openSendEmailDialog($(this).data("email"));
});
$(".bulk-send").click(function() {
var emails = [];
$(".ck:checked").each(function() {
emails.push($(this).data("email"));
});
if(emails.length == 0) {
art.alert("No user");
return;
}
openSendEmailDialog(emails.join(","));
});
function deleteEmails(ids) {
if(!isArray(ids)) {
ids = [ids];
}
ajaxPost("/adminEmail/deleteEmails", {ids: ids.join(",")}, function(ret) {
if(ret.Ok) {
if(ids.length > 8) {
location.reload();
}
for(var i in ids) {
$("#tr_" + ids[i]).remove();
}
}
});
}
$(".delete-email").click(function() {
var id = $(this).data('id');
deleteEmails(id);
});
$(".bulk-delete").click(function() {
var ids = [];
$(".ck:checked").each(function() {
ids.push($(this).data("id"));
});
if(ids.length == 0) {
art.alert("No email");
return;
}
deleteEmails(ids);
});
});
</script>
{{template "admin/end.html" .}}

View File

@@ -0,0 +1,33 @@
{{if gt .pageInfo.TotalPage 1}}
<ul class="pagination pagination-sm m-t-none m-b-none">
<li class="{{if eq $.pageInfo.CurPage 1}}disabled{{end}}" >
<a href="{{if eq $.pageInfo.CurPage 1}}javascript:;{{else}}{{sub $.pageInfo.CurPage | urlConcat $.url "page" }}{{end}}">
<i class="fa fa-chevron-left">
</i>
</a>
</li>
{{range $i := N 1 .pageInfo.TotalPage}}
{{if eq $i $.pageInfo.CurPage}}
<li class="active">
<a href="javascript:;">
{{$i}}
</a>
</li>
{{else}}
<li class="">
<a href="{{urlConcat $.url "page" $i}}">
{{$i}}
</a>
</li>
{{end}}
{{end}}
<li class="{{if eq .pageInfo.CurPage .pageInfo.TotalPage}}disabled{{end}}" >
<a href="{{if eq .pageInfo.CurPage .pageInfo.TotalPage}}javascript:;{{else}}{{add $.pageInfo.CurPage | urlConcat $.url "page" }}{{end}}">
<i class="fa fa-chevron-right">
</i>
</a>
</li>
</ul>
{{end}}

View File

@@ -0,0 +1,85 @@
{{template "admin/top.html" .}}
<div class="m-b-md"> <h3 class="m-b-none">Send Email</h3></div>
<div class="row">
<div class="col-sm-12">
<form id="formContainer">
<section class="panel panel-default">
<header class="panel-heading font-bold">Email</header>
<div class="panel-body">
<div class="form-group">
<label>Email List</label>
<textarea type="text" rows="10" class="form-control" name="sendEmails">{{.str.sendEmails}}</textarea>
input email line by line
</div>
<div class="form-group">
<label>Select Old Email</label>
<select class="form-control old-emails">
<option value="">---Select---</option>
{{range $subject, $body := .map.oldEmails}}
<option>
{{$subject}}
</option>
{{end}}
</select>
</div>
<div class="form-group">
<label>Subject</label>
<input type="text" class="form-control" id="latestEmailSubject" name="latestEmailSubject" value="{{$.str.latestEmailSubject}}">
</div>
<div class="form-group">
<label>Body</label>
<textarea type="text" rows="10" id="latestEmailBody" class="form-control" name="latestEmailBody">{{$.str.latestEmailBody}}</textarea>
</div>
<label class="checkbox-inline"> <input type="checkbox" id="saveAsOldEmail" name="saveAsOldEmail" value="1"> Save As Old Email </label>
</div>
<footer class="panel-footer text-right bg-light lter">
<button type="submit" id="submit" class="btn btn-success btn-s-xs">Submit</button>
</footer>
</section>
</form>
</div>
</div>
{{template "admin/footer.html" .}}
<script src="/public/admin/js/jquery-validation-1.13.0/jquery.validate.js"></script>
<script>
var oldEmails = eval("(" + {{json .map.oldEmails}} + ")");
$(function() {
init_validator("#formContainer");
$(".old-emails").change(function() {
var subject = $(this).val();
var body = oldEmails[subject];
if(subject) {
$("#latestEmailSubject").val(subject);
$("#latestEmailBody").val(body);
$("#saveAsOldEmail").prop("checked", false);
}
});
$("#submit").click(function(e){
e.preventDefault();
var t = this;
if($("#formContainer").valid()) {
$(t).button('loading');
ajaxPost("/adminEmail/sendEmailToEmails", getFormJsonData("formContainer"), function(ret){
$(t).button('reset')
if(!ret.Ok) {
art.alert(ret.Msg)
} else {
art.tips("Success");
}
});
}
});
});
</script>
{{template "admin/end.html" .}}

View File

@@ -0,0 +1,105 @@
{{template "admin/top.html" .}}
<div class="m-b-md"> <h3 class="m-b-none">Send Email to Users</h3></div>
<div class="row">
<div class="col-sm-12">
<form id="formContainer">
<section class="panel panel-default">
<header class="panel-heading font-bold">User filter</header>
<div class="panel-body">
<div class="form-group">
<label>Search Email/Username</label>
<input type="text" class="form-control" name="userFilterEmail" value="{{.str.userFilterEmail}}">
</div>
<div class="form-group">
<label class="checkbox-inline"> <input type="checkbox" name="verified" value="1"> Verfied </label>
</div>
<div class="form-group">
<label>White List</label>
<textarea type="text" rows="10" class="form-control" name="userFilterWhiteList">{{.str.userFilterWhiteList}}</textarea>
input email line by line
</div>
<div class="form-group">
<label>Black List</label>
<textarea type="text" rows="10" class="form-control" name="userFilterBlackList">{{.str.userFilterBlackList}}</textarea>
input email line by line
</div>
</div>
</section>
<section class="panel panel-default">
<header class="panel-heading font-bold">Email</header>
<div class="panel-body">
<div class="form-group">
<label>Select Old Email</label>
<select class="form-control old-emails">
<option value="">---Select---</option>
{{range $subject, $body := .map.oldEmails}}
<option>
{{$subject}}
</option>
{{end}}
</select>
</div>
<div class="form-group">
<label>Subject</label>
<input type="text" class="form-control" id="latestEmailSubject" name="latestEmailSubject" value="{{$.str.latestEmailSubject}}">
</div>
<div class="form-group">
<label>Body</label>
<textarea type="text" rows="10" id="latestEmailBody" class="form-control" name="latestEmailBody">{{$.str.latestEmailBody}}</textarea>
</div>
<label class="checkbox-inline"> <input type="checkbox" id="saveAsOldEmail" name="saveAsOldEmail" value="1"> Save As Old Email </label>
</div>
<footer class="panel-footer text-right bg-light lter">
<button type="submit" id="submit" class="btn btn-success btn-s-xs">Submit</button>
</footer>
</section>
</form>
</div>
</div>
{{template "admin/footer.html" .}}
<script src="/public/admin/js/jquery-validation-1.13.0/jquery.validate.js"></script>
<script>
var oldEmails = eval("(" + {{json .map.oldEmails}} + ")");
$(function() {
init_validator("#formContainer");
$(".old-emails").change(function() {
var subject = $(this).val();
var body = oldEmails[subject];
if(subject) {
$("#latestEmailSubject").val(subject);
$("#latestEmailBody").val(body);
$("#saveAsOldEmail").prop("checked", false);
}
});
$("#submit").click(function(e){
e.preventDefault();
var t = this;
if($("#formContainer").valid()) {
$(t).button('loading');
ajaxPost("/adminEmail/sendToUsers", getFormJsonData("formContainer"), function(ret){
$(t).button('reset')
if(!ret.Ok) {
art.alert(ret.Msg)
} else {
art.tips("Success");
}
});
}
});
});
</script>
{{template "admin/end.html" .}}

View File

@@ -0,0 +1,62 @@
{{template "admin/top.html" .}}
<div class="m-b-md"> <h3 class="m-b-none">Email Configuration</h3></div>
<div class="row">
<div class="col-sm-6">
<form id="add_user_form">
<section class="panel panel-default">
<div class="panel-body">
<div class="form-group">
<label>Host</label>
<input type="text" class="form-control" name="emailHost" value="{{.str.emailHost}}" placeholder="eg. smtp.ym.163.com">
</div>
<div class="form-group">
<label>Port</label>
<input type="text" class="form-control" name="emailPort" value="{{.str.emailPort}}" placeholder="eg. 25">
</div>
<div class="form-group">
<label>Username</label>
<input type="text" class="form-control" name="emailUsername" value="{{.str.emailUsername}}" placeholder="">
</div>
<div class="form-group">
<label>Password</label>
<input type="text" class="form-control" name="emailPassword" value="{{.str.emailPassword}}" placeholder="">
</div>
</div>
<footer class="panel-footer text-right bg-light lter">
<button type="submit" id="submit" class="btn btn-success btn-s-xs">Submit</button>
</footer>
</section>
</form>
</div>
</div>
{{template "admin/footer.html" .}}
<script src="/public/admin/js/jquery-validation-1.13.0/jquery.validate.js"></script>
<script>
$(function() {
init_validator("#add_user_form");
$("#submit").click(function(e){
e.preventDefault();
var t = this;
if($("#add_user_form").valid()) {
$(t).button('loading');
ajaxPost("/adminEmail/set", getFormJsonData("add_user_form"), function(ret){
$(t).button('reset')
if(!ret.Ok) {
art.alert(ret.Msg)
} else {
art.tips("Success");
}
});
}
});
});
</script>
{{template "admin/end.html" .}}

View File

@@ -0,0 +1,325 @@
{{template "admin/top.html" .}}
<div class="m-b-md"> <h3 class="m-b-none">Email Template</h3></div>
<style>
.preview {
overflow: auto;
padding: 10px 0;
}
label {
}
</style>
<form id="add_user_form">
<section class="panel panel-default">
<header class="panel-heading bg-light">
<ul class="nav nav-tabs nav-justified">
<li class="active"><a href="#tab1" data-toggle="tab">Layout</a></li>
<li class=""><a href="#tab2" data-toggle="tab">Register</a></li>
<li class=""><a href="#tab3" data-toggle="tab">Update Email</a></li>
<li ><a href="#tab4" data-toggle="tab">Find Passord</a></li>
<li ><a href="#tab5" data-toggle="tab">Invite Register</a></li>
<li ><a href="#tab6" data-toggle="tab">Blog Comment</a></li>
</ul>
</header>
<div class="panel-body">
<div class="tab-content">
<div class="tab-pane active" id="tab1">
<div class="row">
<div class="col-sm-12">
<section class="panel panel-default">
<div class="panel-body">
<b>Layout</b>
<div>
Available tokens:
<code>$.subject</code>
<code>$.siteUrl</code>
</div>
<div class="form-group">
<label>Header</label>
<textarea type="text" id="emailHeader" rows="10" class="form-control" name="emailTemplateHeader">{{.str.emailTemplateHeader}}</textarea>
</div>
<div class="form-group">
<label>Footer</label>
<textarea type="text" id="emailFooter" rows="10" class="form-control" name="emailTemplateFooter">{{.str.emailTemplateFooter}}</textarea>
</div>
</div>
</section>
</div>
</div>
</div>
<div class="tab-pane" id="tab2">
<div class="row">
<div class="col-sm-12">
<section class="panel panel-default">
<div class="panel-body">
<b>Register Welcome And Email Validation:</b>
<div>
Available tokens:
<code>header</code>
<code>footer</code>
<code>$.siteUrl</code>
<code>$.tokenUrl</code>
<code>$.token</code>
<code>$.tokenTimeout</code>
<code>$.user.userId</code>
<code>$.user.email</code>
<code>$.user.username</code>
</div>
<div class="form-group">
<label>Subject</label>
<input type="text" class="form-control" name="emailTemplateRegisterSubject" value="{{.str.emailTemplateRegisterSubject}}">
</div>
<div class="form-group">
<label>Body</label>
<textarea type="text" rows="10" class="form-control" name="emailTemplateRegister">{{.str.emailTemplateRegister}}</textarea>
</div>
<div>
Preview
<div class="preview">
</div>
</div>
</div>
</section>
</div>
</div>
</div>
<div class="tab-pane" id="tab3">
<div class="row">
<div class="col-sm-12">
<section class="panel panel-default">
<div class="panel-body">
<b>Update Email and Send Active Email</b>
<div>
Available tokens:
<code>header</code>
<code>footer</code>
<code>$.siteUrl</code>
<code>$.tokenUrl</code>
<code>$.token</code>
<code>$.tokenTimeout</code>
<code>$.user.userId</code>
<code>$.user.email</code>
<code>$.user.username</code>
</div>
<div class="form-group">
<label>Subject</label>
<input type="text" class="form-control" name="emailTemplateUpdateEmailSubject" value="{{.str.emailTemplateUpdateEmailSubject}}">
</div>
<div class="form-group">
<label>Body</label>
<textarea type="text" rows="10" class="form-control" name="emailTemplateUpdateEmail">{{.str.emailTemplateUpdateEmail}}</textarea>
</div>
<div>
Preview
<div class="preview">
</div>
</div>
</div>
</section>
</div>
</div>
</div>
<div class="tab-pane" id="tab4">
<div class="row">
<div class="col-sm-12">
<section class="panel panel-default">
<div class="panel-body">
<b>Find Passord</b>
<div>
Available tokens:
<code>header</code>
<code>footer</code>
<code>$.siteUrl</code>
<code>$.tokenUrl</code>
<code>$.token</code>
<code>$.tokenTimeout</code>
</div>
<div class="form-group">
<label>Subject</label>
<input type="text" class="form-control" name="emailTemplateFindPasswordSubject" value="{{.str.emailTemplateFindPasswordSubject}}">
</div>
<div class="form-group">
<label>Body</label>
<textarea type="text" rows="10" class="form-control" name="emailTemplateFindPassword">{{.str.emailTemplateFindPassword}}</textarea>
</div>
<div>
Preview
<div class="preview">
</div>
</div>
</div>
</section>
</div>
</div>
</div>
<div class="tab-pane" id="tab5">
<div class="row">
<div class="col-sm-12">
<section class="panel panel-default">
<div class="panel-body">
<b>Invite Register</b>
<div>
Available tokens:
<code>header</code>
<code>footer</code>
<code>$.siteUrl</code>
<code>$.registerUrl</code>
<code>$.user.username</code>
<code>$.user.email</code>
<code>$.content</code>
</div>
<div class="form-group">
<label>Subject</label>
<input type="text" class="form-control" name="emailTemplateInviteSubject" value="{{.str.emailTemplateInviteSubject}}">
</div>
<div class="form-group">
<label>Body</label>
<textarea type="text" rows="10" class="form-control" name="emailTemplateInvite">{{.str.emailTemplateInvite}}</textarea>
</div>
<div>
Preview
<div class="preview">
</div>
</div>
</div>
</section>
</div>
</div>
</div>
<div class="tab-pane" id="tab6">
<div class="row">
<div class="col-sm-12">
<section class="panel panel-default">
<div class="panel-body">
<b>Blog Comment</b>
<div>
Available tokens:
<code>header</code>
<code>footer</code>
<code>$.siteUrl</code>
<code>$.blogUrl</code>
<br />
<code>$.commentContent</code>
<br />
<code>$.blog.id</code>
<code>$.blog.title</code>
<code>$.blog.url</code>
<br />
<code>$.commentUser.userId</code>
<code>$.commentUser.username</code>
<code>$.commentUser.email</code>
<code>$.commentUser.isBlogAuthor</code>
<br />
<code>$.commentedUser.userId</code>
<code>$.commentedUser.username</code>
<code>$.commentedUser.email</code>
<code>$.commentedUser.isBlogAuthor</code>
</div>
<div class="form-group">
<label>Subject</label>
<input type="text" class="form-control" name="emailTemplateCommentSubject" value="{{.str.emailTemplateCommentSubject}}">
</div>
<div class="form-group">
<label>Body</label>
<textarea type="text" rows="10" class="form-control" name="emailTemplateComment">{{.str.emailTemplateComment}}</textarea>
</div>
<div>
Preview
<div class="preview">
</div>
</div>
</div>
</section>
</div>
</div>
</div>
</div>
</div>
</section>
<footer class="panel-footer text-right bg-light lter">
<button type="submit" id="submit" class="btn btn-success btn-s-xs">Submit</button>
</footer>
</form>
{{template "admin/footer.html" .}}
<script src="/public/admin/js/jquery-validation-1.13.0/jquery.validate.js"></script>
<script>
$(function() {
$("code").each(function() {
var h = $(this).text();
$(this).text("{" + "{" + h + "}" + "}");
});
function previewEmail(t) {
var $p = $(t).closest(".row");
var tpl = $(t).val();
var subject = $p.find("input").val() || "";
var $preview = $p.find(".preview");
var header = $("#emailHeader").val();
var footer = $("#emailFooter").val();
header = header.replace("{" + "{$.subject}" + "}", subject);
tpl = tpl.replace("{" + "{header}" + "}", header);
tpl = tpl.replace("{" + "{footer}" + "}", footer);
$preview.html(tpl);
}
$("textarea").each(function() {
previewEmail(this);
});
$("textarea").keyup(function() {
previewEmail(this);
});
init_validator("#add_user_form");
$("#submit").click(function(e){
e.preventDefault();
var t = this;
if($("#add_user_form").valid()) {
$(t).button('loading');
ajaxPost("/adminEmail/template", getFormJsonData("add_user_form"), function(ret){
$(t).button('reset')
if(!ret.Ok) {
art.alert(ret.Msg)
} else {
art.tips("Success");
}
});
}
});
});
</script>
{{template "admin/end.html" .}}

View File

@@ -9,11 +9,11 @@
<div class="panel-body">
<div class="form-group">
<label>Demo Username</label>
<input type="text" class="form-control" name="demoUsername" value="{{.demoUsername}}">
<input type="text" class="form-control" name="demoUsername" value="{{.str.demoUsername}}">
</div>
<div class="form-group">
<label>Demo Password</label>
<input type="text" class="form-control" name="demoPassword" value="{{.demoPassword}}">
<input type="text" class="form-control" name="demoPassword" value="{{.str.demoPassword}}">
</div>
</div>

View File

@@ -0,0 +1,173 @@
{{template "admin/top.html" .}}
<div class="m-b-md"> <h3 class="m-b-none">Register Share Note</h3></div>
<div class="row">
<div class="col-sm-6">
<form id="formContainer">
<section class="panel panel-default">
<div class="panel-body">
<div class="form-group">
<label>Shared UserId</label>
<input type="text" class="form-control" name="registerSharedUserId" value="{{.str.registerSharedUserId}}">
</div>
<div class="form-group">
<label>Shared Notebooks</label>
<div class="clearfix" id="notebooks">
{{range $notebook := .arrMap.registerSharedNotebooks}}
<div class="row">
<div class="col-xs-8">
<input type="text" class="form-control" placeholder="notebookId" name="registerSharedNotebookIds[]"
value="{{$notebook.notebookId}}"
>
</div>
<div class="col-xs-4">
<select class="form-control" name="registerSharedNotebookPerms[]">
<option value="0" {{if eq $notebook.perm "0"}}selected{{end}}>Read Only</option>
<option value="1" {{if eq $notebook.perm "1"}}selected{{end}}>Writable</option>
</select>
</div>
</div>
{{end}}
<div class="row">
<div class="col-xs-8">
<input type="text" class="form-control" placeholder="notebookId" name="registerSharedNotebookIds[]">
</div>
<div class="col-xs-4">
<select class="form-control" name="registerSharedNotebookPerms[]">
<option value="0">Read Only</option>
<option value="1">Writable</option>
</select>
</div>
</div>
<div class="row">
<div class="col-xs-8">
<input type="text" class="form-control" placeholder="notebookId" name="registerSharedNotebookIds[]">
</div>
<div class="col-xs-4">
<select class="form-control" name="registerSharedNotebookPerms[]">
<option value="0">Read Only</option>
<option value="1">Writable</option>
</select>
</div>
</div>
</div>
The notebooks will shared to register user
</div>
<div class="form-group">
<label>Shared Notes</label>
<div class="clearfix" id="notebooks">
{{range $note := .arrMap.registerSharedNotes}}
<div class="row">
<div class="col-xs-8">
<input type="text" class="form-control" name="registerSharedNoteIds[]"
value="{{$note.noteId}}"
/>
</div>
<div class="col-xs-4">
<select class="form-control" name="registerSharedNotePerms[]">
<option value="0" {{if eq $note.perm "0"}}selected{{end}}>Read Only</option>
<option value="1" {{if eq $note.perm "1"}}selected{{end}}>Writable</option>
</select>
</div>
</div>
{{end}}
<div class="row">
<div class="col-xs-8">
<input type="text" class="form-control" placeholder="noteId" name="registerSharedNoteIds[]">
</div>
<div class="col-xs-4">
<select class="form-control" name="registerSharedNotePerms[]">
<option value="0">Read Only</option>
<option value="1">Writable</option>
</select>
</div>
</div>
<div class="row">
<div class="col-xs-8">
<input type="text" class="form-control" placeholder="noteId" name="registerSharedNoteIds[]">
</div>
<div class="col-xs-4">
<select class="form-control" name="registerSharedNotePerms[]">
<option value="0">Read Only</option>
<option value="1">Writable</option>
</select>
</div>
</div>
</div>
The notes will shared to register user
</div>
<div class="form-group">
<label>Copy Notes</label>
<div class="clearfix" id="notebooks">
{{range $noteId := .arr.registerCopyNoteIds}}
<div class="row">
<div class="col-xs-12">
<input type="text" class="form-control" name="registerCopyNoteIds[]"
value="{{$noteId}}"
placeholder="noteId"
/>
</div>
</div>
{{end}}
<div class="row">
<div class="col-xs-12">
<input type="text" class="form-control" name="registerCopyNoteIds[]" placeholder="noteId"/>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<input type="text" class="form-control" name="registerCopyNoteIds[]" placeholder="noteId"/>
</div>
</div>
</div>
The notes will copy to register user
</div>
</div>
<footer class="panel-footer text-right bg-light lter">
<button type="submit" id="submit" class="btn btn-success btn-s-xs">Submit</button>
</footer>
</section>
</form>
</div>
</div>
{{template "admin/footer.html" .}}
<script src="/public/admin/js/jquery-validation-1.13.0/jquery.validate.js"></script>
<script>
var a = "{{json .arrMap}}"
$(function() {
init_validator("#formContainer");
$("#submit").click(function(e){
e.preventDefault();
var t = this;
if($("#formContainer").valid()) {
$(t).button('loading');
ajaxPost("/adminSetting/shareNote", getFormJsonData("formContainer"), function(ret){
$(t).button('reset')
if(!ret.Ok) {
art.alert(ret.Msg)
} else {
art.tips(ret.Msg || "Success");
}
});
}
});
});
</script>
{{template "admin/end.html" .}}

View File

@@ -45,7 +45,7 @@ $(function() {
ajaxPost("/auth/doRegister", getFormJsonData("add_user_form"), function(ret){
$(t).button('reset')
if(!ret.Ok) {
art.alert(ret.Msg)
art.alert(ret.Msg);
} else {
art.tips("Success");
}

View File

@@ -1,24 +1,18 @@
{{template "admin/top.html" .}}
<div class="m-b-md"> <h3 class="m-b-none">User</h3></div>
<div class="m-b-md"> <h3 class="m-b-none">Users</h3></div>
<section class="panel panel-default">
<div class="row wrapper">
<div class="col-sm-5 m-b-xs">
<select class="input-sm form-control input-s-sm inline v-middle">
<option value="0">
<option value="">
Bulk action
</option>
<option value="1">
Delete selected
</option>
<option value="2">
Bulk edit
</option>
<option value="3">
Export
Send Email
</option>
</select>
<button class="btn btn-sm btn-default">
<button class="btn btn-sm btn-default bulk-btn">
Apply
</button>
</div>
@@ -90,7 +84,7 @@
{{range .users}}
<tr>
<td>
<input type="checkbox" name="post[]" value="2">
<input type="checkbox" class="ck" data-email="{{.Email}}" value="2">
</td>
<td>
{{.Email}}
@@ -103,15 +97,9 @@
</td>
<td>
{{.CreatedTime|datetime}}
<a href="#" class="active" data-toggle="class">
<i class="fa fa-check text-success text-active">
</i>
<i class="fa fa-times text-danger text">
</i>
</a>
</td>
<td>
<a href="#" class="btn btn-default">Send Email</a>
<a href="#" class="btn btn-default send-email" data-email="{{.Email}}">Send Email</a>
</td>
</tr>
{{end}}
@@ -122,32 +110,21 @@
<div class="row">
<div class="col-sm-4 hidden-xs">
<select class="input-sm form-control input-s-sm inline v-middle">
<option value="0">
<option value="">
Bulk action
</option>
<option value="1">
Delete selected
</option>
<option value="2">
Bulk edit
</option>
<option value="3">
Export
Send Email
</option>
</select>
<button class="btn btn-sm btn-default">
<button class="btn btn-sm btn-default bulk-btn">
Apply
</button>
</div>
<div class="col-sm-4 text-center">
<small class="text-muted inline m-t-sm m-b-sm">
showing 20-30 of 50 items
</small>
</div>
<div class="col-sm-4 text-right text-center-xs">
<div class="col-sm-8 text-right text-center-xs">
{{set . "url" (urlConcat "/adminUser/index" "sorter" .sorter "keywords" .keywords)}}
{{template "admin/user/page.html" .}}
</div>
</div>
</footer>
@@ -157,6 +134,23 @@
<script>
$(function() {
$(".send-email").click(function() {
openSendEmailDialog($(this).data("email"));
});
$(".bulk-btn").click(function() {
// email
if($(this).prev().val() == "1") {
var emails = [];
$(".ck:checked").each(function() {
emails.push($(this).data("email"));
});
if(emails.length == 0) {
art.alert("No user");
return;
}
openSendEmailDialog(emails.join(","));
}
});
});
</script>

View File

@@ -5,8 +5,7 @@
</section>
</section>
</section>
<!-- Bootstrap -->
<!-- App -->
<script src="/js/jquery-1.9.0.min.js"></script>
<script src="/js/bootstrap.js"></script>
<script src="/public/admin/js/artDialog/jquery.artDialog.js?skin=default"></script>
@@ -14,23 +13,18 @@
<script src="/public/admin/js/admin.js"></script>
<script>
$(function() {
var pathname = location.pathname;
var arr = pathname.split("/");
if(arr.length == 0){
return;
var pathname = location.pathname; // admin/t
var search = location.search; // ?t=xxx, 如果有?page呢
var fullPath = pathname;
if(search.indexOf("?t=") >= 0) {
var fullPath = pathname + search; // /admin/t?t=xxx
}
var controller = "";
var action = "";
if(arr[0] == "") {
arr = arr.slice(1);
}
controller = arr[0];
if(arr.length > 1) {
action = arr[1];
}
$("#nav > li").removeClass("active");
$("#" + controller + "Nav").addClass("active");
$('a[href="' + pathname + '"]').parent().addClass("active");
$("#nav > li").removeClass("active");
// 自己
var $thisLi = $('#nav a[href^="' + fullPath + '"]').parent();
$thisLi.addClass("active");
// 父也active
$thisLi.parent().parent().addClass('active');
});
</script>

View File

@@ -1,15 +1,15 @@
{{template "admin/top.html" .}}
<div class="m-b-md"> <h3 class="m-b-none">Workset</h3> <small>Welcome you!</small> </div>
<div class="m-b-md"> <h3 class="m-b-none">Dashboard</h3></div>
<section class="panel panel-default">
<div class="row m-l-none m-r-none bg-light lter">
<div class="col-sm-6 col-md-3 padder-v b-r b-light">
<span class="fa-stack fa-2x pull-left m-r-sm">
<i class="fa fa-circle fa-stack-2x text-info"></i>
<i class="fa fa-male fa-stack-1x text-white"></i>
<i class="fa fa-users fa-stack-1x text-white"></i>
</span>
<a class="clear" href="#">
<span class="h3 block m-t-xs"><strong>52,000</strong></span>
<small class="text-muted text-uc">users</small>
<a class="clear" href="/adminUser/index">
<span class="h3 block m-t-xs"><strong>{{.countUser}}</strong></span>
<small class="text-muted text-uc">Users</small>
</a>
</div>
<div class="col-sm-6 col-md-3 padder-v b-r b-light lt">
@@ -17,9 +17,19 @@
<i class="fa fa-circle fa-stack-2x text-warning"></i>
<i class="fa fa-file-o fa-stack-1x text-white"></i>
</span>
<a class="clear" href="#">
<span class="h3 block m-t-xs"><strong>1312,000</strong></span>
<small class="text-muted text-uc">notes</small>
<a class="clear" href="javascript:;">
<span class="h3 block m-t-xs"><strong>{{.countNote}}</strong></span>
<small class="text-muted text-uc">Notes</small>
</a>
</div>
<div class="col-sm-6 col-md-3 padder-v b-r b-light">
<span class="fa-stack fa-2x pull-left m-r-sm">
<i class="fa fa-circle fa-stack-2x text-info"></i>
<i class="fa fa-bold fa-stack-1x text-white"></i>
</span>
<a class="clear" href="/adminBlog/index">
<span class="h3 block m-t-xs"><strong>{{.countBlog}}</strong></span>
<small class="text-muted text-uc">Blogs</small>
</a>
</div>
</div>
@@ -30,51 +40,10 @@
<h4 class="font-thin padder">
Leanote Events
</h4>
<ul class="list-group">
<li class="list-group-item">
<p>
Wellcome
<a href="#" class="text-info">
@Drew Wllon
</a>
and play this web application template, have fun1
</p>
<small class="block text-muted">
<i class="fa fa-clock-o">
</i>
2 minuts ago
</small>
</li>
<li class="list-group-item">
<p>
Morbi nec
<a href="#" class="text-info">
@Jonathan George
</a>
nunc condimentum ipsum dolor sit amet, consectetur
</p>
<small class="block text-muted">
<i class="fa fa-clock-o">
</i>
1 hour ago
</small>
</li>
<li class="list-group-item">
<p>
<a href="#" class="text-info">
@Josh Long
</a>
Vestibulum ullamcorper sodales nisi nec adipiscing elit.
</p>
<small class="block text-muted">
<i class="fa fa-clock-o">
</i>
2 hours ago
</small>
</li>
</ul>
<ul class="list-group" id="eventsList"></ul>
</section>
<!--
<section class="panel panel-default">
<form>
<textarea class="form-control no-border" rows="3" placeholder="Suggestions to leanote"></textarea>
@@ -84,23 +53,32 @@
POST
</button>
<ul class="nav nav-pills nav-sm">
<!--
<li>
<a href="#">
<i class="fa fa-camera text-muted">
</i>
</a>
</li>
<li>
<a href="#">
<i class="fa fa-video-camera text-muted">
</i>
</a>
</li>
</ul>
-->
</footer>
</section>
-->
{{template "admin/footer.html" .}}
<script>
$(function() {
// leanote动态
// http://leanote.com/blog/cate/5446753cfacfaa4f56000000
var url = "http://localhost:9000/blog/listCateLatest/54269c83e5276724ac000000";
function renderItem(item) {
return '<li class="list-group-item"><p><a target="_blank" href="http://leanote.com/blog/view/' + item.NoteId + '">' + item.Title + '</a></p><small class="block text-muted"><i class="fa fa-clock-o"></i> ' + goNowToDatetime(item.PublicTime) + '</small></li>';
}
$.getJSON(url, function(data) {
log(data);
if(typeof data == "object" && data.Ok) {
var list = data.List;
var html = "";
for(var i = 0; i < list.length; ++i) {
var item = list[i];
html += renderItem(item);
}
$("#eventsList").html(html);
}
});
});
</script>
{{template "admin/end.html" .}}

View File

@@ -1,8 +1,21 @@
<nav class="nav-primary hidden-xs">
<ul class="nav" id="nav">
<li class="active" id="adminUserNav">
<a href="index.html">
<li class="active">
<a href="/admin/index">
<i class="fa fa-dashboard icon">
<b class="bg-success">
</b>
</i>
<span>
Dashboard
</span>
</a>
</li>
<li id="adminUserNav">
<a href="#">
<i class="fa fa-users icon">
<b class="bg-danger">
</b>
@@ -22,7 +35,7 @@
<li>
<a href="/adminUser/index">
<span>
List
Users
</span>
</a>
</li>
@@ -47,11 +60,65 @@
</span>
</a>
</li>
<li id="adminEmailNav">
<a href="#layout">
<i class="fa fa-envelope-o icon">
<b class="bg-warning">
</b>
</i>
<span class="pull-right">
<i class="fa fa-angle-down text">
</i>
<i class="fa fa-angle-up text-active">
</i>
</span>
<span>
Email
</span>
</a>
<ul class="nav lt">
<li>
<a href="/admin/t?t=email/set">
<span>
Configuration
</span>
</a>
</li>
<li>
<a href="/admin/t?t=email/template">
<span>
Template
</span>
</a>
</li>
<li>
<a href="/admin/t?t=email/sendToUsers">
<span>
Send Email to Users
</span>
</a>
</li>
<li>
<a href="/admin/t?t=email/send">
<span>
Send Email
</span>
</a>
</li>
<li>
<a href="/adminEmail/list">
<span>
Email Logs
</span>
</a>
</li>
</ul>
</li>
<li id="adminSettingNav">
<a href="#layout">
<i class="fa fa-cog icon">
<b class="bg-warning">
<b class="bg-info">
</b>
</i>
<span class="pull-right">
@@ -66,60 +133,56 @@
</a>
<ul class="nav lt">
<li>
<a href="layout-c.html">
<span>
Register
</span>
</a>
</li>
<li>
<a href="layout-c.html">
<span>
Login
</span>
</a>
</li>
<li>
<a href="layout-r.html">
<span>
Email
</span>
</a>
</li>
<li>
<a href="layout-h.html">
<span>
Share
</span>
</a>
</li>
<li>
<a href="/adminSetting/blog">
<span>
Blog
</span>
</a>
</li>
<li>
<a href="/adminSetting/demo">
<a href="/admin/t?t=setting/demo">
<span>
Demo User
</span>
</a>
</li>
<li>
<a href="/admin/t?t=setting/shareNote">
<span>
Register Share Note
</span>
</a>
</li>
</ul>
</li>
<li>
<a href="#layout">
<a href="#">
<i class="fa fa-columns icon">
<b class="bg-warning">
<b class="bg-success">
</b>
</i>
<span class="pull-right">
<i class="fa fa-angle-down text">
</i>
<i class="fa fa-angle-up text-active">
</i>
</span>
<span>
Others
Data
</span>
</a>
<ul class="nav lt">
<li>
<a href="/admin/t?t=data/configuration">
<span>
Mongodb Tool Configuration
</span>
</a>
</li>
<li>
<a href="/adminData/index">
<span>
Backup & Restore
</span>
</a>
</li>
</ul>
</li>
</ul>
</nav>

View File

@@ -26,18 +26,28 @@
<ul class="nav navbar-nav navbar-right m-n hidden-xs nav-user">
<li class="hidden-xs">
<a href="/index" class="dk">
Index
<a href="http://leanote.com" class="dk" target="_blank">
Leanote Home
</a>
</li>
<li class="hidden-xs">
<a href="/note" class="dk">
<a href="https://github.com/leanote/leanote" class="dk" target="_blank">
Leanote Github
</a>
</li>
<li class="hidden-xs">
<a href="http://lea.leanote.com" class="dk" target="_blank">
lea++
</a>
</li>
<li class="hidden-xs">
<a href="/note" class="dk" target="_blank">
My Note
</a>
</li>
<li class="hidden-xs">
<a href="/blog/admin" class="dk">
Blog
<a href="/blog/admin" class="dk" target="_blank">
My Blog
</a>
</li>
<li class="hidden-xs">
@@ -52,6 +62,7 @@
<!-- .aside -->
<aside class="bg-light lter b-r aside-md hidden-print hidden-xs" id="nav">
<section class="vbox">
<!--
<header class="header bg-primary lter text-center clearfix">
<div class="btn-group">
<div class="hidden-nav-xs">
@@ -61,6 +72,8 @@
</div>
</div>
</header>
-->
<section class="w-f scrollable">
<div class="slim-scroll" data-height="auto" data-disable-fade-out="true"
data-distance="0" data-size="5px" data-color="#333333">
@@ -69,14 +82,17 @@
<!-- / nav -->
</div>
</section>
<footer class="footer lt hidden-xs b-t b-light">
<footer class="footer lt hidden-xs b-t b-light" style="min-height: initial;
padding: 10px 15px;text-align:center;">
<a href="http://leanote.com" target="_blank">leanote</a> © 2014
<!--
<a href="#nav" data-toggle="class:nav-xs" class="pull-right btn btn-sm btn-default btn-icon">
<i class="fa fa-angle-left text">
</i>
<i class="fa fa-angle-right text-active">
</i>
</a>
-->
</footer>
</section>
</aside>
@@ -84,7 +100,7 @@
<section id="content">
<section class="vbox">
<section class="scrollable padder">
<!-- 导航 -->
<!-- 导航
<ul class="breadcrumb no-border no-radius b-b b-light pull-in">
<li>
<a href="index.html">
@@ -102,5 +118,6 @@
Components
</li>
</ul>
-->
<!-- 主要内容区 -->

View File

@@ -10,10 +10,8 @@
</div>
<div class="desc">
{{.userBlog.AboutMe | raw}}
</div>
<!-- comment -->
{{template "blog/comment.html" .}}
</div>
</div>
</div>
</div>

View File

@@ -1,4 +1,190 @@
{{if .userBlog.CanComment}}
<!---->
<div class="entry-controls clearfix">
<div class="vote-section-wrapper clearfix">
<button class="btn btn-default btn-zan" id="likeBtn"><i class="fa fa-thumbs-o-up"></i> <span id="likeNum">{{.blog.LikeNum}}</span> {{msg . "like"}}</button>
<span class="control-item read-counts"><i class="fa fa-eye"></i> {{if .blog.ReadNum}}{{.blog.ReadNum}}{{else}}1{{end}} {{msg . "viewers"}}</span>
</div>
<div class="right-section">
<div id="weixinQRCode"></div>
<!-- google+
<g:plusone size=”medium”></g:plusone>
-->
<button class="btn btn-share btn-default btn-weibo"><i class="fa fa-weibo"></i> {{msg . "sinaWeibo"}}</button>
<button class="btn btn-share btn-default btn-weixin"><i class="fa fa-wechat"></i> {{msg . "weixin"}}</button>
<div class="dropdown" style="display: inline-block; cursor: pointer; padding: 5px 10px;">
<!-- open -->
<div class="dropdown-toggle" data-hover="dropdown" data-toggle="dropdown">
<i class="fa fa-share-square-o"></i>
{{msg . "moreShare"}}
</div>
<ul class="dropdown-menu" role="menu">
<li><a href="#" class="btn-share tencent-weibo"><i class="fa fa-tencent-weibo"></i> {{msg . "tencentWeibo"}}</a></li>
<li><a href="#" class="btn-share qq"><i class="fa fa-qq"></i> {{msg . "qqZone"}}</a></li>
<li><a href="#" class="btn-share renren"><i class="fa fa-renren"></i> {{msg . "renren"}}</a></li>
</ul>
</div>
<!-- 举报 -->
{{if eq .locale "zh"}}
<div style="display: inline-block">
<a id="reportBtn"><i class="fa fa-flag-o"></i> {{msg . "report"}}</a>
</div>
{{end}}
</div>
<div class="voters clearfix" id="likers">
</div>
</div>
<script type="text/x-jsrender" id="tLikers">
[[for users]]
<a id="liker_[[:UserId]]" href="[[:~root.blogUrl]]/[[:Username]]" target="_blank" class="voter">
[[if Logo]]
<img alt="avatar" class="avatar-small" src="[[:Logo]]">
[[else]]
<img alt="avatar" class="avatar-small" src="/images/blog/default_avatar.png">
[[/if]]
</a>
[[/for]]
</script>
{{if and .userBlog.CanComment (not (eq .userBlog.CommentType "disqus"))}}
<script type="text/x-jsrender" id="tComments">
[[for comments]]
<li class="comment-item">
<!-- 头像 -->
<a ui-hovercard="" target="_blank" class="avatar-link" title="[[:UserInfo.Username]]" href="[[:~root.blogUrl]]/[[:UserInfo.Username]]">
<img class="avatar" src="[[:UserInfo.Logo]]">
</a>
<!-- 评论 -->
<div class="comment-body">
<div class="comment-hd">
<a href="[[:~root.blogUrl]]/[[:UserInfo.Username]]" target="_blank" >[[:UserInfo.Username]]</a>
[[if IsAuthorComment]]
<span>({{msg . "author"}})</span>
[[/if]]
<!-- 回复其它人 -->
[[if ToUserInfo]]
<span class="in-reply-to">
{{rawMsg . "reply"}}
<a href="[[:~root.blogUrl]]/[[:ToUserInfo.Username]]">[[:ToUserInfo.Username]]</a>
</span>
[[if ToUserIsAuthor]]
<span>({{msg . "author"}})</span>
[[/if]]
[[/if]]
</div>
<div class="comment-content ng-binding" ng-bind-html="comment.content">
[[html:Content]]
</div>
<div class="comment-ft clearfix" data-comment-id="[[:CommentId]]" >
<span title="" ui-time="" class="date">[[:PublishDate]] </span>
<span class="like-num [[if !LikeNum]]hide[[/if]]" title="[[:LikeNum]] {{rawMsg . "like"}}"><span class="like-num-i">[[:LikeNum]]</span> {{rawMsg . "like"}}</span></span>
[[if ~root.visitUserInfo.UserId]]
[[if IsMyNote && !IsMyComment]]
<a href="javascript:;" class="comment-trash op-link "><i class="fa fa-trash"></i> {{rawMsg . "delete"}}</a>
[[/if]]
[[if !IsMyComment]]
<a href="javascript:;" class="comment-reply op-link ">
<i class="fa fa-reply"></i>
{{rawMsg . "reply"}}
</a>
<a href="javascript:;" class="comment-like op-link"><i class="fa fa-thumbs-o-up"></i> <span class="like-text">[[if IsILikeIt]]{{rawMsg . "unlike"}}[[else]]{{rawMsg . "like"}}[[/if]]</span></a>
{{if eq .locale "zh"}}
<a href="javascript:;" class="comment-report op-link "><i class="fa fa-flag-o"></i> {{rawMsg . "report"}}</a>
{{end}}
[[else]]
<a href="javascript:;" class="comment-trash op-link "><i class="fa fa-trash"></i> {{rawMsg . "delete"}}</a>
[[/if]]
[[/if]]
</div>
<!-- 回复该评论 -->
[[if ~root.visitUserInfo.UserId]]
<form class="comment-form comment-box-ft">
<div class="clearfix">
<div class="avatar-wrap">
<img class="avatar" src="[[:~root.visitUserInfo.Logo]]">
</div>
<div class="editor-wrap">
<textarea class="editable" id="commentContent" name="commentContent" placeholder="{{rawMsg . "reply"}}"></textarea>
</div>
</div>
<div class="command clearfix" style="display: block;">
<button class="reply-comment-btn save btn btn-primary" data-comment-id="[[:CommentId]]">{{rawMsg . "comment"}}</button>
<a class="cancel reply-cancel btn-link">{{rawMsg . "cancel"}}</a>
</div>
</form>
[[/if]]
</div>
</li>
[[/for]]
</script>
<!-- 评论 -->
<div class="comment-box hide">
{{if .visitUserInfo.UserId}}
<form class="comment-form comment-box-ft" id="commentForm">
<div class="clearfix">
<div class="avatar-wrap">
<img class="avatar" src="{{.visitUserInfo.Logo}}">
</div>
<div class="editor-wrap">
<textarea class="editable" id="commentContent" name="commentContent" placeholder="{{msg . "comment"}}" style="height: 100px;"></textarea>
</div>
</div>
<div class="command clearfix" style="display: block;">
<button id="commentBtn" class="reply-comment-btn save btn btn-primary">{{msg . "comment"}}</button>
</div>
</form>
{{else}}
<div class="needLogin">
<a onclick="goLogin()">{{msg . "signIn"}}</a>, {{msg . "submitComment"}}.
<br />
没有帐号? <a onclick="goRegister()">{{msg . "signUp"}}</a>
</div>
{{end}}
<div class="box-header">
<span class="counter">
<i class="icon icon-comment"></i><span id="commentNum">{{.blog.CommentNum}}</span> {{msg . "comments"}}
</span>
</div>
<ul id="comments">
</ul>
</div>
<div id="moreComments">
<div class="hide comments-more">
<a>More...</a>
</div>
<div class="comments-loading">
<img src="/images/loading-32.gif" />
</div>
</div>
{{if eq .locale "zh"}}
<div id="reportMsg" class="hide">
<form class="report-form" name="reportForm">
<ul class="options clearfix">
<li><label><input required="" value="{{msg . "reportReason1"}}" name="reason" type="radio">{{msg . "reportReason1"}}</label></li>
<li><label><input required="" value="{{msg . "reportReason2"}}" name="reason" type="radio">{{msg . "reportReason2"}}</label></li>
<li><label><input required="" value="{{msg . "reportReason3"}}" name="reason" type="radio">{{msg . "reportReason3"}}</label></li>
<li><label><input required="" value="{{msg . "reportReason4"}}" name="reason" type="radio">{{msg . "reportReason4"}}</label></li>
<li><label><input required="" value="" name="reason" type="radio">{{msg . "other"}}</label></li>
</ul>
<p class="input-container" style="display: none">
<input placeholder="{{msg . "reportReason"}}" type="text" name="detail" class="form-control reason-text basic-input" />
</p>
<p class="footnote"></p>
</form>
</div>
{{end}}
{{end}}
{{if and .userBlog.CanComment (eq .userBlog.CommentType "disqus")}}
<div id="disqus_thread"></div>
<!-- comment -->
<script type="text/javascript">

View File

@@ -4,57 +4,51 @@
<div class="col-md-4">
<h3>{{msg . "blogNavs"}}</h3>
<ul>
<li><a href="/blog/{{$userId}}">{{msg . "home"}}</a></li>
<li><a href="{{$.blogUrl}}/{{$.userInfo.Username}}">{{msg . "home"}}</a></li>
{{range .notebooks}}
<li>
<a href="/blog/{{$userId}}/{{.NotebookId.Hex}}">{{.Title}}</a>
<a href="{{$.cateUrl}}/{{.NotebookId.Hex}}">{{.Title}}</a>
</li>
{{end}}
<li><a href="/blog/aboutMe/{{$userId}}">{{msg . "aboutMe"}}</a></li>
<li><a href="{{$.aboutMeUrl}}">{{msg . "aboutMe"}}</a></li>
</ul>
</div>
<div class="col-md-4">
<h3>{{msg . "latestPosts"}}</h3>
<ul>
{{range .recentBlogs}}
<li title="{{.Title}}"><a href="/blog/view/{{.NoteId.Hex}}/">{{.Title}}</a></li>
<li title="{{.Title}}"><a href="{{$.blogUrl}}/view/{{.NoteId.Hex}}/">{{.Title}}</a></li>
{{end}}
</ul>
</div>
<div class="col-md-4">
<h3>{{msg . "quickLinks"}}</h3>
<ul>
<li><a href="/note">{{msg . "myNote"}}</a></li>
<li><a href="/login">{{msg . "login"}}</a></li>
<li><a href="http://leanote.com" target="_blank">leanote</a></li>
<li><a href="{{$.noteUrl}}">{{msg . "myNote"}}</a></li>
<li><a href="{{$.siteUrl}}/login">leanote {{msg . "login"}}</a></li>
<li><a href="http://leanote.com" target="_blank">leanote {{msg . "home"}}</a></li>
<li><a href="http://lea.leanote.com" target="_blank">lea++</a></li>
<li><a href="http://bbs.leanote.com" target="_blank">leanote {{msg . "community"}}</a></li>
<li><a href="https://github.com/leanote/leanote" target="_blank">leanote github</a></li>
</ul>
</div>
</div>
</div>
<script src="/js/jquery-1.9.0.min.js"></script>
<script src="/js/bootstrap-min.js"></script>
<script src="{{$.siteUrl}}/js/jquery-1.9.0.min.js"></script>
<script src="{{$.siteUrl}}/js/bootstrap-min.js"></script>
<script src="{{$.siteUrl}}/js/bootstrap-hover-dropdown.js"></script>
<script src="{{$.siteUrl}}/js/i18n/blog.{{.locale}}.js"></script>
{{if not .isMe}}
<script src="{{$.siteUrl}}/blog/isMe.js?userId={{.userBlog.UserId.Hex}}"></script>
{{end}}
<script>
$(function() {
/*
$("#searchInput").click(function() {
$("#search").width("130px");
$("#searchInput").width("100px");
});
$("#searchInput").blur(function() {
$("#search").width(0);
$("#searchInput").width(0);
});
*/
});
// 搜索
function search(e) {
var key = $("#searchInput").val();
if(!key) {
location.href = "/blog/" + UserInfo.Username;
location.href = "{{$.searchUrl}}";
} else {
var tpl = '<form action="/blog/searchBlog/' + UserInfo.Username +'" method="get">';
var tpl = '<form action="{{$.searchUrl}}" method="get">';
tpl += '<input name="key" value="' + key + '" />';
tpl += "</form";
$(tpl).submit();

View File

@@ -10,10 +10,15 @@
<title>{{.title}}</title>
<!-- Bootstrap core CSS -->
<link href="/css/bootstrap.css" rel="stylesheet">
<link href="/css/font-awesome-4.0.3/css/font-awesome.css" rel="stylesheet">
<link id="styleLink" href="/css/blog/{{if .userBlog.Style}}{{.userBlog.Style}}{{else}}blog_default{{end}}.css" rel="stylesheet">
<link href="{{.siteUrl}}/css/bootstrap.css" rel="stylesheet">
<!-- 字体必须同一域 -->
{{if .set}}
<link href="{{.siteUrl}}/css/font-awesome-4.2.0/css/font-awesome.css" rel="stylesheet">
{{else}}
<link href="{{$.staticUrl}}/css/font-awesome-4.2.0/css/font-awesome.css" rel="stylesheet">
{{end}}
<link id="styleLink" href="{{.siteUrl}}/css/blog/{{if .userBlog.Style}}{{.userBlog.Style}}{{else}}blog_default{{end}}.css" rel="stylesheet">
<link href="{{.siteUrl}}/css/blog/comment.css" rel="stylesheet">
<script>
function log(o) {
}
@@ -30,7 +35,7 @@ function log(o) {
{{$userId := .userBlog.UserId.Hex}} <!-- 可不要了 -->
{{$username := .userInfo.Username}}
<h1>
<a href="/blog/{{$username}}" id="logo">
<a href="{{.indexUrl}}" id="logo">
{{if .userBlog.Logo}}
<img src="{{.userBlog.Logo}}" title="{{.userBlog.Title}}"/>
{{else}}
@@ -54,23 +59,29 @@ function log(o) {
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="{{.indexUrl}}">
{{if .userBlog.Logo}}
<img src="{{.userBlog.Logo}}" title="{{.userBlog.Title}}"/>
{{else}}
{{.userBlog.Title | raw}}
{{end}}
</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
{{$navNotebookId := .notebookId}}
<li class="{{if .index}}active{{end}}"><a href="/blog/{{$username}}">{{msg . "home"}}</a></li>
<li class="{{if .index}}active{{end}}"><a href="{{.indexUrl}}">{{msg . "home"}}</a></li>
{{range .notebooks}}
{{$notebookId := .NotebookId.Hex}}
<li class="{{if $navNotebookId}}{{if eq $navNotebookId $notebookId}}active{{else}}{{end}}{{end}}">
<a href="/blog/{{$username}}/{{$notebookId}}"
<a href="{{$.cateUrl}}/{{$notebookId}}"
>{{.Title}}</a>
</li>
{{end}}
<li class="{{if .aboutMe}}active{{end}}"><a href="/blog/aboutMe/{{$username}}">{{msg . "aboutMe"}}</a></li>
{{if .isMe}}
<li class="{{if .set}}active{{end}}"><a href="/blog/set">{{msg . "blogSet"}}</a></li>
<li><a href="/note" >{{msg . "myNote"}}</a></li>
{{end}}
<li class="{{if .aboutMe}}active{{end}}"><a href="{{.aboutMeUrl}}">{{msg . "aboutMe"}}</a></li>
<!-- 同源上传logo -->
<li class="is-me {{if .set}}active{{end}} {{if not .isMe}}hide{{end}}" ><a href="{{$.siteUrl}}/blog/set/">{{msg . "blogSet"}}</a></li>
<li><a href="{{$.noteUrl}}" class="is-me {{if not .isMe}}hide{{end}}">{{msg . "myNote"}}</a></li>
</ul>
<form class="navbar-form navbar-right" id="search" onsubmit="search(event);return false;">
<div class="input-group">
@@ -85,4 +96,9 @@ function log(o) {
<script>
var UserInfo = {UserId: "{{$userId}}", Email: "{{.userInfo.Email}}", Username: "{{.userInfo.Username}}"};
var UserBlogInfo={CanComment: {{.userBlog.CanComment}}, CommentType: "{{.userBlog.CommentType}}"};
var indexUrl = "{{$.indexUrl}}";
var viewUrl = "{{$.viewUrl}}";
var blogUrl = "{{$.blogUrl}}";
var staticUrl = "{{$.staticUrl}}"; // blog.leanote.com, life.leanote.com, aaa.com
</script>

View File

@@ -3,7 +3,7 @@
<div id="postsContainer">
<div class="container">
{{if .notebookId}}
<h2>{{msg . "blogClass"}}: {{.notebook.Title}}</h2>
<h2>{{msg . "blogClass"}} - {{.notebook.Title}}</h2>
{{end}}
</div>
<div id="posts">
@@ -11,30 +11,35 @@
{{range .blogs}}
<div class="each-post">
<div class="title">
<a href="/blog/view/{{.NoteId.Hex}}" title="{{msg $G "fullBlog"}}">
<a href="{{$.viewUrl}}/{{.NoteId.Hex}}" title="{{msg $ "fullBlog"}}">
{{.Title}}
</a>
</div>
<div class="created-time">
<i class="fa fa-bookmark-o" style="color: #666"></i>
<i class="fa fa-bookmark-o"></i>
{{if .Tags}}
{{blogTags .Tags}}
{{blogTags $ .Tags}}
{{else}}
{{msg $G "noTag"}}
{{msg $ "noTag"}}
{{end}}
|
<i class="fa fa-calendar" style="color: #666"></i> {{msg $G "updatedTime"}} {{.UpdatedTime | datetime}} |
<i class="fa fa-calendar" style="color: #666"></i> {{msg $G "createdTime"}} {{.CreatedTime | datetime}}
<i class="fa fa-calendar"></i> {{msg $ "updatedTime"}} {{.UpdatedTime | datetime}} |
<i class="fa fa-calendar"></i> {{msg $ "createdTime"}} {{.CreatedTime | datetime}}
</div>
<div class="desc">
{{.Content | raw}}
</div>
<a class="more" href="/blog/view/{{.NoteId.Hex}}" title="{{msg $G "fullBlog"}}">More...</a>
<a class="more" href="{{$.viewUrl}}/{{.NoteId.Hex}}" title="{{msg $ "fullBlog"}}">{{msg $ "more"}}.</a>
</div>
{{end}}
<!-- 分页 -->
<ul class="pager">
{{page .userInfo.Username .notebookId .page .pageSize .count}}
{{if .notebookId}}
{{set $ "pageUrl" (concatStr $.cateUrl "/" .notebookId)}}
{{else}}
{{set $ "pageUrl" $.indexUrl}}
{{end}}
{{page $.pageUrl .page .pageSize .count}}
</ul>
</div>
</div>

View File

@@ -2,38 +2,38 @@
<div id="postsContainer">
<div class="container">
<h2>搜索 {{.key}} </h2>
<h2>{{msg . "search"}} - {{.key}} </h2>
</div>
<div id="posts">
{{range .blogs}}
<div class="each-post">
<div class="title">
<a href="/blog/view/{{.NoteId.Hex}}" title="全文">
<a href="{{$.viewUrl}}/{{.NoteId.Hex}}" title="{{msg $ "fullBlog"}}">
{{.Title}}
</a>
</div>
<div class="created-time">
<i class="fa fa-bookmark-o" style="color: #666"></i>
<i class="fa fa-bookmark-o"></i>
{{if .Tags}}
{{blogTags .Tags}}
{{blogTags $ .Tags}}
{{else}}
{{msg $ "noTag"}}
{{end}}
|
<i class="fa fa-calendar" style="color: #666"></i> 更新 {{.UpdatedTime | datetime}} |
<i class="fa fa-calendar" style="color: #666"></i> 创建 {{.CreatedTime | datetime}}
<i class="fa fa-calendar"></i> {{msg $ "updatedTime"}} {{.UpdatedTime | datetime}} |
<i class="fa fa-calendar"></i> {{msg $ "createdTime"}} {{.CreatedTime | datetime}}
</div>
<div class="desc">
{{.Content | raw}}
</div>
<a class="more" href="/blog/view/{{.NoteId.Hex}}" title="更多">More...</a>
<a class="more" href="{{$.viewUrl}}/{{.NoteId.Hex}}" title="{{msg $ "fullBlog"}}">{{msg $ "more"}}</a>
</div>
{{end}}
{{if not .blogs }}
<div class="each-post">
{{msg . "none"}}
</div>
{{end}}
</div>

View File

@@ -1,11 +1,12 @@
{{template "Blog/header.html" .}}
<!-- -->
<link rel="stylesheet" href="/tinymce/skins/custom/skin.min.css" type="text/css">
<!-- set页面不是自定义域名和二级域名页 -->
<link rel="stylesheet" href="{{.siteUrl}}/tinymce/skins/custom/skin.min.css" type="text/css">
<style>
.tab-pane {
padding-top: 10px;
}
</style>
<div id="postsContainer">
<div id="posts">
@@ -20,9 +21,18 @@
<div class="tab-pane" id="styleInfo">
<form class="form-horizontal" role="form">
<div class="form-group">
<label for="Style" class="col-sm-2 control-label">{{msg . "theme"}}</label>
<label class="col-sm-2 control-label"></label>
<div class="col-sm-10">
<label><input type="radio" name="Style"
<div class="alert alert-success" id="styleMsg" style="display: none; margin-bottom: 3px;"></div>
</div>
</div>
<div class="form-group">
<label for="Style" class="col-sm-2 control-label">{{msg . "theme"}}</label>
<div class="col-sm-10" style="margin-top: 6px;" id="themeList">
<label>
<img class="preview" src="{{$.siteUrl}}/images/blog/theme/default.png" />
<input type="radio" name="Style"
value="blog_default"
{{if not .userBlog.Style}}
checked="checked"
@@ -31,15 +41,23 @@
checked="checked"
{{end}}
{{end}}>
{{msg . "default"}} </label>
<label><input type="radio" name="Style"
{{msg . "default"}}
</label>
<label>
<img class="preview" src="{{$.siteUrl}}/images/blog/theme/elegent.png" />
<input type="radio" name="Style"
value="blog_daqi"
{{if eq .userBlog.Style "blog_daqi"}}checked="checked"{{end}}>
{{msg . "elegant"}}</label>
<label><input type="radio" name="Style"
{{msg . "elegant"}}
</label>
<label>
<img class="preview" src="{{$.siteUrl}}/images/blog/theme/left_nav_fix.png" />
<input type="radio" name="Style"
value="blog_left_fixed"
{{if eq .userBlog.Style "blog_left_fixed"}}checked="checked"{{end}}>
{{msg . "navFixed"}}</label>
{{msg . "navFixed"}}
</label>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
@@ -47,26 +65,50 @@
<span class="msg"></span>
</div>
</div>
</div>
</form>
</div>
<div class="tab-pane" id="commentInfo">
<form class="form-horizontal" role="form">
<div class="form-group">
<label for="subTitle" class="col-sm-2 control-label">{{msg . "openComment"}}</label>
<label class="col-sm-2 control-label"></label>
<div class="col-sm-10">
<div class="alert alert-success" id="commentMsg" style="display: none; margin-bottom: 3px;"></div>
</div>
</div>
<div class="form-group">
<label for="subTitle" class="col-sm-2 control-label">{{msg . "chooseComment"}}</label>
<div class="col-sm-10">
<label>
<input type="checkbox" id="CanComment" name="CanComment"
{{if .userBlog.CanComment}}checked="checked"{{end}} >
{{if .userBlog.CanComment}}checked="checked"{{end}} > {{msg . "openComment"}}
</label>
<br />
{{msg . "commentSys"}}
<div id="disqusSet">
<div id="commentSet" {{if not .userBlog.CanComment}}style="display: none"{{end}}>
<label>
<input type="radio"
name="commentType"
value="default"
{{if or (not .userBlog.CommentType) (eq .userBlog.CommentType "default")}}checked="checked"{{end}} > Default
</label>
<label>
<input type="radio" name="commentType" id="disqus"
value="disqus"
{{if eq .userBlog.CommentType "disqus"}}checked="checked"{{end}} > Disqus
</label>
<div id="disqusSet" {{if not (eq .userBlog.CommentType "disqus")}}style="display: none"{{end}}>
<label for="DisqusId">Disqus Id</label> <input type="text"
class="form-control" style="display: inline; width: 50%"
id="DisqusId" name="DisqusId"
value="{{if .userBlog.DisqusId}}{{.userBlog.DisqusId}}{{else}}leanote{{end}}">
<br />
{{msg . "disqusHelp"}}
<a target="_blank" href="http://leanote.com/blog/view/52db8463e01c530ef8000001">{{msg . "needHelp"}}</a>)
<a target="_blank" href="http://leanote.com/blog/view/52db8463e01c530ef8000001">{{msg . "needHelp"}}</a>
</div>
</div>
</div>
</div>
<div class="form-group">
@@ -75,9 +117,9 @@
<span class="msg"></span>
</div>
</div>
</div>
</form>
</div>
<div class="tab-pane active" id="baseInfo">
<div class="form-horizontal" role="form" id="userBlogForm">
<div class="form-group">
@@ -101,7 +143,7 @@
<div class="col-sm-10">
<input type="hidden" name="Logo" id="Logo"
value="{{.userBlog.Logo}}" />
<form id="formLogo" action="/file/uploadBlogLogo" method="post"
<form id="formLogo" action="{{$.siteUrl}}/file/uploadBlogLogo" method="post"
enctype="multipart/form-data" target="logoTarget"
onsubmit="inProgress()">
<input type="file" class="form-control" id="logo2" name="file"
@@ -126,7 +168,7 @@
<div class="col-sm-10">
<input type="text" class="form-control" id="SubTitle"
name="SubTitle" value="{{.userBlog.SubTitle}}"
placeholder="eg: leanote, {{msg $ "moto"}}">
placeholder="eg: leanote, Not Just A Notebook">
</div>
</div>
@@ -158,11 +200,11 @@
{{template "Blog/footer.html" .}}
<script src="/js/common-min.js"></script>
<script type="text/javascript" src="/tinymce/tinymce.min.js"></script>
<script src="{{.siteUrl}}/js/common-min.js"></script>
<script type="text/javascript" src="{{.siteUrl}}/tinymce/tinymce.min.js"></script>
<script>
var urlPrefix = "{{.siteUrl}}";
$(function() {
tinymce.init({
selector : "#AboutMe",
@@ -197,12 +239,19 @@ $(function() {
$("#CanComment").click(function() {
if ($(this).is(":checked")) {
$("#commentSet").show();
} else {
$("#commentSet").hide();
}
});
$("input[name='commentType']").click(function() {
if ($("input[name='commentType']:checked").val() == "disqus") {
$("#disqusSet").show();
} else {
$("#disqusSet").hide();
}
});
$("#CanComment").trigger("click").trigger("click"); // 恶心的想法
// 基本设置
@@ -219,7 +268,7 @@ $(function() {
$("#blogDesc").html(data.SubTitle);
$("#logo").html(data.Title);
if(data.Logo) {
$("#logo").html(t('<img src="?" />', data.Logo));
$("#logo").html(t('<img src="?" />', urlPrefix + "/" + data.Logo));
}
}, this);
});
@@ -228,12 +277,14 @@ $(function() {
e.preventDefault();
var data = {
CanComment : $("#CanComment").is(":checked"),
CommentType: $("input[name='commentType']:checked").val(),
DisqusId : $("#DisqusId").val(),
}
post("/blog/setUserBlogComment", data, function(ret) {
showMsg2($("#commentInfo .msg"), "{{msg . "saveSuccess"}}", 2000);
}, this);
});
// 主题
$("#styleInfo .btn-success").click(function(e) {
e.preventDefault();
@@ -256,7 +307,7 @@ function inProgress() {
function uploadFinish(ret) {
if (ret) {
if (ret.resultCode == '1') {
$("#logoImg img").attr("src", ret.filename).parent().show();
$("#logoImg img").attr("src", urlPrefix + "/" + ret.filename).parent().show();
$("#Logo").val(ret.filename);
return;
}
@@ -266,32 +317,6 @@ function uploadFinish(ret) {
// 上传出错
alert("上传出错");
}
function submit() {
var tpl = '<form action="/blog/setBlog" method="post"><input name="Title" value="?" />';
tpl += '<input name="SubTitle" value="?" />';
tpl += '<input name="Logo" value="?" />';
tpl += '<input name="CanComment" value="?" />';
tpl += '<input name="DisqusId" value="?" />';
tpl += '<input name="Style" value="?" />';
tpl += '<textarea name="AboutMe">?</textarea>';
tpl += "</form";
var data = {
Title : $("#Title").val(),
SubTitle : $("#SubTitle").val(),
Logo : $("#Logo").val(),
AboutMe : getEditorContent(),
CanComment : $("#CanComment").is(":checked"),
DisqusId : $("#DisqusId").val(),
Style : $("input[name='Style']:checked").val()
}
if (!data.DisqusId) {
data.DisqusId = "leanote";
}
$(t(tpl, data.Title, data.SubTitle, data.Logo,
data.CanComment, data.DisqusId, data.Style,
data.AboutMe)).submit();
}
</script>
</div>
</body>

View File

@@ -1,6 +1,5 @@
{{template "Blog/header.html" .}}
<!-- -->
<div id="postsContainer">
<div id="posts">
<div class="each-post">
@@ -8,15 +7,30 @@
{{.blog.Title}}
</div>
<div class="created-time">
<i class="fa fa-bookmark-o" style="color: #666"></i>
<i class="fa fa-bookmark-o"></i>
{{if .blog.Tags}}
{{blogTags .blog.Tags}}
{{blogTags $ .blog.Tags}}
{{else}}
{{msg . "noTag"}}
{{end}}
|
<i class="fa fa-calendar" style="color: #666"></i> {{msg . "updatedTime"}} {{.blog.UpdatedTime | datetime}} |
<i class="fa fa-calendar" style="color: #666"></i> {{msg . "createdTime"}} {{.blog.CreatedTime | datetime}}
<i class="fa fa-calendar"></i> {{msg . "updatedTime"}} {{.blog.UpdatedTime | datetime}} |
<i class="fa fa-calendar"></i> {{msg . "createdTime"}} {{.blog.CreatedTime | datetime}}
</div>
<div class="mobile-created-time">
{{ if .userInfo.Logo}}
<img src="{{.userInfo.Logo}}" id="userLogo">
{{else}}
<img src="{{$.siteUrl}}/images/blog/default_avatar.png" id="userLogo">
{{end}}
{{.userInfo.Username}}
{{if .blog.Tags}}
&nbsp;
<i class="fa fa-bookmark-o" style="color: #666"></i>
{{blogTags $ .blog.Tags}}
{{end}}
</div>
<div class="desc" id="content">
@@ -33,75 +47,41 @@
{{else}}
{{.blog.Content | raw}}
{{end}}
<div id="desc" class="hide">{{.blog.Desc}}</div>
</div>
<!-- comment -->
{{template "blog/comment.html" .}}
</div>
</div>
</div>
{{template "Blog/footer.html" .}}
{{template "Blog/highlight.html"}}
<!-- Nav -->
<style>
#blogNav {
display: none;
background-color: #fff;
opacity: 0.7;
position: fixed;
z-index: 10;
padding: 3px;
border-radius: 3px;
}
#blogNavContent {
overflow-y: auto;
max-height: 250px;
display: none;
}
#blogNavNav {
cursor: pointer;
}
#blogNav a {
color: #666;
}
#blogNav:hover {
opacity: 0.9;
}
#blogNav a:hover {
color: #0fb264;
}
#blogNav ul {
padding-left: 20px;
}
#blogNav ul .nav-h1 {
}
#blogNav ul .nav-h2 {
margin-left: 20px;
}
#blogNav ul .nav-h3 {
margin-left: 30px;
}
#blogNav ul .nav-h4 {
margin-left: 40px;
}
#blogNav ul .nav-h5 {
margin-left: 50px;
}
</style>
<div id="blogNav">
<div id="blogNavNav">
<i class="fa fa-align-justify" title="文档导航"></i>
<span>{{msg . "blogNav"}}</span>
</div>
<div id="blogNavContent" style="max-width: 200px">
<div id="blogNavContent">
</div>
</div>
<script src="/public/js/app/blog/nav.js"></script>
<script>
var visitUserInfo = eval("(" + {{.visitUserInfoJson}} + ")");
var urlPrefix = "{{.siteUrl}}";
var noteId = "{{.blog.NoteId.Hex}}";
var preLikeNum = +"{{.blog.LikeNum}}";
var commentNum = +"{{.blog.CommentNum}}";
</script>
<script src="/js/app/blog/common.js"></script>
<script src="/js/jsrender.js"></script>
<script src="/js/jquery-cookie-min.js"></script>
<script src="/js/bootstrap-dialog.min.js"></script>
<script src="/js/jquery.qrcode.min.js"></script>
<script src="/js/app/blog/view.js"></script>
{{if .blog.IsMarkdown }}
<script src="/public/mdeditor/editor/google-code-prettify/prettify.js"></script>
<script src="/public/mdeditor/editor/pagedown/Markdown.Converter.js"></script>
<script src="/public/mdeditor/editor/pagedown/Markdown.Sanitizer.js"></script>
<script src="/public/mdeditor/editor/pagedown/Markdown.Editor.js"></script>
@@ -127,13 +107,19 @@ prettyPrint();
MathJax.Hub.Queue(["Typeset",MathJax.Hub,"wmd-preview"]);
initNav();
weixin();
</script>
{{else}}
<script>
$(function() {
initNav();
weixin();
});
</script>
{{end}}
<!--google+
<script type="text/javascript" src="https://apis.google.com/js/plusone.js"> {lang: 'zh-CN'} </script>
-->
</body>
</html>

View File

@@ -6,7 +6,7 @@
<section id="box">
<div>
<div>
<h1 class="h text-white animated fadeInDownBig">404</h1>
<h1 class="h text-white animated fadeInDownBig">500</h1>
</div>
<div id="errorBox">
<p class="error-info">

View File

@@ -22,6 +22,51 @@ function log(o) {
</head>
<body>
<nav id="headerContainer" style="background-color:#fff" class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/index">
<img src="/images/logo/leanote_black.png" id="" title="leanote, {{msg $ "moto"}}"/>
</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-left">
<li><a href="/index#" target="body" class="smooth-scroll">{{msg . "home"}}</a></li>
<!--
<li><a href="/index#aboutLeanote" target="#aboutLeanote" class="smooth-scroll">{{msg . "aboutLeanote"}}</a> </li>
-->
<li><a href="/index#download" target="#download" class="smooth-scroll">{{msg . "download"}}</a> </li>
<li><a href="/index#donate" target="#donate" class="smooth-scroll">{{msg . "donate"}}</a> </li>
<li><a id="leanoteBlog" href="{{.leaUrl}}/index" target="_blank" title="lea++, leanote博客平台" class="">lea++</a></li>
<li style="position: relative; margin-right: 3px;">
<a href="http://bbs.leanote.com" target="_blank" class="">{{msg . "discussion"}}</a>
<div class="red-circle" style=""></div>
</li>
<li id="loginBtns">
{{if .userInfo.Email}}
{{msg . "hi"}}, {{.userInfo.Username}}
<a href="{{$.noteUrl}}">{{msg . "myNote"}}</a>
<a href="/logout">{{msg . "logout"}}</a>
{{else}}
<a href="/login">{{msg . "login"}}</a>
{{if .openRegister}}
<a href="/register" class="btn-register">{{msg . "register"}}</a>
{{end}}
{{end}}
</li>
</ul>
</div>
</div>
</nav>
<!--
<div id="headerContainer" style="background-color:#fff" class="navbar-fixed-top">
<div class="container" style="clearfix" id="header">
<div class="pull-left">
@@ -35,7 +80,7 @@ function log(o) {
<div class="pull-right" id="loginBtns">
{{if .userInfo.Email}}
{{msg . "hi"}}, {{.userInfo.Username}}
<a href="/note">{{msg . "myNote"}}</a>
<a href="{{$.noteUrl}}">{{msg . "myNote"}}</a>
<a href="/logout">{{msg . "logout"}}</a>
{{else}}
<a href="/login">{{msg . "login"}}</a>
@@ -47,12 +92,9 @@ function log(o) {
<ul id="blogNav" class="pull-right">
<li><a href="/index#" target="body" class="smooth-scroll">{{msg . "home"}}</a></li>
<!--
<li><a href="/index#aboutLeanote" target="#aboutLeanote" class="smooth-scroll">{{msg . "aboutLeanote"}}</a> </li>
-->
<li><a href="/index#download" target="#download" class="smooth-scroll">{{msg . "download"}}</a> </li>
<li><a href="/index#donate" target="#donate" class="smooth-scroll">{{msg . "donate"}}</a> </li>
<li><a id="leanoteBlog" href="http://leanote.com/lea/index" target="_blank" title="lea++, leanote博客平台" class="">lea++</a></li>
<li><a id="leanoteBlog" href="{{.leaUrl}}/index" target="_blank" title="lea++, leanote博客平台" class="">lea++</a></li>
<li style="position: relative; margin-right: 3px;">
<a href="http://bbs.leanote.com" target="_blank" class="">{{msg . "discussion"}}</a>
<div style="position: absolute;
@@ -64,6 +106,6 @@ function log(o) {
border-radius: 9px;"></div>
</li>
</ul>
</div>
</div>
-->

View File

@@ -1,4 +1,13 @@
{{template "home/header_box.html" .}}
<!-- 验证码 -->
<script type="text/x-jsrender" id="tCaptcha">
<div class="form-group">
<label class="control-label">{{rawMsg . "captcha"}}</label>
<input type="text" class="form-control" id="captcha" name="captcha">
<a id="reloadCaptcha" title="{{msg . "reloadCaptcha"}}" onclick="$('#captchaImage').attr('src', '/captcha/get?' + ((new Date()).getTime()))"><img src="/captcha/get" id="captchaImage"/></a>
</div>
</script>
<section id="box" class="animated fadeInUp">
<!--
<div>
@@ -11,6 +20,7 @@
<div id="boxHeader">{{msg . "login"}}</div>
<form>
<div class="alert alert-danger" id="loginMsg"></div>
<input id="from" type="hidden" value="{{.from}}" />
<div class="form-group">
<label class="control-label">{{msg . "usernameOrEmail"}}</label>
<input type="text" class="form-control" id="email" name="email" value="{{.email}}">
@@ -19,6 +29,11 @@
<label class="control-label">{{msg . "password"}}</label>
<input type="password" class="form-control" id="pwd" name="pwd">
</div>
<div id="captchaContainer">
</div>
<div class="clearfix">
<a href="/findPassword" class="pull-right m-t-xs"><small>{{msg . "forgetPassword"}}</small></a>
<button id="loginBtn" class="btn btn-success">{{msg . "login"}}</button>
@@ -57,6 +72,12 @@
<script>
$(function() {
var needCaptcha = {{.needCaptcha}};
if(needCaptcha){
$("#captchaContainer").html($("#tCaptcha").html());
}
$("#email").focus();
if($("#email").val()) {
$("#pwd").focus();
@@ -74,6 +95,7 @@ $(function() {
e.preventDefault();
var email = $("#email").val();
var pwd = $("#pwd").val();
var captcha = $("#captcha").val()
if(!email) {
showMsg("{{msg . "inputUsername"}}", "email");
return;
@@ -87,17 +109,27 @@ $(function() {
return;
}
}
if(needCaptcha && !captcha) {
showMsg("{{msg . "inputCaptcha"}}", "captcha");
return;
}
$("#loginBtn").html("{{msg . "logining"}}...").addClass("disabled");
// hideMsg();
$.post("/doLogin", {email: email, pwd: pwd}, function(e) {
$.post("/doLogin", {email: email, pwd: pwd, captcha: $("#captcha").val()}, function(e) {
$("#loginBtn").html("{{msg . "login"}}").removeClass("disabled");
if(e.Ok) {
$("#loginBtn").html("{{msg . "loginSuccess"}}...");
location.href = '/note';
var from = $("#from").val() || "{{.noteUrl}}" || "/note";
location.href = from;
} else {
showMsg(e.Msg, "pwd");
if(e.Item && $.trim($("#captchaContainer").text()) == "") {
$("#captchaContainer").html($("#tCaptcha").html());
needCaptcha = true
}
showMsg(e.Msg);
}
});
});

View File

@@ -11,6 +11,7 @@
<div id="boxHeader">{{msg . "register"}}</div>
<form>
<div class="alert alert-danger" id="loginMsg"></div>
<input id="from" type="hidden" value="{{.from}}" />
<div class="form-group">
<label class="control-label" for="email">{{msg . "email"}}</label>
<input type="text" class="form-control" id="email" name="email">
@@ -103,7 +104,8 @@ $(function() {
$("#registerBtn").html("{{msg . "register"}}").removeClass("disabled");
if(e.Ok) {
$("#registerBtn").html("{{msg . "registerSuccessAndRdirectToNote"}}");
location.href = '/note';
var from = $("#from").val() || "{{.noteUrl}}" || "/note";
location.href = from;
} else {
showMsg(e.Msg, "email");
}

View File

@@ -0,0 +1,102 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="keywords" content="leanote,leanote.com">
<meta name="description" content="leanote, {{msg $ "moto"}}">
<meta name="author" content="leanote">
<title>{{.title}}</title>
<link href="{{.siteUrl}}/css/bootstrap.css" rel="stylesheet">
<link id="styleLink" href="{{.siteUrl}}/css/toImage.css" rel="stylesheet">
</head>
<body>
<div id="content">
<h1 class="title">
{{.blog.Title}}
</h1>
<div class="created-time">
{{ if .userBlog.Logo}}
<img src="{{.userBlog.Logo}}" id="logo">
{{else}}
<img src="{{$.siteUrl}}/images/blog/default_avatar.png" id="logo">
{{end}}
{{.userInfo.Username}}
{{if .blog.Tags}}
<img src="{{$.siteUrl}}/images/blog/tag.png" id="tag">
{{blogTags .blog.Tags}}
{{end}}
</div>
<div class="desc">
{{if .blog.IsMarkdown }}
<div id="markdownContent" style="display: none">
<!-- 用textarea装html, 防止得到的值失真 -->
<textarea>
{{.content | raw}}
</textarea>
</div>
<div id="parsedContent">
</div>
{{else}}
{{.content | raw}}
{{end}}
</div>
</div>
<div id="footer">
<p>
{{ if .userBlog.Logo}}
<img src="{{.userBlog.Logo}}" id="logo">
{{else}}
<img src="{{$.siteUrl}}/images/blog/default_avatar.png" id="logo">
{{end}}
(<a href="#">http://blog.leanote.com/{{.userInfo.Username}}</a>)
</p>
<img src="{{.siteUrl}}/images/logo/leanote_white.png" id="leanote_logo"/>
<p>
leanote, {{msg $ "moto"}}
</p>
</div>
<script src="{{.siteUrl}}/js/jquery-1.9.0.min.js"></script>
<script src="{{.siteUrl}}/js/bootstrap-min.js"></script>
<link href="{{.siteUrl}}/public/mdeditor/editor/google-code-prettify/prettify.css" type="text/css" rel="stylesheet">
<script src="{{.siteUrl}}/public/mdeditor/editor/google-code-prettify/prettify.js"></script>
<script>
$("pre").addClass("prettyprint linenums");
prettyPrint();
</script>
{{if .blog.IsMarkdown }}
<script src="{{.siteUrl}}/public/mdeditor/editor/google-code-prettify/prettify.js"></script>
<script src="{{.siteUrl}}/public/mdeditor/editor/pagedown/Markdown.Converter.js"></script>
<script src="{{.siteUrl}}/public/mdeditor/editor/pagedown/Markdown.Sanitizer.js"></script>
<script src="{{.siteUrl}}/public/mdeditor/editor/pagedown/Markdown.Editor.js"></script>
<script src="{{.siteUrl}}/public/mdeditor/editor/pagedown/local/Markdown.local.zh.js"></script>
<script src="{{.siteUrl}}/public/mdeditor/editor/Markdown.Extra.js"></script>
<!--mathjax-->
<script type="text/x-mathjax-config">
MathJax.Hub.Config({ tex2jax: { inlineMath: [['$','$'], ["\\(","\\)"]], processEscapes: true }, messageStyle: "none"});
</script>
<script src="{{.siteUrl}}/public/mdeditor/editor/mathJax.js"></script>
<script src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script>
var content = $.trim($("#markdownContent textarea").val());
var converter = Markdown.getSanitizingConverter();
Markdown.Extra.init(converter, {extensions: ["tables", "fenced_code_gfm", "def_list"]});
var html = converter.makeHtml(content);
$("#parsedContent").html(html);
$("pre").addClass("prettyprint linenums");
prettyPrint();
MathJax.Hub.Queue(["Typeset",MathJax.Hub,"wmd-preview"]);
</script>
{{end}}
</body>
</html>

View File

@@ -0,0 +1,4 @@
lif------------
e
you can

View File

@@ -3,17 +3,19 @@
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<meta name="apple-touch-fullscreen" content="yes">
<meta name=”apple-mobile-web-app-capable” content=”yes” />
<meta name="keywords" content="leanote,leanote.com">
<meta name="description" content="leanote, {{msg $ "moto"}}">
<title>leanote, {{msg $ "moto"}}</title>
<meta name="description" content="leanote, Not Just A Notebook">
<title>leanote, Not Just A Notebook</title>
<link href="css/bootstrap.css" rel="stylesheet" />
<link href="/css/bootstrap.css" rel="stylesheet" />
<!-- 先加载, 没有样式, 宽度不定 -->
<link rel="stylesheet" href="tinymce/skins/custom/skin.min.css" type="text/css" />
<link rel="stylesheet" href="tinymce/skins/custom/skin.min.css" rel="stylesheet"/>
<!-- leanote css -->
<link href="css/font-awesome-4.0.3/css/font-awesome.css" rel="stylesheet" />
<link href="css/font-awesome-4.2.0/css/font-awesome.css" rel="stylesheet" />
<link href="css/zTreeStyle/zTreeStyle.css" rel="stylesheet" />
<script>
var hash = location.hash;
@@ -27,7 +29,6 @@ document.write(files);
<!-- For Develop writting mod -->
<script>
function log(o) {
console.log(o);
@@ -56,34 +57,35 @@ function log(o) {
</div>
<!-- search -->
<div class="pull-left" id="searchWrap">
<form class="navbar-form form-inline col-lg-2 hidden-xs" id="searchNote">
<form class="navbar-form form-inline col-lg-2" id="searchNote">
<input class="form-control" placeholder="Search" type="text" id="searchNoteInput">
</form>
</div>
<!-- 全局按钮 -->
<div class="pull-left" style="" id="newNoteWrap">
<!-- 新建笔记 -->
<div id="newMyNote">
<a id="newNoteBtn" title="{{msg . "newNote"}}">
<i class="fa fa-file-o"></i>
{{msg . "newNote"}}
<span class="new-note-text">{{msg . "newNote"}}</span>
<span class="new-note-text-abbr">{{msg . "new"}}</span>
</a>
<span class="new-split">|</span>
<a id="newNoteMarkdownBtn" title="{{msg . "newMarkdown"}}">
Markdown
<span class="new-markdown-text">Markdown</span>
<span class="new-markdown-text-abbr">Md</span>
</a>
<span class="for-split"> - </span>
<span id="curNotebookForNewNote" notebookId=""></span>
<div class="dropdown" style="display: inline-block">
<a class="ios7-a dropdown-toggle"
id="dropdownMenu2" data-toggle="dropdown">
id="listNotebookDropdownMenu" data-toggle="dropdown">
<i class="fa fa-angle-down"></i>
</a>
<div class="dropdown-menu dropdown-list" id="searchNotebookForAddDropdownList">
<input type="text" placeholder="search notebook" class="form-control" id="searchNotebookForAdd"/>
<ul class="clearfix" role="menu" aria-labelledby="dropdownMenu2" id="notebookNavForNewNote">
<input type="text" placeholder="Search notebook" class="form-control" id="searchNotebookForAdd"/>
<ul class="clearfix" role="menu" aria-labelledby="listNotebookDropdownMenu" id="notebookNavForNewNote">
</ul>
</div>
</div>
@@ -93,19 +95,21 @@ function log(o) {
<div id="newSharedNote" style="display: none">
<a id="newSharedNoteBtn">
<i class="fa fa-file-o"></i>
{{msg . "newNote"}}
<span class="new-note-text">{{msg . "newNote"}}</span>
<span class="new-note-text-abbr">{{msg . "new"}}</span>
</a>
<span class="new-split">|</span>
<a id="newShareNoteMarkdownBtn" title="{{msg . "newMarkdown"}}">
Markdown
<span class="new-markdown-text">Markdown</span>
<span class="new-markdown-text-abbr">Md</span>
</a>
<span class="for-split"> - </span>
<span id="curNotebookForNewSharedNote" notebookId="" userId=""></span>
<div class="dropdown" style="display: inline-block">
<a class="ios7-a dropdown-toggle" data-toggle="dropdown">
<a id="listShareNotebookDropdownMenu" class="ios7-a dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-angle-down"></i>
</a>
<div class="dropdown-menu dropdown-list" style="left: -200px;" >
<div class="dropdown-menu dropdown-list" id="searchNotebookForAddShareDropdownList" >
<ul id="notebookNavForNewSharedNote"></ul>
</div>
</div>
@@ -118,20 +122,10 @@ function log(o) {
</span>
</div>
<!--
<div class="pull-left alert-warning" style="line-height: 20px; margin-top: 10px; margin-left: 0px; display: none" id="verifyMsg">
您还没有验证邮箱, 验证邮件已发送至 {{.userInfo.Email}}.
<br />
<a class=".nowToActive">现在去验证</a> <a id="reSendActiveEmail">重新发送</a> <a id="wrongEmail">邮箱填错了?</a>
</div>
-->
<div class="pull-right" style="margin: 0 10px" id="myProfile">
<div class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" style="line-height: 60px;">
<!--
<img src="images/avatar.png" style="height: 40px; border: 1px solid #ccc" />
-->
<a class="dropdown-toggle" title="{{.userInfo.Username}}" data-toggle="dropdown" style="line-height: 60px;">
<img alt="{{.userInfo.Username}}" title="{{.userInfo.Username}}" src="{{if .userInfo.Logo}}{{.userInfo.Logo}}{{else}}/images/blog/default_avatar.png{{end}}" id="myAvatar"/>
<span class="username">
{{if .userInfo.UsernameRaw}}
{{.userInfo.UsernameRaw}}
@@ -143,12 +137,22 @@ function log(o) {
</a>
<ul class="dropdown-menu li-a" role="menu">
<li role="presentation" id="setInfo">
<a>
<i class="fa fa-info"></i>
{{msg . "accountSetting"}}
</a>
</li>
<li role="presentation" id="setAvatarMenu">
<a>
<i class="fa fa-smile-o"></i>
{{msg . "setAvatar"}}
</a>
</li>
<li role="presentation" id="setTheme">
<a>
<i class="fa fa-sun-o"></i>
{{msg . "themeSetting"}}
</a>
</li>
<!--
<li role="presentation" id="yourSuggestions">
@@ -156,6 +160,22 @@ function log(o) {
{{msg . "yourSuggestions"}}
</li>
-->
<li role="presentation" class="my-link" >
<a target="_blank" href="{{$.blogUrl}}/{{.userInfo.Username}}">
<i class="fa fa-bold"></i>
{{msg . "myBlog"}}</a>
</li>
{{if .isAdmin}}
<li role="presentation" class="divider"></li>
<li role="presentation">
<a target="_blank" title="{{msg . "amdin"}}" href="/admin/index">
<i class="fa fa-dashboard"></i>
{{msg . "admin"}}
</a>
</li>
{{end}}
<li role="presentation" class="divider"></li>
<li role="presentation" onclick="logout()">
<i class="fa fa-sign-out"></i>
{{msg . "logout"}}
@@ -164,8 +184,8 @@ function log(o) {
</div>
</div>
<div class="pull-right" style="margin: 0 10px" id="topNav">
<a target="_blank" href="/blog/{{.userInfo.Username}}">
<div class="pull-right top-nav" id="myBlog">
<a target="_blank" href="{{$.blogUrl}}/{{.userInfo.Username}}">
{{msg . "myBlog"}}
</a>
</div>
@@ -239,7 +259,7 @@ function log(o) {
</div>
<div class="folderBody">
<input type="text" class="form-control" id="searchNotebookForList" placeholder="search notebook"/>
<input type="text" class="form-control" id="searchNotebookForList" placeholder="Search notebook"/>
<ul class="ztree" id="notebookList"></ul>
<ul class="ztree" id="notebookListForSearch"></ul>
</div>
@@ -254,10 +274,10 @@ function log(o) {
</div>
<ul class="folderBody clearfix" id="tagNav">
<li><a> <span class="label label-red">{{msg . "red"}}</span></a></li>
<li><a> <span class="label label-blue">{{msg . "blue"}}</span></a></li>
<li><a> <span class="label label-yellow">{{msg . "yellow"}}</span></a></li>
<li><a> <span class="label label-green">{{msg . "green"}}</span></a></li>
<li data-tag="red"><a> <span class="label label-red">{{msg . "red"}}</span></a></li>
<li data-tag="blue"><a> <span class="label label-blue">{{msg . "blue"}}</span></a></li>
<li data-tag="yellow"><a> <span class="label label-yellow">{{msg . "yellow"}}</span></a></li>
<li data-tag="green"><a> <span class="label label-green">{{msg . "green"}}</span></a></li>
</ul>
</div>
@@ -309,9 +329,7 @@ function log(o) {
<div id="noteAndEditor">
<div id="noteList">
<div class="clearfix" id="notesAndSort" style="position: relative">
<div class="pull-left">
<!-- 我的笔记本 -->
<div class="dropdown" id="myNotebookNavForListNav">
<a class="ios7-a dropdown-toggle" id="dropdownMenu1" data-toggle="dropdown">
@@ -370,14 +388,13 @@ function log(o) {
-->
</div>
</div>
</div>
<!-- 笔记列表 -->
<!-- wrap 为了slimScroll -->
<div id="noteItemListWrap" style="position: absolute; left: 0; right: 0; top: 41px; bottom: 3px">
<div id="noteItemList">
</div>
<div id="noteItemListWrap">
<ul id="noteItemList">
</ul>
</div>
</div>
@@ -389,43 +406,41 @@ function log(o) {
<div id="noteReadContainer">
<div id="noteReadTop">
<h2 id="noteReadTitle"></h2>
<div class="clearfix">
<div class="pull-left">
<div class="clearfix" id="noteReadInfo">
<i class="fa fa-bookmark-o"></i>
<span id="noteReadTags"></span>
</div>
<!-- 修改时间 -->
<div class="pull-left" style="margin-left: 10px;">
<i class="fa fa-calendar"></i>{{msg . "update"}}
<span id="noteReadUpdatedTime"></span>
</div>
<!-- 修改时间 -->
<div class="pull-left" style="margin-left: 10px;">
<i class="fa fa-calendar"></i>{{msg . "create"}}
<span id="noteReadCreatedTime"></span>
</div>
</div>
</div>
<div id="noteReadContent">
</div>
</div>
</div>
<!-- 遮罩, 为了resize3Columns用 -->
<div id="noteMask"
style="position: absolute; top: 0px; bottom: 0px; right: 0; left: 10px; z-index: -1"></div>
<div id="noteMask" class="note-mask"></div>
<div id="noteMaskForLoading" class="note-mask">
<img src="/images/loading-24.gif"/>
<br />
loading...
</div>
<div id="editorMask">
该笔记本下空空如也...何不
{{msg . "noNoteNewNoteTips"}}
<br />
<br />
<div id="editorMaskBtns">
<br />
<a class="note">新建笔记</a>
<a class="markdown">新建Markdown笔记</a>
<a class="note">{{msg . "newNote"}}</a>
<a class="markdown">{{msg . "newMarkdownNote"}}</a>
</div>
<div id="editorMaskBtnsEmpty">
Sorry, 这里不能添加笔记的.
{{msg . "canntNewNoteTips"}}
</div>
</div>
<div id="noteTop">
@@ -449,7 +464,9 @@ function log(o) {
<a
class="metro-a dropdown-toggle" data-toggle="dropdown"
id="addTagTrigger" style="cursor: text; padding-left: 0">
<span class="add-tag-text">
{{msg . "clickAddTag"}}
</span>
</a>
<input type="text" id="addTagInput" />
<ul class="dropdown-menu" role="menu" id="tagColor">
@@ -463,34 +480,34 @@ function log(o) {
</div>
</div>
<ul class="pull-right" id="editorTool">
<li><a class="ios7-a " id="saveBtn" title="ctrl+s"
data-toggle="dropdown">{{msg . "save"}}</a></li>
data-toggle="dropdown">
<span class="fa fa-save"></span>
{{msg . "save"}}</a></li>
<li class="dropdown" id="attachDropdown">
<a class="ios7-a dropdown-toggle" data-toggle="dropdown" id="showAttach">
<!--
<span class="fa fa-upload"></span>
-->
<span class="fa fa-paperclip"></span>
{{msg . "attachments"}}<span id="attachNum"></span>
</a>
<div class="dropdown-menu" id="attachMenu">
<ul id="attachList">
</ul>
<form id="uploadAttach" method="post" action="/attach/UploadAttach" enctype="multipart/form-data">
<div id="dropAttach">
<div id="dropAttach" class="dropzone">
<a class="btn btn-success btn-choose-file">
Choose File to Upload
<i class="fa fa-upload"></i>
<span>Choose File</span>
</a>
<a class="btn btn-default" id="downloadAllBtn">
<i class="fa fa-download"></i>
Download All
<span>Download All</span>
</a>
<a class="btn btn-default" id="linkAllBtn">
<i class="fa fa-link"></i>
Link All
<span>Link All</span>
</a>
<input type="file" name="file" multiple/>
</div>
@@ -501,9 +518,13 @@ function log(o) {
</li>
<li><a class="ios7-a " id="tipsBtn"
data-toggle="dropdown">{{msg . "editorTips"}}</a></li>
data-toggle="dropdown">
<span class="fa fa-question"></span>
{{msg . "editorTips"}}</a></li>
<li><a class="ios7-a " id="contentHistory"
data-toggle="dropdown">{{msg . "history"}}</a></li>
data-toggle="dropdown">
<span class="fa fa-history"></span>
{{msg . "history"}}</a></li>
</ul>
</div>
@@ -514,7 +535,7 @@ function log(o) {
<div id="editor">
<!-- 编辑器 -->
<div id="mceToolbar" style="">
<div id="mceToolbar">
<div id="popularToolbar"
style="position: absolute; right: 30px; left: 0"></div>
<a
@@ -554,12 +575,13 @@ function log(o) {
<!-- 为了scroll -->
<div class="clearfix" id="mdEditorPreview">
<div id="left-column" class="pull-left">
<div id="left-column">
<div id="wmd-panel-editor" class="wmd-panel-editor">
<textarea class="wmd-input theme" id="wmd-input" spellcheck="false" tabindex="3"></textarea>
</div>
</div>
<div id="right-column" class="pull-right">
<div id="mdSplitter"></div>
<div id="right-column">
<div id="wmd-panel-preview" class="wmd-panel-preview preview-container">
<div id="wmd-preview" class="wmd-preview"></div>
</div>
@@ -567,7 +589,8 @@ function log(o) {
</div>
<textarea id="md-section-helper"></textarea>
</div>
<!-- for test -->
</div>
</div>
<!-- mdEditor -->
<!-- Hidden Popup Modal -->
@@ -577,7 +600,7 @@ function log(o) {
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="editorDialog-title">操作</h4>
<h4 class="modal-title" id="editorDialog-title"></h4>
</div>
<div class="modal-body">
@@ -591,17 +614,13 @@ function log(o) {
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="editorDialog-confirm">确认</button>
<button type="button" class="btn btn-default" data-dismiss="modal">{{msg . "cancel"}}</button>
<button type="button" class="btn btn-primary" id="editorDialog-confirm">{{msg . "confirm"}}</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
</div>
</div>
<!-- 弹出框 模板 -->
<div class="modal fade bs-modal-sm" id="leanoteDialogRemote" tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel" aria-hidden="true">
</div>
@@ -644,7 +663,7 @@ function log(o) {
</div>
<input type="hidden" id="toEmail"/>
<label for="emailContent">邮件内容</label>
<textarea class="form-control" id="emailContent">Hi, 我是李铁, leanote非常好用, 快来注册吧.</textarea>
<textarea class="form-control" id="emailContent">Hi, 我是life, leanote非常好用, 快来注册吧.</textarea>
</div>
</form>
</div>
@@ -657,26 +676,15 @@ function log(o) {
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<!-- theme -->
<div class="modal fade bs-modal-sm" id="setThemeDialog" tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" class="modalTitle">主题设置</h4>
<h4 class="modal-title" class="modalTitle">{{msg . "theme"}}</h4>
</div>
<div class="modal-body">
<style>
#themeForm td {
padding: 5px;
text-align: center;
}
#themeForm img {
border: 1px solid #eee;
padding: 2px;
}
</style>
<table id="themeForm">
<tr>
<td>
@@ -699,7 +707,6 @@ function log(o) {
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{msg . "close"}}</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
@@ -724,17 +731,31 @@ function log(o) {
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<!-- 图片上传 -->
<div class="modal fade bs-modal-sm" id="imageDialog" tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel" aria-hidden="true">
<!-- avatar -->
<div class="modal fade bs-modal-sm" id="avatarDialog" tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content" style="height: 460px;" >
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" class="modalTitle">{{msg . "uploadImage"}}</h4>
<h4 class="modal-title" class="modalTitle">{{msg . "setAvatar"}}</h4>
</div>
<div class="modal-body">
<iframe style="" height="360" src="" scrolling="no" frameBorder="0" width="99%"></iframe>
<form id="uploadAvatar" method="post" action="/file/uploadAvatar" enctype="multipart/form-data">
<div id="dropAvatar" class="dropzone">
<div>
<img src="{{if .userInfo.Logo}}{{.userInfo.Logo}}{{else}}/images/blog/default_avatar.png{{end}}" id="avatar"/>
</div>
<a class="btn btn-success btn-choose-file">
<span class="fa fa-upload"></span> Choose Image
</a>
<input type="file" name="file" multiple/>
</div>
<div id="avatarUploadMsg">
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{msg . "close"}}</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
@@ -781,183 +802,27 @@ function log(o) {
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{msg . "cancel"}}</button>
<button type="button" class="btn btn-success sendWeiboBtn disabled">{{msg . "send"}}</button>
<button type="button" class="btn btn-share btn-default sendRRBtn disabled"><i class="fa fa-renren"></i> 人人</button>
<button type="button" class="btn btn-share btn-default sendQQBtn disabled"><i class="fa fa-qq"></i> QQ空间</button>
<button type="button" class="btn btn-share btn-primary sendTxWeiboBtn disabled"><i class="fa fa-tencent-weibo"></i> 腾讯微博</button>
<button type="button" class="btn btn-share btn-success sendWeiboBtn disabled"><i class="fa fa-weibo"></i> 新浪微博</button>
</div>
</div>
<!-- 激活邮件 -->
<div id="reSendActiveEmailDialog">
<div class="modal-body">
<div style="max-height: 300px; padding: 5px 0; text-align: center; overflow: scroll;" class="weibo">
<div style="max-height: 300px; padding: 5px 0; text-align: center; overflow-y: auto; overflow-x: hidden" class="weibo">
<span class="text">
<img src="/images/loading-24.gif"/>
正在发送邮件到{{.userInfo.Email}}...
{{msg . "emailInSending"}} {{.userInfo.Email}}...
</span>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
<button type="button" class="btn btn-success viewEmailBtn disabled">查看邮件</button>
</div>
</div>
<!-- 帐户设置 -->
<div id="dialogSetInfo">
<div class="modal-body">
<ul class="nav nav-tabs" id="myTabs">
<li><a href="#baseInfo" data-toggle="tab">{{msg . "basicInfo"}}</a></li>
<li><a href="#emailInfo" data-toggle="tab">{{msg . "updateEmail"}}</a></li>
<li><a href="#updatePwd" data-toggle="tab">{{msg . "updatePassword"}}</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="baseInfo">
<form>
<table>
<tr>
<td>
<label for="username">用户名设置</label>
<div class="alert alert-danger" id="usernameMsg" style="display: none"></div>
<input type="text" class="form-control" id="username">
你的邮箱是 {{.userInfo.Email}}, 可以再设置一个唯一的用户名.
<br />
用户名至少4位, 不可含特殊字符.
</td>
</tr>
<tr>
<td>
<button id="usernameBtn" class="btn btn-success">提交</button>
</td>
</tr>
</table>
</form>
</div>
<div class="tab-pane" id="emailInfo">
<form>
<table>
<tr>
<td>
当前邮箱为: <span id="curEmail">{{.userInfo.Email}}</span>
{{if .userInfo.Verified}}
已验证
{{else}}
未验证
<a class="raw nowToActive">现在去验证</a>
<a class="raw reSendActiveEmail">重新发送</a>
{{end}}
<br />
<label for="email">修改邮箱</label>
<div class="alert alert-danger" id="emailMsg" style="display: none"></div>
<input type="text" class="form-control" id="email">
邮箱修改后, 验证之后才有效, 验证之后新的邮箱地址将会作为登录帐号使用.
</td>
</tr>
<tr>
<td>
<button id="emailBtn" class="btn btn-success">发送验证邮箱</button>
</td>
</tr>
</table>
</form>
</div>
<div class="tab-pane" id="updatePwd">
<form>
<table style="width: 80%">
<tr>
<td>
<div class="alert alert-danger" id="pwdMsg" style="display: none"></div>
</td>
</tr>
<tr>
<td style="width: 80px">
<label for="pwd">{{msg . "oldPassword"}}</label>
<input type="password" class="form-control" id="oldPwd" name="oldPwd">
</td>
</tr>
<tr>
<td>
<label for="pwd">{{msg . "newPassword"}}</label>
<input type="password" class="form-control" id="pwd" name="pwd">
</td>
</tr>
<tr>
<td>
<label for="pwd2">{{msg . "password2"}}</label>
<input type="password" class="form-control" id="pwd2" name="pwd2">
</td>
</tr>
<tr>
<td>
<button id="pwdBtn" class="btn btn-success">{{msg . "submit"}}</button>
</td>
</tr>
</table>
</form>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
</div>
</div>
<!-- 账号设置, 是通过third 登录进来的 -->
<div id="thirdDialogSetInfo">
<div class="modal-body">
<ul class="nav nav-tabs" id="thirdMyTabs">
<li><a href="#accountInfo" data-toggle="tab">创建帐号</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="accountInfo">
您现在使用的是第三方帐号登录leanote, 您也可以注册leanote帐号登录, 赶紧注册一个吧.
<br />
注册成功后仍可以使用第三方帐号登录leanote并管理您现有的笔记.
<form>
<div class="alert alert-danger" id="thirdAccountMsg" style="display: none"></div>
<table style="width: 100%">
<tr>
<td>
<label for="thirdEmail">邮箱</label>
</td>
<td>
<input type="text" class="form-control" id="thirdEmail">
</td>
</tr>
<tr>
<td>
<label for="thirdPwd">密码</label>
</td>
<td>
<input type="password" class="form-control" id="thirdPwd">
</td>
<td>
密码至少6位
</td>
</tr>
<tr>
<td>
<label for="thirdPwd2">重复密码</label>
</td>
<td>
<input type="password" class="form-control" id="thirdPwd2">
</td>
</tr>
<tr>
<td></td>
<td>
<button id="accountBtn" class="btn btn-success" style="width: 100%">提交</button>
</td>
</tr>
</table>
</form>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
</div>
<button type="button" class="btn btn-default" data-dismiss="modal">{{msg . "close"}}</button>
<button type="button" class="btn btn-success viewEmailBtn disabled">{{msg . "checkEmail"}}</button>
</div>
</div>
<div class="hide" id="copyDiv"></div>
@@ -969,7 +834,7 @@ function log(o) {
<script src="js/i18n/msg.{{.locale}}.js"></script>
<script src="js/common.js"></script>
<script>
var UrlPrefix = "http://leanote.com"; // 为了发weibo
var UrlPrefix = "{{.siteUrl}}"; // 为了发weibo
var UserInfo = json({{.userInfoJson}});
var notebooks = json({{.notebooks}});
var shareNotebooks = json({{.shareNotebooks}});
@@ -981,12 +846,11 @@ LEA.locale = "{{.locale}}";
</script>
<!-- 渲染view -->
<script src="tinymce/tinymce.js"></script>
<script src="js/app/page.js"></script>
<script src="/js/jQuery-slimScroll-1.3.0/jquery.slimscroll.js"></script>
<script src="/js/contextmenu/jquery.contextmenu.js"></script>
<script src="js/app/page.js"></script>
<script src="tinymce/tinymce.js"></script>
<script src="js/jquery-cookie-min.js"></script>
<script src="js/jquery-cookie.js"></script>
<script src="js/bootstrap-min.js"></script>
<script src="js/app/note.js"></script>
<script src="js/app/tag.js"></script>
@@ -998,13 +862,14 @@ LEA.locale = "{{.locale}}";
Notebook.renderNotebooks(notebooks);
Share.renderShareNotebooks(sharedUserInfos, shareNotebooks);
Note.setNoteCache(noteContentJson);
Note.renderNotes(notes);
if(!isEmpty(notes)) {
Note.changeNote(notes[0].NoteId);
}
Note.setNoteCache(noteContentJson);
Note.renderNoteContent(noteContentJson)
// Note.chanteNote设置content
// Note.renderNoteContent(noteContentJson)
Tag.renderTagNav(tagsJson);
@@ -1012,50 +877,13 @@ Tag.renderTagNav(tagsJson);
initSlimScroll();
</script>
<!-- mdEditor -->
<link href="/public/mdeditor/editor/editor.css" rel="stylesheet">
<script src="/public/mdeditor/editor/pagedown/Markdown.Converter-min.js"></script>
<script src="/public/mdeditor/editor/pagedown/Markdown.Sanitizer-min.js"></script>
<script src="/public/mdeditor/editor/pagedown/Markdown.Editor-min.js"></script>
<script src="/public/mdeditor/editor/pagedown/local/Markdown.local.zh-min.js"></script>
<script src="/public/mdeditor/editor/Markdown.Extra-min.js"></script>
<script src="/public/mdeditor/editor/underscore-min.js"></script>
<script src="/public/mdeditor/editor/scrollLink.js"></script>
<!--mathjax-->
<script type="text/x-mathjax-config">
MathJax.Hub.Config({ tex2jax: { inlineMath: [['$','$'], ["\\(","\\)"]], processEscapes: true }, messageStyle: "none"});
</script>
<script src="/public/mdeditor/editor/mathJax-min.js"></script>
<script src="//cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script src="/public/mdeditor/editor/jquery.waitforimages-min.js"></script>
<script src="/public/mdeditor/editor/google-code-prettify/prettify.js"></script>
<script src="/public/mdeditor/editor/editor.js"></script>
<!-- mdEditor end -->
<!-- context-menu -->
<link rel="stylesheet" href="/js/contextmenu/css/contextmenu.css" type="text/css" />
<!-- code -->
<link href="/public/mdeditor/editor/google-code-prettify/prettify.css" rel="stylesheet" />
<!-- js version 2.0 use require.js -->
<script src="/js/require.js"></script>
<script>
require.config({
baseUrl: '/public',
paths: {
// 'jquery': 'js/jquery-1.9.0.min',
'leaui_image': 'tinymce/plugins/leaui_image/public/js/for_editor',
'attachment_upload': 'js/app/attachment_upload',
'jquery.ui.widget': 'tinymce/plugins/leaui_image/public/js/jquery.ui.widget',
'fileupload': '/tinymce/plugins/leaui_image/public/js/jquery.fileupload',
'iframe-transport': '/tinymce/plugins/leaui_image/public/js/jquery.iframe-transport'
},
shim: {
'fileupload': {deps: ['jquery.ui.widget', 'iframe-transport']}
}
});
require(['leaui_image'], function(leaui_image) {
});
require(['attachment_upload'], function(attachment_upload) {
});
<script src="/js/main.js"></script>
</script>
</body>
</html>

View File

@@ -3,17 +3,19 @@
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<meta name="apple-touch-fullscreen" content="yes">
<meta name=”apple-mobile-web-app-capable” content=”yes” />
<meta name="keywords" content="leanote,leanote.com">
<meta name="description" content="leanote, {{msg $ "moto"}}">
<title>leanote, {{msg $ "moto"}}</title>
<meta name="description" content="leanote, Not Just A Notebook">
<title>leanote, Not Just A Notebook</title>
<link href="css/bootstrap.css" rel="stylesheet" />
<link href="/css/bootstrap.css" rel="stylesheet" />
<!-- 先加载, 没有样式, 宽度不定 -->
<link rel="stylesheet" href="tinymce/skins/custom/skin.min.css" type="text/css" />
<link rel="stylesheet" href="tinymce/skins/custom/skin.min.css" rel="stylesheet"/>
<!-- leanote css -->
<link href="css/font-awesome-4.0.3/css/font-awesome.css" rel="stylesheet" />
<link href="css/font-awesome-4.2.0/css/font-awesome.css" rel="stylesheet" />
<link href="css/zTreeStyle/zTreeStyle.css" rel="stylesheet" />
<script>
var hash = location.hash;
@@ -27,7 +29,6 @@ document.write(files);
<!-- For Develop writting mod -->
<script>
function log(o) {
@@ -56,34 +57,35 @@ function log(o) {
</div>
<!-- search -->
<div class="pull-left" id="searchWrap">
<form class="navbar-form form-inline col-lg-2 hidden-xs" id="searchNote">
<form class="navbar-form form-inline col-lg-2" id="searchNote">
<input class="form-control" placeholder="Search" type="text" id="searchNoteInput">
</form>
</div>
<!-- 全局按钮 -->
<div class="pull-left" style="" id="newNoteWrap">
<!-- 新建笔记 -->
<div id="newMyNote">
<a id="newNoteBtn" title="{{msg . "newNote"}}">
<i class="fa fa-file-o"></i>
{{msg . "newNote"}}
<span class="new-note-text">{{msg . "newNote"}}</span>
<span class="new-note-text-abbr">{{msg . "new"}}</span>
</a>
<span class="new-split">|</span>
<a id="newNoteMarkdownBtn" title="{{msg . "newMarkdown"}}">
Markdown
<span class="new-markdown-text">Markdown</span>
<span class="new-markdown-text-abbr">Md</span>
</a>
<span class="for-split"> - </span>
<span id="curNotebookForNewNote" notebookId=""></span>
<div class="dropdown" style="display: inline-block">
<a class="ios7-a dropdown-toggle"
id="dropdownMenu2" data-toggle="dropdown">
id="listNotebookDropdownMenu" data-toggle="dropdown">
<i class="fa fa-angle-down"></i>
</a>
<div class="dropdown-menu dropdown-list" id="searchNotebookForAddDropdownList">
<input type="text" placeholder="search notebook" class="form-control" id="searchNotebookForAdd"/>
<ul class="clearfix" role="menu" aria-labelledby="dropdownMenu2" id="notebookNavForNewNote">
<input type="text" placeholder="Search notebook" class="form-control" id="searchNotebookForAdd"/>
<ul class="clearfix" role="menu" aria-labelledby="listNotebookDropdownMenu" id="notebookNavForNewNote">
</ul>
</div>
</div>
@@ -93,19 +95,21 @@ function log(o) {
<div id="newSharedNote" style="display: none">
<a id="newSharedNoteBtn">
<i class="fa fa-file-o"></i>
{{msg . "newNote"}}
<span class="new-note-text">{{msg . "newNote"}}</span>
<span class="new-note-text-abbr">{{msg . "new"}}</span>
</a>
<span class="new-split">|</span>
<a id="newShareNoteMarkdownBtn" title="{{msg . "newMarkdown"}}">
Markdown
<span class="new-markdown-text">Markdown</span>
<span class="new-markdown-text-abbr">Md</span>
</a>
<span class="for-split"> - </span>
<span id="curNotebookForNewSharedNote" notebookId="" userId=""></span>
<div class="dropdown" style="display: inline-block">
<a class="ios7-a dropdown-toggle" data-toggle="dropdown">
<a id="listShareNotebookDropdownMenu" class="ios7-a dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-angle-down"></i>
</a>
<div class="dropdown-menu dropdown-list" style="left: -200px;" >
<div class="dropdown-menu dropdown-list" id="searchNotebookForAddShareDropdownList" >
<ul id="notebookNavForNewSharedNote"></ul>
</div>
</div>
@@ -118,20 +122,10 @@ function log(o) {
</span>
</div>
<!--
<div class="pull-left alert-warning" style="line-height: 20px; margin-top: 10px; margin-left: 0px; display: none" id="verifyMsg">
您还没有验证邮箱, 验证邮件已发送至 {{.userInfo.Email}}.
<br />
<a class=".nowToActive">现在去验证</a> <a id="reSendActiveEmail">重新发送</a> <a id="wrongEmail">邮箱填错了?</a>
</div>
-->
<div class="pull-right" style="margin: 0 10px" id="myProfile">
<div class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" style="line-height: 60px;">
<!--
<img src="images/avatar.png" style="height: 40px; border: 1px solid #ccc" />
-->
<a class="dropdown-toggle" title="{{.userInfo.Username}}" data-toggle="dropdown" style="line-height: 60px;">
<img alt="{{.userInfo.Username}}" title="{{.userInfo.Username}}" src="{{if .userInfo.Logo}}{{.userInfo.Logo}}{{else}}/images/blog/default_avatar.png{{end}}" id="myAvatar"/>
<span class="username">
{{if .userInfo.UsernameRaw}}
{{.userInfo.UsernameRaw}}
@@ -143,12 +137,22 @@ function log(o) {
</a>
<ul class="dropdown-menu li-a" role="menu">
<li role="presentation" id="setInfo">
<a>
<i class="fa fa-info"></i>
{{msg . "accountSetting"}}
</a>
</li>
<li role="presentation" id="setAvatarMenu">
<a>
<i class="fa fa-smile-o"></i>
{{msg . "setAvatar"}}
</a>
</li>
<li role="presentation" id="setTheme">
<a>
<i class="fa fa-sun-o"></i>
{{msg . "themeSetting"}}
</a>
</li>
<!--
<li role="presentation" id="yourSuggestions">
@@ -156,6 +160,22 @@ function log(o) {
{{msg . "yourSuggestions"}}
</li>
-->
<li role="presentation" class="my-link" >
<a target="_blank" href="{{$.blogUrl}}/{{.userInfo.Username}}">
<i class="fa fa-bold"></i>
{{msg . "myBlog"}}</a>
</li>
{{if .isAdmin}}
<li role="presentation" class="divider"></li>
<li role="presentation">
<a target="_blank" title="{{msg . "amdin"}}" href="/admin/index">
<i class="fa fa-dashboard"></i>
{{msg . "admin"}}
</a>
</li>
{{end}}
<li role="presentation" class="divider"></li>
<li role="presentation" onclick="logout()">
<i class="fa fa-sign-out"></i>
{{msg . "logout"}}
@@ -164,12 +184,8 @@ function log(o) {
</div>
</div>
<div class="pull-right" style="line-height: 60px; margin:0 10px">
<a target="_blank" title="lea++, leanote blog platform" href="/lea/index">lea++</a>
</div>
<div class="pull-right" style="margin: 0 10px" id="topNav">
<a target="_blank" href="/blog/{{.userInfo.Username}}">
<div class="pull-right top-nav" id="myBlog">
<a target="_blank" href="{{$.blogUrl}}/{{.userInfo.Username}}">
{{msg . "myBlog"}}
</a>
</div>
@@ -243,7 +259,7 @@ function log(o) {
</div>
<div class="folderBody">
<input type="text" class="form-control" id="searchNotebookForList" placeholder="search notebook"/>
<input type="text" class="form-control" id="searchNotebookForList" placeholder="Search notebook"/>
<ul class="ztree" id="notebookList"></ul>
<ul class="ztree" id="notebookListForSearch"></ul>
</div>
@@ -258,10 +274,10 @@ function log(o) {
</div>
<ul class="folderBody clearfix" id="tagNav">
<li><a> <span class="label label-red">{{msg . "red"}}</span></a></li>
<li><a> <span class="label label-blue">{{msg . "blue"}}</span></a></li>
<li><a> <span class="label label-yellow">{{msg . "yellow"}}</span></a></li>
<li><a> <span class="label label-green">{{msg . "green"}}</span></a></li>
<li data-tag="red"><a> <span class="label label-red">{{msg . "red"}}</span></a></li>
<li data-tag="blue"><a> <span class="label label-blue">{{msg . "blue"}}</span></a></li>
<li data-tag="yellow"><a> <span class="label label-yellow">{{msg . "yellow"}}</span></a></li>
<li data-tag="green"><a> <span class="label label-green">{{msg . "green"}}</span></a></li>
</ul>
</div>
@@ -313,9 +329,7 @@ function log(o) {
<div id="noteAndEditor">
<div id="noteList">
<div class="clearfix" id="notesAndSort" style="position: relative">
<div class="pull-left">
<!-- 我的笔记本 -->
<div class="dropdown" id="myNotebookNavForListNav">
<a class="ios7-a dropdown-toggle" id="dropdownMenu1" data-toggle="dropdown">
@@ -374,14 +388,13 @@ function log(o) {
-->
</div>
</div>
</div>
<!-- 笔记列表 -->
<!-- wrap 为了slimScroll -->
<div id="noteItemListWrap" style="position: absolute; left: 0; right: 0; top: 41px; bottom: 3px">
<div id="noteItemList">
</div>
<div id="noteItemListWrap">
<ul id="noteItemList">
</ul>
</div>
</div>
@@ -393,43 +406,41 @@ function log(o) {
<div id="noteReadContainer">
<div id="noteReadTop">
<h2 id="noteReadTitle"></h2>
<div class="clearfix">
<div class="pull-left">
<div class="clearfix" id="noteReadInfo">
<i class="fa fa-bookmark-o"></i>
<span id="noteReadTags"></span>
</div>
<!-- 修改时间 -->
<div class="pull-left" style="margin-left: 10px;">
<i class="fa fa-calendar"></i>{{msg . "update"}}
<span id="noteReadUpdatedTime"></span>
</div>
<!-- 修改时间 -->
<div class="pull-left" style="margin-left: 10px;">
<i class="fa fa-calendar"></i>{{msg . "create"}}
<span id="noteReadCreatedTime"></span>
</div>
</div>
</div>
<div id="noteReadContent">
</div>
</div>
</div>
<!-- 遮罩, 为了resize3Columns用 -->
<div id="noteMask"
style="position: absolute; top: 0px; bottom: 0px; right: 0; left: 10px; z-index: -1"></div>
<div id="noteMask" class="note-mask"></div>
<div id="noteMaskForLoading" class="note-mask">
<img src="/images/loading-24.gif"/>
<br />
loading...
</div>
<div id="editorMask">
该笔记本下空空如也...何不
{{msg . "noNoteNewNoteTips"}}
<br />
<br />
<div id="editorMaskBtns">
<br />
<a class="note">新建笔记</a>
<a class="markdown">新建Markdown笔记</a>
<a class="note">{{msg . "newNote"}}</a>
<a class="markdown">{{msg . "newMarkdownNote"}}</a>
</div>
<div id="editorMaskBtnsEmpty">
Sorry, 这里不能添加笔记的.
{{msg . "canntNewNoteTips"}}
</div>
</div>
<div id="noteTop">
@@ -453,7 +464,9 @@ function log(o) {
<a
class="metro-a dropdown-toggle" data-toggle="dropdown"
id="addTagTrigger" style="cursor: text; padding-left: 0">
<span class="add-tag-text">
{{msg . "clickAddTag"}}
</span>
</a>
<input type="text" id="addTagInput" />
<ul class="dropdown-menu" role="menu" id="tagColor">
@@ -467,34 +480,34 @@ function log(o) {
</div>
</div>
<ul class="pull-right" id="editorTool">
<li><a class="ios7-a " id="saveBtn" title="ctrl+s"
data-toggle="dropdown">{{msg . "save"}}</a></li>
data-toggle="dropdown">
<span class="fa fa-save"></span>
{{msg . "save"}}</a></li>
<li class="dropdown" id="attachDropdown">
<a class="ios7-a dropdown-toggle" data-toggle="dropdown" id="showAttach">
<!--
<span class="fa fa-upload"></span>
-->
<span class="fa fa-paperclip"></span>
{{msg . "attachments"}}<span id="attachNum"></span>
</a>
<div class="dropdown-menu" id="attachMenu">
<ul id="attachList">
</ul>
<form id="uploadAttach" method="post" action="/attach/UploadAttach" enctype="multipart/form-data">
<div id="dropAttach">
<div id="dropAttach" class="dropzone">
<a class="btn btn-success btn-choose-file">
Choose File to Upload
<i class="fa fa-upload"></i>
<span>Choose File</span>
</a>
<a class="btn btn-default" id="downloadAllBtn">
<i class="fa fa-download"></i>
Download All
<span>Download All</span>
</a>
<a class="btn btn-default" id="linkAllBtn">
<i class="fa fa-link"></i>
Link All
<span>Link All</span>
</a>
<input type="file" name="file" multiple/>
</div>
@@ -505,9 +518,13 @@ function log(o) {
</li>
<li><a class="ios7-a " id="tipsBtn"
data-toggle="dropdown">{{msg . "editorTips"}}</a></li>
data-toggle="dropdown">
<span class="fa fa-question"></span>
{{msg . "editorTips"}}</a></li>
<li><a class="ios7-a " id="contentHistory"
data-toggle="dropdown">{{msg . "history"}}</a></li>
data-toggle="dropdown">
<span class="fa fa-history"></span>
{{msg . "history"}}</a></li>
</ul>
</div>
@@ -518,7 +535,7 @@ function log(o) {
<div id="editor">
<!-- 编辑器 -->
<div id="mceToolbar" style="">
<div id="mceToolbar">
<div id="popularToolbar"
style="position: absolute; right: 30px; left: 0"></div>
<a
@@ -558,12 +575,13 @@ function log(o) {
<!-- 为了scroll -->
<div class="clearfix" id="mdEditorPreview">
<div id="left-column" class="pull-left">
<div id="left-column">
<div id="wmd-panel-editor" class="wmd-panel-editor">
<textarea class="wmd-input theme" id="wmd-input" spellcheck="false" tabindex="3"></textarea>
</div>
</div>
<div id="right-column" class="pull-right">
<div id="mdSplitter"></div>
<div id="right-column">
<div id="wmd-panel-preview" class="wmd-panel-preview preview-container">
<div id="wmd-preview" class="wmd-preview"></div>
</div>
@@ -571,7 +589,8 @@ function log(o) {
</div>
<textarea id="md-section-helper"></textarea>
</div>
<!-- for test -->
</div>
</div>
<!-- mdEditor -->
<!-- Hidden Popup Modal -->
@@ -581,7 +600,7 @@ function log(o) {
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="editorDialog-title">操作</h4>
<h4 class="modal-title" id="editorDialog-title"></h4>
</div>
<div class="modal-body">
@@ -595,17 +614,13 @@ function log(o) {
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="editorDialog-confirm">确认</button>
<button type="button" class="btn btn-default" data-dismiss="modal">{{msg . "cancel"}}</button>
<button type="button" class="btn btn-primary" id="editorDialog-confirm">{{msg . "confirm"}}</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
</div>
</div>
<!-- 弹出框 模板 -->
<div class="modal fade bs-modal-sm" id="leanoteDialogRemote" tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel" aria-hidden="true">
</div>
@@ -648,7 +663,7 @@ function log(o) {
</div>
<input type="hidden" id="toEmail"/>
<label for="emailContent">邮件内容</label>
<textarea class="form-control" id="emailContent">Hi, 我是李铁, leanote非常好用, 快来注册吧.</textarea>
<textarea class="form-control" id="emailContent">Hi, 我是life, leanote非常好用, 快来注册吧.</textarea>
</div>
</form>
</div>
@@ -661,26 +676,15 @@ function log(o) {
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<!-- theme -->
<div class="modal fade bs-modal-sm" id="setThemeDialog" tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" class="modalTitle">主题设置</h4>
<h4 class="modal-title" class="modalTitle">{{msg . "theme"}}</h4>
</div>
<div class="modal-body">
<style>
#themeForm td {
padding: 5px;
text-align: center;
}
#themeForm img {
border: 1px solid #eee;
padding: 2px;
}
</style>
<table id="themeForm">
<tr>
<td>
@@ -703,7 +707,6 @@ function log(o) {
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{msg . "close"}}</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
@@ -728,17 +731,31 @@ function log(o) {
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<!-- 图片上传 -->
<div class="modal fade bs-modal-sm" id="imageDialog" tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel" aria-hidden="true">
<!-- avatar -->
<div class="modal fade bs-modal-sm" id="avatarDialog" tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content" style="height: 460px;" >
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" class="modalTitle">{{msg . "uploadImage"}}</h4>
<h4 class="modal-title" class="modalTitle">{{msg . "setAvatar"}}</h4>
</div>
<div class="modal-body">
<iframe style="" height="360" src="" scrolling="no" frameBorder="0" width="99%"></iframe>
<form id="uploadAvatar" method="post" action="/file/uploadAvatar" enctype="multipart/form-data">
<div id="dropAvatar" class="dropzone">
<div>
<img src="{{if .userInfo.Logo}}{{.userInfo.Logo}}{{else}}/images/blog/default_avatar.png{{end}}" id="avatar"/>
</div>
<a class="btn btn-success btn-choose-file">
<span class="fa fa-upload"></span> Choose Image
</a>
<input type="file" name="file" multiple/>
</div>
<div id="avatarUploadMsg">
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{msg . "close"}}</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
@@ -785,183 +802,27 @@ function log(o) {
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{msg . "cancel"}}</button>
<button type="button" class="btn btn-success sendWeiboBtn disabled">{{msg . "send"}}</button>
<button type="button" class="btn btn-share btn-default sendRRBtn disabled"><i class="fa fa-renren"></i> 人人</button>
<button type="button" class="btn btn-share btn-default sendQQBtn disabled"><i class="fa fa-qq"></i> QQ空间</button>
<button type="button" class="btn btn-share btn-primary sendTxWeiboBtn disabled"><i class="fa fa-tencent-weibo"></i> 腾讯微博</button>
<button type="button" class="btn btn-share btn-success sendWeiboBtn disabled"><i class="fa fa-weibo"></i> 新浪微博</button>
</div>
</div>
<!-- 激活邮件 -->
<div id="reSendActiveEmailDialog">
<div class="modal-body">
<div style="max-height: 300px; padding: 5px 0; text-align: center; overflow: scroll;" class="weibo">
<div style="max-height: 300px; padding: 5px 0; text-align: center; overflow-y: auto; overflow-x: hidden" class="weibo">
<span class="text">
<img src="/images/loading-24.gif"/>
正在发送邮件到{{.userInfo.Email}}...
{{msg . "emailInSending"}} {{.userInfo.Email}}...
</span>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
<button type="button" class="btn btn-success viewEmailBtn disabled">查看邮件</button>
</div>
</div>
<!-- 帐户设置 -->
<div id="dialogSetInfo">
<div class="modal-body">
<ul class="nav nav-tabs" id="myTabs">
<li><a href="#baseInfo" data-toggle="tab">{{msg . "basicInfo"}}</a></li>
<li><a href="#emailInfo" data-toggle="tab">{{msg . "updateEmail"}}</a></li>
<li><a href="#updatePwd" data-toggle="tab">{{msg . "updatePassword"}}</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="baseInfo">
<form>
<table>
<tr>
<td>
<label for="username">用户名设置</label>
<div class="alert alert-danger" id="usernameMsg" style="display: none"></div>
<input type="text" class="form-control" id="username">
你的邮箱是 {{.userInfo.Email}}, 可以再设置一个唯一的用户名.
<br />
用户名至少4位, 不可含特殊字符.
</td>
</tr>
<tr>
<td>
<button id="usernameBtn" class="btn btn-success">提交</button>
</td>
</tr>
</table>
</form>
</div>
<div class="tab-pane" id="emailInfo">
<form>
<table>
<tr>
<td>
当前邮箱为: <span id="curEmail">{{.userInfo.Email}}</span>
{{if .userInfo.Verified}}
已验证
{{else}}
未验证
<a class="raw nowToActive">现在去验证</a>
<a class="raw reSendActiveEmail">重新发送</a>
{{end}}
<br />
<label for="email">修改邮箱</label>
<div class="alert alert-danger" id="emailMsg" style="display: none"></div>
<input type="text" class="form-control" id="email">
邮箱修改后, 验证之后才有效, 验证之后新的邮箱地址将会作为登录帐号使用.
</td>
</tr>
<tr>
<td>
<button id="emailBtn" class="btn btn-success">发送验证邮箱</button>
</td>
</tr>
</table>
</form>
</div>
<div class="tab-pane" id="updatePwd">
<form>
<table style="width: 80%">
<tr>
<td>
<div class="alert alert-danger" id="pwdMsg" style="display: none"></div>
</td>
</tr>
<tr>
<td style="width: 80px">
<label for="pwd">{{msg . "oldPassword"}}</label>
<input type="password" class="form-control" id="oldPwd" name="oldPwd">
</td>
</tr>
<tr>
<td>
<label for="pwd">{{msg . "newPassword"}}</label>
<input type="password" class="form-control" id="pwd" name="pwd">
</td>
</tr>
<tr>
<td>
<label for="pwd2">{{msg . "password2"}}</label>
<input type="password" class="form-control" id="pwd2" name="pwd2">
</td>
</tr>
<tr>
<td>
<button id="pwdBtn" class="btn btn-success">{{msg . "submit"}}</button>
</td>
</tr>
</table>
</form>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
</div>
</div>
<!-- 账号设置, 是通过third 登录进来的 -->
<div id="thirdDialogSetInfo">
<div class="modal-body">
<ul class="nav nav-tabs" id="thirdMyTabs">
<li><a href="#accountInfo" data-toggle="tab">创建帐号</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="accountInfo">
您现在使用的是第三方帐号登录leanote, 您也可以注册leanote帐号登录, 赶紧注册一个吧.
<br />
注册成功后仍可以使用第三方帐号登录leanote并管理您现有的笔记.
<form>
<div class="alert alert-danger" id="thirdAccountMsg" style="display: none"></div>
<table style="width: 100%">
<tr>
<td>
<label for="thirdEmail">邮箱</label>
</td>
<td>
<input type="text" class="form-control" id="thirdEmail">
</td>
</tr>
<tr>
<td>
<label for="thirdPwd">密码</label>
</td>
<td>
<input type="password" class="form-control" id="thirdPwd">
</td>
<td>
密码至少6位
</td>
</tr>
<tr>
<td>
<label for="thirdPwd2">重复密码</label>
</td>
<td>
<input type="password" class="form-control" id="thirdPwd2">
</td>
</tr>
<tr>
<td></td>
<td>
<button id="accountBtn" class="btn btn-success" style="width: 100%">提交</button>
</td>
</tr>
</table>
</form>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
</div>
<button type="button" class="btn btn-default" data-dismiss="modal">{{msg . "close"}}</button>
<button type="button" class="btn btn-success viewEmailBtn disabled">{{msg . "checkEmail"}}</button>
</div>
</div>
<div class="hide" id="copyDiv"></div>
@@ -973,7 +834,7 @@ function log(o) {
<script src="js/i18n/msg.{{.locale}}.js"></script>
<script src="js/common-min.js"></script>
<script>
var UrlPrefix = "http://leanote.com"; // 为了发weibo
var UrlPrefix = "{{.siteUrl}}"; // 为了发weibo
var UserInfo = json({{.userInfoJson}});
var notebooks = json({{.notebooks}});
var shareNotebooks = json({{.shareNotebooks}});
@@ -985,12 +846,11 @@ LEA.locale = "{{.locale}}";
</script>
<!-- 渲染view -->
<script src="tinymce/tinymce.js"></script>
<script src="js/app/page-min.js"></script>
<script src="/js/jQuery-slimScroll-1.3.0/jquery.slimscroll.js"></script>
<script src="/js/contextmenu/jquery.contextmenu-min.js"></script>
<script src="js/app/page-min.js"></script>
<script src="tinymce/tinymce.js"></script>
<script src="js/jquery-cookie-min.js"></script>
<script src="js/jquery-cookie.js"></script>
<script src="js/bootstrap-min.js"></script>
<script src="js/app/note-min.js"></script>
<script src="js/app/tag-min.js"></script>
@@ -1002,13 +862,14 @@ LEA.locale = "{{.locale}}";
Notebook.renderNotebooks(notebooks);
Share.renderShareNotebooks(sharedUserInfos, shareNotebooks);
Note.setNoteCache(noteContentJson);
Note.renderNotes(notes);
if(!isEmpty(notes)) {
Note.changeNote(notes[0].NoteId);
}
Note.setNoteCache(noteContentJson);
Note.renderNoteContent(noteContentJson)
// Note.chanteNote设置content
// Note.renderNoteContent(noteContentJson)
Tag.renderTagNav(tagsJson);
@@ -1016,50 +877,13 @@ Tag.renderTagNav(tagsJson);
initSlimScroll();
</script>
<!-- mdEditor -->
<link href="/public/mdeditor/editor/editor.css" rel="stylesheet">
<script src="/public/mdeditor/editor/pagedown/Markdown.Converter-min.js"></script>
<script src="/public/mdeditor/editor/pagedown/Markdown.Sanitizer-min.js"></script>
<script src="/public/mdeditor/editor/pagedown/Markdown.Editor-min.js"></script>
<script src="/public/mdeditor/editor/pagedown/local/Markdown.local.zh-min.js"></script>
<script src="/public/mdeditor/editor/Markdown.Extra-min.js"></script>
<script src="/public/mdeditor/editor/underscore-min.js"></script>
<script src="/public/mdeditor/editor/scrollLink-min.js"></script>
<!--mathjax-->
<script type="text/x-mathjax-config">
MathJax.Hub.Config({ tex2jax: { inlineMath: [['$','$'], ["\\(","\\)"]], processEscapes: true }, messageStyle: "none"});
</script>
<script src="/public/mdeditor/editor/mathJax-min.js"></script>
<script src="//cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script src="/public/mdeditor/editor/jquery.waitforimages-min.js"></script>
<script src="/public/mdeditor/editor/google-code-prettify/prettify.js"></script>
<script src="/public/mdeditor/editor/editor-min.js"></script>
<!-- mdEditor end -->
<!-- context-menu -->
<link rel="stylesheet" href="/js/contextmenu/css/contextmenu.css" type="text/css" />
<!-- code -->
<link href="/public/mdeditor/editor/google-code-prettify/prettify.css" rel="stylesheet" />
<!-- js version 2.0 use require.js -->
<script src="/js/require.js"></script>
<script>
require.config({
baseUrl: '/public',
paths: {
// 'jquery': 'js/jquery-1.9.0.min',
'leaui_image': 'tinymce/plugins/leaui_image/public/js/for_editor',
'attachment_upload': 'js/app/attachment_upload',
'jquery.ui.widget': 'tinymce/plugins/leaui_image/public/js/jquery.ui.widget',
'fileupload': '/tinymce/plugins/leaui_image/public/js/jquery.fileupload',
'iframe-transport': '/tinymce/plugins/leaui_image/public/js/jquery.iframe-transport'
},
shim: {
'fileupload': {deps: ['jquery.ui.widget', 'iframe-transport']}
}
});
require(['leaui_image'], function(leaui_image) {
});
require(['attachment_upload'], function(attachment_upload) {
});
<script src="/js/main-min.js"></script>
</script>
</body>
</html>

View File

@@ -1,18 +1,32 @@
{{template "home/header_box.html" .}}
<section id="box">
<section id="box" class="animated fadeInUp">
<div>
<h1>
leanote | we got a error
</h1>
<form class="form-inline" id="boxForm">
<p>
<h1 id="logo">leanote</h1>
<div id="boxForm">
<div id="boxHeader">We got a error</div>
<form>
<div class="alert alert-danger" id="loginMsg" style="display: block">
Sorry, we can't get your infomation.
<br />
Please <a href="/login">Sign in</a> Or <a href="/register?email={{.email}}">Sign up</a>
</p>
Please <a href="/login">{{msg . "login"}}</a> Or <a href="/register?email={{.email}}">{{msg . "register"}}</a>
</div>
</form>
</div>
</div>
</section>
<div id="boxFooter">
<p>
<a href="/login">{{msg . "login"}}</a>
&nbsp;
<a href="/index">{{msg . "home"}}</a>
</p>
<p>
<a href="/index">leanote</a> © 2014
</p>
</div>
</body>
</html>

View File

@@ -1,37 +1,36 @@
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="modalTitle">分享 <b>{{.title}}</b></h4>
<h4 class="modal-title" id="modalTitle">{{msg . "share"}} <b>{{.title}}</b></h4>
</div>
{{$noteOrNotebookId := .noteOrNotebookId}}
<div class="modal-body">
<button class="btn btn-default" id="addShareNotebookBtn">添加分享</button>
<button class="btn btn-default" id="addShareNotebookBtn">{{msg . "addShare"}}</button>
<div id="shareMsg" class="alert alert-danger" style="display: none; margin: 5px 0 0 0;"></div>
<table class="table table-hover" id="shareNotebookTable">
<thead>
<tr>
<th>#</th>
<th>好友邮箱</th>
<th>权限</th>
<th>删除分享</th>
<th>{{msg . "friendEmail"}}</th>
<th>{{msg . "permission"}}</th>
<th width="150px">{{msg . "delete"}}</th>
</tr>
</thead>
<tbody>
<tr id="tr1">
<td>#</td>
<td>
<input id="friendsEmail" type="text" class="form-control" style="width: 200px" placeholder="好友邮箱">
<input id="friendsEmail" type="text" class="form-control" placeholder="{{msg . "friendEmail"}}">
</td>
<td>
<label for="readPerm1"><input type="radio" name="perm1" checked="checked" value="0" id="readPerm1"> 只读</label>
<label for="writePerm1"><input type="radio" name="perm1" value="1" id="writePerm1"> 可编辑</label>
<label for="readPerm1"><input type="radio" name="perm1" checked="checked" value="0" id="readPerm1"> {{msg . "readOnly"}}</label>
<label for="writePerm1"><input type="radio" name="perm1" value="1" id="writePerm1"> {{msg . "writable"}}</label>
</td>
<td>
<button class="btn btn-success" onclick="addShareNoteOrNotebook(1)">分享</button>
<button class="btn btn-warning" onclick="deleteShareNoteOrNotebook(1)">删除</button>
<button class="btn btn-success" onclick="addShareNoteOrNotebook(1)">{{msg . "share"}}</button>
<button class="btn btn-warning" onclick="deleteShareNoteOrNotebook(1)">{{msg . "delete"}}</button>
</td>
</tr>
{{range $i, $v := .noteOrNotebookShareUserInfos}}
@@ -41,13 +40,13 @@
<td>{{$v.Email}}</td>
<td>
{{if eq $v.Perm 0}}
<a href="#" noteOrNotebookId="{{$noteOrNotebookId}}" perm="{{$v.Perm}}" toUserId="{{$toUserId}}" title="点击改变权限" class="btn btn-default change-perm">只读</a>
<a href="#" noteOrNotebookId="{{$noteOrNotebookId}}" perm="{{$v.Perm}}" toUserId="{{$toUserId}}" title="点击改变权限" class="btn btn-default change-perm">{{msg . "readOnly"}}</a>
{{else}}
<a href="#" noteOrNotebookId="{{$noteOrNotebookId}}" perm="{{$v.Perm}}" toUserId="{{$toUserId}}" title="点击改变权限" class="btn btn-default change-perm">可编辑</a>
<a href="#" noteOrNotebookId="{{$noteOrNotebookId}}" perm="{{$v.Perm}}" toUserId="{{$toUserId}}" title="点击改变权限" class="btn btn-default change-perm">{{msg . "writable"}}</a>
{{end}}
</td>
<td>
<a href="#" noteOrNotebookId="{{$noteOrNotebookId}}" toUserId="{{$toUserId}}" class="btn btn-warning delete-share">删除</a>
<a href="#" noteOrNotebookId="{{$noteOrNotebookId}}" toUserId="{{$toUserId}}" class="btn btn-warning delete-share">{{msg . "delete"}}</a>
</td>
</tr>
{{end}}
@@ -56,7 +55,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
<button type="button" class="btn btn-default" data-dismiss="modal">{{msg . "close"}}</button>
</div>
</div><!-- /.modal-content -->

272
app/views/User/account.html Normal file
View File

@@ -0,0 +1,272 @@
<div class="modal-dialog modal-sm" id="accountInfoDialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="modalTitle">{{msg . "accountSetting"}}</h4>
</div>
{{if .userInfo.Email}}
<div class="modal-body">
<ul class="nav nav-tabs" id="infoTabs">
<li class="active"><a href="#baseInfo" data-toggle="tab">{{msg . "basicInfo"}}</a></li>
<li><a href="#emailInfo" data-toggle="tab">{{msg . "updateEmail"}}</a></li>
<li><a href="#updatePwd" data-toggle="tab">{{msg . "updatePassword"}}</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="baseInfo">
<form>
<div class="alert alert-danger" id="usernameMsg" style="display: none"></div>
<label for="username">{{msg . "setUsername"}}</label>
<input type="text" class="form-control" id="username"
value="{{.userInfo.Username}}"
data-rules='[
{rule: "required", msg: "inputUsername"},
{rule: "noSpecialChars", msg: "noSpecialChars"},
{rule: "minLength", data: 4, msg: "minLength", msgData: 4}
]'
data-msg_target="#usernameMsg"
/>
{{msg . "setUsernameTips" .userInfo.Email}}
<div>
<button id="usernameBtn" class="btn btn-success">{{msg . "submit"}}</button>
</div>
</form>
</div>
<div class="tab-pane" id="emailInfo">
<form>
{{msg . "currentEmail" .userInfo.Email}}
{{if .userInfo.Verified}}
<span class="label label-green">{{msg . "verified"}}</span>
{{else}}
<span class="label label-red">{{msg . "unVerified"}}</span>
<a class="raw nowToActive">{{msg . "verifiedNow"}}</a>
{{msg . "or"}}
<a class="raw reSendActiveEmail">{{msg . "resendVerifiedEmail"}}</a>
{{end}}
<br />
<label for="email">{{msg . "updateEmail"}}</label>
<div class="alert alert-danger" id="emailMsg" style="display: none" placeholder="New Email"></div>
<input type="text" class="form-control"
id="email"
data-rules='[
{rule: "required", msg: "inputEmail"},
{rule: "email", msg: "errorEmail"}
]'
data-msg_target="#emailMsg"
/>
{{msg . "updateEmailTips"}}
<div>
<button id="emailBtn" class="btn btn-success">{{msg . "sendVerifiedEmail"}}</button>
</div>
</form>
</div>
<div class="tab-pane" id="updatePwd">
<form>
<div class="alert alert-danger" id="pwdMsg" style="display: none"></div>
<div class="form-group">
<label class="control-label" for="oldPwd">{{msg . "oldPassword"}}</label>
<input type="password" class="form-control" id="oldPwd" name="oldPwd"
data-rules='[
{rule: "required", msg: "inputPassword"}
]'
data-msg_target="#pwdMsg"
/>
</div>
<div class="form-group">
<label class="control-label" for="pwd">{{msg . "newPassword"}}</label>
<input type="password" class="form-control" id="pwd" name="pwd"
data-rules='[
{rule: "required", msg: "inputNewPassword"},
{rule: "password", msg: "errorPassword"}
]'
data-msg_target="#pwdMsg"
>
{{msg . "passwordTips"}}
</div>
<div class="form-group">
<label class="control-label" for="pwd2">{{msg . "password2"}}</label>
<input type="password" class="form-control" id="pwd2" name="pwd2"
data-rules='[
{rule: "required", msg: "inputPassword2"},
{rule: "equalTo", data:"#pwd", msg: "confirmPassword"}
]'
data-msg_target="#pwdMsg"
/>
</div>
<button id="pwdBtn" class="btn btn-success">{{msg . "submit"}}</button>
</form>
</div>
</div>
</div>
{{else}}
<div class="modal-body">
<ul class="nav nav-tabs" id="thirdMyTabs">
<li class="active"><a href="#accountInfo" data-toggle="tab">{{msg . "createAccount"}}</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="accountInfo">
{{msg . "thirdCreateAcountTips"}}
<form>
<div class="alert alert-danger" id="thirdAccountMsg" style="display: none"></div>
<div class="form-group">
<label class="control-label" for="thirdEmail">{{msg . "email"}}</label>
<input type="text" class="form-control" id="thirdEmail" name="email"
data-rules='[
{rule: "required", msg: "inputEmail"},
{rule: "email", msg: "errorEmail"}
]'
data-msg_target="#thirdAccountMsg"
>
</div>
<div class="form-group">
<label class="control-label" for="thirdPwd">{{msg . "password"}}</label>
<input type="password" class="form-control" id="thirdPwd" name="pwd"
data-rules='[
{rule: "required", msg: "inputPassword"},
{rule: "password", msg: "errorPassword"}
]'
data-msg_target="#thirdAccountMsg"
/>
{{msg . "passwordTips"}}
</div>
<div class="form-group">
<label class="control-label" for="thirdPwd2">{{msg . "password2"}}</label>
<input type="password" class="form-control" id="thirdPwd2" name="pwd2"
data-rules='[
{rule: "required", msg: "inputPassword2"},
{rule: "equalTo", data:"#thirdPwd", msg: "confirmPassword"}
]'
data-msg_target="#thirdAccountMsg"
>
</div>
<button id="accountBtn" class="btn btn-success">{{msg . "submit"}}</button>
</form>
</div>
</div>
</div>
{{end}}
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{msg . "close"}}</button>
</div>
</div>
</div>
<script>
$('#infoTabs a').eq({{.tab}}).tab('show');
//--------------
// 第三方账号设置
var acountVd = new vd.init("#accountInfo");
$("#accountInfoDialog").on("click", "#accountBtn", function(e) {
e.preventDefault();
if(!acountVd.valid()) {
return;
}
var email = $("#thirdEmail").val();
var pwd = $("#thirdPwd").val();
var pwd2 = $("#thirdPwd2").val();
post("/user/addAccount", {email: email, pwd: pwd}, function(ret) {
if(ret.Ok) {
showAlert("#thirdAccountMsg", getMsg("createAccountSuccess"), "success");
UserInfo.Email = email;
$("#curEmail").html(email);
hideDialogRemote(1000);
} else {
showAlert("#thirdAccountMsg", ret.Msg || getMsg("createAccountFailed"), "danger");
}
}, this);
});
//-------------
var usernameVd = new vd.init("#baseInfo");
$("#usernameBtn").click(function(e) {
e.preventDefault();
if(!usernameVd.valid()) {
return;
}
var username = $("#username").val();
post("/user/updateUsername", {username: username}, function(ret) {
if(ret.Ok) {
UserInfo.UsernameRaw = username;
UserInfo.Username = username.toLowerCase();
$(".username").html(username);
showAlert('#usernameMsg', getMsg("updateUsernameSuccess"), "success");
} else {
showAlert('#usernameMsg', ret.Msg || getMsg("usernameIsExisted"), "danger");
}
}, "#usernameBtn");
});
// 修改邮箱
var emailVd = new vd.init("#emailInfo");
$("#emailBtn").click(function(e) {
e.preventDefault();
if(!emailVd.valid()) {
return;
}
var email = $("#email").val();
post("/user/updateEmailSendActiveEmail", {email: email}, function(e) {
if(e.Ok) {
var url = getEmailLoginAddress(email);
showAlert("#emailMsg", getMsg("verifiedEmaiHasSent") +" <a href='" + url + "' target='_blank'>" + getMsg("checkEmail") + "</a>", "success");
} else {
showAlert("#emailMsg", e.Msg || getMsg("emailSendFailed"), "danger");
}
}, "#emailBtn");
});
// 修改密码
var updatePwdVd = new vd.init("#updatePwd");
$("#pwdBtn").click(function(e) {
e.preventDefault();
if(!updatePwdVd.valid()) {
return;
}
var oldPwd = $("#oldPwd").val();
var pwd = $("#pwd").val();
post("/user/updatePwd", {oldPwd: oldPwd, pwd: pwd}, function(e) {
if(e.Ok) {
showAlert("#pwdMsg", getMsg("updatePasswordSuccess"), "success");
} else {
showAlert("#pwdMsg", e.Msg, "danger");
}
}, "#pwdBtn");
});
// 重新发送
$(".reSendActiveEmail").click(function() {
// 弹框出来
showDialog("reSendActiveEmailDialog", {title: getMsg("sendVerifiedEmail"), postShow: function() {
ajaxGet("/user/reSendActiveEmail", {}, function(ret) {
if (typeof ret == "object" && ret.Ok) {
$("#leanoteDialog .text").html(getMsg("sendSuccess"))
$("#leanoteDialog .viewEmailBtn").removeClass("disabled");
$("#leanoteDialog .viewEmailBtn").click(function() {
hideDialog();
var url = getEmailLoginAddress(UserInfo.Email);
window.open(url, "_blank");
});
} else {
$("#leanoteDialog .text").html(getMsg("sendFailed"))
}
});
}});
});
// 现在去验证
$(".nowToActive").click(function() {
var url = getEmailLoginAddress(UserInfo.Email);
window.open(url, "_blank");
});
</script>

View File

@@ -1,22 +1,32 @@
{{template "home/header_box.html" .}}
<section id="box">
<div id="posts">
<h1>
leanote 验证邮箱 -
{{if .ok}}成功{{else}}失败{{end}}
</h1>
<form class="form-inline" id="boxForm">
<section id="box" class="animated fadeInUp">
<div>
<h1 id="logo">leanote</h1>
<div id="boxForm">
<div id="boxHeader">验证邮箱 - {{if .ok}}成功{{else}}失败{{end}}</div>
<form>
<div class="alert alert-danger" id="loginMsg"> </div>
您的邮箱 {{.email}} 验证
{{if .ok}}成功{{else}}失败{{end}}
{{if .msg}}<br />{{.msg}}{{end}}
{{if .msg}}<br />
{{.msg}}{{end}}
<br />
<a href="/note">回到我的笔记</a>
</form>
</div>
</div>
</section>
<div id="boxFooter">
<p>
<a href="/index">{{msg . "home"}}</a>
</p>
<p>
<a href="/index">leanote</a> © 2014
</p>
</div>
</body>
</html>

View File

@@ -1,12 +1,12 @@
{{template "home/header_box.html" .}}
<section id="box">
<div>
<h1>
leanote 验证邮箱 -
{{if .ok}}成功{{else}}失败{{end}}
</h1>
<form class="form-inline" id="boxForm">
<section id="box" class="animated fadeInUp">
<div>
<h1 id="logo">leanote</h1>
<div id="boxForm">
<div id="boxHeader">验证邮箱 - {{if .ok}}成功{{else}}失败{{end}}</div>
<form>
<div class="alert alert-danger" id="loginMsg"> </div>
您的邮箱 {{.email}} 验证
{{if .ok}}成功{{else}}失败{{end}}
{{if .ok}}
@@ -20,6 +20,16 @@
<a href="/note">回到我的笔记</a>
</form>
</div>
</div>
</section>
<div id="boxFooter">
<p>
<a href="/index">{{msg . "home"}}</a>
</p>
<p>
<a href="/index">leanote</a> © 2014
</p>
</div>
</body>
</html>

View File

@@ -1,24 +1,24 @@
# blog
blog=Blog
aboutMe=About me
blogSet=Set blog
blogNavs=Navs
quickLinks=Quick links
latestPosts=Latest posts
noBlog=No blog
noTag=No tag
blogClass=Classification
blogClass=Category
updatedTime=Updated at
createdTime=Created at
fullBlog=Full blog
blogNav=Blog nav
more=More...
previous=Previous
next=Next
#
# set blog
#
blogSet=Blog configuration
baseInfoSet=Base info
commentSet=Comment
themeSet=Theme
@@ -26,16 +26,66 @@ theme=Theme
blogName=Title
blogLogo=Logo
blogDesc=Description
aboutMe=About Me
#domain
domainSet=Domain
subDomain=Sub domain
domain=Custom domain
# theme
elegant=Elegant
navFixed=Nav fixed at left side
openComment=Open comment?
commentSys=leanote use <a href="http://disqus.com" target="_blank">Disqus</a> as comment system
disqusHelp=Please input your Disqus Id or use "leanote"
chooseComment=Comment System
disqusHelp=Please input your Disqus Id
needHelp=Need help?
blogLogoTips=Upload image to replace blog title
saveSuccess=Save success
community=Community
home=Home
none=None
moreShare=More
sinaWeibo=Weibo
weixin=Weichat
tencentWeibo=Tencent Weibo
qqZone=QQ Zone
renren=Renren
report=Report
like=Like
unlike=Unlike
viewers=Viewers
author=Author
delete=Delete
reply=Reply
comment=Comment
comments=Comments
cancel=Cancel
confirm=Confirm
signIn=Sign In
signUp=Sign Up
submitComment=Submit
reportReason1=不友善内容
reportReason2=广告等垃圾信息
reportReason3=违法违规内容
reportReason4=不宜公开讨论的政治内容
other=Other
reportReason=Reason
chooseReason=请选择举报理由
reportSuccess=举报成功, 我们处理后会通知作者, 感谢您的监督
error=Error
reportComment?=举报该评论?
reportBlog?=举报该博客?
confirmDeleteComment=Are you sure?
scanQRCode=Open weichat and scan the QR code
justNow=Just now
minutesAgo=minutes ago
hoursAgo=hours ago
daysAgo=days ago
weeksAgo=weeks ago
monthsAgo=months ago
a=a

View File

@@ -14,10 +14,14 @@ updatedTime=更新
createdTime=创建
fullBlog=全文
blogNav=导航
more=更多...
previous=上一页
next=下一页
#
# set blog
#
blogSet=博客设置
baseInfoSet=基本设置
commentSet=评论设置
themeSet=主题设置
@@ -25,17 +29,69 @@ theme=主题
blogName=博客标题
blogLogo=博客Logo
blogDesc=博客描述
aboutMe=关于我
#domain
domainSet=域名设置
subDomain=博客子域名
domain=自定义域名
# theme
elegant=大气
navFixed=导航左侧固定
openComment=开启评论?
commentSys=leanote 使用 <a href="http://disqus.com" target="_blank">Disqus</a> 作为评论系统
chooseComment=选择评论系统
disqusHelp=请填写您申请的Disqus唯一url前缀. 建议您申请Disqus帐号, 这样可以自己管理评论. 或使用leanote的默认Disqus Id.
needHelp=需要帮助?
blogLogoTips=上传logo将显示logo(替代博客标题)
saveSuccess=保存成功
community=社区
home=主页
none=无
moreShare=更多分享
sinaWeibo=新浪微博
weixin=微信
tencentWeibo=腾讯微博
qqZone=QQ空间
renren=人人网
report=举报
like=赞
unlike=取消赞
viewers=人读过
author=作者
delete=删除
reply=回复
comment=评论
comments=条评论
cancel=取消
confirm=确认
signIn=登录
signUp=注册
submitComment=发表评论
reportReason1=不友善内容
reportReason2=广告等垃圾信息
reportReason3=违法违规内容
reportReason4=不宜公开讨论的政治内容
other=其它
reportReason=举报理由
chooseReason=请选择举报理由
reportSuccess=举报成功, 我们处理后会通知作者, 感谢您的监督
error=错误
reportComment?=举报该评论?
reportBlog?=举报该博客?
confirmDeleteComment=确定删除该评论?
scanQRCode=打开微信扫一扫二维码
justNow=刚刚
minutesAgo=分钟前
hoursAgo=个小时前
daysAgo=天前
weeksAgo=周前
monthsAgo=个月前
a=a

View File

@@ -1,6 +1,7 @@
# leanote
app=leanote
moto=Not Just A Notebook!
moto2=Knowledge, Sharing, Cooperation, Blog... all in leanote
moto2=Knowledge, Blog, Sharing, Cooperation... all in leanote
moto3=Brief But Not Simple
fork github=Fork leanote on Github
@@ -27,6 +28,10 @@ blogInfo=You can public your knowledge and leanote is your blog!
suggestionsInfo=help us to improve our service.
yourContact=Your contact
emailOrOthers=Email or other contact way
captcha=Captcha
reloadCaptcha=Reload Captcha
captchaError=Captcha Do Not Match
inputCaptcha=Captcha is required
hi=Hi
welcomeUseLeanote=Welcome!
@@ -53,7 +58,7 @@ hadAcount = Already have an account?
hasAcount = Do not have an account?
# 注册
registerSuccessAndRdirectToNote=register success, now redirect to my note...
registerSuccessAndRdirectToNote=Register success, redirecting...
# 找回密码
passwordTips=The length is at least 6
@@ -75,11 +80,17 @@ save=Save
editorTips=Tips
editorTipsInfo=<h4>1. Short cuts</h4>ctrl+shift+c Toggle code <br /> ctrl+shift+i Insert/edit image <h4>2. shift+enter Get out of current block</h4> eg. <img src="/images/outofcode.png" style="width: 90px"/> in this situation you can use shift+enter to get out of current code block.
newNote=New note
newMarkdownNote=New Markdown Note
noNoteNewNoteTips=The notebook is empty, why not...
canntNewNoteTips=Sorry, cannot new note in here, please choose a notebook at first.
new=New
newMarkdown=New markdown note
clickAddTag=Click to add Tag
notebook=Notebook
myNotebook=My notebook
addNotebook=Add notebook
search=Search
clearSearch=Clear Search
all=Newest
trash=Trash
delete=Delete
@@ -113,12 +124,14 @@ green=green
# 设置
accountSetting=Account
themeSetting=Theme
setAvatar=Avatar
logout=Logout
basicInfo=Basic
updateEmail=Update email
usernameSetting=Update username
oldPassword=Old password
newPassword=New password
admin=Admin
default=Default
simple=Simple
@@ -139,5 +152,87 @@ howToInstallLeanote=How to install leanote
attachments = Attachments
donate = Donate
# contextmenu
shareToFriends=Share to friends
publicAsBlog=Public as blog
cancelPublic=Cancel public
move=Move
copy=Copy
rename=Rename
addChildNotebook=Add child notebook
deleteAllShared=Delete shared user
deleteSharedNotebook=Delete shared notebook
copyToMyNotebook=Copy to my notebook
####note-dev
emailInSending=In sending to
checkEmail=Check email
setUsername=Set username
setUsernameTips=Your current email is: <code>%s</code>. You can set a unique username. <br />Username' length is at least 4 and cannot contains special characters.
currentEmail=Your current email is: <code>%s</code>
updateEmail=Update email
updateEmailTips=You must verify the email after you update the email. The verified email will be your new account.
sendVerifiedEmail=Send verification email
verified=Verified
unVerified=Unverfied
verifiedNow=Verify now
resendVerifiedEmail=Resend verification email
# 分享
defaulthhare=Default
addShare=Add Friend
friendEmail=Friend email
permission=Permission
readOnly=Read only
writable=Writable
inputFriendEmail=Friend email is required
clickToChangePermission=Click to change permission
sendInviteEmailToYourFriend=Send invite email to your friend
copySuccess=Copy success
copyFailed=Copy failed
friendNotExits=Your friend hasn't %s's account, invite register link: %s
emailBodyRequired=Email body is required
clickToCopy=Click to copy
sendSuccess=success
inviteEmailBody=Hi,I am %s, %s is awesome, come on!
# 历史记录
historiesNum=We have saved at most <b>10</b> latest histories with each note
noHistories=No histories
fold=Fold
unfold=Unfold
datetime=Datetime
restoreFromThisVersion=Restore from this version
confirmBackup=Are you sure to restore from this version? We will backup the current note.
createAccount=Create account
createAccountSuccess=Account create success
createAccountFailed=Account create failed
thirdCreateAcountTips=You are using the 3th account to login %(app)s, you can create a %(app)s account too. <br />After you create %(app)s account, you can use the account and the 3th account to login %(app)s.
## valid msg
inputUsername=input username
updateUsernameSuccess=Update username success
usernameIsExisted=Username is already exists
noSpecialChars=username cannot contains special chars
minLength=The length is at least %s
errorEmail=Please input the right email
verifiedEmaiHasSent=The verification email has been sent, please check your email.
emailSendFailed=Email send failed
inputPassword=Password is required
inputNewPassword=The new password is required
inputPassword2=Please input the new password again
errorPassword=The passowd's length is at least 6 and be sure as complex as possible
confirmPassword=Password not matched
updatePasswordSuccess=Update password success
errorDomain=The custom domain is invalid, eg. www.myblog.com
domainExisted=Custom domain is already existed
errorSubDomain=Please input the valid sub domain, the length is at least 4 and no special chars
subDomainExisted=Sub domain is already existed
# lea++
leaDesc=leanote blog platform
recommend=Recommend
latest=Latest
# error
notFound=This page cann't found.

View File

@@ -1,4 +1,5 @@
# leanote
app=leanote
moto=不只是笔记!
moto2=知识管理, 博客, 分享, 协作... 尽在leanote
moto3=简约而不简单
@@ -27,6 +28,30 @@ blogInfo=将笔记公开, 让知识传播的更远!
suggestionsInfo=帮助我们完善leanote
yourContact=您的联系方式
emailOrOthers=Email或其它联系方式
captcha=验证码
reloadCaptcha=刷新验证码
captchaError=验证码错误
inputCaptcha=请输入验证码
hi=Hi
welcomeUseLeanote=Welcome!
myNote=My note
curUser=Email
# form
submit=submit
register=Sign up
login=Sign in
password2=Confirm your password
email=Email
inputUsername=Username(email) is required
inputEmail=Email is required
wrongEmail=Wrong email
wrongUsernameOrPassword=Wrong username or password
inputPassword=Password is required
wrongPassword=Wrong password
logining=Sign in
loginSuccess=login success
hi=Hi
welcomeUseLeanote=欢迎使用leanote
@@ -53,7 +78,7 @@ hadAcount = 已有帐户?
hasAcount = 还无帐户?
# 注册
registerSuccessAndRdirectToNote=注册成功, 正在转至我的笔记...
registerSuccessAndRdirectToNote=注册成功, 正在转...
# 找回密码
passwordTips=密码至少6位
@@ -76,11 +101,17 @@ save=保存
editorTips=帮助
editorTipsInfo=<h4>1. 快捷键</h4>ctrl+shift+c 代码块切换 <br /> ctrl+shift+i 插入/修改图片<h4>2. shift+enter 跳出当前区域</h4>比如在代码块中<img src="/images/outofcode.png" style="width: 90px"/>按shift+enter可跳出当前代码块.
newNote=新建笔记
newMarkdownNote=新建Markdown笔记
noNoteNewNoteTips=该笔记本下空空如也...何不
canntNewNoteTips=Sorry, 这里不能添加笔记的. 你需要先选择一个笔记本.
new=新建
newMarkdown=新建Markdown笔记
clickAddTag=点击添加标签
notebook=笔记本
myNotebook=我的笔记本
addNotebook=添加笔记本
search=搜索
clearSearch=清除搜索
all=最新
trash=废纸篓
delete=删除
@@ -114,12 +145,14 @@ green=绿色
# 设置
accountSetting=帐户设置
themeSetting=主题设置
setAvatar=头像设置
logout=退出
basicInfo=基本信息
updateEmail=修改Email
usernameSetting=用户名设置
oldPassword=旧密码
newPassword=新密码
admin=后台管理
default=默认
simple=简约
@@ -143,6 +176,89 @@ howToInstallLeanote=leanote安装步骤
attachments = 附件
donate = 捐赠
# contextmenu
shareToFriends=分享给好友
publicAsBlog=公开为博客
cancelPublic=取消公开为博客
move=移动
copy=复制
rename=重命名
addChildNotebook=添加子笔记本
deleteAllShared=删除所有共享
deleteSharedNotebook=删除共享笔记本
copyToMyNotebook=复制到我的笔记本
####note-dev
emailInSending=正在发送邮件到
checkEmail=查看邮件
setUsername=用户名设置
setUsernameTips=你的邮箱是 <code>%s</code>, 可以再设置一个唯一的用户名.<br />用户名至少4位, 不可含特殊字符.
currentEmail=当前邮箱为: <code>%s</code>
updateEmail=修改邮箱
updateEmailTips=邮箱修改后, 验证之后才有效, 验证之后新的邮箱地址将会作为登录帐号使用.
sendVerifiedEmail=发送验证邮箱
sendSuccess=发送成功
sendFailed=发送失败
verified=已验证
unVerified=未验证
verifiedNow=现在去验证
resendVerifiedEmail=重新发送验证邮件
# 分享
defaulthhare=默认共享
addShare=添加分享
friendEmail=好友邮箱
permission=权限
readOnly=只读
writable=可写
inputFriendEmail=请输入好友邮箱
clickToChangePermission=点击改变权限
sendInviteEmailToYourFriend=发送邀请email给Ta
copySuccess=复制成功
copyFailed=对不起, 复制失败, 请自行复制
friendNotExits=该用户还没有注册%s, 复制邀请链接发送给Ta, 邀请链接: %s
emailBodyRequired=邮件内容不能为空
clickToCopy=点击复制
sendSuccess=发送成功
inviteEmailBody=Hi, 你好, 我是%s, %s非常好用, 快来注册吧!
# 历史记录
historiesNum=leanote会保存笔记的最近<b>10</b>份历史记录
noHistories=无历史记录
fold=折叠
unfold=展开
datetime=日期
restoreFromThisVersion=从该版本还原
confirmBackup=确定要从该版还原? 还原前leanote会备份当前版本到历史记录中.
createAccount=创建帐号
createAccountSuccess=帐号创建成功
createAccountFailed=帐号创建失败
thirdCreateAcountTips=您现在使用的是第三方帐号登录%(app)s, 您也可以注册%(app)s帐号登录, 赶紧注册一个吧. <br />注册成功后仍可以使用第三方帐号登录leanote并管理您现有的笔记.
## valid msg
cannotUpdateDemo=抱歉, Demo用户不允许修改
inputUsername=请输入用户名
updateUsernameSuccess=用户名修改成功
usernameIsExisted=用户名已存在
noSpecialChars=不能包含特殊字符
minLength=长度至少为%s
errorEmail=请输入正确的email
verifiedEmaiHasSent=验证邮件已发送, 请及时查阅邮件并验证.
emailSendFailed=邮件发送失败
inputPassword=请输入密码
inputNewPassword=请输入新密码
inputPassword2=请输入确认密码
errorPassword=请输入长度不少于6位的密码, 尽量复杂
confirmPassword=两次密码输入不正确
updatePasswordSuccess=修改密码成功
errorDomain=请输入正确的域名, 如www.myblog.com
domainExisted=域名已存在
errorSubDomain=请输入正确的博客子域名, 长度至少为4, 不能包含特殊字符
subDomainExisted=博客子域名已存在
# lea++
leaDesc=leanote博客平台
recommend=推荐
latest=最新
# 必须要加这个, 奇怪
[CN]

2004
public/admin/config.codekit Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -897,7 +897,6 @@ html {
body {
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
background-color: transparent;
-webkit-font-smoothing: antialiased;
}
.h1,
.h2,

View File

@@ -1080,7 +1080,7 @@ html {
body {
font-family: "Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;
background-color: transparent;
-webkit-font-smoothing: antialiased;
// -webkit-font-smoothing: antialiased;
}
.h1,.h2,.h3,.h4,.h5,.h6 {

View File

@@ -25,7 +25,7 @@ function openDialog(config) {
var d = art.dialog(config);
if(config.url) {
ajaxGetHtml(config.url, {}, function(ret) {
$.get(config.url, {}, function(ret) {
d.content(ret);
});
}
@@ -258,6 +258,18 @@ function enter_submit(btnId) {
}
}
// send email dialog
function openSendEmailDialog(emails) {
openDialog({width: 500, url: "/adminEmail/sendEmailDialog?emails=" + emails, title: "Send Email"});
}
function goNowToDatetime(goNow) {
if(!goNow) {
return "";
}
return goNow.substr(0, 10) + " " + goNow.substr(11, 8);
}
!function ($) {
$(function(){

1
public/admin/js/min/admin-min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,258 @@
html, * {
// -webkit-font-smoothing: antialiased;
}
#posts img {
max-width: 100%;
}
#content {
* {
font-size: 16px;
}
h1 {
font-size: 30px;
}
h2 {
font-size: 24px;
}
h3 {
font-size: 18px;
}
h4 {
font-size: 14px;
}
}
// animation
@-webkit-keyframes dropdown {
0% {
margin-top: -25px;
opacity: 0
}
90% {
margin-top: 2px
}
100% {
margin-top: 0;
opacity: 1
}
}
@-moz-keyframes dropdown {
0% {
margin-top: -25px;
opacity: 0
}
90% {
margin-top: 2px
}
100% {
margin-top: 0;
opacity: 1
}
}
@-ms-keyframes dropdown {
0% {
margin-top: -25px;
opacity: 0
}
90% {
margin-top: 2px
}
100% {
margin-top: 0;
opacity: 1
}
}
@keyframes dropdown {
0% {
margin-top: -25px;
opacity: 0
}
90% {
margin-top: 2px
}
100% {
margin-top: 0;
opacity: 1
}
}
@-webkit-keyframes pulldown {
0% {
top: 0;
opacity: 0
}
90% {
top: 90%;
}
100% {
top: 100%;
opacity: 1
}
}
@-moz-keyframes pulldown {
0% {
top: 0;
opacity: 0
}
90% {
top: 90%;
}
100% {
top: 100%;
opacity: 1
}
}
@-ms-keyframes pulldown {
0% {
top: 0;
opacity: 0
}
90% {
top: 90%;
}
100% {
top: 100%;
opacity: 1
}
}
@keyframes pulldown {
0% {
top: 0;
opacity: 0
}
90% {
top: 90%;
}
100% {
top: 100%;
opacity: 1
}
}
a, .btn {
-webkit-transition: all 0.2s ease;
-moz-transition: all 0.2s ease;
transition: all 0.2s ease;
}
.btn:focus {
outline: none;
}
ul.dropdown-menu {
box-shadow: rgba(0, 0, 0, 0.172549) 0px 6px 12px 0px;
&:before {
content: "";
width: 20px;
height: 12px;
position: absolute;
top: -12px;
right: 20px;
background-image: url("../../images/triangle_2x.png");
background-size: 20px 12px;
}
}
ul.dropdown-menu {
display: block;
visibility: hidden;
opacity: 0;
}
.open ul.dropdown-menu {
-webkit-animation: pulldown .2s;
animation: pulldown .2s;
visibility: visible;
opacity: 1;
}
.created-time .fa {
color: #666;
}
#blogNav {
display: none;
background-color: #fff;
opacity: 0.7;
position: fixed;
z-index: 10;
padding: 3px;
border-radius: 3px;
}
#blogNavContent {
overflow-y: auto;
max-height: 250px;
display: none;
-webkit-overflow-scrolling: touch !important; // for iphone
}
#blogNavNav {
cursor: pointer;
}
#blogNav a {
color: #666;
}
#blogNav:hover {
opacity: 0.9;
}
#blogNav a:hover {
color: #0fb264;
}
#blogNav ul {
padding-left: 20px;
}
#blogNav ul .nav-h1 {
}
#blogNav ul .nav-h2 {
margin-left: 20px;
}
#blogNav ul .nav-h3 {
margin-left: 30px;
}
#blogNav ul .nav-h4 {
margin-left: 40px;
}
#blogNav ul .nav-h5 {
margin-left: 50px;
}
.mobile-created-time {
display: none;
}
#footer {
padding-bottom: 10px;
}
.navbar-brand {
display: none;
}
// 主题列表
#themeList {
label {
text-align: center;
margin-bottom: 5px;
}
.preview {
display: block;
width: 400px;
background: #fff;
border: 1px solid #ccc;
padding: 5px;
border-radius: 5px;
}
}

Some files were not shown because too many files have changed in this diff Show More