Merge branch 'feature-blog-theme'

This commit is contained in:
life committed 2014-11-12 17:38:55 +08:00
commit 4d7a9c8089
518 files changed
+25660 -1010

No files matched your search

+7 -2
View File
@@ -11,6 +11,7 @@ import (
"strings"
"time"
"io"
"fmt"
"archive/tar"
"compress/gzip"
)
@@ -56,8 +57,12 @@ func (c Attach) uploadAttach(noteId string) (re info.Re) {
return re
}
// > 5M?
if(len(data) > 5 * 1024 * 1024) {
resultMsg = "File is bigger than 5M"
maxFileSize := configService.GetUploadSize("uploadAttachSize");
if maxFileSize <= 0 {
maxFileSize = 1000
}
if(float64(len(data)) > maxFileSize * float64(1024*1024)) {
resultMsg = fmt.Sprintf("附件大于%vM", maxFileSize)
return re
}
+5 -2
View File
@@ -183,7 +183,7 @@ func (c BaseController) SetLocale() string {
lang = "en";
}
c.RenderArgs["locale"] = lang;
c.RenderArgs["siteUrl"] = siteUrl;
c.RenderArgs["siteUrl"] = configService.GetSiteUrl();
c.RenderArgs["blogUrl"] = configService.GetBlogUrl()
c.RenderArgs["leaUrl"] = configService.GetLeaUrl()
@@ -196,6 +196,9 @@ func (c BaseController) SetLocale() string {
func (c BaseController) SetUserInfo() {
userInfo := c.GetUserInfo()
c.RenderArgs["userInfo"] = userInfo
if(userInfo.Username == configService.GetAdminUsername()) {
c.RenderArgs["isAdmin"] = true
}
}
// life
@@ -239,4 +242,4 @@ func (c BaseController) RenderRe(re info.Re) revel.Result {
}
}
return c.RenderJson(re)
}
}
File diff suppressed because it is too large. Load diff
+20 -7
View File
@@ -9,6 +9,7 @@ import (
"github.com/leanote/leanote/app/info"
"io/ioutil"
"os"
"fmt"
"strconv"
"strings"
)
@@ -21,7 +22,7 @@ type File struct {
// 上传的是博客logo
// TODO logo不要设置权限, 另外的目录
func (c File) UploadBlogLogo() revel.Result {
re := c.uploadImage("logo", "");
re := c.uploadImage("blogLogo", "");
c.RenderArgs["fileUrlPath"] = re.Id
c.RenderArgs["resultCode"] = re.Code
@@ -100,7 +101,7 @@ func (c File) uploadImage(from, albumId string) (re info.Re) {
}
defer file.Close()
// 生成上传路径
if(from == "logo") {
if(from == "logo" || from == "blogLogo") {
fileUrlPath = "public/upload/" + c.GetUserId() + "/images/logo"
} else {
fileUrlPath = "files/" + c.GetUserId() + "/images"
@@ -131,10 +132,22 @@ func (c File) uploadImage(from, albumId string) (re info.Re) {
return re
}
var maxFileSize float64
if(from == "logo") {
maxFileSize = configService.GetUploadSize("uploadAvatarSize");
} else if from == "blogLogo" {
maxFileSize = configService.GetUploadSize("uploadBlogLogoSize");
} else {
maxFileSize = configService.GetUploadSize("uploadImageSize");
}
if maxFileSize <= 0 {
maxFileSize = 1000
}
// > 2M?
if(len(data) > 5 * 1024 * 1024) {
if(float64(len(data)) > maxFileSize * float64(1024*1024)) {
resultCode = 0
resultMsg = "图片大于2M"
resultMsg = fmt.Sprintf("图片大于%vM", maxFileSize)
return re
}
@@ -161,7 +174,7 @@ func (c File) uploadImage(from, albumId string) (re info.Re) {
id := bson.NewObjectId();
fileInfo.FileId = id
fileId = id.Hex()
if(from == "logo") {
if(from == "logo" || from == "blogLogo") {
fileId = "public/upload/" + c.GetUserId() + "/images/logo/" + filename
}
@@ -303,7 +316,7 @@ func (c File) UploadImage(renderHtml string) revel.Result {
re := c.uploadImage("", "");
c.RenderArgs["fileUrlPath"] = siteUrl + re.Id
c.RenderArgs["fileUrlPath"] = configService.GetSiteUrl() + re.Id
c.RenderArgs["resultCode"] = re.Code
c.RenderArgs["resultMsg"] = re.Msg
@@ -314,4 +327,4 @@ func (c File) UploadImage(renderHtml string) revel.Result {
func (c File) UploadImageJson(from, noteId string) revel.Result {
re := c.uploadImage(from, "");
return c.RenderJson(re)
}
}
+4
View File
@@ -14,6 +14,10 @@ type Index struct {
// leanote展示页, 没有登录的, 或已登录明确要进该页的
func (c Index) Index() revel.Result {
if configService.HomePageIsAdminsBlog(){
blog := Blog{c.BaseController}
return blog.Index(configService.GetAdminUsername());
}
c.SetUserInfo()
c.RenderArgs["title"] = "leanote"
c.RenderArgs["openRegister"] = configService.GlobalStringConfigs["openRegister"]
+15 -5
View File
@@ -7,6 +7,7 @@ import (
. "github.com/leanote/leanote/app/lea"
"github.com/leanote/leanote/app/info"
"os/exec"
// "time"
// "github.com/leanote/leanote/app/types"
// "io/ioutil"
// "fmt"
@@ -52,8 +53,9 @@ func (c Note) Index() revel.Result {
}
// 当然, 还需要得到第一个notes的content
//...
Log(configService.GetAdminUsername())
c.RenderArgs["isAdmin"] = configService.GetAdminUsername() == userInfo.Username
c.RenderArgs["isAdmin"] = leanoteUserId == userInfo.Username
c.RenderArgs["userInfo"] = userInfo
c.RenderArgs["userInfoJson"] = c.Json(userInfo)
c.RenderArgs["notebooks"] = c.Json(notebooks)
@@ -66,6 +68,8 @@ func (c Note) Index() revel.Result {
c.RenderArgs["tagsJson"] = c.Json(tagService.GetTags(c.GetUserId()))
c.RenderArgs["globalConfigs"] = configService.GetGlobalConfigForUser()
if isDev, _ := revel.Config.Bool("mode.dev"); isDev {
return c.RenderTemplate("note/note-dev.html")
} else {
@@ -121,7 +125,7 @@ func (c Note) UpdateNoteOrContent(noteOrContent NoteOrContent) revel.Result {
// 新添加note
if noteOrContent.IsNew {
userId := c.GetObjectUserId();
myUserId := userId
// myUserId := userId
// 为共享新建?
if noteOrContent.FromUserId != "" {
userId = bson.ObjectIdHex(noteOrContent.FromUserId)
@@ -143,7 +147,7 @@ func (c Note) UpdateNoteOrContent(noteOrContent NoteOrContent) revel.Result {
Content: noteOrContent.Content,
Abstract: noteOrContent.Abstract};
note = noteService.AddNoteAndContent(note, noteContent, myUserId)
note = noteService.AddNoteAndContentForController(note, noteContent, c.GetUserId())
return c.RenderJson(note)
}
@@ -296,7 +300,7 @@ func (c Note) Html2Image(noteId string) revel.Result {
appKey, _ := revel.Config.String("app.secret")
cookieDomain, _ := revel.Config.String("cookie.domain")
// 生成之
url := siteUrl + "/note/toImage?noteId=" + noteId + "&appKey=" + appKey;
url := configService.GetSiteUrl() + "/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 == "" {
@@ -350,4 +354,10 @@ func (c Note) Html2Image(noteId string) revel.Result {
}
*/
}
}
// 设置/取消Blog; 置顶
func (c Note) SetNote2Blog(noteId string, isBlog, isTop bool) revel.Result {
re := noteService.ToBlog(c.GetUserId(), noteId, isBlog, isTop)
return c.RenderJson(re)
}
+6
View File
@@ -68,4 +68,10 @@ func (c Notebook) DragNotebooks(data string) revel.Result {
json.Unmarshal([]byte(data), &info)
return c.RenderJson(notebookService.DragNotebooks(c.GetUserId(), info.CurNotebookId, info.ParentNotebookId, info.Siblings))
}
// 设置notebook <-> blog
func (c Notebook) SetNotebook2Blog(notebookId string, isBlog bool) revel.Result {
re := notebookService.ToBlog(c.GetUserId(), notebookId, isBlog)
return c.RenderJson(re)
}
+101
View File
@@ -0,0 +1,101 @@
package controllers
import (
"github.com/revel/revel"
// "strings"
// "time"
// "encoding/json"
// "github.com/leanote/leanote/app/info"
// . "github.com/leanote/leanote/app/lea"
// "github.com/leanote/leanote/app/lea/blog"
// "gopkg.in/mgo.v2/bson"
// "github.com/leanote/leanote/app/types"
// "io/ioutil"
// "math"
// "os"
// "path"
)
type Preview struct {
Blog
}
// 得到要预览的主题绝对路径
func (c Preview) getPreviewThemeAbsolutePath(themeId string) bool {
if themeId != "" {
c.Session["themeId"] = themeId // 存到session中, 下次的url就不能带了
} else {
themeId = c.Session["themeId"] // 直接从session中获取
}
if themeId == "" {
return false
}
theme := themeService.GetTheme(c.GetUserId(), themeId)
c.RenderArgs["isPreview"] = true
c.RenderArgs["themePath"] = theme.Path
if theme.Path == "" {
return false
}
return true
}
func (c Preview) Index(userIdOrEmail string, themeId string) revel.Result {
if !c.getPreviewThemeAbsolutePath(themeId) {
return c.E404()
}
return c.Blog.Index(c.GetUserId())
// return blog.RenderTemplate("index.html", c.RenderArgs, c.getPreviewThemeAbsolutePath(themeId))
}
func (c Preview) Tag(userIdOrEmail, tag string) revel.Result {
if !c.getPreviewThemeAbsolutePath("") {
return c.E404()
}
return c.Blog.Tag(c.GetUserId(), tag)
}
func (c Preview) Tags(userIdOrEmail string) revel.Result {
if !c.getPreviewThemeAbsolutePath("") {
return c.E404()
}
return c.Blog.Tags(c.GetUserId())
// if tag == "" {
// return blog.RenderTemplate("tags.html", c.RenderArgs, c.getPreviewThemeAbsolutePath(""))
// }
// return blog.RenderTemplate("tag_posts.html", c.RenderArgs, c.getPreviewThemeAbsolutePath(""))
}
func (c Preview) Archives(userIdOrEmail string, notebookId string, year, month int) revel.Result {
if !c.getPreviewThemeAbsolutePath("") {
return c.E404()
}
return c.Blog.Archives(c.GetUserId(), notebookId, year, month)
// return blog.RenderTemplate("archive.html", c.RenderArgs, c.getPreviewThemeAbsolutePath(""))
}
func (c Preview) Cate(userIdOrEmail, notebookId string) revel.Result {
if !c.getPreviewThemeAbsolutePath("") {
return c.E404()
}
return c.Blog.Cate(userIdOrEmail, notebookId)
// return blog.RenderTemplate("cate.html", c.RenderArgs, c.getPreviewThemeAbsolutePath(""))
}
func (c Preview) Post(userIdOrEmail, noteId string) revel.Result {
if !c.getPreviewThemeAbsolutePath("") {
return c.E404()
}
return c.Blog.Post(userIdOrEmail, noteId)
// return blog.RenderTemplate("view.html", c.RenderArgs, c.getPreviewThemeAbsolutePath(""))
}
func (c Preview) Single(userIdOrEmail, singleId string) revel.Result {
if !c.getPreviewThemeAbsolutePath("") {
return c.E404()
}
return c.Blog.Single(userIdOrEmail, singleId)
// return blog.RenderTemplate("single.html", c.RenderArgs, c.getPreviewThemeAbsolutePath(""))
}
func (c Preview) Search(userIdOrEmail, keywords string) revel.Result {
if !c.getPreviewThemeAbsolutePath("") {
return c.E404()
}
return c.Blog.Search(c.GetUserId(), keywords)
// return blog.RenderTemplate("search.html", c.RenderArgs, c.getPreviewThemeAbsolutePath(""))
}
+47 -1
View File
@@ -4,7 +4,7 @@ import (
"github.com/revel/revel"
// "encoding/json"
// "gopkg.in/mgo.v2/bson"
// . "github.com/leanote/leanote/app/lea"
. "github.com/leanote/leanote/app/lea"
"github.com/leanote/leanote/app/info"
// "github.com/leanote/leanote/app/types"
// "io/ioutil"
@@ -87,6 +87,8 @@ func (c Share) ListNoteShareUserInfo(noteId string) revel.Result {
noteShareUserInfos := shareService.ListNoteShareUserInfo(noteId, c.GetUserId())
c.RenderArgs["noteOrNotebookShareUserInfos"] = noteShareUserInfos
c.RenderArgs["noteOrNotebookShareGroupInfos"] = shareService.GetNoteShareGroups(noteId, c.GetUserId())
c.RenderArgs["isNote"] = true
c.RenderArgs["noteOrNotebookId"] = note.NoteId.Hex();
c.RenderArgs["title"] = note.Title
@@ -99,6 +101,9 @@ func (c Share) ListNotebookShareUserInfo(notebookId string) revel.Result {
notebookShareUserInfos := shareService.ListNotebookShareUserInfo(notebookId, c.GetUserId())
c.RenderArgs["noteOrNotebookShareUserInfos"] = notebookShareUserInfos
c.RenderArgs["noteOrNotebookShareGroupInfos"] = shareService.GetNotebookShareGroups(notebookId, c.GetUserId())
LogJ(c.RenderArgs["noteOrNotebookShareGroupInfos"])
c.RenderArgs["isNote"] = false
c.RenderArgs["noteOrNotebookId"] = notebook.NotebookId.Hex();
c.RenderArgs["title"] = notebook.Title
@@ -139,4 +144,45 @@ func (c Share) DeleteShareNotebookBySharedUser(notebookId string, fromUserId str
// 删除fromUserId分享给我的所有note, notebook
func (c Share) DeleteUserShareNoteAndNotebook(fromUserId string) revel.Result {
return c.RenderJson(shareService.DeleteUserShareNoteAndNotebook(fromUserId, c.GetUserId()));
}
//-------------
// 用户组
// 将笔记分享给分组
func (c Share) AddShareNoteGroup(noteId, groupId string, perm int) revel.Result {
re := info.NewRe()
re.Ok = shareService.AddShareNoteGroup(c.GetUserId(), noteId, groupId, perm);
return c.RenderJson(re);
}
// 删除
func (c Share) DeleteShareNoteGroup(noteId, groupId string) revel.Result {
re := info.NewRe()
re.Ok = shareService.DeleteShareNoteGroup(c.GetUserId(), noteId, groupId);
return c.RenderJson(re);
}
// 更新, 也是一样, 先删后加
func (c Share) UpdateShareNoteGroupPerm(noteId, groupId string, perm int) revel.Result {
return c.AddShareNoteGroup(noteId, groupId, perm)
}
//------
// 将笔记分享给分组
func (c Share) AddShareNotebookGroup(notebookId, groupId string, perm int) revel.Result {
re := info.NewRe()
re.Ok = shareService.AddShareNotebookGroup(c.GetUserId(), notebookId, groupId, perm);
return c.RenderJson(re);
}
// 删除
func (c Share) DeleteShareNotebookGroup(notebookId, groupId string) revel.Result {
re := info.NewRe()
re.Ok = shareService.DeleteShareNotebookGroup(c.GetUserId(), notebookId, groupId);
return c.RenderJson(re);
}
// 更新, 也是一样, 先删后加
func (c Share) UpdateShareNotebookGroupPerm(notebookId, groupId string, perm int) revel.Result {
return c.AddShareNotebookGroup(notebookId, groupId, perm)
}
+2 -2
View File
@@ -16,7 +16,7 @@ type AdminBlog struct {
func (c AdminBlog) Index(sorter, keywords string) revel.Result {
pageNumber := c.GetPage()
sorterField, isAsc := c.getSorter("CreatedTime", false, []string{"title", "userId", "isRecommed", "createdTime"});
pageInfo, blogs := blogService.ListAllBlogs("", keywords, false, pageNumber, userPageSize, sorterField, isAsc);
pageInfo, blogs := blogService.ListAllBlogs("", "", keywords, false, pageNumber, userPageSize, sorterField, isAsc);
c.RenderArgs["pageInfo"] = pageInfo
c.RenderArgs["blogs"] = blogs
c.RenderArgs["keywords"] = keywords
@@ -27,4 +27,4 @@ func (c AdminBlog) SetRecommend(noteId string, recommend bool) revel.Result {
re := info.NewRe()
re.Ok = blogService.SetRecommend(noteId, recommend);
return c.RenderJson(re)
}
}
+3 -2
View File
@@ -18,8 +18,8 @@ func (c Admin) Index() revel.Result {
c.SetLocale()
c.RenderArgs["countUser"] = userService.CountUser()
c.RenderArgs["countNote"] = noteService.CountNote()
c.RenderArgs["countBlog"] = noteService.CountBlog()
c.RenderArgs["countNote"] = noteService.CountNote("")
c.RenderArgs["countBlog"] = noteService.CountBlog("")
return c.RenderTemplate("admin/index.html");
}
@@ -30,6 +30,7 @@ func (c Admin) T(t string) revel.Result {
c.RenderArgs["arr"] = configService.GlobalArrayConfigs
c.RenderArgs["map"] = configService.GlobalMapConfigs
c.RenderArgs["arrMap"] = configService.GlobalArrMapConfigs
c.RenderArgs["version"] = configService.GetVersion()
return c.RenderTemplate("admin/" + t + ".html")
}
@@ -5,6 +5,7 @@ import (
// . "github.com/leanote/leanote/app/lea"
"github.com/leanote/leanote/app/info"
"strings"
"fmt"
)
// admin 首页
@@ -110,10 +111,28 @@ func (c AdminSetting) OpenRegister(openRegister string) revel.Result {
return c.RenderJson(re)
}
func (c AdminSetting) HomePage(homePage string) revel.Result {
re := info.NewRe()
if homePage == "0" {
homePage = ""
}
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "homePage", homePage)
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)
}
func (c AdminSetting) UploadSize(uploadImageSize, uploadAvatarSize, uploadBlogLogoSize, uploadAttachSize float64) revel.Result {
re := info.NewRe()
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "uploadImageSize", fmt.Sprintf("%v", uploadImageSize))
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "uploadAvatarSize", fmt.Sprintf("%v", uploadAvatarSize))
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "uploadBlogLogoSize", fmt.Sprintf("%v", uploadBlogLogoSize))
re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "uploadAttachSize", fmt.Sprintf("%v", uploadAttachSize))
return c.RenderJson(re)
}
@@ -3,7 +3,7 @@ package admin
import (
"github.com/revel/revel"
// "encoding/json"
// . "github.com/leanote/leanote/app/lea"
"github.com/leanote/leanote/app/info"
// "io/ioutil"
)
@@ -15,4 +15,10 @@ type AdminUpgrade struct {
func (c AdminUpgrade) UpgradeBlog() revel.Result {
upgradeService.UpgradeBlog()
return nil;
}
func (c AdminUpgrade) UpgradeBetaToBeta2() revel.Result {
re := info.NewRe()
re.Ok, re.Msg = upgradeService.UpgradeBetaToBeta2(c.GetUserId())
return c.RenderJson(re)
}
+42 -4
View File
@@ -2,7 +2,9 @@ package admin
import (
"github.com/revel/revel"
// . "github.com/leanote/leanote/app/lea"
. "github.com/leanote/leanote/app/lea"
// "time"
"github.com/leanote/leanote/app/info"
)
// admin 首页
@@ -13,10 +15,13 @@ type AdminUser struct {
// admin 主页
var userPageSize = 10
func (c AdminUser) Index(sorter, keywords string) revel.Result {
func (c AdminUser) Index(sorter, keywords string, pageSize int) revel.Result {
pageNumber := c.GetPage()
sorterField, isAsc := c.getSorter("CreatedTime", false, []string{"email", "username", "verified", "createdTime"});
pageInfo, users := userService.ListUsers(pageNumber, userPageSize, sorterField, isAsc, keywords);
if userPageSize == 0 {
pageSize = userPageSize
}
sorterField, isAsc := c.getSorter("CreatedTime", false, []string{"email", "username", "verified", "createdTime", "accountType"});
pageInfo, users := userService.ListUsers(pageNumber, pageSize, sorterField, isAsc, keywords);
c.RenderArgs["pageInfo"] = pageInfo
c.RenderArgs["users"] = users
c.RenderArgs["keywords"] = keywords
@@ -26,3 +31,36 @@ func (c AdminUser) Index(sorter, keywords string) revel.Result {
func (c AdminUser) Add() revel.Result {
return c.RenderTemplate("admin/user/add.html");
}
// 添加
func (c AdminUser) Register(email, pwd string) revel.Result {
re := info.NewRe();
if re.Ok, re.Msg = Vd("email", email); !re.Ok {
return c.RenderRe(re);
}
if re.Ok, re.Msg = Vd("password", pwd); !re.Ok {
return c.RenderRe(re);
}
// 注册
re.Ok, re.Msg = authService.Register(email, pwd)
return c.RenderRe(re)
}
// 修改帐户
func (c AdminUser) ResetPwd(userId string) revel.Result {
userInfo := userService.GetUserInfo(userId)
c.RenderArgs["userInfo"] = userInfo
return c.RenderTemplate("admin/user/reset_pwd.html");
}
func (c AdminUser) DoResetPwd(userId, pwd string) revel.Result {
re := info.NewRe();
if re.Ok, re.Msg = Vd("password", pwd); !re.Ok {
return c.RenderRe(re);
}
re.Ok, re.Msg = userService.ResetPwd(c.GetUserId(), userId, pwd)
return c.RenderRe(re)
}
+2 -3
View File
@@ -28,7 +28,6 @@ var configService *service.ConfigService
var emailService *service.EmailService
var upgradeService *service.UpgradeService
var adminUsername = "admin"
// 拦截器
// 不需要拦截的url
// Index 除了Note之外都不需要
@@ -83,7 +82,7 @@ func AuthInterceptor(c *revel.Controller) revel.Result {
// 验证是否已登录
// 必须是管理员
if username, ok := c.Session["Username"]; ok && username == adminUsername {
if username, ok := c.Session["Username"]; ok && username == configService.GetAdminUsername() {
return nil // 已登录
}
@@ -129,4 +128,4 @@ func init() {
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &AdminEmail{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &AdminUpgrade{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &AdminData{})
}
}
+50 -38
View File
@@ -3,6 +3,7 @@ package controllers
import (
"github.com/leanote/leanote/app/service"
"github.com/leanote/leanote/app/info"
"github.com/leanote/leanote/app/lea/blog"
// . "github.com/leanote/leanote/app/lea"
"github.com/revel/revel"
"strings"
@@ -19,53 +20,57 @@ var blogService *service.BlogService
var tagService *service.TagService
var pwdService *service.PwdService
var tokenService *service.TokenService
var suggestionService *service.SuggestionService
var albumService *service.AlbumService
var noteImageService *service.NoteImageService
var suggestionService *service.SuggestionService
var albumService *service.AlbumService
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 themeService *service.ThemeService
var pageSize = 1000
var defaultSortField = "UpdatedTime"
var leanoteUserId = "admin" // 不能更改
var siteUrl = "http://leanote.com"
// 拦截器
// 不需要拦截的url
// Index 除了Note之外都不需要
var commonUrl = map[string]map[string]bool{"Index": map[string]bool{"Index": true,
"Login": true,
"DoLogin": true,
"Logout": true,
"Register": true,
"DoRegister": true,
"FindPasswword": true,
"DoFindPassword": true,
"FindPassword2": true,
"FindPasswordUpdate": true,
"Suggestion": true,
},
var commonUrl = map[string]map[string]bool{"Index": map[string]bool{"Index": true,
"Login": true,
"DoLogin": true,
"Logout": true,
"Register": true,
"DoRegister": true,
"FindPasswword": true,
"DoFindPassword": true,
"FindPassword2": true,
"FindPasswordUpdate": true,
"Suggestion": true,
},
"Note": map[string]bool{"ToImage": true},
"Blog": map[string]bool{"Index": true,
"View": true,
"AboutMe": true,
"Cate": true,
"Search": true,
"View": true,
"AboutMe": true,
"Cate": true,
"ListCateLatest": true,
"Search": true,
"GetLikeAndComments": true,
"IncReadNum": true,
"ListComments": true,
},
"IncReadNum": true,
"ListComments": true,
"Single": true,
"Archive": true,
"Tags": true,
},
// 用户的激活与修改邮箱都不需要登录, 通过链接地址
"User": map[string]bool{"UpdateEmail": true,
"ActiveEmail":true,
},
"Oauth": map[string]bool{"GithubCallback": true},
"File": map[string]bool{"OutputImage": true, "OutputFile": true},
"ActiveEmail": true,
},
"Oauth": map[string]bool{"GithubCallback": true},
"File": map[string]bool{"OutputImage": true, "OutputFile": true},
"Attach": map[string]bool{"Download": true, "DownloadAll": true},
}
func needValidate(controller, method string) bool {
// 在里面
if v, ok := commonUrl[controller]; ok {
@@ -76,31 +81,31 @@ func needValidate(controller, method string) bool {
return true
} else {
// controller不在这里的, 肯定要验证
return true;
return true
}
}
func AuthInterceptor(c *revel.Controller) revel.Result {
// 全部变成首字大写
var controller = strings.Title(c.Name)
var method = strings.Title(c.MethodName)
// 是否需要验证?
if !needValidate(controller, method) {
return nil
}
// 验证是否已登录
if userId, ok := c.Session["UserId"]; ok && userId != "" {
return nil // 已登录
}
// 没有登录, 判断是否是ajax操作
if c.Request.Header.Get("X-Requested-With") == "XMLHttpRequest" {
re := info.NewRe()
re.Msg = "NOTLOGIN"
return c.RenderJson(re)
}
return c.Redirect("/login")
}
@@ -118,7 +123,7 @@ func InitService() {
tokenService = service.TokenS
noteImageService = service.NoteImageS
fileService = service.FileS
albumService= service.AlbumS
albumService = service.AlbumS
attachService = service.AttachS
pwdService = service.PwdS
suggestionService = service.SuggestionS
@@ -126,6 +131,12 @@ func InitService() {
configService = service.ConfigS
emailService = service.EmailS
sessionService = service.SessionS
themeService = service.ThemeS
}
// 初始化博客模板
// 博客模板不由revel的
func initBlogTemplate() {
}
func init() {
@@ -137,10 +148,11 @@ func init() {
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &User{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &File{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &Attach{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &Blog{})
// revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &Blog{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &NoteContentHistory{})
revel.OnAppStart(func() {
siteUrl, _ = revel.Config.String("site.url")
// 博客初始化模板
blog.Init()
})
}
}
@@ -0,0 +1,59 @@
package member
import (
// "github.com/revel/revel"
// "gopkg.in/mgo.v2/bson"
// "encoding/json"
. "github.com/leanote/leanote/app/lea"
"github.com/leanote/leanote/app/controllers"
// "io/ioutil"
// "fmt"
// "math"
// "strconv"
"strings"
)
// 公用Controller, 其它Controller继承它
type MemberBaseController struct {
controllers.BaseController // 不能用*BaseController
}
// 得到sorterField 和 isAsc
// okSorter = ['email', 'username']
func (c MemberBaseController) getSorter(sorterField string, isAsc bool, okSorter []string) (string, bool){
sorter := ""
c.Params.Bind(&sorter, "sorter")
if sorter == "" {
return sorterField, isAsc;
}
// sorter形式 email-up, email-down
s2 := strings.Split(sorter, "-")
if len(s2) != 2 {
return sorterField, isAsc;
}
// 必须是可用的sorter
if okSorter != nil && len(okSorter) > 0 {
if !InArray(okSorter, s2[0]) {
return sorterField, isAsc;
}
}
sorterField = strings.Title(s2[0])
if s2[1] == "up" {
isAsc = true
} else {
isAsc = false
}
c.RenderArgs["sorter"] = sorter
return sorterField, isAsc;
}
func (c MemberBaseController) updateConfig(keys []string) {
userId := c.GetUserId()
for _, key := range keys {
v := c.Params.Values.Get(key)
configService.UpdateGlobalStringConfig(userId, key, v)
}
}
@@ -0,0 +1,519 @@
package member
import (
"github.com/revel/revel"
. "github.com/leanote/leanote/app/lea"
"github.com/leanote/leanote/app/info"
"os"
"io/ioutil"
"time"
"fmt"
"strings"
// "github.com/leanote/leanote/app/lea/blog"
)
// 博客管理
type MemberBlog struct {
MemberBaseController
}
func (c MemberBlog) common() info.UserBlog {
userId := c.GetUserId()
userInfo := userService.GetUserInfo(userId)
c.RenderArgs["userInfo"] = userInfo
// 得到博客设置信息
c.RenderArgs["allowCustomDomain"] = configService.GetGlobalStringConfig("allowCustomDomain")
userBlog := blogService.GetUserBlog(userId)
c.RenderArgs["userBlog"] = userBlog
c.SetUserInfo()
c.SetLocale()
return userBlog
}
// 得到sorterField 和 isAsc
// okSorter = ['email', 'username']
func (c MemberBlog) getSorter(sorterField string, isAsc bool, okSorter []string) (string, bool){
sorter := ""
c.Params.Bind(&sorter, "sorter")
if sorter == "" {
return sorterField, isAsc;
}
// sorter形式 email-up, email-down
s2 := strings.Split(sorter, "-")
if len(s2) != 2 {
return sorterField, isAsc;
}
// 必须是可用的sorter
if okSorter != nil && len(okSorter) > 0 {
if !InArray(okSorter, s2[0]) {
return sorterField, isAsc;
}
}
sorterField = strings.Title(s2[0])
if s2[1] == "up" {
isAsc = true
} else {
isAsc = false
}
c.RenderArgs["sorter"] = sorter
return sorterField, isAsc;
}
// 博客列表
var userPageSize = 15
func (c MemberBlog) Index(sorter, keywords string) revel.Result {
c.RenderArgs["title"] = "Posts"
pageNumber := c.GetPage()
sorterField, isAsc := c.getSorter("CreatedTime", false, []string{"title", "urlTitle", "updatedTime", "publicTime", "createdTime"});
pageInfo, blogs := blogService.ListAllBlogs(c.GetUserId(), "", keywords, false, pageNumber, userPageSize, sorterField, isAsc);
c.RenderArgs["pageInfo"] = pageInfo
c.RenderArgs["blogs"] = blogs
c.RenderArgs["keywords"] = keywords
userAndBlog := userService.GetUserAndBlog(c.GetUserId())
c.RenderArgs["userAndBlog"] = userAndBlog
return c.RenderTemplate("member/blog/list.html");
}
// 修改笔记的urlTitle
func (c MemberBlog) UpdateBlogUrlTitle(noteId, urlTitle string) revel.Result {
re := info.NewRe()
re.Ok, re.Item = blogService.UpateBlogUrlTitle(c.GetUserId(), noteId, urlTitle)
return c.RenderJson(re)
}
// 修改笔记的urlTitle
func (c MemberBlog) UpdateBlogAbstract(noteId string) revel.Result {
c.RenderArgs["title"] = "Update Post Abstract"
note := noteService.GetNoteAndContent(noteId, c.GetUserId());
if !note.Note.IsBlog {
return c.E404();
}
c.RenderArgs["note"] = note
c.RenderArgs["noteId"] = noteId
return c.RenderTemplate("member/blog/update_abstract.html");
}
func (c MemberBlog) DoUpdateBlogAbstract(noteId, imgSrc, desc, abstract string) revel.Result {
re := info.NewRe()
re.Ok = blogService.UpateBlogAbstract(c.GetUserId(), noteId, imgSrc, desc, abstract)
return c.RenderJson(re)
}
// 基本信息设置
func (c MemberBlog) Base() revel.Result {
c.common()
c.RenderArgs["title"] = "Blog Base Info"
return c.RenderTemplate("member/blog/base.html");
}
func (c MemberBlog) Comment() revel.Result {
c.common()
c.RenderArgs["title"] = "Comment"
return c.RenderTemplate("member/blog/comment.html");
}
func (c MemberBlog) Domain() revel.Result {
c.common()
c.RenderArgs["title"] = "Domain"
return c.RenderTemplate("member/blog/domain.html");
}
func (c MemberBlog) Paging() revel.Result {
c.common()
c.RenderArgs["title"] = "Paging"
return c.RenderTemplate("member/blog/paging.html");
}
func (c MemberBlog) Cate() revel.Result {
userBlog := c.common()
c.RenderArgs["title"] = "Cate"
notebooks := blogService.ListBlogNotebooks(c.GetUserId())
notebooksMap := map[string]info.Notebook{}
for _, each := range notebooks {
notebooksMap[each.NotebookId.Hex()] = each
}
var i = 0;
notebooks2 := make([]info.Notebook, len(notebooks))
// 先要保证已有的是正确的排序
cateIds := userBlog.CateIds
has := map[string]bool{} // cateIds中有的
if cateIds != nil && len(cateIds) > 0 {
for _, cateId := range cateIds {
if n, ok := notebooksMap[cateId]; ok {
notebooks2[i] = n
i++
has[cateId] = true
}
}
}
// 之后
for _, each := range notebooks {
id := each.NotebookId.Hex()
if !has[id] {
notebooks2[i] = each
i++
}
}
c.RenderArgs["notebooks"] = notebooks2
return c.RenderTemplate("member/blog/cate.html");
}
// 修改分类排序
func (c MemberBlog) UpateCateIds(cateIds []string) revel.Result {
re := info.NewRe()
re.Ok = blogService.UpateCateIds(c.GetUserId(), cateIds)
return c.RenderJson(re)
}
func (c MemberBlog) UpdateCateUrlTitle(cateId, urlTitle string) revel.Result {
re := info.NewRe()
re.Ok, re.Item = blogService.UpateCateUrlTitle(c.GetUserId(), cateId, urlTitle)
return c.RenderJson(re)
}
// 保存之, 包含增加与保存
func (c MemberBlog) DoAddOrUpdateSingle(singleId, title, content string) revel.Result {
re := info.NewRe()
re.Ok = blogService.AddOrUpdateSingle(c.GetUserId(), singleId, title, content)
return c.RenderJson(re)
}
func (c MemberBlog) AddOrUpdateSingle(singleId string) revel.Result {
c.common()
c.RenderArgs["title"] = "Add Single"
c.RenderArgs["singleId"] = singleId
if singleId != "" {
c.RenderArgs["single"] = blogService.GetSingle(singleId)
}
return c.RenderTemplate("member/blog/add_single.html");
}
func (c MemberBlog) SortSingles(singleIds []string) revel.Result {
re := info.NewRe()
re.Ok = blogService.SortSingles(c.GetUserId(), singleIds)
return c.RenderJson(re)
}
func (c MemberBlog) DeleteSingle(singleId string) revel.Result {
re := info.NewRe()
re.Ok = blogService.DeleteSingle(c.GetUserId(), singleId)
return c.RenderJson(re)
}
// 修改页面标题
func (c MemberBlog) UpdateSingleUrlTitle(singleId, urlTitle string) revel.Result {
re := info.NewRe()
re.Ok, re.Item = blogService.UpdateSingleUrlTitle(c.GetUserId(), singleId, urlTitle)
return c.RenderJson(re)
}
func (c MemberBlog) Single() revel.Result {
c.common()
c.RenderArgs["title"] = "Cate"
c.RenderArgs["singles"] = blogService.GetSingles(c.GetUserId())
return c.RenderTemplate("member/blog/single.html");
}
// 主题
func (c MemberBlog) Theme() revel.Result {
c.common()
activeTheme, otherThemes := themeService.GetUserThemes(c.GetUserId())
c.RenderArgs["activeTheme"] = activeTheme
c.RenderArgs["otherThemes"] = otherThemes
c.RenderArgs["optionThemes"] = themeService.GetDefaultThemes()
c.RenderArgs["title"] = "Theme"
return c.RenderTemplate("member/blog/theme.html");
}
// 编辑主题
var baseTpls = []string{"header.html", "footer.html", "index.html", "cate.html", "search.html", "post.html", "single.html", "tags.html", "tag_posts.html", "archive.html", "share_comment.html", "404.html", "theme.json", "style.css", "blog.js"}
func (c MemberBlog) UpdateTheme(themeId string, isNew int) revel.Result {
// 查看用户是否有该theme, 若没有则复制default之
// 得到主题的文件列表
userBlog := blogService.GetUserBlog(c.GetUserId())
if themeId == "" {
_, themeId = themeService.NewThemeForFirst(userBlog)
return c.Redirect("/member/blog/updateTheme?themeId=" + themeId)
}
c.common()
c.RenderArgs["title"] = "Upate Theme"
c.RenderArgs["isNew"] = isNew
// 先复制之
c.RenderArgs["themeId"] = themeId
// 得到脚本目录
userId := c.GetUserId()
theme := themeService.GetTheme(userId, themeId)
c.RenderArgs["theme"] = theme
path := revel.BasePath + "/" + theme.Path
tpls := ListDir(path)
myTpls := make([]string, len(baseTpls))
tplMap := map[string]bool{}
for i, t := range baseTpls {
myTpls[i] = t
tplMap[t] = true
}
// 得到没有的tpls
for _, t := range tpls {
if t == "images" {
continue;
}
if !tplMap[t] {
myTpls = append(myTpls, t)
}
}
c.RenderArgs["myTpls"] = myTpls
return c.RenderTemplate("member/blog/update_theme.html");
}
// 得到文件内容
func (c MemberBlog) GetTplContent(themeId string, filename string) revel.Result {
re := info.NewRe()
re.Ok = true
re.Item = themeService.GetTplContent(c.GetUserId(), themeId, filename)
return c.RenderJson(re)
}
func (c MemberBlog) UpdateTplContent(themeId, filename, content string) revel.Result {
re := info.NewRe()
re.Ok, re.Msg = themeService.UpdateTplContent(c.GetUserId(), themeId, filename, content)
return c.RenderJson(re)
}
func (c MemberBlog) DeleteTpl(themeId, filename string) revel.Result {
re := info.NewRe()
re.Ok = themeService.DeleteTpl(c.GetUserId(), themeId, filename)
return c.RenderJson(re)
}
func (c MemberBlog) ListThemeImages(themeId string) revel.Result {
re := info.NewRe()
userId := c.GetUserId()
path := themeService.GetThemeAbsolutePath(userId, themeId) + "/images"
os.MkdirAll(path, 0755)
images := ListDir(path)
re.List = images
re.Ok = true
return c.RenderJson(re)
}
func (c MemberBlog) DeleteThemeImage(themeId, filename string) revel.Result {
re := info.NewRe()
path := themeService.GetThemeAbsolutePath(c.GetUserId(), themeId) + "/images/" + filename
re.Ok = DeleteFile(path)
return c.RenderJson(re)
}
// 上传主题图片
func (c MemberBlog) UploadThemeImage(themeId string) revel.Result {
re := c.uploadImage(themeId);
c.RenderArgs["fileUrlPath"] = re.Id
c.RenderArgs["resultCode"] = re.Code
c.RenderArgs["resultMsg"] = re.Msg
return c.RenderTemplate("file/blog_logo.html")
}
func (c MemberBlog) uploadImage(themeId string) (re info.Re) {
var fileId = ""
var resultCode = 0 // 1表示正常
var resultMsg = "内部错误" // 错误信息
var Ok = false
defer func() {
re.Id = fileId // 只是id, 没有其它信息
re.Code = resultCode
re.Msg = resultMsg
re.Ok = Ok
}()
file, handel, err := c.Request.FormFile("file")
if err != nil {
return re
}
defer file.Close()
// 生成上传路径
dir := themeService.GetThemeAbsolutePath(c.GetUserId(), themeId) + "/images"
err = os.MkdirAll(dir, 0755)
if err != nil {
return re
}
// 生成新的文件名
filename := handel.Filename
var ext string;
_, ext = SplitFilename(filename)
if(ext != ".gif" && ext != ".jpg" && ext != ".png" && ext != ".bmp" && ext != ".jpeg") {
resultMsg = "不是图片"
return re
}
filename = filename
data, err := ioutil.ReadAll(file)
if err != nil {
LogJ(err)
return re
}
// > 2M?
if(len(data) > 5 * 1024 * 1024) {
resultCode = 0
resultMsg = "图片大于2M"
return re
}
toPath := dir + "/" + filename;
err = ioutil.WriteFile(toPath, data, 0777)
if err != nil {
LogJ(err)
return re
}
TransToGif(toPath, 0, true)
resultCode = 1
resultMsg = "上传成功!"
return re
}
//
// 使用主题
func (c MemberBlog) ActiveTheme(themeId string) revel.Result {
re := info.NewRe()
re.Ok = themeService.ActiveTheme(c.GetUserId(), themeId)
return c.RenderJson(re)
}
// 删除主题
func (c MemberBlog) DeleteTheme(themeId string) revel.Result {
re := info.NewRe()
re.Ok = themeService.DeleteTheme(c.GetUserId(), themeId)
return c.RenderJson(re)
}
// 管理员公开主题
func (c MemberBlog) PublicTheme(themeId string) revel.Result {
re := info.NewRe()
re.Ok = themeService.PublicTheme(c.GetUserId(), themeId)
return c.RenderJson(re)
}
// 导出
func (c MemberBlog) ExportTheme(themeId string) revel.Result {
re := info.NewRe()
var path string
re.Ok, path = themeService.ExportTheme(c.GetUserId(), themeId)
if !re.Ok {
return c.RenderText("error...")
}
fw, err := os.Open(path)
if err != nil {
return c.RenderText("error")
}
return c.RenderBinary(fw, GetFilename(path), revel.Attachment, time.Now()) // revel.Attachment
}
// 导入主题
func (c MemberBlog) ImportTheme() revel.Result {
re := info.NewRe()
file, handel, err := c.Request.FormFile("file")
if err != nil {
re.Msg = fmt.Sprintf("%v", err)
return c.RenderJson(re)
}
defer file.Close()
// 生成上传路径
userId := c.GetUserId()
dir := revel.BasePath + "/public/upload/" + userId + "/tmp"
err = os.MkdirAll(dir, 0755)
if err != nil {
re.Msg = fmt.Sprintf("%v", err)
return c.RenderJson(re)
}
// 生成新的文件名
filename := handel.Filename
var ext string;
_, ext = SplitFilename(filename)
if(ext != ".zip") {
re.Msg = "请上传zip文件"
return c.RenderJson(re)
}
filename = filename
data, err := ioutil.ReadAll(file)
if err != nil {
return c.RenderJson(re)
}
// > 10M?
if(len(data) > 10 * 1024 * 1024) {
re.Msg = "文件大于10M"
return c.RenderJson(re)
}
toPath := dir + "/" + filename;
err = ioutil.WriteFile(toPath, data, 0777)
if err != nil {
re.Msg = fmt.Sprintf("%v", err)
return c.RenderJson(re)
}
// 上传好后, 增加之
themeService.ImportTheme(c.GetUserId(), toPath)
re.Ok = true
return c.RenderJson(re)
}
// 安装
func (c MemberBlog) InstallTheme(themeId string) revel.Result {
re := info.NewRe()
re.Ok = themeService.InstallTheme(c.GetUserId(), themeId)
return c.RenderJson(re)
}
// 新建主题
func (c MemberBlog) NewTheme() revel.Result {
_, themeId := themeService.NewTheme(c.GetUserId())
return c.Redirect("/member/blog/updateTheme?isNew=1&themeId=" + themeId)
}
//-----------
//
func (c MemberBlog) SetUserBlogBase(userBlog info.UserBlogBase) revel.Result {
re := info.NewRe()
re.Ok = blogService.UpdateUserBlogBase(c.GetUserId(), userBlog)
return c.RenderJson(re)
}
func (c MemberBlog) SetUserBlogComment(userBlog info.UserBlogComment) revel.Result {
re := info.NewRe()
re.Ok = blogService.UpdateUserBlogComment(c.GetUserId(), userBlog)
return c.RenderJson(re)
}
func (c MemberBlog) SetUserBlogStyle(userBlog info.UserBlogStyle) revel.Result {
re := info.NewRe()
re.Ok = blogService.UpdateUserBlogStyle(c.GetUserId(), userBlog)
return c.RenderJson(re)
}
func (c MemberBlog) SetUserBlogPaging(perPageSize int, sortField string, isAsc bool) revel.Result {
re := info.NewRe()
re.Ok, re.Msg = blogService.UpdateUserBlogPaging(c.GetUserId(), perPageSize, sortField, isAsc)
return c.RenderRe(re)
}
@@ -0,0 +1,58 @@
package member
import (
"github.com/revel/revel"
"github.com/leanote/leanote/app/info"
)
// 分组管理
type MemberGroup struct {
MemberBaseController
}
// 首页, 显示所有分组和用户
func (c MemberGroup) Index() revel.Result {
c.SetUserInfo()
c.SetLocale()
c.RenderArgs["title"] = "My Group"
c.RenderArgs["groups"] = groupService.GetGroupsAndUsers(c.GetUserId())
return c.RenderTemplate("member/group/index.html");
}
// 添加分组
func (c MemberGroup) AddGroup(title string) revel.Result {
re := info.NewRe()
re.Ok, re.Item = groupService.AddGroup(c.GetUserId(), title)
return c.RenderJson(re)
}
func (c MemberGroup) UpdateGroupTitle(groupId, title string) revel.Result {
re := info.NewRe()
re.Ok = groupService.UpdateGroupTitle(c.GetUserId(), groupId, title)
return c.RenderJson(re)
}
func (c MemberGroup) DeleteGroup(groupId string) revel.Result {
re := info.NewRe()
re.Ok, re.Msg = groupService.DeleteGroup(c.GetUserId(), groupId)
return c.RenderJson(re)
}
// 添加用户
func (c MemberGroup) AddUser(groupId, email string) revel.Result {
re := info.NewRe()
userInfo := userService.GetUserInfoByAny(email)
if userInfo.UserId == "" {
re.Msg = "userNotExists"
} else {
re.Ok, re.Msg = groupService.AddUser(c.GetUserId(), groupId, userInfo.UserId.Hex())
re.Item = userInfo
}
return c.RenderRe(re)
}
func (c MemberGroup) DeleteUser(groupId, userId string) revel.Result {
re := info.NewRe()
re.Ok, re.Msg = groupService.DeleteUser(c.GetUserId(), groupId, userId)
return c.RenderRe(re)
}
@@ -0,0 +1,37 @@
package member
import (
"github.com/revel/revel"
)
// admin 首页
type MemberIndex struct {
MemberBaseController
}
// admin 主页
func (c MemberIndex) Index() revel.Result {
c.SetUserInfo()
c.RenderArgs["title"] = "Leanote Member Center"
c.RenderArgs["countNote"] = noteService.CountNote(c.GetUserId())
c.RenderArgs["countBlog"] = noteService.CountBlog(c.GetUserId())
c.SetLocale()
return c.RenderTemplate("member/index.html");
}
// 模板
func (c MemberIndex) 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 MemberIndex) GetView(view string) revel.Result {
return c.RenderTemplate("admin/" + view);
}
@@ -0,0 +1,46 @@
package member
import (
"github.com/revel/revel"
)
// 帐户信息
type MemberUser struct {
MemberBaseController
}
func (c MemberUser) Username() revel.Result {
c.SetUserInfo()
c.SetLocale()
c.RenderArgs["title"] = "Username"
return c.RenderTemplate("member/user/username.html");
}
func (c MemberUser) Email() revel.Result {
c.SetUserInfo()
c.SetLocale()
c.RenderArgs["title"] = "Email"
return c.RenderTemplate("member/user/email.html");
}
func (c MemberUser) Password() revel.Result {
c.SetUserInfo()
c.SetLocale()
c.RenderArgs["title"] = "Password"
return c.RenderTemplate("member/user/password.html");
}
func (c MemberUser) Avatar() revel.Result {
c.SetUserInfo()
c.SetLocale()
c.RenderArgs["title"] = "Avatar"
c.RenderArgs["globalConfigs"] = configService.GetGlobalConfigForUser()
return c.RenderTemplate("member/user/avatar.html");
}
func (c MemberUser) AddAccount() revel.Result {
c.SetUserInfo()
c.SetLocale()
c.RenderArgs["title"] = "Add Account"
return c.RenderTemplate("member/user/add_account.html");
}
+133
View File
@@ -0,0 +1,133 @@
package member
import (
"github.com/leanote/leanote/app/service"
"github.com/leanote/leanote/app/info"
// . "github.com/leanote/leanote/app/lea"
"github.com/revel/revel"
// "strings"
)
var userService *service.UserService
var groupService *service.GroupService
var noteService *service.NoteService
var trashService *service.TrashService
var notebookService *service.NotebookService
var noteContentHistoryService *service.NoteContentHistoryService
var authService *service.AuthService
var shareService *service.ShareService
var blogService *service.BlogService
var tagService *service.TagService
var pwdService *service.PwdService
var tokenService *service.TokenService
var suggestionService *service.SuggestionService
var albumService *service.AlbumService
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 themeService *service.ThemeService
// 拦截器
// 不需要拦截的url
// Index 除了Note之外都不需要
var commonUrl = map[string]map[string]bool{"Index": map[string]bool{"Index": true,
"Login": true,
"DoLogin": true,
"Logout": true,
"Register": true,
"DoRegister": true,
"FindPasswword": true,
"DoFindPassword": true,
"FindPassword2": true,
"FindPasswordUpdate": true,
"Suggestion": true,
},
"Blog": map[string]bool{"Index": true,
"View": true,
"AboutMe": true,
"SearchBlog": true,
},
// 用户的激活与修改邮箱都不需要登录, 通过链接地址
"User": map[string]bool{"UpdateEmail": true,
"ActiveEmail":true,
},
"Oauth": map[string]bool{"GithubCallback": true},
"File": map[string]bool{"OutputImage": true, "OutputFile": true},
"Attach": map[string]bool{"Download": true, "DownloadAll": true},
}
func needValidate(controller, method string) bool {
// 在里面
if v, ok := commonUrl[controller]; ok {
// 在commonUrl里
if _, ok2 := v[method]; ok2 {
return false
}
return true
} else {
// controller不在这里的, 肯定要验证
return true;
}
}
func AuthInterceptor(c *revel.Controller) revel.Result {
// 全部变成首字大写
/*
var controller = strings.Title(c.Name)
var method = strings.Title(c.MethodName)
// 是否需要验证?
if !needValidate(controller, method) {
return nil
}
*/
// 验证是否已登录
// 必须是管理员
if _, ok := c.Session["Username"]; ok {
return nil // 已登录
}
// 没有登录, 判断是否是ajax操作
if c.Request.Header.Get("X-Requested-With") == "XMLHttpRequest" {
re := info.NewRe()
re.Msg = "NOTLOGIN"
return c.RenderJson(re)
}
return c.Redirect("/login")
}
// 最外层init.go调用
// 获取service, 单例
func InitService() {
notebookService = service.NotebookS
noteService = service.NoteS
noteContentHistoryService = service.NoteContentHistoryS
trashService = service.TrashS
shareService = service.ShareS
userService = service.UserS
groupService = service.GroupS
tagService = service.TagS
blogService = service.BlogS
tokenService = service.TokenS
noteImageService = service.NoteImageS
fileService = service.FileS
albumService= service.AlbumS
attachService = service.AttachS
pwdService = service.PwdS
suggestionService = service.SuggestionS
authService = service.AuthS
configService = service.ConfigS
emailService = service.EmailS
upgradeService = service.UpgradeS
themeService = service.ThemeS
}
func init() {
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &MemberIndex{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &MemberUser{})
revel.InterceptFunc(AuthInterceptor, revel.BEFORE, &MemberBlog{})
revel.OnAppStart(func() {
})
}
+2
View File
@@ -0,0 +1,2 @@
包括基本信息设置
博客设置
+50 -26
View File
@@ -2,9 +2,9 @@ package db
import (
"fmt"
"github.com/revel/revel"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
"github.com/revel/revel"
)
// Init mgo and the common DAO
@@ -23,9 +23,12 @@ var ShareNotebooks *mgo.Collection
var HasShareNotes *mgo.Collection
var Blogs *mgo.Collection
var Users *mgo.Collection
var Groups *mgo.Collection
var GroupUsers *mgo.Collection
var Tags *mgo.Collection
var TagNotes *mgo.Collection
//var TagNotes *mgo.Collection
var TagCounts *mgo.Collection
var UserBlogs *mgo.Collection
@@ -46,6 +49,8 @@ var EmailLogs *mgo.Collection
var BlogLikes *mgo.Collection
var BlogComments *mgo.Collection
var Reports *mgo.Collection
var BlogSingles *mgo.Collection
var Themes *mgo.Collection
// session
var Sessions *mgo.Collection
@@ -54,7 +59,7 @@ var Sessions *mgo.Collection
func Init() {
var url string
var ok bool
config := revel.Config;
config := revel.Config
url, ok = config.String("db.url")
dbname, _ := config.String("db.dbname")
if !ok {
@@ -66,9 +71,9 @@ func Init() {
if username == "" || password == "" {
usernameAndPassword = ""
}
url = "mongodb://" + usernameAndPassword + host + ":" + port + "/" + dbname
url = "mongodb://" + usernameAndPassword + host + ":" + port + "/" + dbname
}
// [mongodb://][user:pass@]host1[:port1][,host2[:port2],...][/database][?options]
// mongodb://myuser:mypass@localhost:40001,otherhost:40001/mydb
var err error
@@ -82,55 +87,61 @@ func Init() {
// notebook
Notebooks = Session.DB(dbname).C("notebooks")
// notes
Notes = Session.DB(dbname).C("notes")
// noteContents
NoteContents = Session.DB(dbname).C("note_contents")
NoteContentHistories = Session.DB(dbname).C("note_content_histories")
// share
ShareNotes = Session.DB(dbname).C("share_notes")
ShareNotebooks = Session.DB(dbname).C("share_notebooks")
HasShareNotes = Session.DB(dbname).C("has_share_notes")
// user
Users = Session.DB(dbname).C("users")
// group
Groups = Session.DB(dbname).C("groups")
GroupUsers = Session.DB(dbname).C("group_users")
// blog
Blogs = Session.DB(dbname).C("blogs")
// tag
Tags = Session.DB(dbname).C("tags")
TagNotes = Session.DB(dbname).C("tag_notes")
// TagNotes = Session.DB(dbname).C("tag_notes")
TagCounts = Session.DB(dbname).C("tag_count")
// blog
UserBlogs = Session.DB(dbname).C("user_blogs")
BlogSingles = Session.DB(dbname).C("blog_singles")
Themes = Session.DB(dbname).C("themes")
// find password
Tokens = Session.DB(dbname).C("tokens")
// Suggestion
Suggestions = Session.DB(dbname).C("suggestions")
// Album & file
Albums = Session.DB(dbname).C("albums")
Files = Session.DB(dbname).C("files")
Attachs = Session.DB(dbname).C("attachs")
NoteImages = Session.DB(dbname).C("note_images")
Configs = Session.DB(dbname).C("configs")
EmailLogs = Session.DB(dbname).C("email_logs")
// 社交
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")
}
@@ -174,22 +185,29 @@ func UpdateByIdAndUserId2(collection *mgo.Collection, id, userId bson.ObjectId,
return Err(err)
}
func UpdateByIdAndUserIdField(collection *mgo.Collection, id, userId, field string, value interface{}) bool {
return UpdateByIdAndUserId(collection, id, userId, bson.M{"$set": bson.M{field:value}})
return UpdateByIdAndUserId(collection, id, userId, bson.M{"$set": bson.M{field: value}})
}
func UpdateByIdAndUserIdMap(collection *mgo.Collection, id, userId string, v bson.M) bool {
return UpdateByIdAndUserId(collection, id, userId, bson.M{"$set": v})
}
func UpdateByIdAndUserIdField2(collection *mgo.Collection, id, userId bson.ObjectId, field string, value interface{}) bool {
return UpdateByIdAndUserId2(collection, id, userId, bson.M{"$set": bson.M{field:value}})
return UpdateByIdAndUserId2(collection, id, userId, bson.M{"$set": bson.M{field: value}})
}
func UpdateByIdAndUserIdMap2(collection *mgo.Collection, id, userId bson.ObjectId, v bson.M) bool {
return UpdateByIdAndUserId2(collection, id, userId, bson.M{"$set": v})
}
//
//
func UpdateByQField(collection *mgo.Collection, q interface{}, field string, value interface{}) bool {
_, err := collection.UpdateAll(q, bson.M{"$set": bson.M{field: value}})
return Err(err)
}
func UpdateByQI(collection *mgo.Collection, q interface{}, v interface{}) bool {
_, err := collection.UpdateAll(q, bson.M{"$set": v})
return Err(err)
}
// 查询条件和值
func UpdateByQMap(collection *mgo.Collection, q interface{}, v interface{}) bool {
_, err := collection.UpdateAll(q, bson.M{"$set": v})
@@ -255,6 +273,7 @@ func GetByQWithFields(collection *mgo.Collection, q bson.M, fields []string, i i
}
collection.Find(q).Select(selector).One(i)
}
// 查询某些字段, q是查询条件, fields是字段名列表
func ListByQWithFields(collection *mgo.Collection, q bson.M, fields []string, i interface{}) {
selector := make(bson.M, len(fields))
@@ -270,6 +289,11 @@ func GetByIdAndUserId2(collection *mgo.Collection, id, userId bson.ObjectId, i i
collection.Find(GetIdAndUserIdBsonQ(id, userId)).One(i)
}
// 按field去重
func Distinct(collection *mgo.Collection, q bson.M, field string, i interface{}) {
collection.Find(q).Distinct(field, i)
}
//----------------------
func Count(collection *mgo.Collection, q interface{}) int {
@@ -303,9 +327,9 @@ func Err(err error) bool {
fmt.Println(err)
// 删除时, 查找
if err.Error() == "not found" {
return true;
return true
}
return false
}
return true
}
}
+49
View File
@@ -0,0 +1,49 @@
package info
import (
"time"
)
// 仅仅为了博客的主题
type BlogInfoCustom struct {
UserId string
Username string
UserLogo string
Title string
SubTitle string
Logo string
OpenComment bool
CommentType string
ThemeId string
SubDomain string
Domain string
}
type Post struct {
NoteId string
Title string
UrlTitle string
ImgSrc string
CreatedTime time.Time
UpdatedTime time.Time
PublicTime time.Time
Desc string
Abstract string
Content string
Tags []string
CommentNum int
ReadNum int
LikeNum int
IsMarkdown bool
}
// 归档
type ArchiveMonth struct {
Month int
Posts []*Post
}
type Archive struct {
Year int
MonthAchives []ArchiveMonth
Posts []*Post
}
+51 -9
View File
@@ -9,6 +9,7 @@ import (
type BlogItem struct {
Note
Abstract string
Content string // 可能是content的一部分, 截取. 点击more后就是整个信息了
HasMore bool // 是否是否还有
User User // 用户信息
@@ -18,13 +19,13 @@ type UserBlogBase struct {
Logo string `Logo`
Title string `Title` // 标题
SubTitle string `SubTitle` // 副标题
AboutMe string `AboutMe` // 关于我
// AboutMe string `AboutMe` // 关于我
}
type UserBlogComment struct {
CanComment bool `CanComment` // 是否可以评论
CanComment bool `CanComment` // 是否可以评论
CommentType string `CommentType` // default 或 disqus
DisqusId string `DisqusId`
DisqusId string `DisqusId`
}
type UserBlogStyle struct {
@@ -38,18 +39,48 @@ type UserBlog struct {
Logo string `Logo`
Title string `Title` // 标题
SubTitle string `SubTitle` // 副标题
AboutMe string `AboutMe` // 关于我
AboutMe string `AboutMe` // 关于我, 弃用
CanComment bool `CanComment` // 是否可以评论
CanComment bool `CanComment` // 是否可以评论
CommentType string `CommentType` // default 或 disqus
DisqusId string `DisqusId`
DisqusId string `DisqusId`
Style string `Style` // 风格
Css string `Css` // 自定义css
ThemeId bson.ObjectId `ThemeId,omitempty` // 主题Id
ThemePath string `bson:"ThemePath" json:"-"` // 不存值, 从Theme中获取, 相对路径 public/
CateIds []string `CateIds,omitempty` // 分类Id, 排序好的
Singles []map[string]string `Singles,omitempty` // 单页, 排序好的, map包含: ["Title"], ["SingleId"]
PerPageSize int `PerPageSize,omitempty`
SortField string `SortField` // 排序字段
IsAsc bool `IsAsc,omitempty` // 排序类型, 降序, 升序, 默认是false, 表示降序
SubDomain string `SubDomain` // 二级域名
Domain string `Domain` // 自定义域名
}
// 博客统计信息
type BlogStat struct {
NoteId bson.ObjectId `bson:"_id,omitempty"`
ReadNum int `ReadNum,omitempty` // 阅读次数 2014/9/28
LikeNum int `LikeNum,omitempty` // 点赞次数 2014/9/28
CommentNum int `CommentNum,omitempty` // 评论次数 2014/9/28
}
// 单页
type BlogSingle struct {
SingleId bson.ObjectId `bson:"_id,omitempty"`
UserId bson.ObjectId `UserId`
Title string `Title`
UrlTitle string `UrlTitle` // 2014/11/11
Content string `Content`
UpdatedTime time.Time `UpdatedTime`
CreatedTime time.Time `CreatedTime`
}
//------------------------
@@ -57,7 +88,7 @@ type UserBlog struct {
// 点赞记录
type BlogLike struct {
LikeId bson.ObjectId `bson:"_id"`
LikeId bson.ObjectId `bson:"_id,omitempty"`
NoteId bson.ObjectId `NoteId`
UserId bson.ObjectId `UserId`
CreatedTime time.Time `CreatedTime`
@@ -65,7 +96,7 @@ type BlogLike struct {
// 评论
type BlogComment struct {
CommentId bson.ObjectId `bson:"_id"`
CommentId bson.ObjectId `bson:"_id,omitempty"`
NoteId bson.ObjectId `NoteId`
UserId bson.ObjectId `UserId` // UserId回复ToUserId
@@ -84,3 +115,14 @@ type BlogCommentPublic struct {
BlogComment
IsILikeIt bool
}
type BlogUrls struct {
IndexUrl string
CateUrl string
SearchUrl string
SingleUrl string
PostUrl string
ArchiveUrl string
TagsUrl string
TagPostsUrl string
}
+25
View File
@@ -0,0 +1,25 @@
package info
import (
"gopkg.in/mgo.v2/bson"
"time"
)
// 分组
type Group struct {
GroupId bson.ObjectId `bson:"_id"` // 谁的
UserId bson.ObjectId `UserId` // 所有者Id
Title string `Title` // 标题
UserCount int `UserCount` // 用户数
CreatedTime time.Time `CreatedTime`
Users []User `Users,omitempty` // 分组下的用户, 不保存, 仅查看
}
// 分组好友
type GroupUser struct {
GroupUserId bson.ObjectId `bson:"_id"` // 谁的
GroupId bson.ObjectId `GroupId` // 分组
UserId bson.ObjectId `UserId` // 用户
CreatedTime time.Time `CreatedTime`
}
+8 -6
View File
@@ -20,9 +20,11 @@ type Note struct {
IsTrash bool `IsTrash` // 是否是trash, 默认是false
IsBlog bool `IsBlog,omitempty` // 是否设置成了blog 2013/12/29 新加
IsRecommend bool `IsRecommend,omitempty` // 是否为推荐博客 2014/9/24新加
IsTop bool `IsTop,omitempty` // blog是否置顶
IsBlog bool `IsBlog,omitempty` // 是否设置成了blog 2013/12/29 新加
UrlTitle string `UrlTitle,omitempty` // 博客的url标题, 为了更友好的url, 在UserId, UrlName下唯一
IsRecommend bool `IsRecommend,omitempty` // 是否为推荐博客 2014/9/24新加
IsTop bool `IsTop,omitempty` // blog是否置顶
HasSelfDefined bool `HasSelfDefined` // 是否已经自定义博客图片, desc, abstract
// 2014/9/28 添加评论社交功能
ReadNum int `ReadNum,omitempty` // 阅读次数 2014/9/28
@@ -35,9 +37,9 @@ type Note struct {
CreatedTime time.Time `CreatedTime`
UpdatedTime time.Time `UpdatedTime`
RecommendTime time.Time `RecommendTime,omitempty` // 推荐时间
PublicTime time.Time `PublicTime,omitempty` // 发表时间, 公开为博客则设置
UpdatedUserId bson.ObjectId `bson:"UpdatedUserId"` // 如果共享了, 并可写, 那么可能是其它他修改了
RecommendTime time.Time `RecommendTime,omitempty` // 推荐时间
PublicTime time.Time `PublicTime,omitempty` // 发表时间, 公开为博客则设置
UpdatedUserId bson.ObjectId `bson:"UpdatedUserId"` // 如果共享了, 并可写, 那么可能是其它他修改了
}
// 内容
+1
View File
@@ -13,6 +13,7 @@ type Notebook struct {
ParentNotebookId bson.ObjectId `bson:"ParentNotebookId,omitempty"` // 上级
Seq int `Seq` // 排序
Title string `Title` // 标题
UrlTitle string `UrlTitle` // Url标题 2014/11.11加
NumberNotes int `NumberNotes` // 笔记数
IsTrash bool `IsTrash,omitempty` // 是否是trash, 默认是false
IsBlog bool `IsBlog,omitempty` // 是否是Blog 2013/12/29 新加
+6 -2
View File
@@ -76,7 +76,9 @@ type SharingNotebookAndNotes struct {
type ShareNotebook struct {
ShareNotebookId bson.ObjectId `bson:"_id,omitempty"` // 必须要设置bson:"_id" 不然mgo不会认为是主键
UserId bson.ObjectId `bson:"UserId"`
ToUserId bson.ObjectId `bson:"ToUserId"`
ToUserId bson.ObjectId `bson:"ToUserId,omitempty"`
ToGroupId bson.ObjectId `bson:"ToGroupId,omitempty"` // 分享给的用户组
ToGroup Group `ToGroup,omitempty` // 仅仅为了显示, 不存储, 分组信息
NotebookId bson.ObjectId `bson:"NotebookId"`
Seq int `bson:"Seq"` // 排序
Perm int `bson:"Perm"` // 权限, 其下所有notes 0只读, 1可修改
@@ -134,7 +136,9 @@ type ShareNotebooksByUser struct {
type ShareNote struct {
ShareNoteId bson.ObjectId `bson:"_id,omitempty"` // 必须要设置bson:"_id" 不然mgo不会认为是主键
UserId bson.ObjectId `bson:"UserId"`
ToUserId bson.ObjectId `bson:"ToUserId"`
ToUserId bson.ObjectId `bson:"ToUserId,omitempty"`
ToGroupId bson.ObjectId `bson:"ToGroupId,omitempty"` // 分享给的用户组
ToGroup Group `ToGroup,omitempty` // 仅仅为了显示, 不存储, 分组信息
NoteId bson.ObjectId `bson:"NoteId"`
Perm int `bson:"Perm"` // 权限, 0只读, 1可修改
CreatedTime time.Time `CreatedTime`
+24 -1
View File
@@ -5,15 +5,38 @@ import (
)
// 这里主要是为了统计每个tag的note数目
// 暂时没用
/*
type TagNote struct {
TagId bson.ObjectId `bson:"_id,omitempty"` // 必须要设置bson:"_id" 不然mgo不会认为是主键
UserId bson.ObjectId `bson:"UserId"`
Tag string `Title` // 标题
NoteNum int `NoteNum` // note数目
}
*/
// 每个用户一条记录, 存储用户的所有tags
type Tag struct {
UserId bson.ObjectId `bson:"_id"`
Tags []string `Tags`
}
}
type TagCount struct {
TagCountId bson.ObjectId `bson:"_id,omitempty"`
UserId bson.ObjectId `UserId` // 谁的
Tag string `Tag`
IsBlog bool `IsBlog` // 是否是博客的tag统计
Count int `Count` // 统计数量
}
/*
type TagsCounts []TagCount
func (this TagsCounts) Len() int {
return len(this)
}
func (this TagsCounts) Less(i, j int) bool {
return this[i].Count > this[j].Count
}
func (this TagsCounts) Swap(i, j int) {
this[i], this[j] = this[j], this[i]
}
*/
+26
View File
@@ -0,0 +1,26 @@
package info
import (
"gopkg.in/mgo.v2/bson"
"time"
)
// 主题, 每个用户有多个主题, 这里面有主题的配置信息
// 模板, css, js, images, 都在路径Path下
type Theme struct {
ThemeId bson.ObjectId `bson:"_id,omitempty"` // 必须要设置bson:"_id" 不然mgo不会认为是主键
UserId bson.ObjectId `UserId`
Name string `Name`
Version string `Version`
Author string `Author`
AuthorUrl string `AuthorUrl`
Path string `Path` // 文件夹路径
Info map[string]interface{} `Info` // 所有信息
IsActive bool `IsActive` // 是否在用
IsDefault bool `IsDefault` // leanote默认主题, 如果用户修改了默认主题, 则先copy之. 也是admin用户的主题
Style string `Style,omitempty` // 之前的, 只有default的用户才有blog_default, blog_daqi, blog_left_fixed
CreatedTime time.Time `CreatedTime`
UpdatedTime time.Time `UpdatedTime`
}
+43
View File
@@ -34,4 +34,47 @@ type User struct {
ThirdUserId string `ThirdUserId` // 用户Id, 在第三方中唯一可识别
ThirdUsername string `ThirdUsername` // 第三方中username, 为了显示
ThirdType int `ThirdType` // 第三方类型
// 用户的帐户类型
ImageNum int `bson:"ImageNum" json:"-"` // 图片数量
ImageSize int `bson:"ImageSize" json:"-"` // 图片大小
AttachNum int `bson:"AttachNum" json:"-"` // 附件数量
AttachSize int `bson:"AttachSize" json:"-"` // 附件大小
PerAttachSize int `bson:"PerAttachSize" json:"-"` // 单个附件大小
AccountType string `bson:"AccountType" json:"-"` // normal(为空), premium
AccountStartTime time.Time `bson:"AccountStartTime" json:"-"` // 开始日期
AccountEndTime time.Time `bson:"AccountEndTime" json:"-"` // 结束日期
// 阈值
MaxImageNum int `bson:"MaxImageNums" json:"-"` // 图片数量
MaxImageSize int `bson:"MaxImageSize" json:"-"` // 图片大小
MaxAttachNum int `bson:"MaxAttachNum" json:"-"` // 图片数量
MaxAttachSize int `bson:"MaxAttachSize" json:"-"` // 图片大小
MaxPerAttachSize int `bson:"MaxPerAttachSize" json:"-"` // 单个附件大小
}
type UserAccount struct {
AccountType string `bson:"AccountType" json:"-"` // normal(为空), premium
AccountStartTime time.Time `bson:"AccountStartTime" json:"-"` // 开始日期
AccountEndTime time.Time `bson:"AccountEndTime" json:"-"` // 结束日期
// 阈值
MaxImageNum int `bson:"MaxImageNums" json:"-"` // 图片数量
MaxImageSize int `bson:"MaxImageSize" json:"-"` // 图片大小
MaxAttachNum int `bson:"MaxAttachNum" json:"-"` // 图片数量
MaxAttachSize int `bson:"MaxAttachSize" json:"-"` // 图片大小
MaxPerAttachSize int `bson:"MaxPerAttachSize" json:"-"` // 单个附件大小
}
// 用户与博客信息结合, 公开
type UserAndBlog struct {
UserId bson.ObjectId `bson:"_id,omitempty"` // 必须要设置bson:"_id" 不然mgo不会认为是主键
Email string `Email` // 全是小写
Username string `Username` // 不区分大小写, 全是小写
Logo string `Logo`
BlogTitle string `BlogTitle` // 博客标题
BlogLogo string `BlogLogo` // 博客Logo
BlogUrl string `BlogUrl` // 博客链接, 主页
BlogUrls // 各个页面
}
+3 -2
View File
@@ -9,6 +9,7 @@ import (
type Page struct {
CurPage int // 当前页码
TotalPage int // 总页
PerPageSize int
Count int // 总记录数
List interface{}
}
@@ -18,5 +19,5 @@ func NewPage(page, perPageSize, count int, list interface{}) Page {
if count > 0 {
totalPage = int(math.Ceil(float64(count) / float64(perPageSize)))
}
return Page{page, totalPage, count, list}
}
return Page{page, totalPage, perPageSize, count, list}
}
+22 -3
View File
@@ -7,6 +7,7 @@ import (
"github.com/leanote/leanote/app/db"
"github.com/leanote/leanote/app/controllers"
"github.com/leanote/leanote/app/controllers/admin"
"github.com/leanote/leanote/app/controllers/member"
_ "github.com/leanote/leanote/app/lea/binder"
"github.com/leanote/leanote/app/lea/session"
"github.com/leanote/leanote/app/lea/memcache"
@@ -19,6 +20,7 @@ import (
"strconv"
"time"
"encoding/json"
"net/url"
)
func init() {
@@ -56,6 +58,11 @@ func init() {
i = i - 1;
return i
}
// 增加或减少
revel.TemplateFuncs["incr"] = func(n, i int) int {
n = n + i;
return n
}
revel.TemplateFuncs["join"] = func(arr []string) template.HTML {
if arr == nil {
return template.HTML("")
@@ -72,13 +79,24 @@ func init() {
}
return str
}
revel.TemplateFuncs["decodeUrlValue"] = func(i string) string {
v, _ := url.ParseQuery("a=" + i)
return v.Get("a")
}
revel.TemplateFuncs["json"] = func(i interface{}) string {
b, _ := json.Marshal(i)
return string(b)
}
revel.TemplateFuncs["jsonJs"] = func(i interface{}) template.JS {
b, _ := json.Marshal(i)
return template.JS(string(b))
}
revel.TemplateFuncs["datetime"] = func(t time.Time) template.HTML {
return template.HTML(t.Format("2006-01-02 15:04:05"))
}
revel.TemplateFuncs["dateFormat"] = func(t time.Time, format string) template.HTML {
return template.HTML(t.Format(format))
}
revel.TemplateFuncs["unixDatetime"] = func(unixSec string) template.HTML {
sec, _ := strconv.Atoi(unixSec)
t := time.Unix(int64(sec), 0)
@@ -135,8 +153,6 @@ func init() {
}
*/
revel.TemplateFuncs["li"] = func(a string) string {
Log(a)
Log("life==")
return ""
}
// str连接
@@ -248,6 +264,7 @@ func init() {
// https://groups.google.com/forum/#!topic/golang-nuts/OEdSDgEC7js
// http://play.golang.org/p/snygrVpQva
// http://grokbase.com/t/gg/golang-nuts/142a6dhfh3/go-nuts-text-template-using-comparison-operators-eq-gt-etc-on-non-existent-variable-causes-the-template-to-stop-outputting-but-with-no-error-correct-behaviour
/*
revel.TemplateFuncs["gt"] = func(a1, a2 interface{}) bool {
switch a1.(type) {
case string:
@@ -273,6 +290,7 @@ func init() {
}
return false
}
*/
/*
{{range $i := N 1 10}}
@@ -302,6 +320,7 @@ func init() {
service.InitService()
controllers.InitService()
admin.InitService()
member.InitService()
service.ConfigS.InitGlobalConfigs()
})
}
}
+107 -15
View File
@@ -1,10 +1,11 @@
package lea
import (
"strings"
"path/filepath"
"os"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
// 分离文件名与扩展名(包含.)
@@ -14,14 +15,14 @@ func SplitFilename(filename string) (baseName, ext string) {
ext = SubstringByte(filename, strings.LastIndex(filename, "."))
baseName = strings.TrimRight(filename, ext)
ext = strings.ToLower(ext)
return;
return
}
// 转换文件的格式
// toExt包含.
func TransferExt(path string, toExt string) string {
dir := filepath.Dir(path) + "/" // 文件路径
name := filepath.Base(path) // 文件名 a.jpg
dir := filepath.Dir(path) + "/" // 文件路径
name := filepath.Base(path) // 文件名 a.jpg
// 获取文件名与路径
baseName, _ := SplitFilename(name)
return dir + baseName + toExt
@@ -38,7 +39,7 @@ func GetFilesize(path string) int64 {
if err == nil {
return fileinfo.Size()
}
return 0;
return 0
}
// 清空dir下所有的文件和文件夹
@@ -79,12 +80,103 @@ func CopyFile(srcName, dstName string) (written int64, err error) {
return io.Copy(dst, src)
}
func CopyDir(source string, dest string) (err error) {
// get properties of source dir
sourceinfo, err := os.Stat(source)
if err != nil {
return err
}
// create dest dir
err = os.MkdirAll(dest, sourceinfo.Mode())
if err != nil {
return err
}
directory, _ := os.Open(source)
objects, err := directory.Readdir(-1)
for _, obj := range objects {
sourcefilepointer := source + "/" + obj.Name()
destinationfilepointer := dest + "/" + obj.Name()
if obj.IsDir() {
// create sub-directories - recursively
err = CopyDir(sourcefilepointer, destinationfilepointer)
if err != nil {
// fmt.Println(err)
}
} else {
// perform copy
_, err = CopyFile(sourcefilepointer, destinationfilepointer)
if err != nil {
// fmt.Println(err)
}
}
}
return
}
func DeleteFile(path string) bool {
err := os.Remove(path)
if err != nil {
return false
}
return true
}
func IsDirExists(path string) bool {
fi, err := os.Stat(path)
if err != nil {
return os.IsExist(err)
}else{
return fi.IsDir()
}
return false
}
fi, err := os.Stat(path)
if err != nil {
return os.IsExist(err)
} else {
return fi.IsDir()
}
return false
}
// 获得文件str内容
func GetFileStrContent(path string) string {
fileBytes, err := ioutil.ReadFile(path)
if err != nil {
return ""
}
return string(fileBytes)
}
func IsFileExist(filename string) bool {
var exist = true
if _, err := os.Stat(filename); os.IsNotExist(err) {
exist = false
}
return exist
}
// 写入string内容
func PutFileStrContent(path, content string) bool {
var f *os.File
var err1 error
defer (func() {
if f != nil {
f.Close()
}
})()
f, err1 = os.OpenFile(path, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0666) //打开文件
// Log(err1)
// var n int
_, err1 = io.WriteString(f, content) //写入文件(字符串)
// Log(content)
// Log(err1)
// Log(n)
// Log(path)
if err1 != nil {
Log(err1)
return false
}
return true
}
+25
View File
@@ -27,6 +27,12 @@ var rulesStr = `{
],
"domain": [
{"rule": "domain", "msg": "errorDomain"}
],
"perPageSize": [
{"rule": "min", "data": "1", "msg": "errorPerPageSize"}
],
"sortField": [
{"rule": "sortField", "msg": "errorSortField"}
]
}
`
@@ -49,6 +55,25 @@ var rules = map[string]func(string, map[string]string)(bool, string) {
ok = len(value) >= dataI
return
},
"min": func(value string, rule map[string]string)(ok bool, msg string) {
if value == "" {
return
}
data := rule["data"]
dataI, _ := strconv.Atoi(data)
vI, _ := strconv.Atoi(value)
ok = vI >= dataI
return
},
"sortField": func(value string, rule map[string]string)(ok bool, msg string) {
if value == "" {
return
}
sortFields := []string{"PublicTime", "CreatedTime", "UpdatedTime", "Title"}
ok = InArray(sortFields, value)
return
},
"password": func(value string, rule map[string]string)(ok bool, msg string) {
if value == "" {
+230
View File
@@ -0,0 +1,230 @@
package archive
import (
"archive/tar"
"compress/gzip"
"fmt"
"io"
"os"
"path"
)
// main functions shows how to TarGz a directory/file and
// UnTarGz a file
// Gzip and tar from source directory or file to destination file
// you need check file exist before you call this function
func main() {
/*
os.Mkdir("/home/ty4z2008/tar", 0777)
w, err := CopyFile("/home/ty4z2008/tar/1.pdf", "/home/ty4z2008/src/1.pdf")
//targetfile,sourcefile
if err != nil {
fmt.Println(err.Error())
}
fmt.Println(w)
TarGz("/home/ty4z2008/tar/1.pdf", "/home/ty4z2008/test.tar.gz") //压缩
//UnTarGz("/home/ty4z2008/1.tar.gz", "/home/ty4z2008") //解压
os.RemoveAll("/home/ty4z2008/tar")
*/
// TaZip("/Users/life/Desktop/j", "/Users/life/Desktop/aaa.tar.gz")
Zip("/Users/life/Desktop/j", "/Users/life/Desktop/aaa.zip")
fmt.Println("ok")
}
func TarGz(srcDirPath string, destFilePath string) (ok bool) {
defer func() { //必须要先声明defer,否则不能捕获到panic异常
if err := recover(); err != nil {
ok = false
}
}()
fw, err := os.Create(destFilePath)
if err != nil {
panic(err)
}
defer fw.Close()
// Gzip writer
gw := gzip.NewWriter(fw)
defer gw.Close()
// Tar writer
tw := tar.NewWriter(gw)
defer tw.Close()
// Check if it's a file or a directory
f, err := os.Open(srcDirPath)
if err != nil {
panic(err)
}
fi, err := f.Stat()
if err != nil {
panic(err)
}
if fi.IsDir() {
// handle source directory
// fmt.Println("Cerating tar.gz from directory...")
tarGzDir(srcDirPath, path.Base(srcDirPath), tw)
} else {
// handle file directly
// fmt.Println("Cerating tar.gz from " + fi.Name() + "...")
tarGzFile(srcDirPath, fi.Name(), tw, fi)
}
ok = true
return
}
// Deal with directories
// if find files, handle them with tarGzFile
// Every recurrence append the base path to the recPath
// recPath is the path inside of tar.gz
func tarGzDir(srcDirPath string, recPath string, tw *tar.Writer) {
// Open source diretory
dir, err := os.Open(srcDirPath)
if err != nil {
panic(err)
}
defer dir.Close()
// Get file info slice
fis, err := dir.Readdir(0)
if err != nil {
panic(err)
}
for _, fi := range fis {
// Append path
curPath := srcDirPath + "/" + fi.Name()
// Check it is directory or file
if fi.IsDir() {
// Directory
// (Directory won't add unitl all subfiles are added)
// fmt.Printf("Adding path...%s\n", curPath)
tarGzDir(curPath, recPath+"/"+fi.Name(), tw)
} else {
// File
// fmt.Printf("Adding file...%s\n", curPath)
}
tarGzFile(curPath, recPath+"/"+fi.Name(), tw, fi)
}
}
// Deal with files
func tarGzFile(srcFile string, recPath string, tw *tar.Writer, fi os.FileInfo) {
if fi.IsDir() {
// fmt.Println("??")
// Create tar header
hdr := new(tar.Header)
// if last character of header name is '/' it also can be directory
// but if you don't set Typeflag, error will occur when you untargz
hdr.Name = recPath // + "/"
// fmt.Println(hdr.Name)
hdr.Typeflag = tar.TypeDir
// hdr.Size = 0
//hdr.Mode = 0755 | c_ISDIR
// hdr.Mode = int64(fi.Mode()) // 加这个会有错误!!!
// hdr.ModTime = fi.ModTime() // 加这个会有错误!!
// Write hander
err := tw.WriteHeader(hdr)
if err != nil {
panic(err)
}
} else {
// File reader
fr, err := os.Open(srcFile)
if err != nil {
panic(err)
}
defer fr.Close()
// Create tar header
hdr := new(tar.Header)
hdr.Name = recPath
// fmt.Println(hdr.Name)
hdr.Size = fi.Size()
hdr.Mode = int64(fi.Mode())
hdr.ModTime = fi.ModTime()
// Write hander
err = tw.WriteHeader(hdr)
if err != nil {
panic(err)
}
// Write file data
_, err = io.Copy(tw, fr)
if err != nil {
panic(err)
}
}
}
// Ungzip and untar from source file to destination directory
// you need check file exist before you call this function
func UnTarGz(srcFilePath string, destDirPath string) {
// fmt.Println("UnTarGzing " + srcFilePath + "...")
// Create destination directory
os.Mkdir(destDirPath, os.ModePerm)
fr, err := os.Open(srcFilePath)
if err != nil {
panic(err)
}
defer fr.Close()
// Gzip reader
gr, err := gzip.NewReader(fr)
if err != nil {
panic(err)
}
defer gr.Close()
// Tar reader
tr := tar.NewReader(gr)
for {
hdr, err := tr.Next()
if err == io.EOF {
// End of tar archive
break
}
//handleError(err)
// fmt.Println("UnTarGzing file..." + hdr.Name)
// Check if it is diretory or file
if hdr.Typeflag != tar.TypeDir {
// Get files from archive
// Create diretory before create file
os.MkdirAll(destDirPath+"/"+path.Dir(hdr.Name), os.ModePerm)
// Write data to file
fw, _ := os.Create(destDirPath + "/" + hdr.Name)
if err != nil {
panic(err)
}
_, err = io.Copy(fw, tr)
if err != nil {
panic(err)
}
}
}
// fmt.Println("Well done!")
}
// Copyfile
func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return
}
defer src.Close()
dst, err := os.OpenFile(dstName, os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return
}
defer dst.Close()
return io.Copy(dst, src)
}
+192
View File
@@ -0,0 +1,192 @@
package archive
import (
"archive/zip"
"fmt"
"strings"
"io"
"os"
"path"
)
// main functions shows how to TarGz a directory/file and
// UnTarGz a file
// Gzip and tar from source directory or file to destination file
// you need check file exist before you call this function
func Zip(srcDirPath string, destFilePath string) (ok bool) {
defer func() { //必须要先声明defer,否则不能捕获到panic异常
if err := recover(); err != nil {
ok = false
}
}()
fw, err := os.Create(destFilePath)
if err != nil {
panic(err)
}
defer fw.Close()
// Tar writer
tw := zip.NewWriter(fw)
defer tw.Close()
// Check if it's a file or a directory
f, err := os.Open(srcDirPath)
if err != nil {
panic(err)
}
fi, err := f.Stat()
if err != nil {
panic(err)
}
if fi.IsDir() {
// handle source directory
// fmt.Println("Cerating tar.gz from directory...")
zipDir(srcDirPath, path.Base(srcDirPath), tw)
} else {
// handle file directly
// fmt.Println("Cerating tar.gz from " + fi.Name() + "...")
zipFile(srcDirPath, fi.Name(), tw, fi)
}
ok = true
return
}
// Deal with directories
// if find files, handle them with zipFile
// Every recurrence append the base path to the recPath
// recPath is the path inside of tar.gz
func zipDir(srcDirPath string, recPath string, tw *zip.Writer) {
// Open source diretory
dir, err := os.Open(srcDirPath)
if err != nil {
panic(err)
}
defer dir.Close()
// Get file info slice
fis, err := dir.Readdir(0)
if err != nil {
panic(err)
}
for _, fi := range fis {
// Append path
curPath := srcDirPath + "/" + fi.Name()
// Check it is directory or file
if fi.IsDir() {
// Directory
// (Directory won't add unitl all subfiles are added)
// fmt.Printf("Adding path...%s\n", curPath)
zipDir(curPath, recPath+"/"+fi.Name(), tw)
} else {
// File
// fmt.Printf("Adding file...%s\n", curPath)
}
zipFile(curPath, recPath+"/"+fi.Name(), tw, fi)
}
}
// Deal with files
func zipFile(srcFile string, recPath string, tw *zip.Writer, fi os.FileInfo) {
if fi.IsDir() {
// fmt.Println("??")
// Create tar header
/*
fh, err := zip.FileInfoHeader(fi)
if err != nil {
panic(err)
}
fh.Name = recPath // + "/"
err = tw.WriteHeader(hdr)
tw.Create(recPath)
*/
} else {
// File reader
fr, err := os.Open(srcFile)
if err != nil {
panic(err)
}
defer fr.Close()
// Write hander
w, err2 := tw.Create(recPath)
if err2 != nil {
panic(err)
}
// Write file data
_, err = io.Copy(w, fr)
if err != nil {
panic(err)
}
}
}
// Ungzip and untar from source file to destination directory
// you need check file exist before you call this function
func Unzip(srcFilePath string, destDirPath string) (ok bool, msg string) {
ok = false
msg = ""
defer func() { //必须要先声明defer,否则不能捕获到panic异常
if err := recover(); err != nil {
msg = fmt.Sprintf("%v", err)
ok = false
}
}()
os.Mkdir(destDirPath, os.ModePerm)
r, err := zip.OpenReader(srcFilePath);
if err != nil {
panic(err)
}
defer r.Close();
for _, f := range r.File {
// fmt.Println("FileName : ", f.Name); // j/aaa.zip
rc, err := f.Open();
if err!=nil {
panic(err)
}
// 把首文件夹去掉, 即j去掉, 分离出文件夹和文件名
paths := strings.Split(f.Name, "/")
prePath := ""
filename := ""
l := len(paths)
// fmt.Println(l)
if l > 1 {
// 去掉第1个文件夹
if l == 2 {
filename = paths[1]
} else {
filename = paths[l-1]
prePath = strings.Join(paths[1:l-1], "/")
}
} else {
filename = f.Name
}
// fmt.Println(prePath)
// 相对于目标文件件下的路径
destPath := destDirPath + "/" + filename
if prePath != "" {
os.MkdirAll(destDirPath + "/" + prePath, os.ModePerm)
destPath = destDirPath + "/" + prePath + "/" + filename
}
// Write data to file
// fmt.Println(destPath)
fw, _ := os.Create(destPath)
if err != nil {
panic(err)
}
_, err = io.Copy(fw, rc)
if err != nil {
panic(err)
}
}
ok = true
return
}
+302
View File
@@ -0,0 +1,302 @@
package blog
import (
. "github.com/leanote/leanote/app/lea"
"github.com/revel/revel"
"html/template"
"io/ioutil"
// "os"
"fmt"
"bytes"
"io"
"net/http"
"regexp"
"strconv"
"strings"
)
//--------------------
// leanote 自定义主题
// 不使用revel的模板机制
// By life
//--------------------
var ts = []string{"header.html", "footer.html", "highlight.html", "comment.html", "view.html", "404.html"}
var selfTs = []string{"header.html", "footer.html", "index.html", "about_me.html"} // 用户自定义的文件列表
type BlogTpl struct {
Template *template.Template
PathContent map[string]string // path => content
}
func (this *BlogTpl) Content(name string) string {
return this.PathContent[name]
}
var BlogTplObject *BlogTpl
var CloneTemplate *template.Template
type RenderTemplateResult struct {
Template *template.Template
PathContent map[string]string
RenderArgs map[string]interface{}
IsPreview bool // 是否是预览
CurBlogTpl *BlogTpl
}
func parseTemplateError(err error) (templateName string, line int, description string) {
description = err.Error()
i := regexp.MustCompile(`:\d+:`).FindStringIndex(description)
if i != nil {
line, err = strconv.Atoi(description[i[0]+1 : i[1]-1])
if err != nil {
}
templateName = description[:i[0]]
if colon := strings.Index(templateName, ":"); colon != -1 {
templateName = templateName[colon+1:]
}
templateName = strings.TrimSpace(templateName)
description = description[i[1]+1:]
}
return templateName, line, description
}
func (r *RenderTemplateResult) render(req *revel.Request, resp *revel.Response, wr io.Writer) {
err := r.Template.Execute(wr, r.RenderArgs)
if err == nil {
return
}
var templateContent []string
templateName, line, description := parseTemplateError(err)
var content = ""
if templateName == "" {
templateName = r.Template.Name()
content = r.PathContent[templateName]
} else {
content = r.PathContent[templateName]
}
if content != "" {
templateContent = strings.Split(content, "\n")
}
compileError := &revel.Error{
Title: "Template Execution Error",
Path: templateName,
Description: description,
Line: line,
SourceLines: templateContent,
}
// 这里, 错误!!
// 这里应该导向到本主题的错误页面
resp.Status = 500
ErrorResult{r.RenderArgs, compileError, r.IsPreview, r.CurBlogTpl}.Apply(req, resp)
}
func (r *RenderTemplateResult) Apply(req *revel.Request, resp *revel.Response) {
// Handle panics when rendering templates.
defer func() {
if err := recover(); err != nil {
}
}()
chunked := revel.Config.BoolDefault("results.chunked", false)
// If it's a HEAD request, throw away the bytes.
out := io.Writer(resp.Out)
if req.Method == "HEAD" {
out = ioutil.Discard
}
// In a prod mode, write the status, render, and hope for the best.
// (In a dev mode, always render to a temporary buffer first to avoid having
// error pages distorted by HTML already written)
if chunked && !revel.DevMode {
resp.WriteHeader(http.StatusOK, "text/html; charset=utf-8")
r.render(req, resp, out) // 这里!!!
return
}
// Render the template into a temporary buffer, to see if there was an error
// rendering the template. If not, then copy it into the response buffer.
// Otherwise, template render errors may result in unpredictable HTML (and
// would carry a 200 status code)
var b bytes.Buffer
r.render(req, resp, &b)
if !chunked {
resp.Out.Header().Set("Content-Length", strconv.Itoa(b.Len()))
}
resp.WriteHeader(http.StatusOK, "text/html; charset=utf-8")
b.WriteTo(out)
}
// 博客模板
func Init() {
BlogTplObject = &BlogTpl{PathContent: map[string]string{}}
BlogTplObject.Template = template.New("blog").Funcs(revel.TemplateFuncs)
for _, path := range ts {
fileBytes, _ := ioutil.ReadFile(revel.ViewsPath + "/Blog/" + path)
fileStr := string(fileBytes)
path := "blog/" + path
// path := path
BlogTplObject.PathContent[path] = fileStr
BlogTplObject.Template.New(path).Parse(fileStr) // 以blog为根
}
// 复制一份
CloneTemplate, _ = BlogTplObject.Template.Clone()
}
// name = index.html, search.html, cate.html, page.html
// basePath 表未用户主题的基路径, 如/xxx/public/upload/32323232/themes/theme1, 如果没有, 则表示用自带的
// isPreview 如果是, 错误提示则显示系统的 500 错误详情信息, 供debug
//
func RenderTemplate(name string, args map[string]interface{}, basePath string, isPreview bool) revel.Result {
var r *RenderTemplateResult
// 传来的主题路径为空, 则用系统的
// 都不会为空的
if basePath == "" {
path := "blog/" + name
// path := name
t := BlogTplObject.Template.Lookup(path)
r = &RenderTemplateResult{
Template: t,
PathContent: BlogTplObject.PathContent, // 为了显示错误
RenderArgs: args, // 把args给它
}
} else {
// 复制一份
newBlogTplObject := &BlogTpl{}
var err error
newBlogTplObject.Template, err = CloneTemplate.Clone() // 复制一份, 为防止多用户出现问题, 因为newBlogTplObject是全局的
if err != nil {
return nil
}
newBlogTplObject.PathContent = map[string]string{}
for k, v := range BlogTplObject.PathContent {
newBlogTplObject.PathContent[k] = v
}
// 将该basePath下的所有文件提出
files := ListDir(basePath)
Log(basePath)
LogJ(files);
for _, t := range files {
if !strings.Contains(t, ".html") {
continue;
}
fileBytes, err := ioutil.ReadFile(basePath + "/" + t)
if err != nil {
continue
}
fileStr := string(fileBytes)
newBlogTplObject.PathContent[t] = fileStr
newBlogTplObject.Template.New(t).Parse(fileStr)
}
// 如果本主题下没有, 则用系统的
t := newBlogTplObject.Template.Lookup(name)
if t == nil {
path := "blog/" + name
t = BlogTplObject.Template.Lookup(path)
}
r = &RenderTemplateResult{
Template: t,
PathContent: newBlogTplObject.PathContent, // 为了显示错误
RenderArgs: args,
CurBlogTpl: newBlogTplObject,
IsPreview: isPreview,
}
}
return r
}
////////////////////
// 错误显示
type ErrorResult struct {
RenderArgs map[string]interface{}
Error error
IsPreview bool
CurBlogTpl *BlogTpl
}
// 错误显示出
func (r ErrorResult) Apply(req *revel.Request, resp *revel.Response) {
format := req.Format
status := resp.Status
if status == 0 {
status = http.StatusInternalServerError
}
contentType := revel.ContentTypeByFilename("xxx." + format)
if contentType == revel.DefaultFileContentType {
contentType = "text/plain"
}
// Get the error template.
var err error
templatePath := fmt.Sprintf("errors/%d.%s", status, format)
err = nil
// tmpl, err := revel.MainTemplateLoader.Template("index.html") // 这里找到错误页面主题
// This func shows a plaintext error message, in case the template rendering
// doesn't work.
showPlaintext := func(err error) {
revel.PlaintextErrorResult{fmt.Errorf("Server Error:\n%s\n\n"+
"Additionally, an error occurred when rendering the error page:\n%s",
r.Error, err)}.Apply(req, resp)
}
// 根据是否是preview来得到404模板
// 是, 则显示系统的错误信息, blog-500.html
var tmpl *template.Template
if r.IsPreview {
tmpl = r.CurBlogTpl.Template.Lookup("blog/404.html")
} else {
tmpl = r.CurBlogTpl.Template.Lookup("404.html")
}
if tmpl == nil {
if err == nil {
err = fmt.Errorf("Couldn't find template %s", templatePath)
}
showPlaintext(err)
return
}
// If it's not a revel error, wrap it in one.
var revelError *revel.Error
switch e := r.Error.(type) {
case *revel.Error:
revelError = e
case error:
revelError = &revel.Error{
Title: "Server Error",
Description: e.Error(),
}
}
if revelError == nil {
panic("no error provided")
}
if r.RenderArgs == nil {
r.RenderArgs = make(map[string]interface{})
}
r.RenderArgs["Error"] = revelError
r.RenderArgs["Router"] = revel.MainRouter
// 不是preview就不要显示错误了
LogJ(revelError)
// if r.IsPreview {
var b bytes.Buffer
out := io.Writer(resp.Out)
// out = ioutil.Discard
err = tmpl.Execute(&b, r.RenderArgs)
resp.WriteHeader(http.StatusOK, "text/html; charset=utf-8")
b.WriteTo(out)
// }
}
+10
View File
@@ -3,6 +3,7 @@ import (
"strings"
"os"
// "path/filepath"
"net"
"net/http"
"io/ioutil"
. "github.com/leanote/leanote/app/lea"
@@ -89,4 +90,13 @@ func trimQueryParams(url string) string {
url = Substr(url, 0, pos);
}
return url;
}
// 通过domain得到ip
func GetIpFromDomain(domain string) string {
ip, _ := net.LookupIP(domain)
if ip != nil && len(ip) > 0 {
return ip[0].String()
}
return ""
}
+5 -1
View File
@@ -44,8 +44,12 @@ func RouterFilter(c *revel.Controller, fc []revel.Filter) {
if route.ControllerName != "Static" {
// api设置
// leanote.com/api/user/get => ApiUser::Get
if strings.HasPrefix(path, "/api/") || strings.HasPrefix(path, "api/") {
//* /api/login ApiAuth.Login, 这里的设置, 其实已经转成了ApiAuth了
if strings.HasPrefix(path, "/api") && !strings.HasPrefix(route.ControllerName, "Api"){
route.ControllerName = "Api" + route.ControllerName
} else if strings.HasPrefix(path, "/member") && !strings.HasPrefix(route.ControllerName, "Member") {
// member设置
route.ControllerName = "Member" + route.ControllerName
}
// end
}
+4 -1
View File
@@ -35,7 +35,7 @@ func (this *AuthService) Login(emailOrUsername, pwd string) info.User {
func (this *AuthService) Register(email, pwd string) (bool, string) {
// 用户是否已存在
if userService.IsExistsUser(email) {
return false, email + " 已被注册"
return false, "userHasBeenRegistered-" + email
}
user := info.User{UserId: bson.NewObjectId(), Email: email, Username: email, Pwd: Md5(pwd)}
return this.register(user)
@@ -90,7 +90,10 @@ func (this *AuthService) register(user info.User) (bool, string) {
Title: user.Username + " 's Blog",
SubTitle: "love leanote!",
AboutMe: "Hello, I am (^_^)",
CanComment: true,
})
// 添加一个单页面
blogService.AddOrUpdateSingle(user.UserId.Hex(), "", "About Me", "Hello, I am (^_^)")
}
return true, ""
+800 -158
View File
File diff suppressed because it is too large. Load diff
+62 -19
View File
@@ -17,6 +17,9 @@ import (
// 配置服务
// 只是全局的, 用户的配置没有
type ConfigService struct {
adminUserId string
siteUrl string
adminUsername string
// 全局的
GlobalAllConfigs map[string]interface{}
GlobalStringConfigs map[string]string
@@ -24,9 +27,6 @@ type ConfigService struct {
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{}{}
@@ -35,16 +35,17 @@ func (this *ConfigService) InitGlobalConfigs() bool {
this.GlobalMapConfigs = map[string]map[string]string{}
this.GlobalArrMapConfigs = map[string][]map[string]string{}
adminUsername, _ := revel.Config.String("adminUsername")
if adminUsername == "" {
adminUsername = "admin"
this.adminUsername, _ = revel.Config.String("adminUsername")
if this.adminUsername == "" {
this.adminUsername = "admin"
}
this.siteUrl, _ = revel.Config.String("site.url")
userInfo := userService.GetUserInfoByAny(adminUsername)
userInfo := userService.GetUserInfoByAny(this.adminUsername)
if userInfo.UserId == "" {
return false
}
adminUserId = userInfo.UserId.Hex()
this.adminUserId = userInfo.UserId.Hex()
configs := []info.Config{}
db.ListByQ(db.Configs, bson.M{"UserId": userInfo.UserId}, &configs)
@@ -68,6 +69,16 @@ func (this *ConfigService) InitGlobalConfigs() bool {
return true
}
func (this *ConfigService) GetSiteUrl() string {
return this.siteUrl;
}
func (this *ConfigService) GetAdminUsername() string {
return this.adminUsername
}
func (this *ConfigService) GetAdminUserId() string {
return this.adminUserId
}
// 通用方法
func (this *ConfigService) updateGlobalConfig(userId, key string, value interface{}, isArr, isMap, isArrMap bool) bool {
// 判断是否存在
@@ -183,7 +194,7 @@ func (this *ConfigService) UpdateShareNoteConfig(registerSharedUserId string,
if registerSharedUserId == "" {
ok = true
msg = "share userId is blank, So it share nothing to register"
this.UpdateGlobalStringConfig(adminUserId, "registerSharedUserId", "")
this.UpdateGlobalStringConfig(this.adminUserId, "registerSharedUserId", "")
return
} else {
user := userService.GetUserInfo(registerSharedUserId)
@@ -192,7 +203,7 @@ func (this *ConfigService) UpdateShareNoteConfig(registerSharedUserId string,
msg = "no such user: " + registerSharedUserId
return
} else {
this.UpdateGlobalStringConfig(adminUserId, "registerSharedUserId", registerSharedUserId)
this.UpdateGlobalStringConfig(this.adminUserId, "registerSharedUserId", registerSharedUserId)
}
}
@@ -219,7 +230,7 @@ func (this *ConfigService) UpdateShareNoteConfig(registerSharedUserId string,
}
}
}
this.UpdateGlobalArrMapConfig(adminUserId, "registerSharedNotebooks", notebooks)
this.UpdateGlobalArrMapConfig(this.adminUserId, "registerSharedNotebooks", notebooks)
notes := []map[string]string{}
// 共享笔记
@@ -244,7 +255,7 @@ func (this *ConfigService) UpdateShareNoteConfig(registerSharedUserId string,
}
}
}
this.UpdateGlobalArrMapConfig(adminUserId, "registerSharedNotes", notes)
this.UpdateGlobalArrMapConfig(this.adminUserId, "registerSharedNotes", notes)
// 复制
noteIds := []string{}
@@ -265,7 +276,7 @@ func (this *ConfigService) UpdateShareNoteConfig(registerSharedUserId string,
}
}
}
this.UpdateGlobalArrayConfig(adminUserId, "registerCopyNoteIds", noteIds)
this.UpdateGlobalArrayConfig(this.adminUserId, "registerCopyNoteIds", noteIds)
ok = true
return
@@ -277,7 +288,7 @@ func (this *ConfigService) AddBackup(path, remark string) bool {
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)
return this.UpdateGlobalArrMapConfig(this.adminUserId, "backups", backups)
}
func (this *ConfigService) getBackupDirname() string {
@@ -349,7 +360,7 @@ func (this *ConfigService) Restore(createdTime string) (ok bool, msg string) {
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 kk
// 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
@@ -398,7 +409,7 @@ func (this *ConfigService) DeleteBackup(createdTime string) (bool, string) {
// 删除之
backups = append(backups[0:i], backups[i+1:]...)
ok := this.UpdateGlobalArrMapConfig(adminUserId, "backups", backups)
ok := this.UpdateGlobalArrMapConfig(this.adminUserId, "backups", backups)
return ok, ""
}
@@ -416,7 +427,7 @@ func (this *ConfigService) UpdateBackupRemark(createdTime, remark string) (bool,
}
backup["remark"] = remark;
ok := this.UpdateGlobalArrMapConfig(adminUserId, "backups", backups)
ok := this.UpdateGlobalArrMapConfig(this.adminUserId, "backups", backups)
return ok, ""
}
@@ -451,7 +462,7 @@ func init() {
port = "";
}
siteUrl, _ = revel.Config.String("site.url") // 已包含:9000, http, 去掉成 leanote.com
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://") {
@@ -461,7 +472,6 @@ func init() {
})
}
func (this *ConfigService) GetSchema() string {
return schema;
}
@@ -539,3 +549,36 @@ func (this *ConfigService) IsGoodSubDomain(domain string) bool {
}
return true
}
// 上传大小
func (this *ConfigService) GetUploadSize(key string) float64 {
f, _ := strconv.ParseFloat(this.GetGlobalStringConfig(key), 64)
return f;
}
func (this *ConfigService) GetUploadSizeLimit() map[string]float64 {
return map[string]float64{
"uploadImageSize": this.GetUploadSize("uploadImageSize"),
"uploadBlogLogoSize":this.GetUploadSize("uploadBlogLogoSize"),
"uploadAttachSize":this.GetUploadSize("uploadAttachSize"),
"uploadAvatarSize":this.GetUploadSize("uploadAvatarSize"),
}
}
// 为用户得到全局的配置
// NoteController调用
func (this *ConfigService) GetGlobalConfigForUser() map[string]interface{} {
uploadSizeConfigs := this.GetUploadSizeLimit();
config := map[string]interface{}{}
for k, v := range uploadSizeConfigs {
config[k] = v
}
return config
}
// 主页是否是管理员的博客页
func (this *ConfigService) HomePageIsAdminsBlog() bool {
return this.GetGlobalStringConfig("homePage") == ""
}
func (this *ConfigService) GetVersion() string {
return "1.0-beta2"
}
+10 -10
View File
@@ -82,9 +82,9 @@ func (this *EmailService) RegisterSendActiveEmail(userInfo info.User, email stri
return false
}
tokenUrl := siteUrl + "/user/activeEmail?token=" + token
tokenUrl := configService.GetSiteUrl() + "/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))),
token2Value := map[string]interface{}{"siteUrl": configService.GetSiteUrl(), "tokenUrl": tokenUrl, "token": token, "tokenTimeout": strconv.Itoa(int(tokenService.GetOverHours(info.TokenActiveEmail))),
"user": map[string]interface{}{
"userId": userInfo.UserId.Hex(),
"email": userInfo.Email,
@@ -122,9 +122,9 @@ func (this *EmailService) UpdateEmailSendActiveEmail(userInfo info.User, email s
tpl := configService.GetGlobalStringConfig("emailTemplateUpdateEmail");
// 发送邮件
tokenUrl := siteUrl + "/user/updateEmail?token=" + token
tokenUrl := configService.GetSiteUrl() + "/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))),
token2Value := map[string]interface{}{"siteUrl": configService.GetSiteUrl(), "tokenUrl": tokenUrl, "token": token, "tokenTimeout": strconv.Itoa(int(tokenService.GetOverHours(info.TokenActiveEmail))),
"newEmail": email,
"user": map[string]interface{}{
"userId": userInfo.UserId.Hex(),
@@ -148,9 +148,9 @@ func (this *EmailService) FindPwdSendEmail(token, email string) (ok bool, msg st
tpl := configService.GetGlobalStringConfig("emailTemplateFindPassword");
// 发送邮件
tokenUrl := siteUrl + "/findPassword/" + token
tokenUrl := configService.GetSiteUrl() + "/findPassword/" + token
// {siteUrl} {tokenUrl} {token} {tokenTimeout} {user.id} {user.email} {user.username}
token2Value := map[string]interface{}{"siteUrl": siteUrl, "tokenUrl": tokenUrl,
token2Value := map[string]interface{}{"siteUrl": configService.GetSiteUrl(), "tokenUrl": tokenUrl,
"token": token, "tokenTimeout": strconv.Itoa(int(tokenService.GetOverHours(info.TokenActiveEmail)))}
ok, msg, subject, tpl = this.renderEmail(subject, tpl, token2Value)
@@ -167,8 +167,8 @@ func (this *EmailService) SendInviteEmail(userInfo info.User, email, content str
subject := configService.GetGlobalStringConfig("emailTemplateInviteSubject");
tpl := configService.GetGlobalStringConfig("emailTemplateInvite");
token2Value := map[string]interface{}{"siteUrl": siteUrl,
"registerUrl": siteUrl + "/register?from=" + userInfo.Username,
token2Value := map[string]interface{}{"siteUrl": configService.GetSiteUrl(),
"registerUrl": configService.GetSiteUrl() + "/register?from=" + userInfo.Username,
"content": content,
"user": map[string]interface{}{
"username": userInfo.Username,
@@ -225,7 +225,7 @@ func (this *EmailService) SendCommentEmail(note info.Note, comment info.BlogComm
// {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(),
token2Value := map[string]interface{}{"siteUrl": configService.GetSiteUrl(), "blogUrl": configService.GetBlogUrl(),
"blog": map[string]string{
"id": note.NoteId.Hex(),
"title": note.Title,
@@ -316,7 +316,7 @@ func (this *EmailService) renderEmail(subject, body string, values map[string]in
var tpl *template.Template
values["siteUrl"] = siteUrl;
values["siteUrl"] = configService.GetSiteUrl();
// subject
if subject != "" {
+130
View File
@@ -0,0 +1,130 @@
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"
)
// 用户组, 用户组用户管理
type GroupService struct {
}
// 添加分组
func (this *GroupService) AddGroup(userId, title string) (bool, info.Group) {
group := info.Group {
GroupId: bson.NewObjectId(),
UserId: bson.ObjectIdHex(userId),
Title: title,
CreatedTime: time.Now(),
}
return db.Insert(db.Groups, group), group
}
// 删除分组
// 判断是否有好友
func (this *GroupService) DeleteGroup(userId, groupId string) (ok bool, msg string) {
if db.Has(db.GroupUsers, bson.M{"GroupId": bson.ObjectIdHex(groupId)}) {
return false, "hasUsers"
}
return db.DeleteByIdAndUserId(db.Groups, groupId, userId), ""
// TODO 删除分组后, 在shareNote, shareNotebook中也要删除
}
// 修改group标题
func (this *GroupService) UpdateGroupTitle(userId, groupId, title string) (ok bool) {
return db.UpdateByIdAndUserIdField(db.Groups, groupId, userId, "Title", title)
}
// 得到用户的所有分组(包括下的所有用户)
func (this *GroupService) GetGroupsAndUsers(userId string) ([]info.Group) {
// 得到分组s
groups := []info.Group{}
db.ListByQ(db.Groups, bson.M{"UserId": bson.ObjectIdHex(userId)}, &groups)
// 得到其下的用户
for i, group := range groups {
group.Users = this.GetUsers(group.GroupId.Hex())
groups[i] = group
}
return groups
}
// 仅仅得到所有分组
func (this *GroupService) GetGroups(userId string) ([]info.Group) {
// 得到分组s
groups := []info.Group{}
db.ListByQ(db.Groups, bson.M{"UserId": bson.ObjectIdHex(userId)}, &groups)
return groups
}
// 得到分组, shareService用
func (this *GroupService) GetGroup(userId, groupId string) (info.Group) {
// 得到分组s
group := info.Group{}
db.GetByIdAndUserId(db.Groups, groupId, userId, &group)
return group
}
// 得到某分组下的用户
func (this *GroupService) GetUsers(groupId string) ([]info.User) {
// 得到UserIds
groupUsers := []info.GroupUser{}
db.ListByQWithFields(db.GroupUsers, bson.M{"GroupId": bson.ObjectIdHex(groupId)}, []string{"UserId"}, &groupUsers)
if len(groupUsers) == 0 {
return nil
}
userIds := make([]bson.ObjectId, len(groupUsers))
for i, each := range groupUsers {
userIds[i] = each.UserId
}
// 得到userInfos
return userService.ListUserInfosByUserIds(userIds)
}
// 得到我所属的所有分组ids
func (this *GroupService) GetBelongToGroupIds(userId string) ([]bson.ObjectId) {
// 得到UserIds
groupUsers := []info.GroupUser{}
db.ListByQWithFields(db.GroupUsers, bson.M{"UserId": bson.ObjectIdHex(userId)}, []string{"GroupId"}, &groupUsers)
if len(groupUsers) == 0 {
return nil
}
groupIds := make([]bson.ObjectId, len(groupUsers))
for i, each := range groupUsers {
groupIds[i] = each.GroupId
}
return groupIds
}
func (this *GroupService) isMyGroup(ownUserId, groupId string) (ok bool) {
return db.Has(db.Groups, bson.M{"_id": bson.ObjectIdHex(groupId), "UserId": bson.ObjectIdHex(ownUserId)})
}
// 为group添加用户
// 用户是否已存在?
func (this *GroupService) AddUser(ownUserId, groupId, userId string) (ok bool, msg string) {
// groupId是否是ownUserId的?
if !this.isMyGroup(ownUserId, groupId) {
return false, "forbidden"
}
// 是否已存在
if db.Has(db.GroupUsers, bson.M{"GroupId": bson.ObjectIdHex(groupId), "UserId": bson.ObjectIdHex(userId)}) {
return false, "hasUsers"
}
return db.Insert(db.GroupUsers, info.GroupUser{
GroupUserId: bson.NewObjectId(),
GroupId: bson.ObjectIdHex(groupId),
UserId: bson.ObjectIdHex(userId),
CreatedTime: time.Now(),
}), ""
}
// 删除用户
func (this *GroupService) DeleteUser(ownUserId, groupId, userId string) (ok bool, msg string) {
// groupId是否是ownUserId的?
if !this.isMyGroup(ownUserId, groupId) {
return false, "forbidden"
}
return db.Delete(db.GroupUsers, bson.M{"GroupId": bson.ObjectIdHex(groupId), "UserId": bson.ObjectIdHex(userId)}), ""
}
+6 -1
View File
@@ -32,7 +32,12 @@ func (this *NoteImageService) GetNoteIds(imageId string) ([]bson.ObjectId) {
// 解析内容中的图片, 建立图片与note的关系
// <img src="/file/outputImage?fileId=12323232" />
// 图片必须是我的, 不然不添加
func (this *NoteImageService) UpdateNoteImages(userId, noteId, content string) bool {
// imgSrc 防止博客修改了, 但内容删除了
func (this *NoteImageService) UpdateNoteImages(userId, noteId, imgSrc, content string) bool {
// 让主图成为内容的一员
if imgSrc != "" {
content = "<img src=\"" + imgSrc + "\" >" + content
}
reg, _ := regexp.Compile("outputImage\\?fileId=([a-z0-9A-Z]{24})")
find := reg.FindAllStringSubmatch(content, -1) // 查找所有的
+81 -14
View File
@@ -111,10 +111,16 @@ func (this *NoteService) ListNoteAbstractsByNoteIds(noteIds []bson.ObjectId) (no
db.ListByQWithFields(db.NoteContents, bson.M{"_id": bson.M{"$in": noteIds}}, []string{"_id", "Abstract"}, &notes)
return
}
func (this *NoteService) ListNoteContentByNoteIds(noteIds []bson.ObjectId) (notes []info.NoteContent) {
notes = []info.NoteContent{}
db.ListByQWithFields(db.NoteContents, bson.M{"_id": bson.M{"$in": noteIds}}, []string{"_id", "Abstract", "Content"}, &notes)
return
}
// 添加笔记
// 首先要判断Notebook是否是Blog, 是的话设为blog
// [ok]
func (this *NoteService) AddNote(note info.Note) info.Note {
if(note.NoteId.Hex() == "") {
noteId := bson.NewObjectId();
@@ -124,6 +130,7 @@ func (this *NoteService) AddNote(note info.Note) info.Note {
note.UpdatedTime = note.CreatedTime
note.IsTrash = false
note.UpdatedUserId = note.UserId
note.UrlTitle = GetUrTitle(note.UserId.Hex(), note.Title, "note")
// 设为blog
notebookId := note.NotebookId.Hex()
@@ -164,13 +171,24 @@ func (this *NoteService) AddNoteContent(noteContent info.NoteContent) info.NoteC
db.Insert(db.NoteContents, noteContent)
// 更新笔记图片
noteImageService.UpdateNoteImages(noteContent.UserId.Hex(), noteContent.NoteId.Hex(), noteContent.Content)
noteImageService.UpdateNoteImages(noteContent.UserId.Hex(), noteContent.NoteId.Hex(), "", noteContent.Content)
return noteContent;
}
// 添加笔记和内容
// 这里使用 info.NoteAndContent 接收?
func (this *NoteService) AddNoteAndContentForController(note info.Note, noteContent info.NoteContent, updatedUserId string) info.Note {
if note.UserId.Hex() != updatedUserId {
if !shareService.HasUpdateNotebookPerm(note.UserId.Hex(), updatedUserId, note.NotebookId.Hex()) {
Log("NO AUTH11")
return info.Note{}
} else {
Log("HAS AUTH -----------")
}
}
return this.AddNoteAndContent(note, noteContent, bson.ObjectIdHex(updatedUserId));
}
func (this *NoteService) AddNoteAndContent(note info.Note, noteContent info.NoteContent, myUserId bson.ObjectId) info.Note {
if(note.NoteId.Hex() == "") {
noteId := bson.NewObjectId()
@@ -189,18 +207,24 @@ func (this *NoteService) AddNoteAndContent(note info.Note, noteContent info.Note
}
// 修改笔记
// [ok] TODO perm还没测
func (this *NoteService) UpdateNote(userId, updatedUserId, noteId string, needUpdate bson.M) bool {
// updatedUserId 要修改userId的note, 此时需要判断是否有修改权限
if userId != updatedUserId {
if !shareService.HasUpdatePerm(userId, updatedUserId, noteId) {
Log("NO AUTH")
Log("NO AUTH2")
return false
} else {
Log("HAS AUTH -----------")
}
}
// 是否已自定义
note := this.GetNoteById(noteId)
if note.IsBlog && note.HasSelfDefined {
delete(needUpdate, "ImgSrc")
delete(needUpdate, "Desc")
}
needUpdate["UpdatedUserId"] = bson.ObjectIdHex(updatedUserId);
needUpdate["UpdatedTime"] = time.Now();
@@ -244,12 +268,18 @@ func (this *NoteService) UpdateNoteContent(userId, updatedUserId, noteId, conten
}
}
if db.UpdateByIdAndUserIdMap(db.NoteContents, noteId, userId,
bson.M{"UpdatedUserId": bson.ObjectIdHex(updatedUserId),
data := bson.M{"UpdatedUserId": bson.ObjectIdHex(updatedUserId),
"Content": content,
"Abstract": abstract,
"UpdatedTime": time.Now()}) {
"UpdatedTime": time.Now()}
// 是否已自定义
note := this.GetNoteById(noteId)
if note.IsBlog && note.HasSelfDefined {
delete(data, "Abstract")
}
if db.UpdateByIdAndUserIdMap(db.NoteContents, noteId, userId, data) {
// 这里, 添加历史记录
noteContentHistoryService.AddHistory(noteId, userId, info.EachHistory{UpdatedUserId: bson.ObjectIdHex(updatedUserId),
Content: content,
@@ -257,7 +287,7 @@ func (this *NoteService) UpdateNoteContent(userId, updatedUserId, noteId, conten
})
// 更新笔记图片
noteImageService.UpdateNoteImages(userId, noteId, content)
noteImageService.UpdateNoteImages(userId, noteId, note.ImgSrc, content)
return true
}
@@ -278,6 +308,29 @@ func (this *NoteService) UpdateTags(noteId string, userId string, tags []string)
return db.UpdateByIdAndUserIdField(db.Notes, noteId, userId, "Tags", tags)
}
func (this *NoteService) ToBlog(userId, noteId string, isBlog, isTop bool) bool {
noteUpdate := bson.M{}
if isTop {
isBlog = true
}
if !isBlog {
isTop = false
}
noteUpdate["IsBlog"] = isBlog
noteUpdate["IsTop"] = isTop
if isBlog {
noteUpdate["PublicTime"] = time.Now()
} else {
noteUpdate["HasSelfDefined"] = false
}
ok := db.UpdateByIdAndUserIdMap(db.Notes, noteId, userId, noteUpdate)
// 重新计算tags
go (func() {
blogService.ReCountBlogTags(userId)
})()
return ok
}
// 移动note
// trash, 正常的都可以用
// 1. 要检查下notebookId是否是自己的
@@ -419,15 +472,20 @@ func (this *NoteService) GetNotebookId(noteId string) bson.ObjectId {
}
//------------------
// 搜索Note
// 搜索Note, 博客使用了
func (this *NoteService) SearchNote(key, userId string, pageNumber, pageSize int, sortField string, isAsc, isBlog bool) (count int, notes []info.Note) {
notes = []info.Note{}
skipNum, sortFieldR := parsePageAndSort(pageNumber, pageSize, sortField, isAsc)
// 利用标题和desc, 不用content
orQ := []bson.M{
bson.M{"Title": bson.M{"$regex": bson.RegEx{".*?" + key + ".*", "i"}}},
bson.M{"Desc": bson.M{"$regex": bson.RegEx{".*?" + key + ".*", "i"}}},
}
// 不是trash的
query := bson.M{"UserId": bson.ObjectIdHex(userId),
"IsTrash": false,
"Title": bson.M{"$regex": bson.RegEx{".*?" + key + ".*", "i"}},
"$or": orQ,
}
if isBlog {
query["IsBlog"] = true
@@ -509,11 +567,20 @@ func (this *NoteService) SearchNoteByTags(tags []string, userId string, pageNumb
return
}
//------------
// 统计
func (this *NoteService) CountNote() int {
return db.Count(db.Notes, bson.M{"IsTrash": false})
func (this *NoteService) CountNote(userId string) int {
q := bson.M{"IsTrash": false}
if userId != "" {
q["UserId"] = bson.ObjectIdHex(userId)
}
return db.Count(db.Notes, q)
}
func (this *NoteService) CountBlog() int {
return db.Count(db.Notes, bson.M{"IsBlog": true, "IsTrash": false})
func (this *NoteService) CountBlog(userId string) int {
q := bson.M{"IsBlog": true, "IsTrash": false}
if userId != "" {
q["UserId"] = bson.ObjectIdHex(userId)
}
return db.Count(db.Notes, q)
}
+39 -24
View File
@@ -101,6 +101,15 @@ func (this *NotebookService) GetNotebookById(notebookId string) info.Notebook {
db.Get(db.Notebooks, notebookId, &notebook)
return notebook
}
func (this *NotebookService) GetNotebookByUserIdAndUrlTitle(userId, notebookIdOrUrlTitle string) info.Notebook {
notebook := info.Notebook{}
if IsObjectId(notebookIdOrUrlTitle) {
db.Get(db.Notebooks, notebookIdOrUrlTitle, &notebook)
} else {
db.GetByQ(db.Notebooks, bson.M{"UserId": bson.ObjectIdHex(userId), "UrlTitle": encodeValue(notebookIdOrUrlTitle)}, &notebook)
}
return notebook
}
// 得到用户下所有的notebook
// 排序好之后返回
@@ -133,6 +142,7 @@ func (this *NotebookService) GetNotebooksByNotebookIds(notebookIds []bson.Object
// 添加
// [ok]
func (this *NotebookService) AddNotebook(notebook info.Notebook) bool {
notebook.UrlTitle = GetUrTitle(notebook.UserId.Hex(), notebook.Title, "notebook")
err := db.Notebooks.Insert(notebook)
if err != nil {
panic(err)
@@ -170,34 +180,39 @@ func (this *NotebookService) UpdateNotebookTitle(notebookId, userId, title strin
// 更新notebook
func (this *NotebookService) UpdateNotebook(userId, notebookId string, needUpdate bson.M) bool {
needUpdate["UpdatedTime"] = time.Now();
// 如果有IsBlog之类的, 需要特殊处理
if isBlog, ok := needUpdate["IsBlog"]; ok {
// 设为blog/取消, 把它下面所有的note都设为isBlog
if is, ok2 := isBlog.(bool); ok2 {
q := bson.M{"UserId": bson.ObjectIdHex(userId),
return db.UpdateByIdAndUserIdMap(db.Notebooks, notebookId, userId, needUpdate)
}
// ToBlog or Not
func (this *NotebookService) ToBlog(userId, notebookId string, isBlog bool) (bool) {
q := bson.M{"UserId": bson.ObjectIdHex(userId),
"NotebookId": bson.ObjectIdHex(notebookId)}
data := bson.M{"IsBlog": is}
if is {
data["PublicTime"] = time.Now()
}
db.UpdateByQMap(db.Notes, q, data)
// noteContents也更新, 这个就麻烦了, noteContents表没有NotebookId
// 先查该notebook下所有notes, 得到id
notes := []info.Note{}
db.ListByQWithFields(db.Notes, q, []string{"_id"}, &notes)
if len(notes) > 0 {
noteIds := make([]bson.ObjectId, len(notes))
for i, each := range notes {
noteIds[i] = each.NoteId
}
db.UpdateByQMap(db.NoteContents, bson.M{"_id": bson.M{"$in": noteIds}}, bson.M{"IsBlog": isBlog})
}
data := bson.M{"IsBlog": isBlog}
if isBlog {
data["PublicTime"] = time.Now()
} else {
data["HasSelfDefined"] = false
}
db.UpdateByQMap(db.Notes, q, data)
// noteContents也更新, 这个就麻烦了, noteContents表没有NotebookId
// 先查该notebook下所有notes, 得到id
notes := []info.Note{}
db.ListByQWithFields(db.Notes, q, []string{"_id"}, &notes)
if len(notes) > 0 {
noteIds := make([]bson.ObjectId, len(notes))
for i, each := range notes {
noteIds[i] = each.NoteId
}
db.UpdateByQMap(db.NoteContents, bson.M{"_id": bson.M{"$in": noteIds}}, bson.M{"IsBlog": isBlog})
}
return db.UpdateByIdAndUserIdMap(db.Notebooks, notebookId, userId, needUpdate)
// 重新计算tags
go (func() {
blogService.ReCountBlogTags(userId)
})()
return true
}
// 查看是否有子notebook
+277 -78
View File
@@ -13,7 +13,10 @@ import (
type ShareService struct {
}
//-----------------
//-----------------------------------
// 返回shareNotebooks, sharedUserInfos
// info.ShareNotebooksByUser, []info.User
// 总体来说, 这个方法比较麻烦, 速度未知. 以后按以下方案来缓存用户基础数据
// 以后建个用户的基本数据表, 放所有notebook, sharedNotebook的缓存!!
@@ -27,23 +30,48 @@ type ShareService struct {
// 3 每个层按seq进行排序
// 4 按用户分组
// [ok]
func (this *ShareService) GetShareNotebooks(userId string) (info.ShareNotebooksByUser, []info.User) {
//-------------
// 查询HasShareNote表得到所有其它用户信息
hasShareNotes := []info.HasShareNote{}
db.ListByQ(db.HasShareNotes, bson.M{"ToUserId": bson.ObjectIdHex(userId)}, &hasShareNotes);
userIds := make([]bson.ObjectId, len(hasShareNotes))
for i, each := range hasShareNotes {
userIds[i] = each.UserId
// 谁共享给了我的Query
func (this *ShareService) getOrQ(userId string) bson.M {
// 得到我参与的组织
groupIds := groupService.GetBelongToGroupIds(userId)
q := bson.M{}
if len(groupIds) > 0 {
orQ := []bson.M{
bson.M{"ToUserId": bson.ObjectIdHex(userId)},
bson.M{"ToGroupId": bson.M{"$in": groupIds}},
}
// 不是trash的
q["$or"] = orQ
} else {
q["ToUserId"] = bson.ObjectIdHex(userId);
}
return q
}
func (this *ShareService) GetShareNotebooks(userId string) (info.ShareNotebooksByUser, []info.User) {
// 得到共享给我的用户s信息
// 得到我参与的组织
q := this.getOrQ(userId)
// 不查hasShareNotes
// 直接查shareNotes, shareNotebooks表得到userId
userIds1 := []bson.ObjectId{}
db.Distinct(db.ShareNotes, q, "UserId", &userIds1)
userIds2 := []bson.ObjectId{}
db.Distinct(db.ShareNotebooks, q, "UserId", &userIds1)
userIds := append(userIds1, userIds2...)
userInfos := userService.GetUserInfosOrderBySeq(userIds);
//--------------------
// 得到他们共享给我的notebooks
// 这里可能会得到重复的记录
// 权限: 笔记本分享给个人 > 笔记本分享给组织
shareNotebooks := []info.ShareNotebook{}
db.ShareNotebooks.Find(bson.M{"ToUserId": bson.ObjectIdHex(userId)}).All(&shareNotebooks)
db.ShareNotebooks.Find(q).Sort("-ToUserId").All(&shareNotebooks) // 按ToUserId降序排序, 那么有ToUserId的在前面
if len(shareNotebooks) == 0 {
return nil, userInfos
@@ -52,12 +80,15 @@ func (this *ShareService) GetShareNotebooks(userId string) (info.ShareNotebooksB
shareNotebooksLen := len(shareNotebooks)
// 找到了所有的notbookId, 那么找notebook表得到其详细信息
notebookIds := make([]bson.ObjectId, shareNotebooksLen)
notebookIds := []bson.ObjectId{}
shareNotebooksMap := make(map[bson.ObjectId]info.ShareNotebook, shareNotebooksLen)
for i, each := range shareNotebooks {
// 默认的是没有notebookId的
notebookIds[i] = each.NotebookId
shareNotebooksMap[each.NotebookId] = each
for _, each := range shareNotebooks {
// 之后的就不要了, 只留权限高的
if _, ok := shareNotebooksMap[each.NotebookId]; !ok {
// 默认的是没有notebookId的
notebookIds = append(notebookIds, each.NotebookId)
shareNotebooksMap[each.NotebookId] = each
}
}
// 1, 2
@@ -127,9 +158,13 @@ func (this *ShareService) parseToSubShareNotebooks(subNotebooks *info.SubNoteboo
func (this *ShareService) ListShareNotesByNotebookId(notebookId, myUserId, sharedUserId string,
page, pageSize int, sortField string, isAsc bool) ([]info.ShareNoteWithPerm) {
// 1 首先判断是否真的sharedUserId 共享了 notebookId 给 myUserId
q := this.getOrQ(myUserId)
q["NotebookId"] = bson.ObjectIdHex(notebookId);
q["UserId"] = bson.ObjectIdHex(sharedUserId);
shareNotebook := info.ShareNotebook{}
db.GetByQ(db.ShareNotebooks, bson.M{"NotebookId": bson.ObjectIdHex(notebookId),
"UserId": bson.ObjectIdHex(sharedUserId), "ToUserId": bson.ObjectIdHex(myUserId)}, &shareNotebook)
db.GetByQ(db.ShareNotebooks,
q,
&shareNotebook)
if shareNotebook.NotebookId == "" {
return nil
@@ -146,7 +181,19 @@ func (this *ShareService) ListShareNotesByNotebookId(notebookId, myUserId, share
for i, note := range notes {
noteIds[i] = note.NoteId
}
notePerms := this.getNotesPerm(noteIds, myUserId, sharedUserId);
// 笔记的权限
shareNotes := []info.ShareNote{}
delete(q, "NotebookId")
q["NoteId"] = bson.M{"$in": noteIds}
db.ShareNotes.Find(q).Sort("-ToUserId").All(&shareNotes) // 给个的权限>给组织的权限
notePerms := map[bson.ObjectId]int{}
for _, each := range shareNotes {
if _, ok := notePerms[each.NoteId]; !ok {
notePerms[each.NoteId] = each.Perm
}
}
Log("笔记权限")
LogJ(notePerms);
// 3.2 组合
notesWithPerm := make([]info.ShareNoteWithPerm, len(notes))
@@ -162,17 +209,21 @@ func (this *ShareService) ListShareNotesByNotebookId(notebookId, myUserId, share
}
// 得到note的perm信息
func (this *ShareService) getNotesPerm(noteIds []bson.ObjectId, myUserId, sharedUserId string) map[bson.ObjectId]int {
shareNotes := []info.ShareNote{}
db.ListByQ(db.ShareNotes, bson.M{"NoteId": bson.M{"$in": noteIds}, "UserId": bson.ObjectIdHex(sharedUserId), "ToUserId": bson.ObjectIdHex(myUserId)}, &shareNotes)
notesPerm := make(map[bson.ObjectId]int, len(shareNotes))
for _, each := range shareNotes {
notesPerm[each.NoteId] = each.Perm
}
return notesPerm
}
//func (this *ShareService) getNotesPerm(noteIds []bson.ObjectId, myUserId, sharedUserId string) map[bson.ObjectId]int {
// shareNotes := []info.ShareNote{}
// db.ListByQ(db.ShareNotes,
// bson.M{
// "NoteId": bson.M{"$in": noteIds},
// "UserId": bson.ObjectIdHex(sharedUserId),
// "ToUserId": bson.ObjectIdHex(myUserId)}, &shareNotes)
//
// notesPerm := make(map[bson.ObjectId]int, len(shareNotes))
// for _, each := range shareNotes {
// notesPerm[each.NoteId] = each.Perm
// }
//
// return notesPerm
//}
// 得到默认的单个的notes 共享集
// 如果真要支持排序, 这里得到所有共享的notes, 到noteService方再sort和limit
@@ -184,9 +235,12 @@ func (this *ShareService) ListShareNotes(myUserId, sharedUserId string,
skipNum, _ := parsePageAndSort(pageNumber, pageSize, sortField, isAsc)
shareNotes := []info.ShareNote{}
q := this.getOrQ(myUserId)
q["UserId"] = bson.ObjectIdHex(sharedUserId);
db.ShareNotes.
Find(bson.M{"UserId": bson.ObjectIdHex(sharedUserId), "ToUserId": bson.ObjectIdHex(myUserId)}).
// Sort(sortFieldR).
Find(q).
Sort("-ToUserId"). // 给个人的权限 > 给组织的权限
Skip(skipNum).
Limit(pageSize).
All(&shareNotes)
@@ -200,15 +254,20 @@ func (this *ShareService) ListShareNotes(myUserId, sharedUserId string,
noteIds[i] = each.NoteId
}
notes := noteService.ListNotesByNoteIds(noteIds)
notesMap := make(map[bson.ObjectId]info.Note, len(notes))
notesMap := map[bson.ObjectId]info.Note{}
for _, each := range notes {
notesMap[each.NoteId] = each
}
// 将shareNotes与notes结合起来
notesWithPerm := make([]info.ShareNoteWithPerm, len(shareNotes))
for i, each := range shareNotes {
notesWithPerm[i] = info.ShareNoteWithPerm{notesMap[each.NoteId], each.Perm}
notesWithPerm := []info.ShareNoteWithPerm{}
hasAdded := map[bson.ObjectId]bool{} // 防止重复, 只要前面权限高的
for _, each := range shareNotes {
if !hasAdded[each.NoteId] {
// 待优化
notesWithPerm = append(notesWithPerm, info.ShareNoteWithPerm{notesMap[each.NoteId], each.Perm})
hasAdded[each.NoteId] = true
}
}
return notesWithPerm
}
@@ -286,19 +345,25 @@ func (this *ShareService) AddShareNote(noteId string, perm int, userId, email st
return db.Insert(db.ShareNotes, shareNote), "", toUserId
}
// updatedUserId是否有查看userId noteId的权限?
// userId是所有者
func (this *ShareService) HasReadPerm(userId, updatedUserId, noteId string) bool {
if !db.Has(db.ShareNotes,
bson.M{"UserId": bson.ObjectIdHex(userId), "ToUserId": bson.ObjectIdHex(updatedUserId), "NoteId": bson.ObjectIdHex(noteId)}) {
q := this.getOrQ(updatedUserId) // (toUserId == "xxx" || ToGroupId in (1, 2,3))
q["UserId"] = bson.ObjectIdHex(userId)
q["NoteId"] = bson.ObjectIdHex(noteId)
if !db.Has(db.ShareNotes, q) {
// noteId的notebookId是否被共享了?
notebookId := noteService.GetNotebookId(noteId)
if notebookId.Hex() == "" {
return false
}
delete(q, "NoteId")
q["NotebookId"] = notebookId
// 判断notebook是否被共享
if !db.Has(db.ShareNotebooks,
bson.M{"UserId": bson.ObjectIdHex(userId), "ToUserId": bson.ObjectIdHex(updatedUserId), "NotebookId": notebookId}) {
if !db.Has(db.ShareNotebooks, q) {
return false
} else {
return true
@@ -310,43 +375,44 @@ func (this *ShareService) HasReadPerm(userId, updatedUserId, noteId string) bool
// updatedUserId是否有修改userId noteId的权限?
func (this *ShareService) HasUpdatePerm(userId, updatedUserId, noteId string) bool {
// 1. noteId是否被共享了?
// 得到该note share的信息
/*
UserId bson.ObjectId `bson:"UserId"`
ToUserId bson.ObjectId `bson:"ToUserId"`
NoteId bson.ObjectId `bson:"NoteId"`
Perm int `bson:"Perm"` // 权限, 0只读, 1可修改
*/
if !db.Has(db.ShareNotes,
bson.M{"UserId": bson.ObjectIdHex(userId), "ToUserId": bson.ObjectIdHex(updatedUserId), "NoteId": bson.ObjectIdHex(noteId), "Perm": 1}) {
// noteId的notebookId是否被共享了?
notebookId := noteService.GetNotebookId(noteId)
if notebookId.Hex() == "" {
return false
}
// 判断notebook是否被共享
if !db.Has(db.ShareNotebooks,
bson.M{"UserId": bson.ObjectIdHex(userId), "ToUserId": bson.ObjectIdHex(updatedUserId), "NotebookId": notebookId, "Perm": 1}) {
return false
} else {
return true
}
} else {
return true
q := this.getOrQ(updatedUserId) // (toUserId == "xxx" || ToGroupId in (1, 2,3))
q["UserId"] = bson.ObjectIdHex(userId)
q["NoteId"] = bson.ObjectIdHex(noteId)
// note的权限
shares := []info.ShareNote{}
db.ShareNotes.Find(q).Sort("-ToUserId").All(&shares) // 个人 > 组织
for _, share := range shares {
return share.Perm == 1 // 第1个权限最大
}
// notebook的权限
notebookId := noteService.GetNotebookId(noteId)
if notebookId.Hex() == "" {
return false
}
delete(q, "NoteId")
q["NotebookId"] = notebookId
shares2 := []info.ShareNotebook{}
db.ShareNotebooks.Find(q).Sort("-ToUserId").All(&shares2) // 个人 > 组织
for _, share := range shares2 {
return share.Perm == 1 // 第1个权限最大
}
return false
}
// updatedUserId是否有修改userId notebookId的权限?
func (this *ShareService) HasUpdateNotebookPerm(userId, updatedUserId, notebookId string) bool {
// 判断notebook是否被共享
if !db.Has(db.ShareNotebooks,
bson.M{"UserId": bson.ObjectIdHex(userId), "ToUserId": bson.ObjectIdHex(updatedUserId), "NotebookId": bson.ObjectIdHex(notebookId), "Perm": 1}) {
return false
} else {
return true
}
q := this.getOrQ(updatedUserId) // (toUserId == "xxx" || ToGroupId in (1, 2,3))
q["UserId"] = bson.ObjectIdHex(userId)
q["NotebookId"] = bson.ObjectIdHex(notebookId)
shares2 := []info.ShareNotebook{}
db.ShareNotebooks.Find(q).Sort("-ToUserId").All(&shares2) // 个人 > 组织
for _, share := range shares2 {
return share.Perm == 1 // 第1个权限最大
}
return false
}
// 共享note, notebook时使用
@@ -372,12 +438,16 @@ func (this *ShareService) HasSharedNotebook(noteId, myUserId, sharedUserId strin
}
// 得到共享的笔记内容
// 首先要判断这个note是否我被共享了
// 并返回笔记的权限!!!
func (this *ShareService) GetShareNoteContent(noteId, myUserId, sharedUserId string) (noteContent info.NoteContent) {
noteContent = info.NoteContent{}
// 是否单独共享了该notebook
// 或者, 其notebook共享了我
if this.HasSharedNote(noteId, myUserId) || this.HasSharedNotebook(noteId, myUserId, sharedUserId) {
// Log(this.HasSharedNote(noteId, myUserId))
// Log(this.HasSharedNotebook(noteId, myUserId, sharedUserId))
Log(this.HasReadPerm(sharedUserId, myUserId, noteId))
if this.HasReadPerm(sharedUserId, myUserId, noteId) {
// if this.HasSharedNote(noteId, myUserId) || this.HasSharedNotebook(noteId, myUserId, sharedUserId) {
db.Get(db.NoteContents, noteId, &noteContent)
} else {
}
@@ -391,7 +461,15 @@ func (this *ShareService) GetShareNoteContent(noteId, myUserId, sharedUserId str
func (this *ShareService) ListNoteShareUserInfo(noteId, userId string) []info.ShareUserInfo {
// 得到shareNote信息, 得到所有的ToUserId
shareNotes := []info.ShareNote{}
db.ListByQLimit(db.ShareNotes, bson.M{"NoteId": bson.ObjectIdHex(noteId), "UserId": bson.ObjectIdHex(userId)}, &shareNotes, 100)
db.ListByQLimit(db.ShareNotes,
bson.M{
"NoteId": bson.ObjectIdHex(noteId),
"UserId": bson.ObjectIdHex(userId),
"ToGroupId": bson.M{"$exists": false},
}, &shareNotes, 100)
// Log("<<>>>>")
// Log(len(shareNotes))
if len(shareNotes) == 0 {
return nil
@@ -450,7 +528,11 @@ func (this *ShareService) ListNotebookShareUserInfo(notebookId, userId string) [
shareNotebooks := []info.ShareNotebook{}
db.ListByQLimit(db.ShareNotebooks,
bson.M{"NotebookId": bson.ObjectIdHex(notebookId), "UserId": bson.ObjectIdHex(userId)},
bson.M{
"NotebookId": bson.ObjectIdHex(notebookId),
"UserId": bson.ObjectIdHex(userId),
"ToGroupId": bson.M{"$exists": false},
},
&shareNotebooks, 100)
if len(shareNotebooks) == 0 {
@@ -555,7 +637,7 @@ func (this *ShareService) HasUpdateNotePerm(noteId, userId string) bool {
}
}
// 用户userId是否有修改noteId的权限
// 用户userId是否有查看noteId的权限
func (this *ShareService) HasReadNotePerm(noteId, userId string) bool {
if noteId == "" || userId == "" {
return false;
@@ -576,4 +658,121 @@ func (this *ShareService) HasReadNotePerm(noteId, userId string) bool {
} else {
return false;
}
}
}
//----------------
// 用户分组
// 得到笔记分享给的groups
func (this *ShareService) GetNoteShareGroups(noteId, userId string) []info.ShareNote {
// 得到分组s
groups := groupService.GetGroups(userId)
// 得到有分享的分组
shares := []info.ShareNote{}
db.ListByQ(db.ShareNotes,
bson.M{"NoteId": bson.ObjectIdHex(noteId), "UserId": bson.ObjectIdHex(userId), "ToGroupId": bson.M{"$exists":true}}, &shares)
mapShares := map[bson.ObjectId]info.ShareNote{}
for _, share := range shares {
mapShares[share.ToGroupId] = share
}
// 所有的groups都有share, 但没有share的group没有shareId
shares2 := make([]info.ShareNote, len(groups))
for i, group := range groups {
share, ok := mapShares[group.GroupId]
if !ok {
share = info.ShareNote{}
}
share.ToGroup = group;
shares2[i] = share
}
return shares2
}
// 共享笔记给分组
func (this *ShareService) AddShareNoteGroup(userId, noteId, groupId string, perm int) (bool) {
// 得到group, 是否是我的group
group := groupService.GetGroup(userId, groupId)
if group.GroupId == "" {
return false
}
// 先删除之
this.DeleteShareNoteGroup(userId, noteId, groupId)
shareNote := info.ShareNote{NoteId: bson.ObjectIdHex(noteId),
UserId: bson.ObjectIdHex(userId), // 冗余字段
ToGroupId: bson.ObjectIdHex(groupId),
Perm: perm,
CreatedTime: time.Now(),
}
return db.Insert(db.ShareNotes, shareNote)
}
// 删除
func (this *ShareService) DeleteShareNoteGroup(userId, noteId, groupId string) bool {
return db.Delete(db.ShareNotes, bson.M{"NoteId": bson.ObjectIdHex(noteId),
"UserId": bson.ObjectIdHex(userId),
"ToGroupId": bson.ObjectIdHex(groupId),
});
}
//-------
// 得到笔记本分享给的groups
func (this *ShareService) GetNotebookShareGroups(notebookId, userId string) []info.ShareNotebook {
// 得到分组s
groups := groupService.GetGroups(userId)
// 得到有分享的分组
shares := []info.ShareNotebook{}
db.ListByQ(db.ShareNotebooks,
bson.M{"NotebookId": bson.ObjectIdHex(notebookId), "UserId": bson.ObjectIdHex(userId), "ToGroupId": bson.M{"$exists":true}}, &shares)
mapShares := map[bson.ObjectId]info.ShareNotebook{}
for _, share := range shares {
mapShares[share.ToGroupId] = share
}
LogJ(shares)
// 所有的groups都有share, 但没有share的group没有shareId
shares2 := make([]info.ShareNotebook, len(groups))
for i, group := range groups {
share, ok := mapShares[group.GroupId]
if !ok {
share = info.ShareNotebook{}
}
share.ToGroup = group;
shares2[i] = share
}
return shares2
}
// 共享笔记给分组
func (this *ShareService) AddShareNotebookGroup(userId, notebookId, groupId string, perm int) (bool) {
// 得到group, 是否是我的group
group := groupService.GetGroup(userId, groupId)
if group.GroupId == "" {
return false
}
// 先删除之
this.DeleteShareNotebookGroup(userId, notebookId, groupId)
shareNotebook := info.ShareNotebook{NotebookId: bson.ObjectIdHex(notebookId),
UserId: bson.ObjectIdHex(userId), // 冗余字段
ToGroupId: bson.ObjectIdHex(groupId),
Perm: perm,
CreatedTime: time.Now(),
}
return db.Insert(db.ShareNotebooks, shareNotebook)
}
// 删除
func (this *ShareService) DeleteShareNotebookGroup(userId, notebookId, groupId string) bool {
return db.Delete(db.ShareNotebooks, bson.M{"NotebookId": bson.ObjectIdHex(notebookId),
"UserId": bson.ObjectIdHex(userId),
"ToGroupId": bson.ObjectIdHex(groupId),
});
}
+507
View File
@@ -0,0 +1,507 @@
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"
"github.com/leanote/leanote/app/lea/archive"
"gopkg.in/mgo.v2/bson"
"time"
"strings"
"os"
"fmt"
"html/template"
"regexp"
"encoding/json"
)
// 主题
type ThemeService struct {
}
var defaultStyle = "blog_default"
var elegantStyle = "blog_daqi"
var fixedStyle = "blog_left_fixed"
// admin用户的主题基路径
func (this *ThemeService) getDefaultThemeBasePath() string {
return revel.BasePath + "/public/blog/themes"
}
// 默认主题路径
func (this *ThemeService) getDefaultThemePath(style string) string {
if style == elegantStyle {
return this.getDefaultThemeBasePath() + "/elegant"
} else if style == fixedStyle {
return this.getDefaultThemeBasePath() + "/nav_fixed"
} else {
return this.getDefaultThemeBasePath() + "/default"
}
}
// blogService用
func (this *ThemeService) GetDefaultThemePath(style string) string {
if style == elegantStyle {
return "public/blog/themes/elegant"
} else if style == fixedStyle {
return "public/blog/themes/nav_fixed"
} else {
return "public/blog/themes/default"
}
}
// 得到默认主题
// style是之前的值, 有3个值 blog_default, blog_daqi, blog_left_fixed
func (this *ThemeService) getDefaultTheme(style string) info.Theme {
if style == elegantStyle {
return info.Theme{
IsDefault: true,
Path: "public/blog/themes/elegant",
Name: "leanote elegant",
Author: "leanote",
AuthorUrl: "http://leanote.com",
Version: "1.0",
}
} else if style == fixedStyle {
return info.Theme{
IsDefault: true,
Path: "public/blog/themes/nav_fixed",
Name: "leanote nav fixed",
Author: "leanote",
AuthorUrl: "http://leanote.com",
Version: "1.0",
}
} else { // blog default
return info.Theme{
IsDefault: true,
Path: "public/blog/themes/default",
Name: "leanote default",
Author: "leanote",
AuthorUrl: "http://leanote.com",
Version: "1.0",
}
}
}
// 用户的主题路径设置
func (this *ThemeService) getUserThemeBasePath(userId string) string {
return revel.BasePath + "/public/upload/" + userId + "/themes"
}
func (this *ThemeService) getUserThemePath(userId, themeId string) string {
return this.getUserThemeBasePath(userId) + "/" + themeId
}
func (this *ThemeService) getUserThemePath2(userId, themeId string) string {
return "public/upload/" + userId + "/themes/" + themeId
}
// 新建主题
// 复制默认主题到文件夹下
func (this *ThemeService) CopyDefaultTheme(userBlog info.UserBlog) (ok bool, themeId string) {
newThemeId := bson.NewObjectId()
themeId = newThemeId.Hex()
userId := userBlog.UserId.Hex()
themePath := this.getUserThemePath(userId, themeId)
err := os.MkdirAll(themePath, 0755)
if err != nil {
return
}
// 复制默认主题
defaultThemePath := this.getDefaultThemePath(userBlog.Style)
err = CopyDir(defaultThemePath, themePath)
if err != nil {
return
}
// 保存到数据库中
theme, _ := this.getThemeConfig(themePath)
theme.ThemeId = newThemeId
theme.Path = this.getUserThemePath2(userId, themeId)
theme.CreatedTime = time.Now()
theme.UpdatedTime = theme.CreatedTime
theme.UserId = bson.ObjectIdHex(userId)
ok = db.Insert(db.Themes, theme)
return ok, themeId
}
// 第一次新建主题
// 设为active true
func (this *ThemeService) NewThemeForFirst(userBlog info.UserBlog) (ok bool, themeId string) {
ok, themeId = this.CopyDefaultTheme(userBlog)
db.UpdateByQField(db.Themes, bson.M{"_id": bson.ObjectIdHex(themeId)}, "IsActive", true)
return
}
// 新建主题, 判断是否有主题了
func (this *ThemeService) NewTheme(userId string) (ok bool, themeId string) {
userBlog := blogService.GetUserBlog(userId)
// 如果还没有主题, 那先复制旧的主题
if userBlog.ThemeId == "" {
themeService.NewThemeForFirst(userBlog)
}
// 再copy一个默认主题
userBlog.Style = "defaultStyle";
ok, themeId = this.CopyDefaultTheme(userBlog)
return
}
// 将字符串转成Theme配置
func (this *ThemeService) parseConfig(configStr string) (theme info.Theme, err error) {
theme = info.Theme{}
// 除去/**/注释
reg, _ := regexp.Compile("/\\*[\\s\\S]*?\\*/")
configStr = reg.ReplaceAllString(configStr, "")
// 转成map
config := map[string]interface{}{}
err = json.Unmarshal([]byte(configStr), &config)
if err != nil {
return
}
// 没有错, 则将Name, Version, Author, AuthorUrl
Name := config["Name"]
if Name != nil {
theme.Name = Name.(string)
}
Version := config["Version"]
if Version != nil {
theme.Version = Version.(string)
}
Author := config["Author"]
if Author != nil {
theme.Author = Author.(string)
}
AuthorUrl := config["AuthorUrl"]
if AuthorUrl != nil {
theme.AuthorUrl = AuthorUrl.(string)
}
theme.Info = config
return
}
// 读取theme.json得到值
func (this *ThemeService) getThemeConfig(themePath string) (theme info.Theme, err error) {
theme = info.Theme{}
configStr := GetFileStrContent(themePath + "/theme.json")
theme, err = this.parseConfig(configStr)
return
}
func (this *ThemeService) GetTheme(userId, themeId string) info.Theme {
theme := info.Theme{}
db.GetByQ(db.Themes, bson.M{"_id": bson.ObjectIdHex(themeId), "UserId": bson.ObjectIdHex(userId)}, &theme)
return theme
}
func (this *ThemeService) GetThemeById(themeId string) info.Theme {
theme := info.Theme{}
db.GetByQ(db.Themes, bson.M{"_id": bson.ObjectIdHex(themeId)}, &theme)
return theme
}
// 得到主题信息, 为了给博客用
func (this *ThemeService) GetThemeInfo(themeId, style string) map[string]interface{} {
q := bson.M{}
if themeId == "" {
if style == "" {
style = defaultStyle
}
q["Style"] = style
q["IsDefault"] = true
} else {
q["_id"] = bson.ObjectIdHex(themeId)
}
theme := info.Theme{}
db.GetByQ(db.Themes, q, &theme)
return theme.Info
}
// 得到用户的主题
// 若用户没有主题, 则至少有一个默认主题
// 第一个返回值是当前active
func (this *ThemeService) GetUserThemes(userId string) (theme info.Theme, themes []info.Theme) {
theme = info.Theme{}
themes = []info.Theme{}
// db.ListByQ(db.Themes, bson.M{"UserId": bson.ObjectIdHex(userId)}, &themes)
// 创建时间逆序
query := bson.M{"UserId": bson.ObjectIdHex(userId)}
q := db.Themes.Find(query);
q.Sort("-CreatedTime").All(&themes)
if len(themes) == 0 {
userBlog := blogService.GetUserBlog(userId)
theme = this.getDefaultTheme(userBlog.Style)
} else {
var has = false
// 第一个是active的主题
themes2 := make([]info.Theme, len(themes))
i := 0
for _, t := range themes {
if t.IsActive {
theme = t
} else {
has = true
themes2[i] = t
i++
}
}
if has {
themes = themes2
} else {
themes = nil
}
}
return
}
// 得到默认主题供选择
func (this *ThemeService) GetDefaultThemes() (themes []info.Theme) {
themes = []info.Theme{}
db.ListByQ(db.Themes, bson.M{"IsDefault": true}, &themes)
return
}
// 得到模板内容
func (this *ThemeService) GetTplContent(userId, themeId, filename string) string {
path := this.GetThemeAbsolutePath(userId, themeId) + "/" + filename
return GetFileStrContent(path)
}
// 得到主题的绝对路径
func (this *ThemeService) GetThemeAbsolutePath(userId, themeId string) string {
theme := info.Theme{}
db.GetByQWithFields(db.Themes, bson.M{"_id": bson.ObjectIdHex(themeId), "UserId": bson.ObjectIdHex(userId)}, []string{"Path"}, &theme)
if theme.Path != "" {
return revel.BasePath + "/" + theme.Path
}
return ""
}
func (this *ThemeService) GetThemePath(userId, themeId string) string {
theme := info.Theme{}
db.GetByQWithFields(db.Themes, bson.M{"_id": bson.ObjectIdHex(themeId), "UserId": bson.ObjectIdHex(userId)}, []string{"Path"}, &theme)
if theme.Path != "" {
return theme.Path
}
return ""
}
// 更新模板内容
func (this *ThemeService) UpdateTplContent(userId, themeId, filename, content string) (ok bool, msg string) {
path := this.GetThemeAbsolutePath(userId, themeId) + "/" + filename
if strings.Contains(filename, ".html") {
// 模板
if ok, msg = this.mustTpl(filename, content); ok {
ok = PutFileStrContent(path, content)
}
return
} else if filename == "theme.json" {
// 主题配置, 判断是否是正确的json
theme, err := this.parseConfig(content)
if err != nil {
return false, fmt.Sprintf("%v", err)
}
// 正确, 更新theme信息
ok = db.UpdateByQMap(db.Themes, bson.M{"_id": bson.ObjectIdHex(themeId), "UserId": bson.ObjectIdHex(userId)},
bson.M{
"Name": theme.Name,
"Version": theme.Version,
"Author": theme.Author,
"AuthorUrl": theme.AuthorUrl,
"Info": theme.Info,
})
if ok {
ok = PutFileStrContent(path, content)
}
return
}
ok = PutFileStrContent(path, content)
return
}
func (this *ThemeService) DeleteTpl(userId, themeId, filename string) (ok bool) {
path := this.GetThemeAbsolutePath(userId, themeId) + "/" + filename
ok = DeleteFile(path)
return
}
// 判断是否有语法错误
func (this *ThemeService) mustTpl(filename, content string) (ok bool, msg string) {
ok = true
defer func() {
if err := recover(); err != nil {
ok = false
Log(err)
msg = fmt.Sprintf("%v", err)
}
}()
template.Must(template.New(filename).Funcs(revel.TemplateFuncs).Parse(content))
return
}
/////////
// 使用主题
func (this *ThemeService) ActiveTheme(userId, themeId string) (ok bool) {
if db.Has(db.Themes, bson.M{"_id": bson.ObjectIdHex(themeId), "UserId": bson.ObjectIdHex(userId)}) {
// 之前的设为false
db.UpdateByQField(db.Themes, bson.M{"UserId": bson.ObjectIdHex(userId), "IsActive": true}, "IsActive", false)
// 现在的设为true
db.UpdateByQField(db.Themes, bson.M{"_id": bson.ObjectIdHex(themeId)}, "IsActive", true)
// UserBlog ThemeId
db.UpdateByQField(db.UserBlogs, bson.M{"_id": bson.ObjectIdHex(userId)}, "ThemeId", bson.ObjectIdHex(themeId))
return true
}
return false
}
// 删除主题
func (this *ThemeService) DeleteTheme(userId, themeId string) (ok bool) {
return db.Delete(db.Themes,bson.M{"_id": bson.ObjectIdHex(themeId), "UserId": bson.ObjectIdHex(userId), "IsActive": false})
}
// 公开主题, 只有管理员才有权限, 之前没公开的变成公开
func (this *ThemeService) PublicTheme(userId, themeId string) (ok bool) {
// 是否是管理员?
userInfo := userService.GetUserInfo(userId)
if userInfo.Username == configService.GetAdminUsername() {
theme := this.GetThemeById(themeId)
return db.UpdateByQField(db.Themes, bson.M{"UserId": bson.ObjectIdHex(userId), "_id": bson.ObjectIdHex(themeId)}, "IsDefault", !theme.IsDefault)
}
return false
}
// 导出主题
func (this *ThemeService) ExportTheme(userId, themeId string) (ok bool, path string) {
theme := this.GetThemeById(themeId)
// 打包
// 验证路径, 别把整个项目打包了
Log(theme.Path)
if theme.Path == "" ||
(!strings.HasPrefix(theme.Path, "public/upload") &&
!strings.HasPrefix(theme.Path, "public/blog/themes")) ||
strings.Contains(theme.Path, "..") {
return
}
sourcePath := revel.BasePath + "/" + theme.Path
targetPath := revel.BasePath + "/public/upload/" + userId + "/tmp"
err := os.MkdirAll(targetPath, 0755)
if err != nil {
Log(err)
return
}
targetName := targetPath + "/" + theme.Name + ".zip"
Log(sourcePath)
Log(targetName)
ok = archive.Zip(sourcePath, targetName)
if !ok {
return
}
return true, targetName
}
// 导入主题
// path == /llllllll/..../public/upload/.../aa.zip, 绝对路径
func (this *ThemeService) ImportTheme(userId, path string) (ok bool, msg string) {
themeIdO := bson.NewObjectId()
themeId := themeIdO.Hex()
targetPath := this.getUserThemePath(userId, themeId) // revel.BasePath + "/public/upload/" + userId + "/themes/" + themeId
if ok, msg = archive.Unzip(path, targetPath); !ok {
DeleteFile(targetPath)
return
}
// 解压成功, 那么新建之
// 保存到数据库中
theme, _ := this.getThemeConfig(targetPath)
if theme.Name == "" {
ok = false
DeleteFile(targetPath)
msg = "解析错误"
return
}
theme.ThemeId = themeIdO
theme.Path = this.getUserThemePath2(userId, themeId)
theme.CreatedTime = time.Now()
theme.UpdatedTime = theme.CreatedTime
theme.UserId = bson.ObjectIdHex(userId)
ok = db.Insert(db.Themes, theme)
if !ok {
DeleteFile(targetPath)
}
DeleteFile(path)
return
}
// 升级用
// public/
func (this *ThemeService) UpgradeThemeBeta2() (ok bool) {
adminUserId := configService.GetAdminUserId()
this.upgradeThemeBeta2(adminUserId, this.GetDefaultThemePath(defaultStyle), true)
this.upgradeThemeBeta2(adminUserId, this.GetDefaultThemePath(elegantStyle), false)
this.upgradeThemeBeta2(adminUserId, this.GetDefaultThemePath(fixedStyle), false)
return true
}
func (this *ThemeService) upgradeThemeBeta2(userId, targetPath string, isActive bool) (ok bool) {
// 解压成功, 那么新建之
// 保存到数据库中
theme, _ := this.getThemeConfig(revel.BasePath + "/" + targetPath)
if theme.Name == "" {
ok = false
return
}
themeIdO := bson.NewObjectId()
theme.ThemeId = themeIdO
theme.Path = targetPath // public
theme.CreatedTime = time.Now()
theme.UpdatedTime = theme.CreatedTime
theme.UserId = bson.ObjectIdHex(userId)
theme.IsActive = isActive
theme.IsDefault = true
ok = db.Insert(db.Themes, theme)
return ok
}
// 安装主题
// 得到该主题路径
func (this *ThemeService) InstallTheme(userId, themeId string) (ok bool) {
theme := this.GetThemeById(themeId)
// 不是默认主题, 即不是admin用户的主题, 不能乱安装
if !theme.IsDefault {
return false
}
// 用户之前是否有主题?
userBlog := blogService.GetUserBlog(userId)
if userBlog.ThemeId == "" {
this.NewThemeForFirst(userBlog)
}
// 生成新主题
newThemeId := bson.NewObjectId()
themeId = newThemeId.Hex()
themePath := this.getUserThemePath(userId, themeId)
err := os.MkdirAll(themePath, 0755)
if err != nil {
return
}
// 复制默认主题
sourceThemePath := revel.BasePath + "/" + theme.Path
err = CopyDir(sourceThemePath, themePath)
if err != nil {
return
}
// 保存到数据库中
theme, _ = this.getThemeConfig(themePath)
theme.ThemeId = newThemeId
theme.Path = this.getUserThemePath2(userId, themeId)
theme.CreatedTime = time.Now()
theme.UpdatedTime = theme.CreatedTime
theme.UserId = bson.ObjectIdHex(userId)
ok = db.Insert(db.Themes, theme)
return ok
}
+82 -3
View File
@@ -19,9 +19,88 @@ func (this *UpgradeService) UpgradeBlog() bool {
// 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())
if note.IsBlog && note.PublicTime.Year() < 100 {
db.UpdateByIdAndUserIdMap2(db.Notes, note.NoteId, note.UserId, bson.M{"PublicTime": note.UpdatedTime, "RecommendTime": note.UpdatedTime})
Log(note.NoteId.Hex())
}
}
return true
}
}
// 11-5自定义博客升级, 将aboutMe移至pages
/*
<li>Migrate "About me" to single(a single post)</li>
<li>Add some default themes to administrator</li>
<li>Generate "UrlTitle" for all notes. "UrlTitle" is a friendly url for post</li>
<li>Generate "UrlTitle" for all notebooks</li>
<li>Generate "UrlTitle" for all singles</li>
*/
func (this *UpgradeService) UpgradeBetaToBeta2(userId string) (ok bool, msg string) {
if configService.GetGlobalStringConfig("UpgradeBetaToBeta2") != "" {
return false, "已升级"
}
// 1. aboutMe -> page
userBlogs := []info.UserBlog{}
db.ListByQ(db.UserBlogs, bson.M{}, &userBlogs)
for _, userBlog := range userBlogs {
blogService.AddOrUpdateSingle(userBlog.UserId.Hex(), "", "About Me", userBlog.AboutMe)
}
// 2. 默认主题, 给admin用户
themeService.UpgradeThemeBeta2()
// 3. UrlTitles
// 3.1 note
notes := []info.Note{}
db.ListByQ(db.Notes, bson.M{}, &notes)
for _, note := range notes {
data := bson.M{}
noteId := note.NoteId.Hex()
// PublicTime, RecommendTime = UpdatedTime
if note.IsBlog && note.PublicTime.Year() < 100 {
data["PublicTime"] = note.UpdatedTime
data["RecommendTime"] = note.UpdatedTime
Log("Time " + noteId)
}
data["UrlTitle"] = GetUrTitle(note.UserId.Hex(), note.Title, "note")
db.UpdateByIdAndUserIdMap2(db.Notes, note.NoteId, note.UserId, data)
Log(noteId)
}
// 3.2
Log("notebook")
notebooks := []info.Notebook{}
db.ListByQ(db.Notebooks, bson.M{}, &notebooks)
for _, notebook := range notebooks {
notebookId := notebook.NotebookId.Hex()
data := bson.M{}
data["UrlTitle"] = GetUrTitle(notebook.UserId.Hex(), notebook.Title, "notebook")
db.UpdateByIdAndUserIdMap2(db.Notebooks, notebook.NotebookId, notebook.UserId, data)
Log(notebookId)
}
// 3.3 single
/*
singles := []info.BlogSingle{}
db.ListByQ(db.BlogSingles, bson.M{}, &singles)
for _, single := range singles {
singleId := single.SingleId.Hex()
blogService.UpdateSingleUrlTitle(single.UserId.Hex(), singleId, single.Title)
Log(singleId)
}
*/
// 删除索引
db.ShareNotes.DropIndex("UserId", "ToUserId", "NoteId")
ok = true
msg = "success"
configService.UpdateGlobalStringConfig(userId, "UpgradeBetaToBeta2", "1")
return
}
+103 -7
View File
@@ -26,7 +26,7 @@ func (this *UserService) AddUser(user info.User) bool {
go func() {
emailService.RegisterSendActiveEmail(user, user.Email)
// 发送给我 life@leanote.com
// emailService.SendEmail("life@leanote.com", "新增用户", "{header}用户名" + user.Email + "{footer}");
emailService.SendEmail("life@leanote.com", "新增用户", "{header}用户名" + user.Email + "{footer}");
}();
}
@@ -41,6 +41,13 @@ func (this *UserService) GetUserId(email string) string {
return user.UserId.Hex()
}
// 得到用户名
func (this *UserService) GetUsername(userId string) string {
user := info.User{}
db.GetByQWithFields(db.Users, bson.M{"_id": bson.ObjectIdHex(userId)}, []string{"Username"}, &user)
return user.Username
}
// 是否存在该用户 email
func (this *UserService) IsExistsUser(email string) bool {
if this.GetUserId(email) == "" {
@@ -75,7 +82,7 @@ func (this *UserService) setUserLogo(user *info.User) {
}
if user.Logo != "" && !strings.HasPrefix(user.Logo, "http") {
user.Logo = strings.Trim(user.Logo, "/")
user.Logo = siteUrl + "/" + user.Logo
user.Logo = configService.GetSiteUrl() + "/" + user.Logo
}
}
@@ -133,6 +140,58 @@ func (this *UserService) MapUserInfoAndBlogInfosByUserIds(userIds []bson.ObjectI
return this.MapUserInfoByUserIds(userIds)
}
// 返回info.UserAndBlog
func (this *UserService) MapUserAndBlogByUserIds(userIds []bson.ObjectId) map[string]info.UserAndBlog {
users := []info.User{}
db.ListByQ(db.Users, bson.M{"_id": bson.M{"$in": userIds}}, &users)
userBlogs := []info.UserBlog{}
db.ListByQ(db.UserBlogs, bson.M{"_id": bson.M{"$in": userIds}}, &userBlogs)
userBlogMap := make(map[bson.ObjectId]info.UserBlog, len(userBlogs))
for _, user := range userBlogs {
userBlogMap[user.UserId] = user
}
userAndBlogMap := make(map[string]info.UserAndBlog, len(users))
for _, user := range users {
this.setUserLogo(&user)
userBlog, ok := userBlogMap[user.UserId]
if !ok {
continue
}
userAndBlogMap[user.UserId.Hex()] = info.UserAndBlog{
UserId: user.UserId,
Username: user.Username,
Email: user.Email,
Logo: user.Logo,
BlogTitle: userBlog.Title,
BlogLogo: userBlog.Logo,
BlogUrl: blogService.GetUserBlogUrl(&userBlog, user.Username),
}
}
return userAndBlogMap
}
// 得到userAndBlog公开信息
func (this *UserService) GetUserAndBlog(userId string) info.UserAndBlog {
user := this.GetUserInfo(userId)
userBlog := blogService.GetUserBlog(userId)
return info.UserAndBlog{
UserId: user.UserId,
Username: user.Username,
Email: user.Email,
Logo: user.Logo,
BlogTitle: userBlog.Title,
BlogLogo: userBlog.Logo,
BlogUrl: blogService.GetUserBlogUrl(&userBlog, user.Username),
BlogUrls: blogService.GetBlogUrls(&userBlog, &user),
}
}
// 通过ids得到users, 按id的顺序组织users
func (this *UserService) GetUserInfosOrderBySeq(userIds []bson.ObjectId) []info.User {
users := []info.User{}
@@ -143,9 +202,11 @@ func (this *UserService) GetUserInfosOrderBySeq(userIds []bson.ObjectId) []info.
usersMap[user.UserId] = user
}
hasAppend := map[bson.ObjectId]bool{} // 为了防止userIds有重复的
users2 := []info.User{};
for _, userId := range userIds {
if user, ok := usersMap[userId]; ok {
if user, ok := usersMap[userId]; ok && !hasAppend[userId] {
hasAppend[userId] = true
users2 = append(users2, user)
}
}
@@ -169,7 +230,7 @@ func (this *UserService) LoginGetUserInfo(emailOrUsername, md5Pwd string) info.U
// 更新username
func (this *UserService) UpdateUsername(userId, username string) (bool, string) {
if userId == "" || username == "" || username == "admin" { // admin用户是内置的, 不能设置
return false, "用户已存在"
return false, "usernameIsExisted"
}
usernameRaw := username // 原先的, 可能是同一个, 但有大小写
username = strings.ToLower(username)
@@ -177,7 +238,7 @@ func (this *UserService) UpdateUsername(userId, username string) (bool, string)
// 先判断是否存在
userIdO := bson.ObjectIdHex(userId)
if db.Has(db.Users, bson.M{"Username": username, "_id": bson.M{"$ne": userIdO}}) {
return false, "用户已存在"
return false, "usernameIsExisted"
}
ok := db.UpdateByQMap(db.Users, bson.M{"_id": userIdO}, bson.M{"Username": username, "UsernameRaw": usernameRaw})
@@ -195,18 +256,42 @@ func (this *UserService) UpdateAvatar(userId, avatarPath string) (bool) {
func (this *UserService) UpdatePwd(userId, oldPwd, pwd string) (bool, string) {
userInfo := this.GetUserInfo(userId)
if userInfo.Pwd != Md5(oldPwd) {
return false, "旧密码错误"
return false, "oldPasswordError"
}
ok := db.UpdateByQField(db.Users, bson.M{"_id": bson.ObjectIdHex(userId)}, "Pwd", Md5(pwd))
return ok, ""
}
// 管理员重置密码
func (this *UserService) ResetPwd(adminUserId, userId, pwd string) (ok bool, msg string) {
if configService.GetAdminUserId() != adminUserId {
return
}
ok = db.UpdateByQField(db.Users, bson.M{"_id": bson.ObjectIdHex(userId)}, "Pwd", Md5(pwd))
return
}
// 修改主题
func (this *UserService) UpdateTheme(userId, theme string) (bool) {
ok := db.UpdateByQField(db.Users, bson.M{"_id": bson.ObjectIdHex(userId)}, "Theme", theme)
return ok
}
// 帐户类型设置
func (this *UserService) UpdateAccount(userId, accountType string, accountStartTime, accountEndTime time.Time,
maxImageNum, maxImageSize, maxAttachNum, maxAttachSize, maxPerAttachSize int) bool {
return db.UpdateByQI(db.Users, bson.M{"_id": bson.ObjectIdHex(userId)}, info.UserAccount{
AccountType: accountType,
AccountStartTime: accountStartTime,
AccountEndTime: accountEndTime,
MaxImageNum: maxImageNum,
MaxImageSize: maxImageSize,
MaxAttachNum: maxAttachNum,
MaxAttachSize: maxAttachSize,
MaxPerAttachSize: maxPerAttachSize,
})
}
//---------------
// 修改email
@@ -261,6 +346,13 @@ func (this *UserService) UpdateEmail(token string) (ok bool, msg, email string)
//---------
// 第三方添加账号
func (this *UserService) ThirdAddUser(userId, email, pwd string) (ok bool, msg string) {
// 判断该用户是否已有了帐户
userInfo := this.GetUserInfo(userId)
if userInfo.Email != "" {
msg = "你已有帐户"
return
}
// 判断email是否存在
if this.IsExistsUser(email) {
msg = "该用户已存在"
@@ -291,7 +383,11 @@ func (this *UserService) ListUsers(pageNumber, pageSize int, sortField string, i
skipNum, sortFieldR := parsePageAndSort(pageNumber, pageSize, sortField, isAsc)
query := bson.M{}
if email != "" {
query["Email"] = bson.M{"$regex": bson.RegEx{".*?" + email + ".*", "i"}}
orQ := []bson.M{
bson.M{"Email": bson.M{"$regex": bson.RegEx{".*?" + email + ".*", "i"}}},
bson.M{"Username": bson.M{"$regex": bson.RegEx{".*?" + email + ".*", "i"}}},
}
query["$or"] = orQ
}
q := db.Users.Find(query);
// 总记录数
+80 -6
View File
@@ -1,7 +1,13 @@
package service
import (
"github.com/revel/revel"
"regexp"
"strings"
"net/url"
"strconv"
"gopkg.in/mgo.v2"
"github.com/leanote/leanote/app/db"
"gopkg.in/mgo.v2/bson"
)
// init service, for share service bettween services
@@ -14,6 +20,7 @@ var noteContentHistoryService, NoteContentHistoryS *NoteContentHistoryService
var trashService, TrashS *TrashService
var shareService, ShareS *ShareService
var userService, UserS *UserService
var groupService, GroupS *GroupService
var tagService, TagS *TagService
var blogService, BlogS *BlogService
var tokenService, TokenS *TokenService
@@ -28,8 +35,7 @@ var emailService, EmailS *EmailService
var AuthS *AuthService
var UpgradeS *UpgradeService
var SessionS, sessionService *SessionService
var siteUrl string
var ThemeS, themeService *ThemeService
// onAppStart调用
func InitService() {
@@ -39,6 +45,7 @@ func InitService() {
TrashS = &TrashService{}
ShareS = &ShareService{}
UserS = &UserService{}
GroupS = &GroupService{}
TagS = &TagService{}
BlogS = &BlogService{}
TokenS = &TokenService{}
@@ -53,6 +60,7 @@ func InitService() {
EmailS = NewEmailService()
UpgradeS = &UpgradeService{}
SessionS = &SessionService{}
ThemeS = &ThemeService{}
notebookService = NotebookS
noteService = NoteS
@@ -60,6 +68,7 @@ func InitService() {
trashService = TrashS
shareService = ShareS
userService = UserS
groupService = GroupS
tagService = TagS
blogService = BlogS
tokenService = TokenS
@@ -70,7 +79,72 @@ func InitService() {
configService = ConfigS
emailService = EmailS
sessionService = SessionS
themeService = ThemeS
}
//----------------
// service 公用方法
// 将name=val的val进行encoding
func decodeValue(val string) string {
v, _ := url.ParseQuery("a=" + val)
return v.Get("a")
}
func encodeValue(val string) string {
if val == "" {
return val
}
v := url.Values{}
v.Set("", val)
return v.Encode()[1:]
}
// 添加笔记时通过title得到urlTitle
func fixUrlTitle(urlTitle string) string {
if urlTitle != "" {
// 把特殊字段给替换掉
// str := `life "%&()+,/:;<>=?@\|`
reg, _ := regexp.Compile("/|#|\\$|!|\\^|\\*|'| |\"|%|&|\\(|\\)|\\+|\\,|/|:|;|<|>|=|\\?|@|\\||\\\\")
urlTitle = reg.ReplaceAllString(urlTitle, "-")
urlTitle = strings.Trim(urlTitle, "-") // 左右单独的-去掉
// 把空格替换成-
// urlTitle = strings.Replace(urlTitle, " ", "-", -1)
for strings.Index(urlTitle, "--") >= 0 { // 防止出现连续的--
urlTitle = strings.Replace(urlTitle, "--", "-", -1)
}
return encodeValue(urlTitle)
}
return urlTitle
}
func getUniqueUrlTitle(userId string, urlTitle string, types string, padding int) string {
urlTitle2 := urlTitle
if padding > 1 {
urlTitle2 = urlTitle + "-" + strconv.Itoa(padding)
}
userIdO := bson.ObjectIdHex(userId)
//
siteUrl, _ = revel.Config.String("site.url")
}
var collection *mgo.Collection
if types == "note" {
collection = db.Notes
} else if types == "notebook" {
collection = db.Notebooks
} else if types == "single" {
collection = db.BlogSingles
}
for db.Has(collection, bson.M{"UserId": userIdO, "UrlTitle": urlTitle2}) { // 用户下唯一
padding++
urlTitle2 = urlTitle + "-" + strconv.Itoa(padding)
}
return urlTitle2
}
// types == note,notebook,single
func GetUrTitle(userId string, title string, types string) string {
urlTitle := strings.Trim(title, " ")
if urlTitle == "" {
urlTitle = "Untitled-" + userId
}
urlTitle = fixUrlTitle(urlTitle)
return getUniqueUrlTitle(userId, urlTitle, types, 1)
}
-3
View File
@@ -97,9 +97,6 @@
</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">
{{set . "url" (urlConcat "/adminBlog/index" "sorter" .sorter "keywords" .keywords)}}
+45 -2
View File
@@ -128,10 +128,18 @@
</i>
</span>
<span>
Setting
Configuration
</span>
</a>
<ul class="nav lt">
<li>
<a href="/admin/t?t=setting/home_page">
<span>
Home Page
</span>
</a>
</li>
<li>
<a href="/admin/t?t=setting/demo">
<span>
@@ -141,7 +149,7 @@
</li>
<li>
<a href="/admin/t?t=setting/openRegister">
<a href="/admin/t?t=setting/open_register">
<span>
Open Register
</span>
@@ -155,6 +163,14 @@
</span>
</a>
</li>
<li>
<a href="/admin/t?t=setting/upload">
<span>
Upload File Size Limit
</span>
</a>
</li>
</ul>
</li>
@@ -192,5 +208,32 @@
</li>
</ul>
</li>
<li>
<a href="#">
<i class="fa fa-space-shuttle icon">
<b class="bg-danger">
</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>
Upgrade
</span>
</a>
<ul class="nav lt">
<li>
<a href="/admin/t?t=upgrade/beta2">
<span>
v1.0-beta to v1.0-beta2
</span>
</a>
</li>
</ul>
</li>
</ul>
</nav>
+49
View File
@@ -0,0 +1,49 @@
{{template "admin/top.html" .}}
<div class="m-b-md"> <h3 class="m-b-none">Home Page</h3></div>
<div class="row">
<div class="col-sm-6">
<form id="form_data">
<section class="panel panel-default">
<div class="panel-body">
<div class="form-group">
<label><a href="/blog" target="_blank">Admin's Blog Page</a></label>
<input type="radio" class="form-control" name="homePage" {{if not .str.homePage}}checked="checked"{{end}} value="0">
</div>
<div class="form-group">
<label><a href="/index" target="_blank">Index Page</a></label>
<input type="radio" class="form-control" name="homePage" {{if .str.homePage}}checked="checked"{{end}} value="1">
</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>
$(function() {
$("#submit").click(function(e){
e.preventDefault();
var t = this;
$(t).button('loading');
ajaxPost("/adminSetting/homePage", getFormJsonData("form_data"), function(ret){
$(t).button('reset');
if(!ret.Ok) {
art.alert(ret.Msg);
} else {
art.tips("Success");
}
});
});
});
</script>
{{template "admin/end.html" .}}
+65
View File
@@ -0,0 +1,65 @@
{{template "admin/top.html" .}}
<div class="m-b-md"> <h3 class="m-b-none">Upload File Size Limit</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>Image</label>
<input type="text" class="form-control" name="uploadImageSize" value="{{.str.uploadImageSize}}">
MB. Default is unlimit;
</div>
<div class="form-group">
<label>Avatar</label>
<input type="text" class="form-control" name="uploadAvatarSize" value="{{.str.uploadAvatarSize}}">
MB. Default is unlimit;
</div>
<div class="form-group">
<label>Blog Logo</label>
<input type="text" class="form-control" name="uploadBlogLogoSize" value="{{.str.uploadBlogLogoSize}}">
MB. Default is unlimit;
</div>
<div class="form-group">
<label>Attachment</label>
<input type="text" class="form-control" name="uploadAttachSize" value="{{.str.uploadAttachSize}}">
MB. Default is unlimit;
</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("/adminSetting/uploadSize", 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" .}}
+47
View File
@@ -0,0 +1,47 @@
{{template "admin/top.html" .}}
<div class="m-b-md"> <h3 class="m-b-none">Upgrade v1.0-beta to v1.0-beta2</h3></div>
<div class="row">
<div class="col-sm-6">
<form id="add_user_form">
<section class="panel panel-default">
<div class="panel-body">
Current Version: <span class="label label-success">leanote v{{.version}}</span>
<ul>
<li>Migrate "About me" to single(a single post)</li>
<li>Add some default themes to administrator</li>
<li>Generate "UrlTitle" for all notes. "UrlTitle" is a friendly url for post</li>
<li>Generate "UrlTitle" for all notebooks</li>
<li>Generate "UrlTitle" for all singles</li>
</ul>
</div>
<footer class="panel-footer text-right bg-light lter">
<button type="submit" id="submit" class="btn btn-success btn-s-xs">Upgrade</button>
</footer>
</section>
</form>
</div>
</div>
{{template "admin/footer.html" .}}
<script>
$(function() {
$("#submit").click(function(e){
e.preventDefault();
var t = this;
$(t).button('loading');
ajaxPost("/adminUpgrade/UpgradeBetaToBeta2", {}, function(ret){
$(t).button('reset')
if(!ret.Ok) {
art.alert(ret.Msg)
} else {
art.tips("Success");
}
});
});
});
</script>
{{template "admin/end.html" .}}
+1 -1
View File
@@ -42,7 +42,7 @@ $(function() {
var t = this;
if($("#add_user_form").valid()) {
$(t).button('loading');
ajaxPost("/auth/doRegister", getFormJsonData("add_user_form"), function(ret){
ajaxPost("/adminUser/register", getFormJsonData("add_user_form"), function(ret){
$(t).button('reset')
if(!ret.Ok) {
art.alert(ret.Msg);
+22 -34
View File
@@ -4,16 +4,8 @@
<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="">
Bulk action
</option>
<option value="1">
Send Email
</option>
</select>
<button class="btn btn-sm btn-default bulk-btn">
Apply
<button class="btn btn-sm btn-default bulk-email">
Send Email
</button>
</div>
<div class="col-sm-4 m-b-xs">
@@ -21,7 +13,7 @@
</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}}" />
<input type="text" class="input-sm form-control" placeholder="Username or Email" id="keywords" value="{{.keywords}}" />
<span class="input-group-btn">
<button class="btn btn-sm btn-default" type="button" data-url="/adminUser/index">Search</button>
</span>
@@ -99,7 +91,8 @@
{{.CreatedTime|datetime}}
</td>
<td>
<a href="#" class="btn btn-default send-email" data-email="{{.Email}}">Send Email</a>
<a href="#" class="btn btn-sm btn-default reset-pwd" data-id="{{.UserId.Hex}}">Reset Password</a>
<a href="#" class="btn btn-sm btn-default send-email" data-email="{{.Email}}">Send Email</a>
</td>
</tr>
{{end}}
@@ -109,16 +102,8 @@
<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="">
Bulk action
</option>
<option value="1">
Send Email
</option>
</select>
<button class="btn btn-sm btn-default bulk-btn">
Apply
<button class="btn btn-sm btn-default bulk-email">
Send Email
</button>
</div>
@@ -137,19 +122,22 @@ $(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(","));
$(".bulk-email").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(","));
});
// reset password
$(".reset-pwd").click(function() {
var id = $(this).data("id");
openDialog({width: 500, url: "/adminUser/resetPwd?userId=" + id, title: "Reset Password"});
});
});
</script>
+52
View File
@@ -0,0 +1,52 @@
<div class="row" style="width: 500px;">
<div class="col-sm-12">
<form id="dataForm">
<section class="panel panel-default">
<header class="panel-heading font-bold">Email</header>
<div class="panel-body">
<div class="form-group pull-in clearfix">
<div class="col-sm-12">
<label>New password</label>
<input id="pwd" type="password" class="form-control" data-rule-required="true" id="pwd" name="pwd" data-rule-minlength="6">
</div>
</div>
<div class="form-group pull-in clearfix">
<div class="col-sm-12">
<label>Confirm password</label>
<input type="password" class="form-control parsley-validated" data-rule-equalto="#pwd" data-rule-required="true" name="password2">
</div>
</div>
</div>
<footer class="panel-footer text-right bg-light lter">
<button type="submit" id="resetPwdBtn" class="btn btn-success btn-s-xs">Submit</button>
</footer>
</section>
</form>
</div>
</div>
<script src="/public/admin/js/jquery-validation-1.13.0/jquery.validate.js"></script>
<script>
$(function() {
var userId = "{{.userInfo.UserId.Hex}}";
init_validator("#dataForm");
$("#resetPwdBtn").click(function(e){
e.preventDefault();
var t = this;
if($("#dataForm").valid()) {
$(t).button('loading');
ajaxPost("/adminUser/doResetPwd", {userId: userId, pwd: $("#pwd").val()}, function(ret){
$(t).button('reset')
if(!ret.Ok) {
art.alert(ret.Msg);
} else {
art.tips("Success");
closeDialog();
}
});
}
});
});
</script>
+101
View File
@@ -0,0 +1,101 @@
{{template "member/top.html" .}}
<div class="m-b-md"> <h3 class="m-b-none">
{{if .page}}
修改页面
{{else}}
添加页面
{{end}}
</h3></div>
<link rel="stylesheet" href="/tinymce/skins/custom/skin.min.css" type="text/css">
<div class="row">
<div class="col-sm-10">
<form id="formData">
<section class="panel panel-default">
<div class="panel-body">
<div class="alert alert-danger" id="baseMsg" style="display: none"></div>
<input type="hidden" id="singleId" value="{{.single.SingleId.Hex}}" />
<div class="form-group">
<label>标题</label>
<input type="text" class="form-control" id="title" name="title"
value="{{.single.Title}}"
data-rules='[
{rule: "required", msg: "请输入页面标题"},
]'
data-msg_target="#baseMsg"
/>
</div>
<div class="form-group">
<label for="content1">内容</label>
<div id="popularToolbar"></div>
<textarea id="content1" name="content">{{.single.Content}}</textarea>
</div>
</div>
<footer class="panel-footer text-right bg-light lter">
<button type="submit" id="baseBtn" class="btn btn-success">{{msg . "submit"}}</button>
</footer>
</section>
</form>
</div>
</div>
{{template "member/footer.html" .}}
<script type="text/javascript" src="/tinymce/tinymce.min.js"></script>
<script>
var urlPrefix = "{{.siteUrl}}";
$(function() {
tinymce.init({
selector : "#content1",
content_css : [ "/css/bootstrap.css", "/css/editor/editor.css" ],
skin : "custom",
language : "{{.locale}}",
height : 300,
width : "100%",
skin : "custom",
plugins : [
"advlist autolink link leanote_image lists charmap hr ",
"searchreplace visualblocks visualchars leanote_code tabfocus",
"table contextmenu directionality textcolor paste fullpage textcolor"],
toolbar1 : "formatselect |fontselect fontsizeselect| forecolor backcolor | bold italic underline strikethrough | bullist numlist |",
menubar : false,
statusbar : false,
font_formats : "Arial=arial,helvetica,sans-serif;"
+ "Arial Black=arial black,avant garde;"
+ "Times New Roman=times new roman,times;"
+ "Courier New=courier new,courier;"
+ "Tahoma=tahoma,arial,helvetica,sans-serif;"
+ "Verdana=verdana,geneva;" + "宋体=SimSun;"
+ "新宋体=NSimSun;" + "黑体=SimHei;"
+ "微软雅黑=Microsoft YaHei",
block_formats : "Header 1=h1;Header 2=h2;Header 3=h3; Header 4=h4;Pre=pre;Paragraph=p"
});
// 基本设置
var vdPage = new vd.init("#formData");
$("#baseBtn").click(function(e) {
e.preventDefault();
if(!vdPage.valid()) {
return;
}
var data = {
singleId: $("#singleId").val(),
title : $("#title").val(),
content : getEditorContent()
}
ajaxPost("/member/blog/doAddOrUpdateSingle/", data, function(re) {
if(reIsOk(re)) {
// 添加成功后, 跳转之
art.tips("Success")
if(!data.pageId) {
location.href = "/member/blog/single";
}
} else {
art.alert(re.Msg || "error")
}
});
});
});
</script>
{{template "member/end.html" .}}
+115
View File
@@ -0,0 +1,115 @@
{{template "member/top.html" .}}
<div class="m-b-md"> <h3 class="m-b-none">{{msg . "baseInfoSet"}}</h3></div>
<div class="row">
<div class="col-sm-10">
<div id="formData">
<section class="panel panel-default">
<div class="panel-body">
<div class="alert alert-danger" id="baseMsg" style="display: none"></div>
<div class="form-horizontal" role="form" id="userBlogForm">
<div class="form-group">
<label for="title" class="col-sm-2 control-label">{{msg . "blogName"}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="Title" name="Title"
placeholder="eg: leanote's blog"
value="{{if .userBlog.Title}}{{.userBlog.Title}}{{else}}{{.userInfo.Email}} 's blog{{end}}">
</div>
</div>
<div class="form-group">
<label for="logo" class="col-sm-2 control-label">{{msg . "blogLogo"}}</label>
<div class="col-sm-10">
<input type="hidden" name="Logo" id="Logo"
value="{{.userBlog.Logo}}" />
<form id="formLogo" action="{{$.siteUrl}}/file/uploadBlogLogo" method="post"
enctype="multipart/form-data" target="logoTarget">
<input type="file" class="form-control" id="logo2" name="file"
onChange='$("#formLogo").submit();' />
<div id="logoImg"
{{if .userBlog.Logo }}
{{else}}
style="display: none"{{end}}
>
<img src="{{.userBlog.Logo}}" style="height: 40px" /> <a
href="#" id="deleteLogo">{{msg . "delete"}}</a>
</div>
</form>
<iframe id="logoTarget" name="logoTarget" src="#" style="display: none"></iframe>
</div>
</div>
<div class="form-group">
<label for="subTitle" class="col-sm-2 control-label">{{msg . "blogDesc"}}</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="SubTitle"
name="SubTitle" value="{{.userBlog.SubTitle}}"
placeholder="eg: leanote, Not Just A Notebook">
</div>
</div>
<div class="form-group">
<label for="aboutMe" class="col-sm-2 control-label"></label>
<div class="col-sm-10">
<div id="popularToolbar"></div>
</div>
</div>
<!--
<div class="form-group">
<label for="aboutMe" class="col-sm-2 control-label">{{msg . "aboutMe"}}</label>
<div class="col-sm-10">
<textarea id="AboutMe" name="AboutMe">{{.userBlog.AboutMe}}</textarea>
</div>
</div>
-->
</div>
</div>
<footer class="panel-footer text-right bg-light lter">
<button type="submit" id="baseBtn" class="btn btn-success">{{msg . "submit"}}</button>
</footer>
</section>
</div>
</div>
</div>
{{template "member/footer.html" .}}
<script type="text/javascript" src="/tinymce/tinymce.min.js"></script>
<script>
var urlPrefix = "{{.siteUrl}}";
$(function() {
$("#deleteLogo").click(function() {
$("#Logo").val("");
$("#logoImg").hide();
});
// 基本设置
$("#baseBtn").click(function() {
var data = {
Title : $("#Title").val(),
SubTitle : $("#SubTitle").val(),
Logo : $("#Logo").val()
}
post("/member/blog/setUserBlogBase", data, function(ret) {
showAlert("#baseMsg", "{{msg . "saveSuccess"}}", "success");
}, this);
});
});
// filename, result, resultCode
function uploadFinish(ret) {
if (ret) {
if (ret.resultCode == '1') {
$("#logoImg img").attr("src", urlPrefix + "/" + ret.filename).parent().show();
$("#Logo").val(ret.filename);
return;
}
alert(ret.result);
return;
}
// 上传出错
alert("error");
}
</script>
{{template "member/end.html" .}}
+63
View File
@@ -0,0 +1,63 @@
{{template "member/top.html" .}}
<div class="m-b-md"> <h3 class="m-b-none">(笔记本)分类</h3></div>
<div class="row">
<div class="col-sm-8">
<section class="panel panel-default">
<div class="panel-body">
分类是公开为博客的笔记本
<br />
拖动可排序
<ul class="list-group gutter list-group-lg list-group-sp sortable">
{{range .notebooks}}
<li class="list-group-item" draggable="true" data-id="{{.NotebookId.Hex}}">
<span class="pull-left media-xs"><i class="fa fa-sort text-muted fa m-r-sm"></i></span>
<div class="pull-right" style="margin-right: 10px">
固定链接: /cate/<input data-id="{{.NotebookId.Hex}}" class="url-title" type="text" value="{{if .UrlTitle}}{{.UrlTitle|decodeUrlValue}}{{else}}{{.NotebookId.Hex}}{{end}}"/>
</div>
{{.Title}}
</li>
{{end}}
</ul>
</div>
<footer class="panel-footer text-right bg-light lter">
<button type="submit" id="baseBtn" class="btn btn-success">{{msg . "submit"}}</button>
</footer>
</section>
</div>
</div>
{{template "member/footer.html" .}}
<script type="text/javascript" src="/public/member/js/jquery.sortable.js"></script>
<script>
$(function() {
$("#baseBtn").click(function(){
var ids = [];
$(".list-group-item").each(function() {
ids.push($(this).data("id"));
});
ajaxPost("/member/blog/", {cateIds: ids}, function(re){
if(reIsOk(re)) {
art.tips("Success");
} else {
art.alert(re.Msg || "error");
}
});
});
$(".url-title").change(function(){
var $t = $(this);
var url = $t.val();
var cateId = $t.data('id');
ajaxPost("/member/blog/updateCateUrlTitle", {cateId: cateId, urlTitle: url}, function(re){
if(reIsOk(re)) {
$t.val(re.Item);
} else {
art.alert(re.Msg || "error");
}
});
});
});
</script>
{{template "member/end.html" .}}
+91
View File
@@ -0,0 +1,91 @@
{{template "member/top.html" .}}
<div class="m-b-md"> <h3 class="m-b-none">{{msg . "commentSet"}}</h3></div>
<div class="row">
<div class="col-sm-8">
<div id="formData">
<section class="panel panel-default">
<div class="panel-body">
<div class="alert alert-danger" id="baseMsg" style="display: none"></div>
<form class="form-horizontal" role="form">
<div class="form-group">
<div class="col-sm-12">
<label>
<input type="checkbox" id="CanComment" name="CanComment"
{{if .userBlog.CanComment}}checked="checked"{{end}} > {{msg . "openComment"}}
</label>
<br />
<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>
</div>
</div>
</div>
</div>
</form>
</div>
<footer class="panel-footer text-right bg-light lter">
<button type="submit" id="baseBtn" class="btn btn-success">{{msg . "submit"}}</button>
</footer>
</section>
</div>
</div>
</div>
{{template "member/footer.html" .}}
<script>
var urlPrefix = "{{.siteUrl}}";
$(function() {
// 评论
$("#baseBtn").click(function(e) {
e.preventDefault();
var data = {
CanComment : $("#CanComment").is(":checked"),
CommentType: $("input[name='commentType']:checked").val(),
DisqusId : $("#DisqusId").val(),
}
post("/member/blog/setUserBlogComment", data, function(ret) {
showAlert("#baseMsg", "{{msg . "saveSuccess"}}", "success");
}, this);
});
$("#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();
}
});
});
</script>
{{template "member/end.html" .}}
+169
View File
@@ -0,0 +1,169 @@
{{template "member/top.html" .}}
<div class="m-b-md"><h3 class="m-b-none">文章列表</h3></div>
<style>
.url-title {
width: 260px;
}
</style>
<section class="panel panel-default">
<div class="row wrapper">
<div class="col-sm-5 m-b-xs">
</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="Title" id="keywords" value="{{.keywords}}" />
<span class="input-group-btn">
<button class="btn btn-sm btn-default" type="button" data-url="/member/blog/index">Search</button>
</span>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-striped b-t b-light">
<thead>
<tr>
{{$url := urlConcat "/member/blog/index" "keywords" .keywords}}
<th style="width: 300px"
{{sorterTh $url "title" .sorter}}
>
Title
<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
style="width: 300px"
{{sorterTh $url "urlTitle" .sorter}}
>
固定链接
<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 "publicTime" .sorter}}
>
发布日期
<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 "updatedTime" .sorter}}
>
更新日期
<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}}
>
创建日期
<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 width="30">
</th>
</tr>
</thead>
<tbody>
{{range .blogs}}
{{if .UrlTitle}}
{{set $ "urlTitle" (decodeUrlValue .UrlTitle)}}
{{else}}
{{set $ "urlTitle" .NoteId.Hex}}
{{end}}
<tr>
<td>
<a href="{{$.userAndBlog.PostUrl}}/{{$.urlTitle}}" class="post" target="_blank">{{.Title|raw}}</a>
</td>
<td>
/post/
<input type="text" data-id="{{.NoteId.Hex}}" class="url-title" value="{{$.urlTitle}}"/>
</td>
<td>
{{.PublicTime|datetime}}
</td>
<td>
{{.UpdatedTime|datetime}}
</td>
<td>
{{.CreatedTime|datetime}}
</td>
<td>
<a
{{if .HasSelfDefined}}
title="已设置"
class="btn btn-sm btn-success"
{{else}}
title="未设置"
class="btn btn-sm btn-default"
{{end}}
href="/member/blog/updateBlogAbstract?noteId={{.NoteId.Hex}}">摘要设置</a>
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
<footer class="panel-footer">
<div class="row">
<div class="col-sm-4 hidden-xs">
</div>
<div class="col-sm-4 text-center">
<small class="text-muted inline m-t-sm m-b-sm">
</small>
</div>
<div class="col-sm-4 text-right text-center-xs">
{{set . "url" (urlConcat "/member/blog/index" "sorter" .sorter "keywords" .keywords)}}
{{template "member/blog/page.html" .}}
</div>
</div>
</footer>
</section>
{{template "member/footer.html" .}}
<script>
$(function() {
var postUrl="{{$.userAndBlog.PostUrl}}";
$(".url-title").change(function(){
var $t = $(this);
var url = $t.val();
var noteId = $t.data('id');
ajaxPost("/member/blog/updateBlogUrlTitle", {noteId: noteId, urlTitle: url}, function(re){
if(reIsOk(re)) {
$t.val(re.Item);
var $a = $t.closest("tr").find(".post");
$a.attr("href", postUrl + "/" + encodeURI(re.Item));
} else {
art.alert(re.Msg || "error");
}
});
});
});
</script>
{{template "member/end.html" .}}
+33
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}}
+76
View File
@@ -0,0 +1,76 @@
{{template "member/top.html" .}}
<div class="m-b-md"> <h3 class="m-b-none">分页与排序设置</h3></div>
<div class="row">
<div class="col-sm-8">
<div id="formData">
<section class="panel panel-default">
<div class="panel-body">
<div class="alert alert-danger" id="domainMsg" style="display: none"></div>
<form class="form-horizontal" role="form" id="dataFrom">
<div class="form-group">
<label for="perPageSize" class="col-sm-2 control-label">每页记录数</label>
<div class="col-sm-10">
<input type="text" class="form-control"
placeholder="10"
id="perPageSize"
name="perPageSize"
value="{{.userBlog.PerPageSize}}" />
</div>
</div>
<div class="form-group">
<label for="perPageSize" class="col-sm-2 control-label">排序字段</label>
<div class="col-sm-10">
<select class="form-control"
id="sortField"
name="sortField"
value="">
<option value="PublicTime" {{if eq $.userBlog.SortField "PublicTime"}}selected{{end}}>公开为博客时间</option>
<option value="CreatedTime" {{if eq $.userBlog.SortField "CreatedTimeTime"}}selected{{end}}>创建时间</option>
<option value="UpdatedTime" {{if eq $.userBlog.SortField "UpdatedTime"}}selected{{end}}>更新时间</option>
<option value="Title" {{if eq $.userBlog.SortField "Title"}}selected{{end}}>标题</option>
</select>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label">排序类型</label>
<div class="col-sm-10" style="margin-top: 5px;">
<label><input type="radio" name="isAsc" value="0" {{if not .userBlog.IsAsc}}checked{{end}}> 降序</label>
<label><input type="radio" name="isAsc" value="1" {{if .userBlog.IsAsc}}checked{{end}}> 升序</label>
</div>
</div>
</form>
</div>
<footer class="panel-footer text-right bg-light lter">
<button type="submit" id="baseBtn" class="btn btn-success">{{msg . "submit"}}</button>
</footer>
</section>
</div>
</div>
</div>
{{template "member/footer.html" .}}
<script>
var urlPrefix = "{{.siteUrl}}";
$(function() {
// domain
$("#baseBtn").click(function(e) {
e.preventDefault();
var data = $("#dataFrom").serialize();
post("/member/blog/setUserBlogPaging", data, function(re) {
if(re.Ok) {
showAlert("#domainMsg", "{{msg . "saveSuccess"}}", "success");
} else {
showAlert("#domainMsg", re.Msg, "danger");
}
}, this);
});
});
</script>
{{template "member/end.html" .}}
+86
View File
@@ -0,0 +1,86 @@
{{template "member/top.html" .}}
<div class="m-b-md"> <h3 class="m-b-none">页面</h3></div>
<div class="row">
<div class="col-sm-8">
<section class="panel panel-default">
<div class="panel-body">
<p>
您可以添加多个单页面
<a href="/member/blog/addOrUpdateSingle" id="addBtn" class="btn btn-success">添加页面</a>
</p>
<ul class="list-group gutter list-group-lg list-group-sp sortable">
{{range .singles}}
<li class="list-group-item" draggable="true" data-id="{{.SingleId}}">
<span class="pull-left media-xs"><i class="fa fa-sort text-muted fa m-r-sm"></i></span>
<span class="pull-right">
<a href="/member/blog/addOrUpdateSingle?singleId={{.SingleId}}"><i class="fa fa-pencil icon-muted fa-fw m-r-xs page-edit" data-id="{{.SingleId}}"></i></a>
<a href="#"><i class="fa fa-times icon-muted fa-fw page-delete" data-id="{{.SingleId}}"></i></a>
</span>
<div class="pull-right" style="margin-right: 10px">
固定链接: /single/<input data-id="{{.SingleId}}" class="url-title" type="text" value="{{if .UrlTitle}}{{.UrlTitle|decodeUrlValue}}{{else}}{{.SingleId}}{{end}}"/>
</div>
<div class="clear">
{{.Title}}
</div>
</li>
{{end}}
</ul>
</div>
{{if .singles}}
<footer class="panel-footer text-right bg-light lter">
<button type="submit" id="baseBtn" class="btn btn-success">保存排序</button>
</footer>
{{end}}
</section>
</div>
</div>
{{template "member/footer.html" .}}
<script type="text/javascript" src="{{.siteUrl}}/public/member/js/jquery.sortable.js"></script>
<script>
$(function() {
$("#baseBtn").click(function(){
var ids = [];
$(".list-group-item").each(function() {
ids.push($(this).data("id"));
});
ajaxPost("/member/blog/sortSingles", {singleIds: ids}, function(re){
if(reIsOk(re)) {
art.tips("Success");
} else {
art.alert(re.Msg || "error");
}
});
});
$(".page-delete").click(function() {
var singleId = $(this).data('id');
art.confirm("Are you sure ?", function() {
ajaxPost("/member/blog/deleteSingle", {singleId: singleId}, function(re){
if(reIsOk(re)) {
art.tips("Success");
location.reload();
} else {
art.alert(re.Msg || "error");
}
});
});
});
$(".url-title").change(function(){
var $t = $(this);
var url = $t.val();
var singleId = $t.data('id');
ajaxPost("/member/blog/updateSingleUrlTitle", {singleId: singleId, urlTitle: url}, function(re){
if(reIsOk(re)) {
$t.val(re.Item);
} else {
art.alert(re.Msg || "error");
}
});
});
});
</script>
{{template "member/end.html" .}}
+221
View File
@@ -0,0 +1,221 @@
{{template "member/top.html" .}}
<div class="m-b-md clearfix">
<h3 class="m-b-none">
{{msg . "themeSet"}}
<small>(<a href="http://blog.leanote.com/post/545f1a5c380782565e000000" target="_blank">{{msg . "needHelp"}}</a>)</small>
</h3>
</div>
<div class="row">
<div class="col-sm-12 theme-container">
<section class="panel panel-default">
<div class="panel-body">
<div>
<form id="uploadAvatar" method="post" action="/member/blog/importTheme" enctype="multipart/form-data">
<div id="dropAvatar" class="dropzone">
<a class="btn btn-default btn-new" href="/member/blog/newTheme"><span class="fa fa-plus"></span> 新建主题</a>
<a class="btn btn-success btn-choose-file">
<span class="fa fa-upload"></span> 导入主题(.zip)
</a>
<input type="file" name="file" multiple/>
<div id="avatarUploadMsg"></div>
</div>
</form>
</div>
<p>
当前主题:
</p>
<ul class="themes">
<li class="theme">
<a class="choose-theme" data-method="put" href="#" rel="nofollow">
<div class="theme-thumb thumb active-theme">
<img src="/{{.activeTheme.Path}}/images/screenshot.png" alt="preview">
</div>
<span class="theme-title">{{.activeTheme.Name}}</span>
</a>
<span class="theme-author">
By: <a href="{{.activeTheme.AuthorUrl}}" target="_blank">{{.activeTheme.Author}}</a>
</span>
<div class="theme-btns">
<div class="btn-group" data-id="{{.activeTheme.ThemeId.Hex}}">
<a class="btn btn-default btn-sm btn-export"><span class="fa fa-download"></span> 导出</a>
<a class="btn btn-default btn-sm" href="{{$.siteUrl}}/preview?themeId={{.activeTheme.ThemeId.Hex}}" target="_blank"><span class="fa fa-eye"></span> 预览</a>
<a class="btn btn-primary btn-sm" target="_blank" href="/member/blog/updateTheme?themeId={{if .activeTheme.ThemeId}}{{.activeTheme.ThemeId.Hex}}{{end}}"><span class="fa fa-pencil"></span> 编辑</a>
</div>
</div>
</li>
</ul>
<hr />
<p>
我的其它主题:
</p>
<ul class="themes">
{{range $.otherThemes}}
{{if .ThemeId}}
<li class="theme">
<a class="choose-theme" data-method="put" href="#" rel="nofollow">
<div class="theme-thumb thumb">
<img src="/{{.Path}}/images/screenshot.png" alt="preview">
</div>
<span class="theme-title">{{.Name}}</span>
</a>
<span class="theme-author">
By: <a href="{{.AuthorUrl}}" target="_blank">{{.Author}}</a>
</span>
<div class="theme-btns">
<div class="btn-group" data-id="{{.ThemeId.Hex}}">
<a class="btn btn-default btn-sm btn-delete" data-loading-text="..."><span class="fa fa-remove"></span> 删除</a>
<a class="btn btn-default btn-sm btn-export"><span class="fa fa-download"></span> 导出</a>
<a class="btn btn-default btn-sm" href="{{$.siteUrl}}/preview?themeId={{.ThemeId.Hex}}" target="_blank"><span class="fa fa-eye"></span> 预览</a>
<a class="btn btn-default btn-sm" target="_blank" href="/member/blog/updateTheme?themeId={{.ThemeId.Hex}}"><span class="fa fa-pencil"></span> 编辑</a>
<a class="btn btn-primary btn-sm btn-active" data-loading-text="..."><span class="fa fa-check"></span> 使用</a>
{{if $.isAdmin}}
<a class="btn btn-default btn-sm btn-public" data-loading-text="...">
{{if .IsDefault}}
公开
{{else}}
私有
{{end}}
</a>
{{end}}
</div>
</div>
</li>
{{end}}
{{else}}
无
{{end}}
</ul>
<hr />
<p>
leanote主题市场:
</p>
<ul class="themes">
{{range $.optionThemes}}
<li class="theme">
<a class="choose-theme" data-method="put" href="#" rel="nofollow">
<div class="theme-thumb thumb">
<img src="/{{.Path}}/images/screenshot.png" alt="preview">
</div>
<span class="theme-title">{{.Name}}</span>
</a>
<span class="theme-author">
By: <a href="{{.AuthorUrl}}" target="_blank">{{.Author}}</a>
</span>
<p class="theme-btns" data-id="{{.ThemeId.Hex}}">
<a class="btn btn-primary btn-sm btn-install"><span class="fa fa-gear"></span> 安装</a>
</p>
</li>
{{end}}
</ul>
</div>
</section>
</div>
</div>
{{template "member/footer.html" .}}
<script src="/js/require.js"></script>
<script>
var urlPrefix = "{{.siteUrl}}";
$(function() {
// 使用主题
$(".btn-active").click(function() {
var id = $(this).parent().data('id');
var t = $(this);
t.button("loading");
ajaxPost("/member/blog/activeTheme", {themeId: id}, function(re) {
t.button("reset");
if(reIsOk(re)) {
art.tips("Success");
location.reload();
} else {
art.alert("Error");
}
});
});
// 导出主题
$(".btn-export").click(function() {
var id = $(this).parent().data('id');
var t = $(this);
window.open("/member/blog/exportTheme?themeId=" + id);
});
// 删除主题
$(".btn-delete").click(function() {
var id = $(this).parent().data('id');
var t = $(this);
art.confirm("Are you sure ?", function() {
t.button("loading");
ajaxPost("/member/blog/deleteTheme", {themeId: id}, function(re) {
t.button("reset");
if(reIsOk(re)) {
art.tips("Success");
location.reload();
} else {
art.alert("Error");
}
});
});
});
// 安装主题
$(".btn-install").click(function() {
var id = $(this).parent().data('id');
var t = $(this);
art.confirm("Are you sure ?", function() {
t.button("loading");
ajaxPost("/member/blog/installTheme", {themeId: id}, function(re) {
t.button("reset");
if(reIsOk(re)) {
art.tips("Success");
location.reload();
} else {
art.alert("Error");
}
});
});
});
{{if .isAdmin}}
// 只有管理员才有公开主题
$(".btn-public").click(function() {
var id = $(this).parent().data('id');
var t = $(this);
art.confirm("Are you sure ?", function() {
t.button("loading");
ajaxPost("/member/blog/publicTheme", {themeId: id}, function(re) {
t.button("reset");
if(reIsOk(re)) {
art.tips("Success");
location.reload();
} else {
art.alert("Error");
}
});
});
});
{{end}}
});
// 导入主题
require.config({
baseUrl: '/public',
paths: {
'import_theme': 'js/app/import_theme',
'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']}
}
});
$(function() {
require(['import_theme'], function(import_theme) {});
});
</script>
{{template "member/end.html" .}}
+127
View File
@@ -0,0 +1,127 @@
{{template "member/top.html" .}}
<style>
.img-src {
max-height: 200px;
}
</style>
<div class="m-b-md"> <h3 class="m-b-none">
{{.note.Title}} - 摘要设置
</h3>
</div>
<link rel="stylesheet" href="/tinymce/skins/custom/skin.min.css" type="text/css">
<div class="row">
<div class="col-sm-10">
<form id="formData">
<section class="panel panel-default">
<div class="panel-body">
以下内容设置后, 以后修改笔记时将不自动获取. 若将该文章取消为博客后, 以下内容会自动获取.
<hr />
<div class="alert alert-danger" id="baseMsg" style="display: none"></div>
<div class="form-group">
<label>
主图片
<a class="btn btn-default btn-select-img">从笔记中自动获取下一张</a>
</label>
<div>
<img src="{{.note.ImgSrc}}" class="img-src" id="imgSrc"/>
</div>
</div>
<div class="form-group">
<label>描述</label>
<textarea type="text" rows="6" class="form-control" id="desc" name="desc">{{.note.Desc}}</textarea>
</div>
<div class="form-group">
<label for="content1">摘要</label>
<div id="popularToolbar"></div>
<textarea id="content1" name="content">{{.note.Abstract}}</textarea>
</div>
</div>
<footer class="panel-footer text-right bg-light lter">
<button type="submit" id="submitBtn" class="btn btn-success">{{msg . "submit"}}</button>
</footer>
<div class="panel-body">
<p>
原文:
</p>
<div id="rawContent">
{{.note.Content|raw}}
</div>
</div>
</section>
</form>
</div>
</div>
{{template "member/footer.html" .}}
<script type="text/javascript" src="/tinymce/tinymce.js"></script>
<script>
var UrlPrefix = "{{.siteUrl}}";
$(function() {
tinymce.init({
selector : "#content1",
content_css : [ "/css/bootstrap.css", "/css/editor/editor.css" ],
skin : "custom",
language : "{{.locale}}",
height : 300,
width : "100%",
skin : "custom",
plugins : [
"advlist autolink link leanote_image lists charmap hr ",
"searchreplace visualblocks visualchars leanote_code tabfocus",
"table contextmenu directionality textcolor paste fullpage textcolor"],
toolbar1 : "formatselect |fontselect fontsizeselect| forecolor backcolor | bold italic underline strikethrough | bullist numlist | leanote_code",
menubar : false,
statusbar : false,
font_formats : "Arial=arial,helvetica,sans-serif;"
+ "Arial Black=arial black,avant garde;"
+ "Times New Roman=times new roman,times;"
+ "Courier New=courier new,courier;"
+ "Tahoma=tahoma,arial,helvetica,sans-serif;"
+ "Verdana=verdana,geneva;" + "宋体=SimSun;"
+ "新宋体=NSimSun;" + "黑体=SimHei;"
+ "微软雅黑=Microsoft YaHei",
block_formats : "Header 1=h1;Header 2=h2;Header 3=h3; Header 4=h4;Pre=pre;Paragraph=p"
});
// 提交
$("#submitBtn").click(function(e) {
e.preventDefault();
var data = {
noteId: "{{.noteId}}",
imgSrc: $("#imgSrc").attr("src"),
desc : $("#desc").val(),
abstract : getEditorContent()
}
ajaxPost("/member/blog/doUpdateBlogAbstract/", data, function(re) {
if(reIsOk(re)) {
art.tips("Success");
location.href = "/member/blog/index";
} else {
art.alert(re.Msg || "error")
}
});
});
// 选择主图
var $imgs = $("#rawContent").find("img");
var imgLen = $imgs.length;
var imgSeq = 0;
var $imgSrc = $("#imgSrc");
$(".btn-select-img").click(function() {
if(imgLen == 0) {
return;
}
imgSeq++;
imgSeq = imgSeq%imgLen;
$imgSrc.attr("src", $imgs.eq(imgSeq).attr("src"));
});
});
</script>
{{template "member/end.html" .}}
+334
View File
@@ -0,0 +1,334 @@
{{template "member/top.html" .}}
<div class="m-b-md">
<h3 class="m-b-none">
{{if .isNew}}
新建主题
{{else}}
编辑主题
-
{{.theme.Name}}
{{end}}
<a class="btn btn-default" href="{{$.siteUrl}}/preview?themeId={{.themeId}}" target="_blank"><span class="fa fa-eye"></span> 预览</a>
</h3>
</div>
<style>
#tplContent * {
font-family: Monaco, Menlo, 'Ubuntu Mono', Consolas, source-code-pro, monospace;
}
.image-list {
list-style: none;
padding: 0;
}
.image-list li {
display: inline-block;
margin-right: 5px;
text-align: center;
}
.image-list li .thumb {
width: 100px;
height: 100px;
line-height: 100px;
overflow: hidden;
border: 1px solid #ccc;
padding: 3px;
border-radius: 5px;
}
.op {
text-align: center;
}
</style>
<div class="row">
<div class="col-sm-3">
<section class="panel panel-default">
<header class="panel-heading">
模板, 样式, 脚本:
</header>
<div class="panel-body" style="height:430px;overflow: auto;">
<div class="list-group bg-white file-list">
{{range .myTpls}}
<a href="#" class="list-group-item " data-file="{{.}}">
<i class="fa fa-remove icon-muted remove-file" title="delete file" ></i>
{{.}}
<span class="badge"></span>
</a>
{{end}}
</div>
</div>
<footer class="panel-footer text-right bg-light lter">
<a class="btn btn-default" id="newFile">新建文件</a>
</footer>
</section>
</div>
<div class="col-sm-9">
<section class="panel panel-default">
<header class="panel-heading">
当前文件: <span id="curTpl">header.html</span>
<span id="msg"></span>
</header>
<div class="panel-body">
<div id="tplContent" class="form-control" style="width: 100%; height: 400px;"></div>
</div>
<footer class="panel-footer text-right bg-light lter">
<button type="submit" id="saveBtn" class="btn btn-success">{{msg . "save"}}</button>
</footer>
</section>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<section class="panel panel-default">
<header class="panel-heading">
图片: images/
</header>
<div class="panel-body">
<ul class="image-list" id="imageList">
</ul>
</div>
<footer class="panel-footer text-right bg-light lter">
<form id="formLogo" action="/member/blog/uploadThemeImage" method="post"
enctype="multipart/form-data" target="logoTarget">
<input type="hidden" name="themeId" value="{{.themeId}}" />
<input type="file" class="form-control" id="logo2" name="file"
onChange='$("#formLogo").submit();' />
</form>
<iframe id="logoTarget" name="logoTarget" src="#" style="display: none"></iframe>
</footer>
</section>
</div>
</div>
{{template "member/footer.html" .}}
<script src="/public/member/js/ace/ace.js" type="text/javascript"></script>
<script>
var editor = ace.edit("tplContent");
editor.setTheme("ace/theme/tomorrow");
var urlPrefix = "{{.siteUrl}}";
var userId = "{{.userInfo.UserId.Hex}}";
var themeId = "{{.themeId}}";
var themeBasePath = "/{{$.theme.Path}}";
var theme = {
cache: {}, // filename => content
curTplO: $("#curTpl"),
tplContentO: $("#tplContent"),
saveBtnO: $("#saveBtn"),
newFileO: $("#newFile"),
curTpl: "",
tplInfos: {"header.html": "头部", "footer.html": "底部", "index.html": "首页", "cate.html": "分类页", "search.html": "搜索页", "single.html": "单页", "archive.html": "归档页", "view.html": "详情页", "tags.html": "标签页", "tag_posts.html": "标签博客", "comment.html": "分享评论", "404.html":"404", "theme.json": "主题配置"},
init: function() {
var self = this;
self.showTplInfo();
$(".file-list").on("click", "a", function() {
$(".file-list a").removeClass("active");
var filename = $(this).data('file');
self.saveCurContent(); // 保存当前的
self.renderTpl(filename); // 再渲染
$(this).addClass("active");
});
self.renderTpl("header.html");
$(".file-list a").eq(0).addClass("active");
self.saveBtnO.click(function(){
self.saveCurContent($(this));
});
self.removeFile();
self.newFile();
self.initImage();
},
removeFile: function() {
var self = this;
$(".file-list").on("click", ".remove-file", function(e) {
e.stopPropagation();
var $a = $(this).closest("a");
var file = $a.data('file');
art.confirm("Are you sure to delete " + file + " ?", function() {
ajaxPost("/member/blog/deleteTpl", {themeId: themeId, filename: file}, function(re) {
if(reIsOk(re)) {
art.tips("Success");
$a.remove();
if(self.curTpl == file) {
$(".file-list a").eq(0).trigger("click");
}
} else {
art.alert(re.Msg);
}
})
});
});
},
newFile: function() {
var self = this;
self.newFileO.click(function() {
art.prompt("文件名", function(file){
if(!file) {
return;
}
var t = '<a href="#" class="list-group-item " data-file="' + file + '"> <i class="fa fa-remove icon-muted remove-file" title="delete file" ></i> ' + file + '<span class="badge"></span></a>';
$(".file-list").append(t);
});
});
},
showTplInfo: function() {
var self = this;
$(".file-list a").each(function() {
var file = $(this).data("file");
var info = "";
info = self.tplInfos[file];
if(!info) {
if(file.indexOf(".css") > 0) {
info = "样式";
} else if(file.indexOf(".js") > 0) {
info = "脚本";
} else if(file.indexOf(".html") > 0) {
info = "模板";
}
} else {
$(this).find(".remove-file").remove();
}
$(this).find(".badge").text(info);
});
},
msgO: $("#msg"),
saveCurContent: function(t) {
var self = this;
var content = editor.getValue()
var filename = self.curTpl;
// 之前的内容是否有改动
if(self.cache[filename] == content) {
return;
}
if(t) {
t.button("loading");
}
self.msgO.html("正在保存...");
ajaxPost("/member/blog/updateTplContent", {themeId: themeId, filename: self.curTpl, content: content}, function(re) {
if(t) {
t.button("reset");
}
if(reIsOk(re)) {
self.cache[filename] = content;
// art.tips("Success");
self.msgO.html("保存成功")
setTimeout(function() {
self.msgO.html("");
}, 3000);
} else {
art.alert(re.Msg);
}
});
},
renderContent: function(name, content) {
var self = this;
self.curTplO.html(name);
self.curTpl = name; // 这里设计
var lang = "html";
if(name.indexOf(".js") > 0 || name.indexOf(".json") > 0) {
lang = "javascript";
} else if(name.indexOf(".css") > 0) {
lang = "css";
}
editor.setValue(content);
editor.session.setMode("ace/mode/" + lang);
editor.gotoLine(1);
setTimeout(function() {
editor.gotoLine(1);
}, 200);
},
renderTpl: function(name) {
var self = this;
var content = "";
self.curTpl = name; // 这里设置
if(self.cache[name]) {
content = self.cache[name];
self.renderContent(name, content);
return;
}
ajaxGet("/member/blog/getTplContent", {themeId: themeId, filename: name}, function(re){
if(reIsOk(re)) {
self.cache[name] = re.Item;
self.renderContent(name, re.Item);
}
});
},
//-----------------
// theme images
imageListO: $("#imageList"),
initImage: function() {
var self = this;
self.reRenderImages();
self.imageListO.on("click", ".delete-image", function(re) {
var file = $(this).data('image');
var t = this;
art.confirm("Are you sure to delete it " + file + " ?", function() {
ajaxPost("/member/blog/deleteThemeImage", {themeId: themeId, filename: file}, function(re) {
if(reIsOk(re)) {
art.tips("Success");
$(t).closest("li").remove();
} else {
art.alert(re.Msg);
}
});
});
});
},
renderImages: function(images) {
var self = this;
if(!images || images.length == 0) {
self.imageListO.html("无图片");
return;
}
var html = "";
for(var i = 0; i < images.length; ++i) {
var image = images[i];
var path = themeBasePath + "/images/" + image;
html += '<li><div class="thumb"><img src="' + path + '"></div><div class="op"><span>' + image + '</span><br /><a class="btn btn-xs btn-warning delete-image" data-image="' + image + '"><span class="fa fa-remove"></span></a></div></li>';
}
self.imageListO.html(html);
},
reRenderImages: function() {
var self = this;
ajaxGet("/member/blog/listThemeImages", {themeId: themeId}, function(re) {
if(reIsOk(re)) {
var images = re.List;
self.renderImages(images);
}
});
},
}
$(function() {
theme.init();
$(window).keydown(function(e) {
if (e.keyCode == 83 && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
theme.saveBtnO.trigger("click");
}
});
});
// filename, result, resultCode
function uploadFinish(ret) {
if (ret) {
if (ret.resultCode == '1') {
theme.reRenderImages();
art.tips(ret.result)
return;
}
art.alert(ret.result);
return;
}
// 上传出错
art.alert("error");
}
</script>
{{template "member/end.html" .}}
+3
View File
@@ -0,0 +1,3 @@
</body>
</html>
+35
View File
@@ -0,0 +1,35 @@
</div>
</section>
</section>
</section>
</section>
</section>
</section>
<script src="/js/jquery-1.9.0.min.js"></script>
<script src="/js/bootstrap.js"></script>
<script src="/js/i18n/msg.{{.locale}}.js"></script>
<script src="/public/admin/js/artDialog/jquery.artDialog.js?skin=default"></script>
<script src="/public/js/common.js"></script>
<script src="/public/member/js/member.js"></script>
<script>
$(function() {
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
}
if(fullPath == "/member/index") {
fullPath = "/member";
}
$("#nav > li").removeClass("active");
// alert(fullPath);
// 自己
var $thisLi = $('#nav a[href="' + fullPath + '"]').parent();
$thisLi.addClass("active");
// 父也active
$thisLi.parent().parent().addClass('active');
});
</script>
+220
View File
@@ -0,0 +1,220 @@
{{template "member/top.html" .}}
<style>
.group-title {
color: #fff;
background: none;
border: none;
outline: none;
width: 200px;
}
</style>
<div class="m-b-md"> <h3 class="m-b-none">项目/成员</h3></div>
<p>
<a class="btn btn-default btn-add-group"><span class="fa fa-plus"></span> 新建分组</a>
</p>
<script type="text/x-jsrender" id="tUser">
<li class="list-group-item" data-id="[[:UserId]]">
[[:Username]] [[if Email != Username]]([[:Email]])[[/if]]
<span class="fa fa-remove pull-right text-xs m-t-sm"></span>
</li>
</script>
<script type="text/x-jsrender" id="tGroup">
<div class="col-sm-4 each-group" data-id="[[:GroupId]]">
<section class="panel panel-default">
<header class="panel-heading bg-success lt no-border">
<div class="clearfix">
<div class="clear">
<div class="h3 m-t-xs m-b-xs text-white">
<input type="text" data-ever="[[:Title]]" value="[[:Title]]" class="group-title" />
<a title="删除分组" class="delete-group">
<i class="fa fa-remove text-white pull-right text-xs m-t-sm"></i>
</a>
</div>
</div>
</div>
</header>
<ul class="list-group no-radius">
<li class="list-group-item">
<input type="text" class="add-user-input form-control" placeholder="输入用户名或邮箱添加成员" />
</li>
</ul>
</section>
</div>
</script>
<div class="row" id="groups">
{{range .groups}}
<div class="col-sm-4 each-group" data-id="{{.GroupId.Hex}}">
<section class="panel panel-default">
<header class="panel-heading bg-success lt no-border">
<div class="clearfix">
<div class="clear">
<div class="h3 m-t-xs m-b-xs text-white">
<input type="text" data-ever="{{.Title}}" value="{{.Title}}" class="group-title" />
<a title="删除分组" class="delete-group">
<i class="fa fa-remove text-white pull-right text-xs m-t-sm"></i>
</a>
</div>
</div>
</div>
</header>
<ul class="list-group no-radius group-users">
{{range .Users}}
<li class="list-group-item" data-id="{{.UserId.Hex}}">
{{.Username}} {{if not (eq .Email .Username)}}({{.Email}}){{end}}
<a title="删除成员" class="delete-user">
<span class="fa fa-remove pull-right text-xs m-t-sm"></span>
</a>
</li>
{{end}}
<li class="list-group-item">
<input type="text" class="add-user-input form-control" placeholder="输入用户名或邮箱添加成员" />
</li>
</ul>
</section>
</div>
{{end}}
</div>
{{template "member/footer.html" .}}
<script src="http://leanote.com/public/blog/js/jsrender.js"></script>
<script>
var group = {
tGroupO: $("#tGroup"),
groupsO: $("#groups"),
tUserO: $("#tUser"),
init: function() {
var self = this;
self.addGroupEvent();
self.updateGroupEvent();
self.deleteGroupEvent();
self.addUserEvent();
self.deleteUserEvent();
var classes = ["bg-warning", "bg-danger", "bg-success", "bg-dark", "bg-primary"];
var i = 0;
var len = classes.length;
$(".panel-heading").each(function() {
$(this).removeClass("bg-success").addClass(classes[i%len]);
i++;
});
},
// 添加用户分组
addGroupEvent: function() {
var self = this;
$('.btn-add-group').click(function() {
ajaxPost("/member/group/addGroup", {title: "分组名"}, function(re) {
if(reIsOk(re)) {
var group = re.Item;
self.groupsO.prepend(self.tGroupO.render(group))
self.groupsO.find(".group-title").eq(0).focus().select();
}
});
});
},
// 修改
updateGroupEvent: function() {
var self = this;
self.groupsO.on("blur", ".group-title", function() {
var t = $(this);
var ever = t.data('ever');
var val = t.val();
if(ever == val) {
return;
}
console.log($(this).closest(".each-group"));
var groupId = $(this).closest(".each-group").data("id");
ajaxPost("/member/group/updateGroupTitle", {groupId: groupId, title: val}, function(re) {
if(reIsOk(re)) {
art.tips("Success")
t.data('ever', val)
}
})
});
},
// 删除分组
deleteGroupEvent: function() {
var self = this;
self.groupsO.on("click", ".delete-group", function() {
var t = $(this);
art.confirm("Are you sure ?", function() {
var groupId = t.closest(".each-group").data("id");
ajaxPost("/member/group/deleteGroup", {groupId: groupId}, function(re) {
if(reIsOk(re)) {
art.tips("Success");
t.closest(".each-group").remove();
} else {
art.alert(re.Msg);
}
});
});
});
},
// 添加好友分组
addUserEvent: function() {
var self = this;
self.groupsO.on("keydown", ".add-user-input", function(e) {
// enter
if (e.keyCode == 13) {
var t = $(this);
var email = t.val();
if(!email) {
t.focus();
return;
}
// 检查是否是合法的email
if(!isEmail(email)) {
// art.tips("非法的email");
// return;
}
var groupId = t.closest(".each-group").data("id");
var p = t.closest("li");
ajaxPost("/member/group/addUser", {groupId: groupId, email: email}, function(re) {
if(reIsOk(re)) {
art.tips("Success");
var html = self.tUserO.render(re.Item)
p.before(html);
t.val("");
} else {
art.alert(re.Msg);
}
})
}
});
},
// 删除用户
deleteUserEvent: function() {
var self = this;
self.groupsO.on("click", ".delete-user", function() {
var t = $(this);
art.confirm("Are you sure ?", function() {
var groupId = t.closest(".each-group").data("id");
var userId = t.closest("li").data("id");
ajaxPost("/member/group/deleteUser", {groupId: groupId, userId: userId}, function(re) {
if(reIsOk(re)) {
art.tips("Success");
t.closest("li").remove();
} else {
art.alert(re.Msg);
}
});
});
});
},
}
$(function() {
group.init();
});
</script>
{{template "member/end.html" .}}
+81
View File
@@ -0,0 +1,81 @@
{{template "member/top.html" .}}
<div class="m-b-md"> <h3 class="m-b-none">{{.userInfo.Username}}, 欢迎来到leanote</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-warning"></i>
<i class="fa fa-file-o fa-stack-1x text-white"></i>
</span>
<a class="clear" href="javascript:;">
<span class="h3 block m-t-xs"><strong>{{.countNote}}</strong></span>
<small class="text-muted text-uc">{{msg . "note"}}</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="javascript:;">
<span class="h3 block m-t-xs"><strong>{{.countBlog}}</strong></span>
<small class="text-muted text-uc">{{msg . "blog"}}</small>
</a>
</div>
</div>
</section>
<!-- 最新动态 -->
<section class="panel panel-default">
<h4 class="font-thin padder">
Leanote 动态
</h4>
<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>
</form>
<footer class="panel-footer bg-light lter">
<button class="btn btn-info pull-right btn-sm">
POST
</button>
<ul class="nav nav-pills nav-sm">
</footer>
</section>
-->
{{template "member/footer.html" .}}
<script>
$(function() {
// leanote动态
var url = "https://leanote.com/blog/listCateLatest/5446753cfacfaa4f56000000";
function renderItem(item) {
return '<li class="list-group-item"><p><a target="_blank" href="http://leanote.com/blog/post/' + item.NoteId + '">' + item.Title + '</a></p><small class="block text-muted"><i class="fa fa-clock-o"></i> ' + goNowToDatetime(item.PublicTime) + '</small></li>';
}
$.ajax({
dataType: "jsonp",//跨域访问 dataType 必须是jsonp 类型。
url: url,
type: "GET",
jsonp: "callback",
jsonpCallback: "jsonpCallback",
success: function(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 "member/end.html" .}}
+157
View File
@@ -0,0 +1,157 @@
<nav class="nav-primary hidden-xs">
<ul class="nav" id="nav">
<li class="active">
<a href="/member">
<i class="fa fa-dashboard icon">
<b class="bg-success">
</b>
</i>
<span>
{{msg . "memberCenter"}}
</span>
</a>
</li>
<li>
<a href="#">
<i class="fa fa-user icon">
<b class="bg-danger">
</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>
帐户信息
</span>
</a>
<!-- 导航列表 -->
<ul class="nav lt">
{{if $.userInfo.Email}}
<li>
<a href="/member/user/username">
<span>
用户名
</span>
</a>
</li>
<li>
<a href="/member/user/avatar">
<span>
头像
</span>
</a>
</li>
<li>
<a href="/member/user/email">
<span>
邮箱
</span>
</a>
</li>
<li>
<a href="/member/user/password">
<span>
密码
</span>
</a>
</li>
{{else}}
<li>
<a href="/member/user/add_account">
<span>
添加leanote帐户
</span>
</a>
</li>
{{end}}
</ul>
</li>
<li id="adminEmailNav">
<a href="#layout">
<i class="fa fa-bold 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>
博客
</span>
</a>
<ul class="nav lt">
<li>
<a href="/member/blog/index">
<span>
文章列表
</span>
</a>
</li>
<li>
<a href="/member/blog/base">
<span>
基本信息
</span>
</a>
</li>
<li>
<a href="/member/blog/comment">
<span>
评论
</span>
</a>
</li>
<li>
<a href="/member/blog/cate">
<span>
分类
</span>
</a>
</li>
<li>
<a href="/member/blog/single">
<span>
单页面
</span>
</a>
</li>
<li>
<a href="/member/blog/paging">
<span>
分页与排序设置
</span>
</a>
</li>
<li>
<a href="/member/blog/theme">
<span>
主题
</span>
</a>
</li>
</ul>
</li>
<li class="active">
<a href="/member/group/index">
<i class="fa fa-users icon">
<b class="bg-success">
</b>
</i>
<span>
项目组/成员
</span>
</a>
</li>
</ul>
</nav>
+118
View File
@@ -0,0 +1,118 @@
<!DOCTYPE html>
<html lang="en" class="app">
<head>
<meta charset="utf-8" />
<title>
{{.title}}
</title>
<meta name="description" content="leanote member center"/>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
<link href="/css/font-awesome-4.2.0/css/font-awesome.css" rel="stylesheet">
<link href="/public/admin/css/bootstrap.3.2.0.min.css" rel="stylesheet">
<link href="/public/member/css/member.css" rel="stylesheet">
<!--[if lt IE 9]>
<script src="/public/admin/js/ie/html5shiv.js"></script>
<script src="/public/admin/js/ie/respond.min.js"></script>
<script src="/public/admin/js/ie/excanvas.js"></script>
<![endif]-->
</head>
<body class="">
<section class="vbox">
<header class="bg-dark dk header navbar navbar-fixed-top-xs">
<div class="navbar-header aside-md" id="logo">
<a href="/member" class="navbar-brand clearfix" data-toggle="fullscreen">
<span class="logo-icon"></span>
<span class="logo-title">{{msg . "memberCenter"}}</span>
</a>
</div>
<ul class="nav navbar-nav navbar-right m-n hidden-xs nav-user">
<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">
{{msg . "myNote"}}
</a>
</li>
<li class="hidden-xs">
<a href="/blog/{{$.session.Username}}" class="dk" target="_blank">
{{msg . "myBlog"}}
</a>
</li>
<li class="hidden-xs">
<a href="/logout" class="dk">
{{msg . "logout"}}
</a>
</li>
<li class="hidden-xs">
<img alt="{{.userInfo.Username}}" title="{{.userInfo.Username}}" src="{{if .userInfo.Logo}}{{.userInfo.Logo}}{{else}}/images/blog/default_avatar.png{{end}}" id="myAvatar"/>
</li>
</ul>
</header>
<section>
<section class="hbox stretch">
<!-- .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">
<a class="btn btn-sm btn-primary">
Welcome, admin!
</a>
</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">
<!-- nav -->
{{template "member/nav.html" .}}
<!-- / nav -->
</div>
</section>
<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>
<!-- /.aside -->
<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">
<i class="fa fa-home">
</i>
Home
</a>
</li>
<li>
<a href="#">
Elements
</a>
</li>
<li class="active">
Components
</li>
</ul>
-->
<!-- 主要内容区 -->
+80
View File
@@ -0,0 +1,80 @@
{{template "member/top.html" .}}
<div class="m-b-md"> <h3 class="m-b-none">用户名设置</h3></div>
<div class="row">
<div class="col-sm-6">
<form id="formData">
<section class="panel panel-default">
<div class="panel-body">
<div class="alert alert-danger" id="thirdAccountMsg" style="display: none"></div>
{{msg . "thirdCreateAcountTips"}}
<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>
</div>
<footer class="panel-footer text-right bg-light lter">
<button type="submit" id="accountBtn" class="btn btn-success">{{msg . "submit"}}</button>
</footer>
</section>
</form>
</div>
</div>
{{template "member/footer.html" .}}
<script>
$(function() {
// 第三方账号设置
var acountVd = new vd.init("#formData");
$("#accountBtn").click(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");
setTimeout(function() {
location.reload();
}, 1000);
} else {
showAlert("#thirdAccountMsg", ret.Msg || getMsg("createAccountFailed"), "danger");
}
}, this);
});
});
</script>
{{template "member/end.html" .}}
+54
View File
@@ -0,0 +1,54 @@
{{template "member/top.html" .}}
<div class="m-b-md"> <h3 class="m-b-none">头像设置</h3></div>
<div class="row">
<div class="col-sm-6">
<form id="uploadAvatar" method="post" action="/file/uploadAvatar" enctype="multipart/form-data">
<section class="panel panel-default">
<div class="panel-body">
<div class="form-group">
<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>
</div>
</div>
</section>
</form>
</div>
</div>
{{template "member/footer.html" .}}
<script src="/js/require.js"></script>
<script>
var UrlPrefix = "{{.siteUrl}}"; // 为了发weibo
var GlobalConfigs = {{.globalConfigs|jsonJs}}; // 2014/11/9 beta2
require.config({
baseUrl: '/public',
paths: {
// ajax upload image/attach
'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']}
}
});
$(function() {
require(['attachment_upload'], function(attachment_upload) {});
});
</script>
{{template "member/end.html" .}}
+117
View File
@@ -0,0 +1,117 @@
{{template "member/top.html" .}}
<div class="m-b-md"> <h3 class="m-b-none">邮箱设置</h3></div>
<div class="row">
<div class="col-sm-6">
<form id="formData">
<section class="panel panel-default">
<div class="panel-body">
{{msg . "currentEmail" .userInfo.Email}}
<br />
{{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}}
<div class="alert alert-danger" id="emailMsg" style="display: none"></div>
<div class="form-group">
<label>{{msg . "email"}}</label>
<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>
</div>
<footer class="panel-footer text-right bg-light lter">
<button type="submit" id="emailBtn" class="btn btn-success">{{msg . "submit"}}</button>
</footer>
</section>
</form>
</div>
<!-- 激活邮件 -->
<div class="modal fade bs-modal-sm" id="reSendActiveEmailDialog" 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">{{msg . "sendVerifiedEmail"}}</h4>
</div>
<div class="modal-body">
<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"/>
{{msg . "emailInSending"}} {{.userInfo.Email}}...
</span>
</div>
</div>
<div class="modal-footer">
<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>
</div>
</div>
{{template "member/footer.html" .}}
<script>
$(function() {
// 修改邮箱
var emailVd = new vd.init("#formData");
$("#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");
});
// 重新发送
$(".reSendActiveEmail").click(function() {
// 弹框出来
showDialog2("#reSendActiveEmailDialog", {postShow: function() {
ajaxGet("/user/reSendActiveEmail", {}, function(ret) {
if (typeof ret == "object" && ret.Ok) {
$("#reSendActiveEmailDialog .text").html(getMsg("sendSuccess"))
$("#reSendActiveEmailDialog .viewEmailBtn").removeClass("disabled");
$("#reSendActiveEmailDialog .viewEmailBtn").click(function() {
hideDialog();
var url = getEmailLoginAddress("{{.userInfo.Email}}");
window.open(url, "_blank");
});
} else {
$("#reSendActiveEmailDialog .text").html(getMsg("sendFailed"))
}
});
}});
});
// 现在去验证
$(".nowToActive").click(function() {
var url = getEmailLoginAddress("{{.userInfo.Email}}");
window.open(url, "_blank");
});
});
</script>
{{template "member/end.html" .}}
+74
View File
@@ -0,0 +1,74 @@
{{template "member/top.html" .}}
<div class="m-b-md"> <h3 class="m-b-none">用户名设置</h3></div>
<div class="row">
<div class="col-sm-6">
<form id="formData">
<section class="panel panel-default">
<div class="panel-body">
<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>
</div>
<footer class="panel-footer text-right bg-light lter">
<button type="submit" id="pwdBtn" class="btn btn-success">{{msg . "submit"}}</button>
</footer>
</section>
</form>
</div>
</div>
{{template "member/footer.html" .}}
<script>
$(function() {
// 修改密码
var updatePwdVd = new vd.init("#formData");
$("#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");
});
});
</script>
{{template "member/end.html" .}}
+57
View File
@@ -0,0 +1,57 @@
{{template "member/top.html" .}}
<div class="m-b-md"> <h3 class="m-b-none">用户名设置</h3></div>
<div class="row">
<div class="col-sm-6">
<form id="formData">
<section class="panel panel-default">
<div class="panel-body">
<div class="alert alert-danger" id="usernameMsg" style="display: none"></div>
<div class="form-group">
<label>{{msg . "username"}}</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>
</div>
<footer class="panel-footer text-right bg-light lter">
<button type="submit" id="usernameBtn" class="btn btn-success">{{msg . "submit"}}</button>
</footer>
</section>
</form>
</div>
</div>
{{template "member/footer.html" .}}
<script>
$(function() {
//-------------
var usernameVd = new vd.init("#formData");
$("#usernameBtn").click(function(e) {
e.preventDefault();
if(!usernameVd.valid()) {
return;
}
var username = $("#username").val();
post("/user/updateUsername", {username: username}, function(ret) {
if(ret.Ok) {
showAlert('#usernameMsg', getMsg("updateUsernameSuccess"), "success");
} else {
showAlert('#usernameMsg', ret.Msg || getMsg("usernameIsExisted"), "danger");
}
}, "#usernameBtn");
});
});
</script>
{{template "member/end.html" .}}
View File
Whitespace-only changes.
+14 -15
View File
@@ -136,30 +136,28 @@ function log(o) {
<i class="fa fa-angle-down"></i>
</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"}}
<li role="presentation">
<a href="/member" target="_blank">
<i class="fa fa-user"></i>
{{msg . "memberCenter"}}
</a>
</li>
<li role="presentation" id="setTheme">
<a>
<i class="fa fa-sun-o"></i>
{{msg . "themeSetting"}}
</a>
</li>
<!--
<li role="presentation" id="yourSuggestions">
<i class="fa fa-comment-o"></i>
{{msg . "yourSuggestions"}}
<li role="presentation" class="divider"></li>
<li role="presentation">
<a href="/member/blog/index" target="_blank">
<i class="fa fa-bold"></i>
{{msg . "blogSet"}}
</a>
</li>
-->
<li role="presentation" class="my-link" >
<a target="_blank" href="{{$.blogUrl}}/{{.userInfo.Username}}">
<i class="fa fa-bold"></i>
@@ -843,6 +841,7 @@ var notes = json({{.notes}});
var noteContentJson = json({{.noteContentJson}});
var tagsJson = json({{.tagsJson}});
LEA.locale = "{{.locale}}";
var GlobalConfigs = {{.globalConfigs|jsonJs}}; // 2014/11/9 beta2
</script>
<!-- 渲染view -->
+13 -15
View File
@@ -136,30 +136,28 @@ function log(o) {
<i class="fa fa-angle-down"></i>
</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"}}
<li role="presentation">
<a href="/member" target="_blank">
<i class="fa fa-user"></i>
{{msg . "memberCenter"}}
</a>
</li>
<li role="presentation" id="setTheme">
<a>
<i class="fa fa-sun-o"></i>
{{msg . "themeSetting"}}
</a>
</li>
<!--
<li role="presentation" id="yourSuggestions">
<i class="fa fa-comment-o"></i>
{{msg . "yourSuggestions"}}
<li role="presentation" class="divider"></li>
<li role="presentation">
<a href="/member/blog/base" target="_blank">
<i class="fa fa-bold"></i>
{{msg . "blogSet"}}
</a>
</li>
-->
<li role="presentation" class="my-link" >
<a target="_blank" href="{{$.blogUrl}}/{{.userInfo.Username}}">
<i class="fa fa-bold"></i>
@@ -7,6 +7,13 @@
{{$noteOrNotebookId := .noteOrNotebookId}}
<div class="modal-body">
<ul id="myTab" class="nav nav-tabs">
<li class="active"><a href="#baseInfo" data-toggle="tab">分享给好友</a></li>
<li class=""><a href="#groupInfo" data-toggle="tab">分享给项目组</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="baseInfo">
<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">
@@ -29,7 +36,7 @@
<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)">{{msg . "share"}}</button>
<button class="btn btn-success" data-loading-text="..." onclick="addShareNoteOrNotebook(1)">{{msg . "share"}}</button>
<button class="btn btn-warning" onclick="deleteShareNoteOrNotebook(1)">{{msg . "delete"}}</button>
</td>
</tr>
@@ -40,18 +47,56 @@
<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">{{msg . "readOnly"}}</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">{{msg . "writable"}}</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">{{msg . "delete"}}</a>
<a href="#" noteOrNotebookId="{{$noteOrNotebookId}}" toUserId="{{$toUserId}}" class="btn btn-warning delete-share">{{msg $ "delete"}}</a>
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
<div class="tab-pane" id="groupInfo">
<p>
将笔记/笔记本分享给项目组后, 该组下的用户便拥有了该笔记/笔记本. <a href="/member/group/index" target="_blank">项目组/成员管理</a>
</p>
<table class="table table-hover">
<thead>
<tr>
<th>分组名</th>
<th>{{msg . "permission"}}</th>
<th width="150px">操作</th>
</tr>
</thead>
<tbody>
{{range $i, $share := .noteOrNotebookShareGroupInfos}}
{{$groupId := $share.ToGroup.GroupId.Hex}}
<tr data-id="{{$groupId}}" data-uid="{{$share.UserId.Hex}}">
<td>
{{$share.ToGroup.Title}}
</td>
<td>
<label><input type="radio" name="perm_{{$groupId}}" {{if not $share.Perm}}checked="checked"{{end}} value="0" class="group-perm"> {{msg $ "readOnly"}}</label>
<label><input type="radio" name="perm_{{$groupId}}" {{if $share.Perm}}checked="checked"{{end}} value="1" class="group-perm"> {{msg $ "writable"}}</label>
</td>
<td> <!-- 有userId 证明已分享 -->
{{if $share.UserId}}
<button class="btn btn-success btn-share-or-not">已分享</button>
{{else}}
<button class="btn btn-default btn-share-or-not">未分享</button>
{{end}}
</td>
</tr>
{{end}}
</table>
</div>
</div>
</div>
<div class="modal-footer">
@@ -64,8 +109,75 @@
Share.dialogIsNote = {{.isNote}};
Share.dialogNoteOrNotebookId = '{{.noteOrNotebookId}}';
$(function() {
setTimeout(function() {
$("#tr1 #friendsEmail").focus();
}, 500);
setTimeout(function() {
$("#tr1 #friendsEmail").focus();
}, 500);
// 分享/删除给分组
$("#groupInfo").on("click", ".btn-share-or-not", function() {
var $t = $(this);
var $ptd = $t.closest("td");
var $ptr = $t.closest("tr");
var hasShared = $ptr.data('uid');
var groupId = $ptr.data('id');
var data = {groupId: groupId};
var url = "";
if(!hasShared) {
var perm = $ptr.find("input:checked").val();
data.perm = perm;
if(Share.dialogIsNote) {
data.noteId = Share.dialogNoteOrNotebookId;
url = "/share/addShareNoteGroup";
} else {
data.notebookId = Share.dialogNoteOrNotebookId;
url = "/share/addShareNotebookGroup";
}
} else {
if(Share.dialogIsNote) {
data.noteId = Share.dialogNoteOrNotebookId;
url = "/share/deleteShareNoteGroup";
} else {
data.notebookId = Share.dialogNoteOrNotebookId;
url = "/share/deleteShareNotebookGroup";
}
}
ajaxPost(url, data, function(re) {
if(reIsOk(re)) {
if(!hasShared) {
var text = '<button class="btn btn-success btn-share-or-not">已分享</button>';
$ptr.data('uid', 'xxxxxx');
} else {
var text = '<button class="btn btn-default btn-share-or-not">未分享</button>';
$ptr.data('uid', '');
}
$ptd.html(text);
}
});
});
$(".group-perm").click(function() {
var $t = $(this);
var $ptr = $t.closest("tr");
var hasShared = $ptr.data('uid');
if(!hasShared) {
return;
}
var groupId = $ptr.data('id');
var data = {groupId: groupId, perm: $t.val()};
var url = ""
if(Share.dialogIsNote) {
data.noteId = Share.dialogNoteOrNotebookId;
url = "/share/updateShareNoteGroupPerm";
} else {
data.notebookId = Share.dialogNoteOrNotebookId;
url = "/share/updateShareNotebookGroupPerm";
}
ajaxPost(url, data, function(re) {
if(!reIsOk(re)) {
alert(re.Msg);
}
});
});
});
</script>
@@ -1,84 +0,0 @@
<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">{{.note.Title}} 的分享状态</h4>
</div>
{{$noteId := .note.NoteId.Hex}}
<div class="modal-body">
{{if .noteShareUserInfos }}
<table class="table table-hover">
<thead>
<tr>
<th>#</th>
<th>Email</th>
<th>权限</th>
<th>删除分享</th>
</tr>
</thead>
<tbody>
{{range $i, $v := .noteShareUserInfos}}
{{$toUserId := $v.ToUserId.Hex}}
<tr>
<td>{{add $i}}</td>
<td>{{$v.Email}}</td>
<td>
{{if eq $v.Perm 0}}
<a href="#" noteId="{{$noteId}}" perm="{{$v.Perm}}" toUserId="{{$toUserId}}" title="点击改变权限" class="btn btn-default change-perm">只读</a>
{{else}}
<a href="#" noteId="{{$noteId}}" perm="{{$v.Perm}}" toUserId="{{$toUserId}}" title="点击改变权限" class="btn btn-default change-perm">可编辑</a>
{{end}}
</td>
<td>
<a href="#" noteId="{{$noteId}}" toUserId="{{$toUserId}}" class="btn btn-warning delete-share">删除</a>
</td>
</tr>
{{end}}
</tbody>
</table>
{{else}}
无分享信息
{{end}}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
<script>
$(function() {
$(".change-perm").click(function() {
var self = this;
var perm = $(this).attr("perm");
var noteId = $(this).attr("noteId");
var toUserId = $(this).attr("toUserId");
var toHtml = "可编辑";
var toPerm = "1";
if(perm == "1") {
toHtml = "只读";
toPerm = "0";
}
ajaxGet("/share/UpdateShareNotePerm", {noteId: noteId, perm: toPerm, toUserId: toUserId}, function(ret) {
if(ret) {
$(self).html(toHtml);
$(self).attr("perm", toPerm);
}
});
});
$(".delete-share").click(function() {
var self = this;
var noteId = $(this).attr("noteId");
var toUserId = $(this).attr("toUserId");
ajaxGet("/share/DeleteShareNote", {noteId: noteId, toUserId: toUserId}, function(ret) {
if(ret) {
$(self).parent().parent().remove();
}
});
});
});
</script>
+65 -21
View File
@@ -23,34 +23,75 @@ GET /findPassword Auth.FindPassword
POST /doFindPassword Auth.DoFindPassword
POST /findPasswordUpdate Auth.FindPasswordUpdate
# blog
GET /blog/getLikeAndComments Blog.GetLikeAndComments
GET /blog/getLike Blog.GetLike
GET /blog/listLikes Blog.ListLikes
GET /blog/incReadNum Blog.IncReadNum
POST /blog/likeBlog Blog.LikeBlog
POST /blog/likeComment Blog.LikeComment
POST /blog/deleteComment Blog.DeleteComment
GET /blog/listComments Blog.ListComments
POST /blog/comment Blog.Comment
GET /blog/ListComments Blog.ListComments
GET /blog/getLikesAndComments Blog.GetLikesAndComments
GET /blog/getLikes Blog.GetLikes
* /blog/incReadNum Blog.IncReadNum
* /blog/likePost Blog.LikePost
* /blog/likeComment Blog.LikeComment
* /blog/deleteComment Blog.DeleteComment
GET /blog/getComments Blog.GetComments
* /blog/commentPost Blog.CommentPost
GET /blog/getPostStat Blog.GetPostStat
GET /blog/tags/:userIdOrEmail Blog.Tags
GET /blog/tags Blog.Tags
GET /blog/tag/:userIdOrEmail/:tag Blog.Tag
GET /blog/tag/:tag Blog.Tag
GET /blog/search/:userIdOrEmail Blog.Search
GET /blog/search Blog.Search
GET /blog/set Blog.Set
GET /blog/aboutMe/:userIdOrEmail Blog.AboutMe
GET /blog/aboutMe Blog.AboutMe
GET /blog/view/:noteId Blog.View
POST /blog/setUserBlogBase Blog.SetUserBlogBase
POST /blog/setUserBlogComment Blog.SetUserBlogComment
POST /blog/setUserBlogStyle Blog.SetUserBlogStyle
GET /blog/archives/:userIdOrEmail Blog.Archives
GET /blog/archives Blog.Archives
GET /blog/post/:noteId Blog.Post
GET /blog/post/:userIdOrEmail/:noteId Blog.Post
GET /blog/view/:noteId Blog.Post
GET /blog/single/:userIdOrEmail/:singleId Blog.Single
GET /blog/single/:singleId Blog.Single
GET /blog/cate/:notebookId Blog.Cate
GET /blog/cate/:userIdOrEmail/:notebookId Blog.Cate
GET /blog/listCateLatest/:notebookId Blog.ListCateLatest
GET /blog/isMe.js Blog.IsMe
GET /blog/:userIdOrEmail Blog.Index
GET /blog Blog.Index
GET /blog/* Blog.E()
#---------------
# preview
GET /preview/tags/:userIdOrEmail Preview.Tags
GET /preview/tags Preview.Tags
GET /preview/tag/:userIdOrEmail/:tag Preview.Tag
GET /preview/tag/:tag Preview.Tag
GET /preview/search/:userIdOrEmail Preview.Search
GET /preview/search Preview.Search
GET /preview/archives/:userIdOrEmail Preview.Archives
GET /preview/archives Preview.Archives
GET /preview/view/:noteId Preview.Post
GET /preview/post/:noteId Preview.Post
GET /preview/post/userIdOrEmail/:noteId Preview.Post
GET /preview/single/userIdOrEmail/:singleId Preview.Single
GET /preview/single/:singleId Preview.Single
GET /preview/cate/:notebookId Preview.Cate
GET /preview/cate/userIdOrEmail/:notebookId Preview.Cate
GET /preview/:userIdOrEmail Preview.Index
GET /preview Preview.Index
# Map static resources from the /app/public folder to the /public path
GET /favicon.ico Static.Serve("public/images/favicon.ico")
@@ -64,9 +105,12 @@ GET /tinymce/*filepath Static.Serve("public/tinymce")
#GET /plugins/*filepath Static.Serve("public/tinymce/plugins")
GET /upload/*filepath Static.Serve("public/upload")
# API
# the prefix of api's controllername is "Api"
# member
# the prefix of member's controllername is "Member"
* /member MemberIndex.Index
* /member/index MemberIndex.Index
# common
* /:controller/:action :controller.:action
* /api/:controller/:action :controller.:action # 需要
* /api/:controller/:action :controller.:action
* /member/:controller/:action :controller.:action
+12
View File
@@ -59,6 +59,7 @@ hasAcount = Do not have an account?
# 注册
registerSuccessAndRdirectToNote=Register success, redirecting...
userHasBeenRegistered=%s has been registered
# 找回密码
passwordTips=The length is at least 6
@@ -87,6 +88,7 @@ new=New
newMarkdown=New markdown note
clickAddTag=Click to add Tag
notebook=Notebook
note=Note
myNotebook=My notebook
addNotebook=Add notebook
search=Search
@@ -129,6 +131,7 @@ logout=Logout
basicInfo=Basic
updateEmail=Update email
usernameSetting=Update username
username=Username
oldPassword=Old password
newPassword=New password
admin=Admin
@@ -222,17 +225,26 @@ 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
oldPasswordError=The old password not match
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
domainNotPointToLeanote=The custom domin hasn't pointed to d.leanote.com
errorPerPageSize=Page size is error
errorSortField=Sort Field is error
# lea++
leaDesc=leanote blog platform
recommend=Recommend
latest=Latest
# 用户中心
memberCenter=Member Center
userNotExists=该成员沿未注册
hasUsers=已存在该成员
# error
notFound=This page cann't found.
+13 -1
View File
@@ -79,6 +79,7 @@ hasAcount = 还无帐户?
# 注册
registerSuccessAndRdirectToNote=注册成功, 正在跳转...
userHasBeenRegistered=%s已被注册
# 找回密码
passwordTips=密码至少6位
@@ -108,6 +109,7 @@ new=新建
newMarkdown=新建Markdown笔记
clickAddTag=点击添加标签
notebook=笔记本
note=笔记
myNotebook=我的笔记本
addNotebook=添加笔记本
search=搜索
@@ -150,6 +152,7 @@ logout=退出
basicInfo=基本信息
updateEmail=修改Email
usernameSetting=用户名设置
username=用户名
oldPassword=旧密码
newPassword=新密码
admin=后台管理
@@ -248,17 +251,26 @@ inputPassword=请输入密码
inputNewPassword=请输入新密码
inputPassword2=请输入确认密码
errorPassword=请输入长度不少于6位的密码, 尽量复杂
oldPasswordError=旧密码错误
confirmPassword=两次密码输入不正确
updatePasswordSuccess=修改密码成功
errorDomain=请输入正确的域名, 如www.myblog.com
domainExisted=域名已存在
errorSubDomain=请输入正确的博客子域名, 长度至少为4, 不能包含特殊字符
subDomainExisted=博客子域名已存在
domainNotPointToLeanote=该域名还未指向 d.leanote.com, 请稍后再试
errorPerPageSize=每页记录数至少为1
errorSortField=排序类型错误
# lea++
leaDesc=leanote博客平台
recommend=推荐
latest=最新
# 用户中心
memberCenter=用户中心
userNotExists=该成员沿未注册
hasUsers=已存在该成员
# 必须要加这个, 奇怪
[CN]
[CN]
@@ -1 +1 @@
{ "indexes" : [ { "v" : 1, "key" : { "_id" : 1 }, "ns" : "leanote.albums", "name" : "_id_" } ] }
{ "indexes" : [ { "v" : 1, "key" : { "_id" : 1 }, "ns" : "leanote_beta2.albums", "name" : "_id_" } ] }
@@ -1 +1 @@
{ "indexes" : [ { "v" : 1, "key" : { "_id" : 1 }, "ns" : "leanote.attachs", "name" : "_id_" } ] }
{ "indexes" : [ { "v" : 1, "key" : { "_id" : 1 }, "ns" : "leanote_beta2.attachs", "name" : "_id_" } ] }
@@ -1 +1 @@
{ "indexes" : [ { "v" : 1, "key" : { "_id" : 1 }, "ns" : "leanote.blog_comments", "name" : "_id_" } ] }
{ "indexes" : [ { "v" : 1, "key" : { "_id" : 1 }, "ns" : "leanote_beta2.blog_comments", "name" : "_id_" } ] }
@@ -1 +1 @@
{ "indexes" : [ { "v" : 1, "key" : { "_id" : 1 }, "ns" : "leanote.blog_likes", "name" : "_id_" } ] }
{ "indexes" : [ { "v" : 1, "key" : { "_id" : 1 }, "ns" : "leanote_beta2.blog_likes", "name" : "_id_" } ] }
@@ -0,0 +1 @@
{ "indexes" : [ { "v" : 1, "key" : { "_id" : 1 }, "ns" : "leanote_beta2.blog_singles", "name" : "_id_" } ] }
Loaded 100 of 518 files, more files were not shown because too many files have changed in this diff. Show more