1、实现网站首页

2、实现文档阅读
3、实现项目发布
4、实现用户权限
pull/25/merge
lifei6671 2017-04-30 22:13:12 +08:00
parent d38417535a
commit ad67558b80
36 changed files with 1486 additions and 233 deletions

14
Dockerfile 100644
View File

@ -0,0 +1,14 @@
FROM golang:1.8.1-alpine
ADD . /go/src/github.com/lifei6671/godoc
WORKDIR /go/src/github.com/lifei6671/godoc
RUN chmod +x start.sh
RUN go build -ldflags "-w" && \
rm -rf commands controllers models routers search vendor .gitignore .travis.yml Dockerfile gide.yaml LICENSE main.go README.md utils graphics Godeps
CMD ["./start.sh"]

View File

@ -4,13 +4,15 @@ import (
"fmt"
"net/url"
"time"
"os"
"encoding/gob"
"github.com/lifei6671/godoc/models"
"github.com/astaxie/beego"
"github.com/astaxie/beego/orm"
"github.com/astaxie/beego/logs"
"os"
"github.com/lifei6671/godoc/conf"
)
// RegisterDataBase 注册数据库
@ -75,7 +77,7 @@ func RegisterLogger() {
logs.EnableFuncCallDepth(true)
logs.Async()
beego.BeeLogger.DelLogger("console")
//beego.BeeLogger.DelLogger("console")
beego.SetLogger("file",`{"filename":"logs/log.log"}`)
beego.SetLogFuncCall(true)
beego.BeeLogger.Async()
@ -101,3 +103,7 @@ func RegisterCommand() {
func RegisterFunction() {
beego.AddFuncMap("config",models.GetOptionValue)
}
func init() {
gob.Register(models.Member{})
}

View File

@ -5,6 +5,12 @@ sessionon = true
sessionname = smart_webhook_id
copyrequestbody = true
#默认Session生成Key的秘钥
beegoserversessionkey=123456
#Session储存方式
sessionprovider=file
sessionproviderconfig=./logs
#生成回调地址时完整的域名
base_url = https://hook.iminho.me

View File

@ -6,6 +6,12 @@ sessionon = true
sessionname = smart_webhook_id
copyrequestbody = true
#默认Session生成Key的秘钥
beegoserversessionkey=123456
#Session储存方式
sessionprovider=file
sessionproviderconfig=./logs
#生成回调地址时完整的域名
base_url = https://hook.iminho.me

View File

@ -77,3 +77,6 @@ func (c *AccountController) Logout(){
c.Redirect(beego.URLFor("AccountController.Login"),302)
}
func (c *AccountController) Captcha() {
}

View File

@ -7,32 +7,42 @@ import (
"github.com/lifei6671/godoc/models"
"github.com/lifei6671/godoc/conf"
"github.com/astaxie/beego"
"strings"
)
type BaseController struct {
beego.Controller
Member *models.Member
Option map[string]string
EnableAnonymous bool
}
// Prepare 预处理.
func (c *BaseController) Prepare (){
c.Data["SiteName"] = "MinDoc"
c.Data["Member"] = models.Member{}
c.EnableAnonymous = false
if member,ok := c.GetSession(conf.LoginSessionName).(models.Member); ok && member.MemberId > 0{
c.Member = &member
c.Data["Member"] = c.Member
}else{
c.Member = models.NewMember()
c.Member.Find(1)
c.Data["Member"] = *c.Member
//c.Member = models.NewMember()
//c.Member.Find(1)
//c.Data["Member"] = *c.Member
}
c.Data["BaseUrl"] = c.Ctx.Input.Scheme() + "://" + c.Ctx.Request.Host
if options,err := models.NewOption().All();err == nil {
c.Option = make(map[string]string,len(options))
for _,item := range options {
c.Data[item.OptionName] = item.OptionValue
c.Option[item.OptionName] = item.OptionValue
if strings.EqualFold(item.OptionName,"ENABLE_ANONYMOUS") && item.OptionValue == "true" {
c.EnableAnonymous = true
}
}
}
}

View File

@ -499,7 +499,28 @@ func (c *BookController) Delete() {
//发布项目
func (c *BookController) Release() {
c.JsonResult(0,"ok")
c.Prepare()
identify := c.GetString("identify")
book ,err := models.NewBookResult().FindByIdentify(identify,c.Member.MemberId)
if err != nil {
if err == models.ErrPermissionDenied {
c.JsonResult(6001,"权限不足")
}
if err == orm.ErrNoRows {
c.JsonResult(6002,"项目不存在")
}
beego.Error(err)
c.JsonResult(6003,"未知错误")
}
if book.RoleId != conf.BookAdmin && book.RoleId != conf.BookFounder && book.RoleId != conf.BookEditor{
c.JsonResult(6003,"权限不足")
}
go models.NewDocument().ReleaseContent(book.BookId)
c.JsonResult(0,"发布任务已推送到任务队列,稍后将在后台执行。")
}
func (c *BookController) SaveSort() {

View File

@ -21,12 +21,135 @@ type DocumentController struct {
BaseController
}
func (p *DocumentController) Index() {
p.TplName = "document/index.tpl"
func isReadable (identify,token string,c *DocumentController) *models.BookResult {
book,err := models.NewBook().FindByFieldFirst("identify",identify)
if err != nil {
beego.Error(err)
c.Abort("500")
}
//如果文档是私有的
if book.PrivatelyOwned == 1 {
is_ok := false
if c.Member != nil{
_, err := models.NewRelationship().FindForRoleId(book.BookId, c.Member.MemberId)
if err == nil {
is_ok = true
}
}
if book.PrivateToken != "" && !is_ok {
//如果有访问的Token并且该项目设置了访问Token并且和用户提供的相匹配则记录到Session中.
//如果用户未提供Token且用户登录了则判断用户是否参与了该项目.
//如果用户未登录则从Session中读取Token.
if token != "" && strings.EqualFold(token, book.PrivateToken) {
c.SetSession(identify, token)
} else if token, ok := c.GetSession(identify).(string); !ok || !strings.EqualFold(token, book.PrivateToken) {
c.Abort("403")
}
}else{
c.Abort("403")
}
}
bookResult := book.ToBookResult()
if c.Member != nil {
rel ,err := models.NewRelationship().FindByBookIdAndMemberId(bookResult.BookId,c.Member.MemberId)
if err == nil {
bookResult.MemberId = rel.MemberId
bookResult.RoleId = rel.RoleId
bookResult.RelationshipId = rel.RelationshipId
}
}
return bookResult
}
func (p *DocumentController) Read() {
p.TplName = "document/kancloud.tpl"
func (c *DocumentController) Index() {
c.Prepare()
identify := c.Ctx.Input.Param(":key")
token := c.GetString("token")
if identify == "" {
c.Abort("404")
}
bookResult := isReadable(identify,token,c)
c.TplName = "document/" + bookResult.Theme + "_read.tpl"
tree,err := models.NewDocument().CreateDocumentTreeForHtml(bookResult.BookId,0)
if err != nil {
beego.Error(err)
c.Abort("500")
}
c.Data["Model"] = bookResult
c.Data["Result"] = template.HTML(tree)
c.Data["Title"] = "概要"
c.Data["Content"] = bookResult.Description
}
func (c *DocumentController) Read() {
c.Prepare()
identify := c.Ctx.Input.Param(":key")
token := c.GetString("token")
id := c.GetString(":id")
if identify == "" || id == ""{
c.Abort("404")
}
bookResult := isReadable(identify,token,c)
c.TplName = "document/" + bookResult.Theme + "_read.tpl"
doc := models.NewDocument()
if doc_id,err := strconv.Atoi(id);err == nil {
doc,err = doc.Find(doc_id)
if err != nil {
beego.Error(err)
c.Abort("500")
}
}else{
doc,err = doc.FindByFieldFirst("identify",id)
if err != nil {
beego.Error(err)
c.Abort("500")
}
}
if doc.BookId != bookResult.BookId {
c.Abort("403")
}
if c.IsAjax() {
var data struct{
DocTitle string `json:"doc_title"`
Body string `json:"body"`
Title string `json:"title"`
}
data.DocTitle = doc.DocumentName
data.Body = doc.Release
data.Title = doc.DocumentName + " - Powered by MinDoc"
c.JsonResult(0,"ok",data)
}
tree,err := models.NewDocument().CreateDocumentTreeForHtml(bookResult.BookId,doc.DocumentId)
if err != nil {
beego.Error(err)
c.Abort("500")
}
c.Data["Model"] = bookResult
c.Data["Result"] = template.HTML(tree)
c.Data["Title"] = doc.DocumentName
c.Data["Content"] = template.HTML(doc.Release)
}
func (c *DocumentController) Edit() {
@ -67,7 +190,7 @@ func (c *DocumentController) Edit() {
c.Data["Result"] = template.JS("[]")
trees ,err := models.NewDocument().FindDocumentTree(bookResult.BookId)
beego.Info("",trees)
if err != nil {
beego.Error("FindDocumentTree => ", err)
}else{

View File

@ -1,9 +1,44 @@
package controllers
import (
"github.com/astaxie/beego"
"github.com/lifei6671/godoc/models"
"github.com/lifei6671/godoc/utils"
)
type HomeController struct {
BaseController
}
func (p *HomeController) Index() {
p.TplName = "home/index.tpl"
func (c *HomeController) Index() {
c.Prepare()
c.TplName = "home/index.tpl"
//如果没有开启匿名访问,则跳转到登录页面
if !c.EnableAnonymous && c.Member == nil {
c.Redirect(beego.URLFor("AccountController.Login"),302)
}
pageIndex,_ := c.GetInt("page",1)
pageSize := 18
member_id := 0
if c.Member != nil {
member_id = c.Member.MemberId
}
books,totalCount,err := models.NewBook().FindForHomeToPager(pageIndex,pageSize,member_id)
if err != nil {
beego.Error(err)
c.Abort("500")
}
if totalCount > 0 {
html := utils.GetPagerHtml(c.Ctx.Request.RequestURI, pageIndex, pageSize, totalCount)
c.Data["PageHtml"] = html
}else {
c.Data["PageHtml"] = ""
}
c.Data["Lists"] = books
}

View File

@ -320,18 +320,27 @@ func (c *ManagerController) Setting() {
if !c.Member.IsAdministrator() {
c.Abort("403")
}
if c.Ctx.Input.IsPost() {
}
options,err := models.NewOption().All()
if c.Ctx.Input.IsPost() {
for _,item := range options {
item.OptionValue = c.GetString(item.OptionName)
item.InsertOrUpdate()
}
c.JsonResult(0,"ok")
}
if err != nil {
c.Abort("500")
}
c.Data["SITE_TITLE"] = c.Option["SITE_NAME"]
for _,item := range options {
c.Data[item.OptionName] = item
}
}
func (c *ManagerController) Comments() {

View File

@ -1 +0,0 @@
## 日志存放目录

View File

@ -3,6 +3,7 @@ package main
import (
_ "github.com/go-sql-driver/mysql"
_ "github.com/lifei6671/godoc/routers"
_ "github.com/garyburd/redigo/redis"
"github.com/astaxie/beego"
"github.com/lifei6671/godoc/commands"
)

View File

@ -6,6 +6,7 @@ import (
"github.com/astaxie/beego/orm"
"github.com/lifei6671/godoc/conf"
"github.com/astaxie/beego/logs"
"strings"
)
// Book struct .
@ -32,8 +33,10 @@ type Book struct {
// CommentStatus 评论设置的状态:open 为允许所有人评论closed 为不允许评论, group_only 仅允许参与者评论 ,registered_only 仅允许注册者评论.
CommentStatus string `orm:"column(comment_status);size(20);default(open)" json:"comment_status"`
CommentCount int `orm:"column(comment_count);type(int)" json:"comment_count"`
Cover string `orm:"column();size(1000)" json:"cover"`
//封面地址
Cover string `orm:"column(cover);size(1000)" json:"cover"`
//主题风格
Theme string `orm:"columen(theme);size(255);default(default)" json:"theme"`
// CreateTime 创建时间 .
CreateTime time.Time `orm:"type(datetime);column(create_time);auto_now_add" json:"create_time"`
MemberId int `orm:"column(member_id);size(100)" json:"member_id"`
@ -109,23 +112,27 @@ func (m *Book) Update(cols... string) error {
return err
}
func (m *Book) FindByField(field string,value interface{}) ([]Book,error) {
//根据指定字段查询结果集.
func (m *Book) FindByField(field string,value interface{}) ([]*Book,error) {
o := orm.NewOrm()
var books []Book
_,err := o.QueryTable(conf.GetDatabasePrefix() + m.TableName()).Filter(field,value).All(&books)
var books []*Book
_,err := o.QueryTable(m.TableNameWithPrefix()).Filter(field,value).All(&books)
return books,err
}
//根据指定字段查询一个结果.
func (m *Book) FindByFieldFirst(field string,value interface{})(*Book,error) {
o := orm.NewOrm()
err := o.QueryTable(conf.GetDatabasePrefix() + m.TableName()).Filter(field,value).One(m)
err := o.QueryTable(m.TableNameWithPrefix()).Filter(field,value).One(m)
return m,err
}
//分页查询指定用户的项目
func (m *Book) FindToPager(pageIndex, pageSize ,memberId int) (books []*BookResult,totalCount int,err error){
relationship := NewRelationship()
@ -242,11 +249,75 @@ func (m *Book) ThoroughDeleteBook(id int) error {
}
func (m *Book) FindForHomeToPager(pageIndex, pageSize ,member_id int) (books []*BookResult,totalCount int,err error) {
o := orm.NewOrm()
offset := (pageIndex - 1) * pageSize
//如果是登录用户
if member_id > 0 {
sql1 := "SELECT COUNT(*) FROM md_books AS book LEFT JOIN md_relationship AS rel ON rel.book_id = book.book_id AND rel.member_id = ? WHERE relationship_id > 0 OR book.privately_owned = 0"
err = o.Raw(sql1,member_id).QueryRow(&totalCount)
if err != nil {
return
}
sql2 := `SELECT book.*,rel1.*,member.account AS create_name FROM md_books AS book
LEFT JOIN md_relationship AS rel ON rel.book_id = book.book_id AND rel.member_id = ?
LEFT JOIN md_relationship AS rel1 ON rel1.book_id = book.book_id AND rel1.role_id = 0
LEFT JOIN md_members AS member ON rel1.member_id = member.member_id
WHERE rel.relationship_id > 0 OR book.privately_owned = 0 ORDER BY order_index DESC ,book.book_id DESC LIMIT ?,?`
_,err = o.Raw(sql2,member_id,offset,pageSize).QueryRows(&books)
return
}else{
count,err1 := o.QueryTable(m.TableNameWithPrefix()).Filter("privately_owned",0).Count()
if err1 != nil {
err = err1
return
}
totalCount = int(count)
sql := `SELECT book.*,rel.*,member.account AS create_name FROM md_books AS book
LEFT JOIN md_relationship AS rel ON rel.book_id = book.book_id AND rel.role_id = 0
LEFT JOIN md_members AS member ON rel.member_id = member.member_id
WHERE book.privately_owned = 0 ORDER BY order_index DESC ,book.book_id DESC LIMIT ?,?`
_,err = o.Raw(sql,offset,pageSize).QueryRows(&books)
return
}
}
func (book *Book) ToBookResult() *BookResult {
m := NewBookResult()
m.BookId = book.BookId
m.BookName = book.BookName
m.Identify = book.Identify
m.OrderIndex = book.OrderIndex
m.Description = strings.Replace(book.Description, "\r\n", "<br/>", -1)
m.PrivatelyOwned = book.PrivatelyOwned
m.PrivateToken = book.PrivateToken
m.DocCount = book.DocCount
m.CommentStatus = book.CommentStatus
m.CommentCount = book.CommentCount
m.CreateTime = book.CreateTime
m.ModifyTime = book.ModifyTime
m.Cover = book.Cover
m.Label = book.Label
m.Status = book.Status
m.Editor = book.Editor
m.Theme = book.Theme
return m
}

View File

@ -2,8 +2,8 @@ package models
import (
"time"
"github.com/astaxie/beego/orm"
"strings"
"github.com/astaxie/beego/logs"
"github.com/lifei6671/godoc/conf"
)
@ -23,6 +23,7 @@ type BookResult struct {
CreateName string `json:"create_name"`
ModifyTime time.Time `json:"modify_time"`
Cover string `json:"cover"`
Theme string `json:"theme"`
Label string `json:"label"`
MemberId int `json:"member_id"`
Editor string `json:"editor"`
@ -39,6 +40,7 @@ func NewBookResult() *BookResult {
return &BookResult{}
}
// 根据项目标识查询项目以及指定用户权限的信息.
func (m *BookResult) FindByIdentify(identify string,member_id int) (*BookResult,error) {
if identify == "" || member_id <= 0 {
@ -77,24 +79,9 @@ func (m *BookResult) FindByIdentify(identify string,member_id int) (*BookResult,
return m, err
}
m.BookId = book.BookId
m.BookName = book.BookName
m.Identify = book.Identify
m.OrderIndex = book.OrderIndex
m.Description = strings.Replace(book.Description, "\r\n", "<br/>", -1)
m.PrivatelyOwned = book.PrivatelyOwned
m.PrivateToken = book.PrivateToken
m.DocCount = book.DocCount
m.CommentStatus = book.CommentStatus
m.CommentCount = book.CommentCount
m.CreateTime = book.CreateTime
m.CreateName = member.Account
m.ModifyTime = book.ModifyTime
m.Cover = book.Cover
m.Label = book.Label
m.Status = book.Status
m.Editor = book.Editor
m = book.ToBookResult()
m.CreateName = member.Account
m.MemberId = relationship.MemberId
m.RoleId = relationship.RoleId
m.RelationshipId = relationship.RelationshipId

View File

@ -112,7 +112,17 @@ func (m *Document) RecursiveDocument(doc_id int) error {
return nil
}
func (m *Document) ReleaseContent(book_id int) {
o := orm.NewOrm()
_,err := o.Raw("UPDATE md_documents SET `release` = content WHERE book_id =?",book_id).Exec()
if err != nil {
beego.Error(err)
}
}

View File

@ -2,6 +2,10 @@ package models
import (
"github.com/astaxie/beego/orm"
"bytes"
"strconv"
"github.com/astaxie/beego"
"html/template"
)
type DocumentTree struct {
@ -9,6 +13,7 @@ type DocumentTree struct {
DocumentName string `json:"text"`
ParentId interface{} `json:"parent"`
Identify string `json:"identify"`
BookIdentify string `json:"-"`
Version int64 `json:"version"`
State *DocumentSelected `json:"state,omitempty"`
}
@ -17,7 +22,7 @@ type DocumentSelected struct {
Opened bool `json:"opened"`
}
//获取项目的文档树状结构
func (m *Document) FindDocumentTree(book_id int) ([]*DocumentTree,error){
o := orm.NewOrm()
@ -30,6 +35,7 @@ func (m *Document) FindDocumentTree(book_id int) ([]*DocumentTree,error){
if err != nil {
return trees,err
}
book,_ := NewBook().Find(book_id)
trees = make([]*DocumentTree,count)
@ -41,6 +47,7 @@ func (m *Document) FindDocumentTree(book_id int) ([]*DocumentTree,error){
tree.DocumentId = item.DocumentId
tree.Identify = item.Identify
tree.Version = item.Version
tree.BookIdentify = book.Identify
if item.ParentId > 0 {
tree.ParentId = item.ParentId
}else{
@ -54,3 +61,102 @@ func (m *Document) FindDocumentTree(book_id int) ([]*DocumentTree,error){
return trees,nil
}
func (m *Document) CreateDocumentTreeForHtml(book_id, selected_id int) (string,error) {
trees,err := m.FindDocumentTree(book_id)
if err != nil {
return "",err
}
parent_id := getSelectedNode(trees,selected_id)
buf := bytes.NewBufferString("")
getDocumentTree(trees,0,selected_id,parent_id,buf)
return buf.String(),nil
}
//使用递归的方式获取指定ID的顶级ID
func getSelectedNode(array []*DocumentTree, parent_id int) int {
for _,item := range array {
if _,ok := item.ParentId.(string); ok && item.DocumentId == parent_id {
return item.DocumentId
}else if pid,ok := item.ParentId.(int); ok && item.DocumentId == parent_id{
return getSelectedNode(array,pid)
}
}
return 0
}
func getDocumentTree(array []*DocumentTree,parent_id int,selected_id int,selected_parent_id int,buf *bytes.Buffer) {
buf.WriteString("<ul>")
for _,item := range array {
pid := 0
if p,ok := item.ParentId.(int);ok {
pid = p
}
if pid == parent_id {
/**
$selected = $item['doc_id'] == $selected_id ? ' class="jstree-clicked"' : '';
$selected_li = $item['doc_id'] == $selected_parent_id ? ' class="jstree-open"' : '';
$menu .= '<li id="'.$item['doc_id'].'"'.$selected_li.'><a href="'. route('document.show',['doc_id'=> $item['doc_id']]) .'" title="' . htmlspecialchars($item['doc_name']) . '"'.$selected.'>' . $item['doc_name'] .'</a>';
$key = array_search($item['doc_id'], array_column($array, 'parent_id'));
if ($key !== false) {
self::createTree($item['doc_id'], $array,$selected_id,$selected_parent_id);
}
$menu .= '</li>';
*/
selected := ""
if item.DocumentId == selected_id {
selected = ` class="jstree-clicked"`
}
selected_li := ""
if item.DocumentId == selected_parent_id {
selected_li = ` class="jstree-open"`
}
buf.WriteString("<li id=\"")
buf.WriteString(strconv.Itoa(item.DocumentId))
buf.WriteString("\"")
buf.WriteString(selected_li)
buf.WriteString("><a href=\"")
if item.Identify != ""{
uri := beego.URLFor("DocumentController.Read",":key",item.BookIdentify,":id" ,item.Identify)
buf.WriteString(uri)
}else{
uri := beego.URLFor("DocumentController.Read",":key",item.BookIdentify,":id" ,item.DocumentId)
buf.WriteString(uri)
}
buf.WriteString("\" title=\"")
buf.WriteString(template.HTMLEscapeString(item.DocumentName) + "\"")
buf.WriteString(selected + ">")
buf.WriteString(template.HTMLEscapeString(item.DocumentName) + "</a>")
for _,sub := range array {
if p,ok := sub.ParentId.(int);ok && p == item.DocumentId{
getDocumentTree(array,p,selected_id,selected_parent_id,buf)
}
}
buf.WriteString("</li>")
}
}
buf.WriteString("</ul>")
}

View File

@ -60,6 +60,7 @@ func (m *Member) Login(account string,password string) (*Member,error) {
ok,err := utils.PasswordVerify(member.Password,password) ;
if ok && err == nil {
m.ResolveRoleName()
return member,nil
}
@ -67,22 +68,23 @@ func (m *Member) Login(account string,password string) (*Member,error) {
}
// Add 添加一个用户.
func (member *Member) Add () (error) {
func (m *Member) Add () (error) {
o := orm.NewOrm()
hash ,err := utils.PasswordHash(member.Password);
hash ,err := utils.PasswordHash(m.Password);
if err != nil {
return err
}
member.Password = hash
m.Password = hash
_,err = o.Insert(member)
_,err = o.Insert(m)
if err != nil {
return err
}
m.ResolveRoleName()
return nil
}
@ -103,6 +105,11 @@ func (m *Member) Find(id int) error{
if err := o.Read(m); err != nil {
return err
}
m.ResolveRoleName()
return nil
}
func (m *Member) ResolveRoleName (){
if m.Role == conf.MemberSuperRole {
m.RoleName = "超级管理员"
}else if m.Role == conf.MemberAdminRole {
@ -110,17 +117,6 @@ func (m *Member) Find(id int) error{
}else if m.Role == conf.MemberGeneralRole {
m.RoleName = "普通用户"
}
return nil
}
func (m *Member) ResolveRoleName (){
if m.Role == 0 {
m.RoleName = "超级管理员"
}else if m.Role == 1 {
m.RoleName = "管理员"
}else if m.Role == 2 {
m.RoleName = "普通用户"
}
}
func (m *Member) FindByAccount (account string) (*Member,error) {
@ -154,13 +150,7 @@ func (m *Member) FindToPager(pageIndex, pageSize int) ([]*Member,int64,error) {
}
for _,m := range members {
if m.Role == 0 {
m.RoleName = "超级管理员"
}else if m.Role == 1 {
m.RoleName = "管理员"
}else if m.Role == 2 {
m.RoleName = "普通用户"
}
m.ResolveRoleName()
}
return members,totalCount,nil
}

View File

@ -67,9 +67,10 @@ func GetOptionValue(key, def string) string {
func (p *Option) InsertOrUpdate() error {
o := orm.NewOrm()
var err error
if p.OptionId > 0 {
_,err = o.Update(o)
_,err = o.Update(p)
}else{
_,err = o.Insert(p)
}

25
routers/filter.go 100644
View File

@ -0,0 +1,25 @@
package routers
import (
"github.com/astaxie/beego"
"github.com/astaxie/beego/context"
"github.com/lifei6671/godoc/conf"
"github.com/lifei6671/godoc/models"
)
func init() {
var FilterUser = func(ctx *context.Context) {
_, ok := ctx.Input.Session(conf.LoginSessionName).(models.Member)
if !ok {
ctx.Redirect(302, beego.URLFor("AccountController.Login"))
}
}
beego.InsertFilter("/manager",beego.BeforeRouter,FilterUser)
beego.InsertFilter("/manager/*",beego.BeforeRouter,FilterUser)
beego.InsertFilter("/setting",beego.BeforeRouter,FilterUser)
beego.InsertFilter("/setting/*",beego.BeforeRouter,FilterUser)
beego.InsertFilter("/book",beego.BeforeRouter,FilterUser)
beego.InsertFilter("/book/*",beego.BeforeRouter,FilterUser)
beego.InsertFilter("/api/*",beego.BeforeRouter,FilterUser)
}

View File

@ -12,9 +12,10 @@ func init() {
beego.Router("/logout", &controllers.AccountController{},"*:Logout")
beego.Router("/register", &controllers.AccountController{},"*:Register")
beego.Router("/find_password", &controllers.AccountController{},"*:FindPassword")
beego.Router("/captcha", &controllers.AccountController{},"*:Captcha")
beego.Router("/manager", &controllers.ManagerController{},"*:Index")
beego.Router("/manager/users", &controllers.ManagerController{})
beego.Router("/manager/users", &controllers.ManagerController{},"*:Users")
beego.Router("/manager/member/create", &controllers.ManagerController{},"post:CreateMember")
beego.Router("/manager/member/update-member-status",&controllers.ManagerController{},"post:UpdateMemberStatus")
beego.Router("/manager/member/change-member-role", &controllers.ManagerController{},"post:ChangeMemberRole")
@ -47,15 +48,15 @@ func init() {
beego.Router("/book/setting/token", &controllers.BookController{},"post:CreateToken")
beego.Router("/book/setting/delete", &controllers.BookController{},"post:Delete")
beego.Router("/docs/:key/edit/?:id", &controllers.DocumentController{},"*:Edit")
beego.Router("/docs/upload",&controllers.DocumentController{},"post:Upload")
beego.Router("/docs/:key/create",&controllers.DocumentController{},"post:Create")
beego.Router("/docs/:key/delete", &controllers.DocumentController{},"post:Delete")
beego.Router("/docs/:key/content/?:id",&controllers.DocumentController{},"*:Content")
beego.Router("/api/:key/edit/?:id", &controllers.DocumentController{},"*:Edit")
beego.Router("/api/upload",&controllers.DocumentController{},"post:Upload")
beego.Router("/api/:key/create",&controllers.DocumentController{},"post:Create")
beego.Router("/api/:key/delete", &controllers.DocumentController{},"post:Delete")
beego.Router("/api/:key/content/?:id",&controllers.DocumentController{},"*:Content")
beego.Router("/docs/:key", &controllers.DocumentController{},"*:Index")
beego.Router("/docs/:key/:id", &controllers.DocumentController{},"*:Read")
beego.Router("/:key/attach_files/:attach_id",&controllers.DocumentController{},"get:DownloadAttachment")
beego.Router("/attach_files/:key/:attach_id",&controllers.DocumentController{},"get:DownloadAttachment")
}

40
start.sh 100644
View File

@ -0,0 +1,40 @@
#!/bin/sh
set -e
cd /go/src/github.com/lifei6671/godoc/
goFile="godoc"
chmod +x $goFile
if [ ! -f "conf/app.conf" ] ; then
cp conf/app.conf.example conf/app.conf
fi
if [ ! -z $db_host ] ; then
sed -i 's/^db_host.*/db_host='$db_host'/g' conf/app.conf
fi
if [ ! -z $db_port ] ; then
sed -i 's/^db_port.*/db_port='$db_port'/g' conf/app.conf
fi
if [ ! -z $db_database ] ; then
sed -i 's/^db_database.*/db_database='$db_database'/g' conf/app.conf
fi
if [ ! -z $db_username ] ; then
sed -i 's/^db_username.*/db_username='$db_username'/g' conf/app.conf
fi
if [ ! -z $db_password ] ; then
sed -i 's/^db_password.*/db_password='$db_password'/g' conf/app.conf
fi
if [ ! -z $httpport ] ; then
sed -i 's/^httpport.*/httpport='$httpport'/g' conf/app.conf
fi
./$goFile

View File

@ -141,6 +141,71 @@ h6 {
zoom:1;border-bottom: 1px solid #ddd
}
.m-manual .manual-tab .tab-util {
position: absolute;
top: 50%;
right: -14px
}
.m-manual .manual-tab .tab-util .item {
color: #999;
cursor: pointer;
height: 24px;
line-height: 24px;
display: inline-block;
margin-top: 4px
}
.manual-fullscreen-switch {
display: block
}
.manual-fullscreen-switch .open,.manual-fullscreen-switch .close {
display: inline-block;
width: 30px;
height: 30px;
cursor: pointer;
background-color: #5cb85c;
border-radius: 50%;
color: #fff;
position: relative;
font-size: 16px;
vertical-align: top;
opacity : 1;
text-shadow:none;
font-weight: 400;
}
.manual-fullscreen-switch .open:hover,.manual-fullscreen-switch .close:hover {
background-color: #449d44;
}
.manual-fullscreen-switch .open:before,.manual-fullscreen-switch .close:before {
position: absolute;
top: 7px;
right: 5px;
}
.manual-fullscreen-switch .open {
display: none;
}
.m-manual.manual-fullscreen-active .manual-fullscreen-switch {
/*margin-top: 30px;*/
}
.m-manual.manual-fullscreen-active .manual-fullscreen-switch .open {
display: inline-block;
}
.m-manual.manual-fullscreen-active .manual-fullscreen-switch .close {
display: none;
}
.m-manual.manual-fullscreen-active .manual-left .m-copyright,.m-manual.manual-fullscreen-active .manual-left .tab-navg,.m-manual.manual-fullscreen-active .manual-left .tab-wrap{
display: none;
}
.m-manual.manual-fullscreen-active .manual-left{
width: 0px;
}
.m-manual .manual-tab .tab-navg:after {
content: '.';
display: block;
@ -149,7 +214,7 @@ h6 {
line-height: 9;
overflow: hidden;
clear: both;
visibility: hidden
visibility: hidden;
}
.m-manual .manual-tab .tab-navg .navg-item {
@ -246,6 +311,9 @@ h6 {
-o-transition-timing-function: linear;
-o-transition-delay: 0s
}
.m-manual.manual-fullscreen-active .manual-right{
left: 0;
}
.m-manual .manual-right .manual-article{
background: #ffffff;
}
@ -266,7 +334,9 @@ h6 {
color: #7e888b
}
.manual-article .article-content{
max-width: 980px;
min-width: 980px;
max-width: 98%;
padding: 10px 20px;
margin-left: auto!important;
margin-right: auto!important
}

View File

@ -193,7 +193,7 @@ textarea{
position: relative;
}
.manual-list .list-item .manual-item-standard .cover {
border: 1px solid #f3f3f3;
border: 1px solid #999999;
width: 171px;
position: relative;
display: inline-block;

View File

@ -84,6 +84,7 @@ $(function () {
if(Object.prototype.toString.call(window.documentCategory) === '[object Array]' && window.documentCategory.length > 0){
$.ajax({
url : window.releaseURL,
data :{"identify" : window.book.identify },
type : "post",
dataType : "json",
success : function (res) {

View File

@ -0,0 +1,74 @@
/* Make clicks pass-through */
#nprogress {
pointer-events: none;
}
#nprogress .bar {
background: #29d;
position: fixed;
z-index: 1031;
top: 0;
left: 0;
width: 100%;
height: 2px;
}
/* Fancy blur effect */
#nprogress .peg {
display: block;
position: absolute;
right: 0px;
width: 100px;
height: 100%;
box-shadow: 0 0 10px #29d, 0 0 5px #29d;
opacity: 1.0;
-webkit-transform: rotate(3deg) translate(0px, -4px);
-ms-transform: rotate(3deg) translate(0px, -4px);
transform: rotate(3deg) translate(0px, -4px);
}
/* Remove these to get rid of the spinner */
#nprogress .spinner {
display: block;
position: fixed;
z-index: 1031;
top: 15px;
right: 15px;
}
#nprogress .spinner-icon {
width: 18px;
height: 18px;
box-sizing: border-box;
border: solid 2px transparent;
border-top-color: #29d;
border-left-color: #29d;
border-radius: 50%;
-webkit-animation: nprogress-spinner 400ms linear infinite;
animation: nprogress-spinner 400ms linear infinite;
}
.nprogress-custom-parent {
overflow: hidden;
position: relative;
}
.nprogress-custom-parent #nprogress .spinner,
.nprogress-custom-parent #nprogress .bar {
position: absolute;
}
@-webkit-keyframes nprogress-spinner {
0% { -webkit-transform: rotate(0deg); }
100% { -webkit-transform: rotate(360deg); }
}
@keyframes nprogress-spinner {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

View File

@ -0,0 +1,476 @@
/* NProgress, (c) 2013, 2014 Rico Sta. Cruz - http://ricostacruz.com/nprogress
* @license MIT */
;(function(root, factory) {
if (typeof define === 'function' && define.amd) {
define(factory);
} else if (typeof exports === 'object') {
module.exports = factory();
} else {
root.NProgress = factory();
}
})(this, function() {
var NProgress = {};
NProgress.version = '0.2.0';
var Settings = NProgress.settings = {
minimum: 0.08,
easing: 'ease',
positionUsing: '',
speed: 200,
trickle: true,
trickleRate: 0.02,
trickleSpeed: 800,
showSpinner: true,
barSelector: '[role="bar"]',
spinnerSelector: '[role="spinner"]',
parent: 'body',
template: '<div class="bar" role="bar"><div class="peg"></div></div><div class="spinner" role="spinner"><div class="spinner-icon"></div></div>'
};
/**
* Updates configuration.
*
* NProgress.configure({
* minimum: 0.1
* });
*/
NProgress.configure = function(options) {
var key, value;
for (key in options) {
value = options[key];
if (value !== undefined && options.hasOwnProperty(key)) Settings[key] = value;
}
return this;
};
/**
* Last number.
*/
NProgress.status = null;
/**
* Sets the progress bar status, where `n` is a number from `0.0` to `1.0`.
*
* NProgress.set(0.4);
* NProgress.set(1.0);
*/
NProgress.set = function(n) {
var started = NProgress.isStarted();
n = clamp(n, Settings.minimum, 1);
NProgress.status = (n === 1 ? null : n);
var progress = NProgress.render(!started),
bar = progress.querySelector(Settings.barSelector),
speed = Settings.speed,
ease = Settings.easing;
progress.offsetWidth; /* Repaint */
queue(function(next) {
// Set positionUsing if it hasn't already been set
if (Settings.positionUsing === '') Settings.positionUsing = NProgress.getPositioningCSS();
// Add transition
css(bar, barPositionCSS(n, speed, ease));
if (n === 1) {
// Fade out
css(progress, {
transition: 'none',
opacity: 1
});
progress.offsetWidth; /* Repaint */
setTimeout(function() {
css(progress, {
transition: 'all ' + speed + 'ms linear',
opacity: 0
});
setTimeout(function() {
NProgress.remove();
next();
}, speed);
}, speed);
} else {
setTimeout(next, speed);
}
});
return this;
};
NProgress.isStarted = function() {
return typeof NProgress.status === 'number';
};
/**
* Shows the progress bar.
* This is the same as setting the status to 0%, except that it doesn't go backwards.
*
* NProgress.start();
*
*/
NProgress.start = function() {
if (!NProgress.status) NProgress.set(0);
var work = function() {
setTimeout(function() {
if (!NProgress.status) return;
NProgress.trickle();
work();
}, Settings.trickleSpeed);
};
if (Settings.trickle) work();
return this;
};
/**
* Hides the progress bar.
* This is the *sort of* the same as setting the status to 100%, with the
* difference being `done()` makes some placebo effect of some realistic motion.
*
* NProgress.done();
*
* If `true` is passed, it will show the progress bar even if its hidden.
*
* NProgress.done(true);
*/
NProgress.done = function(force) {
if (!force && !NProgress.status) return this;
return NProgress.inc(0.3 + 0.5 * Math.random()).set(1);
};
/**
* Increments by a random amount.
*/
NProgress.inc = function(amount) {
var n = NProgress.status;
if (!n) {
return NProgress.start();
} else {
if (typeof amount !== 'number') {
amount = (1 - n) * clamp(Math.random() * n, 0.1, 0.95);
}
n = clamp(n + amount, 0, 0.994);
return NProgress.set(n);
}
};
NProgress.trickle = function() {
return NProgress.inc(Math.random() * Settings.trickleRate);
};
/**
* Waits for all supplied jQuery promises and
* increases the progress as the promises resolve.
*
* @param $promise jQUery Promise
*/
(function() {
var initial = 0, current = 0;
NProgress.promise = function($promise) {
if (!$promise || $promise.state() === "resolved") {
return this;
}
if (current === 0) {
NProgress.start();
}
initial++;
current++;
$promise.always(function() {
current--;
if (current === 0) {
initial = 0;
NProgress.done();
} else {
NProgress.set((initial - current) / initial);
}
});
return this;
};
})();
/**
* (Internal) renders the progress bar markup based on the `template`
* setting.
*/
NProgress.render = function(fromStart) {
if (NProgress.isRendered()) return document.getElementById('nprogress');
addClass(document.documentElement, 'nprogress-busy');
var progress = document.createElement('div');
progress.id = 'nprogress';
progress.innerHTML = Settings.template;
var bar = progress.querySelector(Settings.barSelector),
perc = fromStart ? '-100' : toBarPerc(NProgress.status || 0),
parent = document.querySelector(Settings.parent),
spinner;
css(bar, {
transition: 'all 0 linear',
transform: 'translate3d(' + perc + '%,0,0)'
});
if (!Settings.showSpinner) {
spinner = progress.querySelector(Settings.spinnerSelector);
spinner && removeElement(spinner);
}
if (parent != document.body) {
addClass(parent, 'nprogress-custom-parent');
}
parent.appendChild(progress);
return progress;
};
/**
* Removes the element. Opposite of render().
*/
NProgress.remove = function() {
removeClass(document.documentElement, 'nprogress-busy');
removeClass(document.querySelector(Settings.parent), 'nprogress-custom-parent');
var progress = document.getElementById('nprogress');
progress && removeElement(progress);
};
/**
* Checks if the progress bar is rendered.
*/
NProgress.isRendered = function() {
return !!document.getElementById('nprogress');
};
/**
* Determine which positioning CSS rule to use.
*/
NProgress.getPositioningCSS = function() {
// Sniff on document.body.style
var bodyStyle = document.body.style;
// Sniff prefixes
var vendorPrefix = ('WebkitTransform' in bodyStyle) ? 'Webkit' :
('MozTransform' in bodyStyle) ? 'Moz' :
('msTransform' in bodyStyle) ? 'ms' :
('OTransform' in bodyStyle) ? 'O' : '';
if (vendorPrefix + 'Perspective' in bodyStyle) {
// Modern browsers with 3D support, e.g. Webkit, IE10
return 'translate3d';
} else if (vendorPrefix + 'Transform' in bodyStyle) {
// Browsers without 3D support, e.g. IE9
return 'translate';
} else {
// Browsers without translate() support, e.g. IE7-8
return 'margin';
}
};
/**
* Helpers
*/
function clamp(n, min, max) {
if (n < min) return min;
if (n > max) return max;
return n;
}
/**
* (Internal) converts a percentage (`0..1`) to a bar translateX
* percentage (`-100%..0%`).
*/
function toBarPerc(n) {
return (-1 + n) * 100;
}
/**
* (Internal) returns the correct CSS for changing the bar's
* position given an n percentage, and speed and ease from Settings
*/
function barPositionCSS(n, speed, ease) {
var barCSS;
if (Settings.positionUsing === 'translate3d') {
barCSS = { transform: 'translate3d('+toBarPerc(n)+'%,0,0)' };
} else if (Settings.positionUsing === 'translate') {
barCSS = { transform: 'translate('+toBarPerc(n)+'%,0)' };
} else {
barCSS = { 'margin-left': toBarPerc(n)+'%' };
}
barCSS.transition = 'all '+speed+'ms '+ease;
return barCSS;
}
/**
* (Internal) Queues a function to be executed.
*/
var queue = (function() {
var pending = [];
function next() {
var fn = pending.shift();
if (fn) {
fn(next);
}
}
return function(fn) {
pending.push(fn);
if (pending.length == 1) next();
};
})();
/**
* (Internal) Applies css properties to an element, similar to the jQuery
* css method.
*
* While this helper does assist with vendor prefixed property names, it
* does not perform any manipulation of values prior to setting styles.
*/
var css = (function() {
var cssPrefixes = [ 'Webkit', 'O', 'Moz', 'ms' ],
cssProps = {};
function camelCase(string) {
return string.replace(/^-ms-/, 'ms-').replace(/-([\da-z])/gi, function(match, letter) {
return letter.toUpperCase();
});
}
function getVendorProp(name) {
var style = document.body.style;
if (name in style) return name;
var i = cssPrefixes.length,
capName = name.charAt(0).toUpperCase() + name.slice(1),
vendorName;
while (i--) {
vendorName = cssPrefixes[i] + capName;
if (vendorName in style) return vendorName;
}
return name;
}
function getStyleProp(name) {
name = camelCase(name);
return cssProps[name] || (cssProps[name] = getVendorProp(name));
}
function applyCss(element, prop, value) {
prop = getStyleProp(prop);
element.style[prop] = value;
}
return function(element, properties) {
var args = arguments,
prop,
value;
if (args.length == 2) {
for (prop in properties) {
value = properties[prop];
if (value !== undefined && properties.hasOwnProperty(prop)) applyCss(element, prop, value);
}
} else {
applyCss(element, args[1], args[2]);
}
}
})();
/**
* (Internal) Determines if an element or space separated list of class names contains a class name.
*/
function hasClass(element, name) {
var list = typeof element == 'string' ? element : classList(element);
return list.indexOf(' ' + name + ' ') >= 0;
}
/**
* (Internal) Adds a class to an element.
*/
function addClass(element, name) {
var oldList = classList(element),
newList = oldList + name;
if (hasClass(oldList, name)) return;
// Trim the opening space.
element.className = newList.substring(1);
}
/**
* (Internal) Removes a class from an element.
*/
function removeClass(element, name) {
var oldList = classList(element),
newList;
if (!hasClass(element, name)) return;
// Replace the class name.
newList = oldList.replace(' ' + name + ' ', ' ');
// Trim the opening and closing spaces.
element.className = newList.substring(1, newList.length - 1);
}
/**
* (Internal) Gets a space separated list of the class names on the element.
* The list is wrapped with a single space on each end to facilitate finding
* matches within the list.
*/
function classList(element) {
return (' ' + (element.className || '') + ' ').replace(/\s+/gi, ' ');
}
/**
* (Internal) Removes an element from the DOM.
*/
function removeElement(element) {
element && element.parentNode && element.parentNode.removeChild(element);
}
return NProgress;
});

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="zh-CN">
<html lang="zh-CN" xmlns="http://www.w3.org/1999/html">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
@ -51,7 +51,7 @@
<a href="{{urlfor "DocumentController.Index" ":key" .Model.Identify}}" class="btn btn-default btn-sm pull-right" style="margin-right: 5px;" target="_blank"><i class="fa fa-eye"></i> 阅读</a>
{{if eq .Model.RoleId 0 1 2}}
<a href="{{urlfor "DocumentController.Index" ":key" .Model.Identify}}" class="btn btn-default btn-sm pull-right" style="margin-right: 5px;" target="_blank"><i class="fa fa-upload" aria-hidden="true"></i> 发布</a>
<button class="btn btn-default btn-sm pull-right" style="margin-right: 5px;" id="btnRelease"><i class="fa fa-upload" aria-hidden="true"></i> 发布</button>
{{end}}
</div>
</div>
@ -98,7 +98,27 @@
</div>
<script src="/static/jquery/1.12.4/jquery.min.js"></script>
<script src="/static/bootstrap/js/bootstrap.min.js"></script>
<script src="/static/layer/layer.js"></script>
<script src="/static/js/main.js" type="text/javascript"></script>
<script type="text/javascript">
$(function () {
$("#btnRelease").on("click",function () {
$.ajax({
url : "{{urlfor "BookController.Release" ":key" .Model.Identify}}",
data :{"identify" : "{{.Model.Identify}}" },
type : "post",
dataType : "json",
success : function (res) {
if(res.errcode === 0){
layer.msg("发布任务已推送到任务队列,稍后将在后台执行。");
}else{
layer.msg(res.message);
}
}
});
});
});
</script>
</body>
</html>

View File

@ -60,7 +60,7 @@
<div class="pull-right">
<a :href="'{{urlfor "DocumentController.Index" ":key" ""}}' + item.identify" title="查看文档" data-toggle="tooltip"><i class="fa fa-eye"></i> 查看文档</a>
<template v-if="item.role_id != 3">
<a :href="'/docs/' + item.identify + '/edit'" title="编辑文档" data-toggle="tooltip"><i class="fa fa-edit" aria-hidden="true"></i> 编辑文档</a>
<a :href="'/api/' + item.identify + '/edit'" title="编辑文档" data-toggle="tooltip"><i class="fa fa-edit" aria-hidden="true"></i> 编辑文档</a>
</template>
</div>
<div class="clearfix"></div>

View File

@ -0,0 +1,265 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>编辑文档 - Powered by MinDoc</title>
<!-- Bootstrap -->
<link href="/static/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="/static/font-awesome/css/font-awesome.min.css" rel="stylesheet">
<link href="/static/jstree/3.3.4/themes/default/style.min.css" rel="stylesheet">
<link href="/static/nprogress/nprogress.css" rel="stylesheet">
<link href="/static/css/kancloud.css" rel="stylesheet">
<link href="/static/css/jstree.css" rel="stylesheet">
{{if eq .Model.Editor "markdown"}}
<link href="/static/editor.md/css/editormd.preview.css" rel="stylesheet">
{{else}}
<link href="/static/highlight/styles/zenburn.css" rel="stylesheet">
{{end}}
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="/static/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="/static/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div class="m-manual manual-reader">
<header class="navbar navbar-static-top manual-head" role="banner">
<div class="container-fluid">
<div class="navbar-header pull-left manual-title">
<span class="slidebar" id="slidebar"><i class="fa fa-align-justify"></i></span>
{{.Model.BookName}}
<span style="font-size: 12px;font-weight: 100;"></span>
</div>
<div class="navbar-header pull-right manual-menu">
<div class="dropdown">
<button id="dLabel" class="btn btn-default" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
项目
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right" role="menu" aria-labelledby="dLabel">
{{if gt .Member.MemberId 0}}
{{if eq .Model.RoleId 0 1 2}}
<li><a href="{{urlfor "DocumentController.Edit" ":key" .Model.Identify ":id" ""}}">返回编辑</a> </li>
{{end}}
<li><a href="{{urlfor "BookController.Index"}}">我的项目</a> </li>
<li role="presentation" class="divider"></li>
{{end}}
{{if eq .Model.PrivatelyOwned 0}}
<li><a href="javascript:" data-toggle="modal" data-target="#shareProject">项目分享</a> </li>
<li role="presentation" class="divider"></li>
{{/*<li><a href="https://wiki.iminho.me/export/1" target="_blank"></a> </li>*/}}
{{end}}
<li><a href="{{urlfor "HomeController.Index"}}" title="返回首页">返回首页</a> </li>
</ul>
</div>
</div>
</div>
</header>
<article class="container-fluid manual-body">
<div class="manual-left">
<div class="manual-tab">
<div class="tab-navg">
<span data-mode="view" class="navg-item active"><i class="fa fa-align-justify"></i><b class="text">目录</b></span>
</div>
<div class="tab-util">
<span class="manual-fullscreen-switch">
<b class="open fa fa-angle-right" title="展开"></b>
<b class="close fa fa-angle-left" title="关闭"></b>
</span>
</div>
<div class="tab-wrap">
<div class="tab-item manual-catalog">
<div class="catalog-list read-book-preview" id="sidebar">
{{.Result}}
</div>
</div>
</div>
</div>
<div class="m-copyright">
<p>
本文档使用 <a href="https://doc.iminho.me" target="_blank">MinDoc</a> 发布
</p>
</div>
</div>
<div class="manual-right">
<div class="manual-article">
<div class="article-head">
<div class="container-fluid">
<div class="row">
<div class="col-md-2">
</div>
<div class="col-md-8 text-center">
<h1 id="article-title">{{.Title}}</h1>
</div>
<div class="col-md-2">
</div>
</div>
</div>
</div>
<div class="article-content">
<div class="article-body {{if eq .Model.Editor "markdown"}}markdown-body editormd-preview-container{{else}}editor-content{{end}}" id="page-content">
{{.Content}}
</div>
</div>
</div>
</div>
<div class="manual-progress"><b class="progress-bar"></b></div>
</article>
<div class="manual-mask"></div>
</div>
<!-- Share Modal -->
<div class="modal fade" id="shareProject" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
<h4 class="modal-title" id="myModalLabel">项目分享</h4>
</div>
<div class="modal-body">
<div class="form-group">
<label for="password" class="col-sm-2 control-label">项目地址</label>
<div class="col-sm-10">
<input type="text" value="{{.BaseUrl}}{{urlfor "DocumentController.Index" ":key" .Model.Identify}}" class="form-control" onmouseover="this.select()" id="projectUrl" title="项目地址">
</div>
<div class="clearfix"></div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>
<script src="/static/jquery/1.12.4/jquery.min.js"></script>
<script src="/static/bootstrap/js/bootstrap.min.js"></script>
<script src="/static/jstree/3.3.4/jstree.min.js" type="text/javascript"></script>
<script type="text/javascript" src="/static/nprogress/nprogress.js"></script>
<script type="text/javascript" src="/static/highlight/highlight.js"></script>
<script type="text/javascript" src="/static/highlight/highlightjs-line-numbers.min.js"></script>
<script type="text/javascript">
var events = $("body");
var catalog = null;
function initHighlighting() {
$('pre code').each(function (i, block) {
hljs.highlightBlock(block);
});
hljs.initLineNumbersOnLoad();
}
$(function () {
window.isFullScreen = false;
initHighlighting();
$("#sidebar").jstree({
'plugins':["wholerow","types"],
"types": {
"default" : {
"icon" : false // 删除默认图标
}
},
'core' : {
'check_callback' : true,
"multiple" : false ,
'animation' : 0
}
}).on('select_node.jstree',function (node,selected,event) {
$(".m-manual").removeClass('manual-mobile-show-left');
var url = selected.node.a_attr.href;
if(url === window.location.href){
return false;
}
$.ajax({
url : url,
type : "GET",
beforeSend :function (xhr) {
var body = events.data('body_' + selected.node.id);
var title = events.data('title_' + selected.node.id);
var doc_title = events.data('doc_title_' + selected.node.id);
if(body && title && doc_title){
$("#page-content").html(body);
$("title").text(title);
$("#article-title").text(doc_title);
events.trigger('article.open',url,true);
return false;
}
NProgress.start();
},
success : function (res) {
if(res.errcode === 0){
var body = res.data.body;
var doc_title = res.data.doc_title;
var title = res.data.title;
$("#page-content").html(body);
$("title").text(title);
$("#article-title").text(doc_title);
events.data('body_' + selected.node.id,body);
events.data('title_' + selected.node.id,title);
events.data('doc_title_' + selected.node.id,doc_title);
events.trigger('article.open',url,false);
}else{
layer.msg("加载失败");
}
},
complete : function () {
NProgress.done();
}
});
});
$("#slidebar").on("click",function () {
$(".m-manual").addClass('manual-mobile-show-left');
});
$(".manual-mask").on("click",function () {
$(".m-manual").removeClass('manual-mobile-show-left');
});
$(".manual-fullscreen-switch").on("click",function () {
isFullScreen = !isFullScreen;
if (isFullScreen) {
$(".m-manual").addClass('manual-fullscreen-active');
} else {
$(".m-manual").removeClass('manual-fullscreen-active');
}
});
events.on('article.open', function (event, url,init) {
if ('pushState' in history) {
if (init == false) {
history.replaceState({ }, '', url);
init = true;
} else {
history.pushState({ }, '', url);
}
} else {
location.hash = url;
}
initHighlighting();
});
});
</script>
</body>
</html>

View File

@ -129,7 +129,7 @@
<script type="text/javascript" src="/static/layer/layer.js"></script>
<script src="/static/to-markdown/dist/to-markdown.js" type="text/javascript"></script>
<script src="/static/js/jquery.form.js" type="text/javascript"></script>
<script src="/static/js/edirot.js" type="text/javascript"></script>
<script src="/static/js/editor.js" type="text/javascript"></script>
<script src="/static/js/html-editor.js" type="text/javascript"></script>
</body>
</html>

View File

@ -160,7 +160,7 @@
<script src="/static/editor.md/editormd.js" type="text/javascript"></script>
<script type="text/javascript" src="/static/layer/layer.js"></script>
<script src="/static/js/jquery.form.js" type="text/javascript"></script>
<script src="/static/js/edirot.js" type="text/javascript"></script>
<script src="/static/js/editor.js" type="text/javascript"></script>
<script src="/static/js/markdown.js" type="text/javascript"></script>
</body>
</html>

View File

@ -1,10 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
</html>

View File

@ -25,158 +25,27 @@
<div class="container manual-body">
<div class="row">
<div class="manual-list">
{{range $index,$item := .Lists}}
<div class="list-item">
<dl class="manual-item-standard">
<dt>
<a href="{{urlfor "DocumentController.Index" ":key" "test"}}">
<img src="/uploads/201704/b4c17ca29fe7b7f4dec402d7dd7543c6_100.png" class="cover">
<a href="{{urlfor "DocumentController.Index" ":key" $item.Identify}}" title="{{$item.BookName}}-{{$item.CreateName}}">
<img src="{{$item.Cover}}" class="cover" alt="{{$item.BookName}}-{{$item.CreateName}}">
</a>
</dt>
<dd>
<a href="#" class="name">Docker生产环境实践指南</a>
<a href="{{urlfor "DocumentController.Index" ":key" $item.Identify}}" class="name" title="{{$item.BookName}}-{{$item.CreateName}}">{{$item.BookName}}</a>
</dd>
<dd>
<span class="author">
<b class="text">作者</b>
<b class="text">-</b>
<b class="text">Minho</b>
</span>
</dd>
</dl>
</div>
<div class="list-item">
<dl class="manual-item-standard">
<dt>
<a href="#">
<img src="/uploads/201704/b4c17ca29fe7b7f4dec402d7dd7543c6_100.png">
</a>
</dt>
<dd>
<a href="#" class="name">Docker生产环境实践指南</a>
</dd>
<dd>
<span class="author">
<b class="text">作者</b>
<b class="text">-</b>
<b class="text">Minho</b>
</span>
</dd>
</dl>
</div>
<div class="list-item">
<dl class="manual-item-standard">
<dt>
<a href="#">
<img src="/uploads/201704/b4c17ca29fe7b7f4dec402d7dd7543c6_100.png">
</a>
</dt>
<dd>
<a href="#" class="name">Docker生产环境实践指南</a>
</dd>
<dd>
<span class="author">
<b class="text">作者</b>
<b class="text">-</b>
<b class="text">Minho</b>
</span>
</dd>
</dl>
</div>
<div class="list-item">
<dl class="manual-item-standard">
<dt>
<a href="#">
<img src="/uploads/201704/b4c17ca29fe7b7f4dec402d7dd7543c6_100.png">
</a>
</dt>
<dd>
<a href="#" class="name">Docker生产环境实践指南</a>
</dd>
<dd>
<span class="author">
<b class="text">作者</b>
<b class="text">-</b>
<b class="text">Minho</b>
</span>
</dd>
</dl>
</div>
<div class="list-item">
<dl class="manual-item-standard">
<dt>
<a href="#">
<img src="/uploads/201704/b4c17ca29fe7b7f4dec402d7dd7543c6_100.png">
</a>
</dt>
<dd>
<a href="#" class="name">Docker生产环境实践指南</a>
</dd>
<dd>
<span class="author">
<b class="text">作者</b>
<b class="text">-</b>
<b class="text">Minho</b>
</span>
</dd>
</dl>
</div>
<div class="list-item">
<dl class="manual-item-standard">
<dt>
<a href="#">
<img src="/uploads/201704/b4c17ca29fe7b7f4dec402d7dd7543c6_100.png">
</a>
</dt>
<dd>
<a href="#" class="name">Docker生产环境实践指南</a>
</dd>
<dd>
<span class="author">
<b class="text">作者</b>
<b class="text">-</b>
<b class="text">Minho</b>
</span>
</dd>
</dl>
</div>
<div class="list-item">
<dl class="manual-item-standard">
<dt>
<a href="#">
<img src="/uploads/201704/b4c17ca29fe7b7f4dec402d7dd7543c6_100.png">
</a>
</dt>
<dd>
<a href="#" class="name">Docker生产环境实践指南</a>
</dd>
<dd>
<span class="author">
<b class="text">作者</b>
<b class="text">-</b>
<b class="text">Minho</b>
</span>
</dd>
</dl>
</div>
<div class="list-item">
<dl class="manual-item-standard">
<dt>
<a href="#">
<img src="/uploads/201704/b4c17ca29fe7b7f4dec402d7dd7543c6_100.png">
</a>
</dt>
<dd>
<a href="#" class="name">Docker生产环境实践指南</a>
</dd>
<dd>
<span class="author">
<b class="text">作者</b>
<b class="text">-</b>
<b class="text">Minho</b>
<b class="text">{{$item.CreateName}}</b>
</span>
</dd>
</dl>
</div>
{{end}}
<div class="clearfix"></div>
</div>
</div>

View File

@ -99,7 +99,21 @@
<script src="/static/js/main.js" type="text/javascript"></script>
<script type="text/javascript">
$(function () {
$("#gloablEditForm").ajaxForm({
beforeSubmit : function () {
var title = $.trim($("#siteName").val());
if (title === ""){
return showError("网站标题不能为空");
}
},success : function (res) {
if(res.errcode === 0) {
showSuccess("保存成功")
}else{
showError(res.message);
}
}
});
});
</script>
</body>

View File

@ -1,7 +1,13 @@
<header class="navbar navbar-static-top navbar-fixed-top manual-header" role="banner">
<div class="container">
<div class="navbar-header col-sm-12 col-md-6 col-lg-5">
<a href="/" class="navbar-brand">MinDoc</a>
<a href="/" class="navbar-brand">
{{if .SITE_TITLE}}
{{.SITE_TITLE}}
{{else}}
{{.SITE_NAME}}
{{end}}
</a>
<div class="btn-group dropdown-menu-right pull-right slidebar visible-xs-inline-block visible-sm-inline-block">
<button class="btn btn-default dropdown-toggle hidden-lg" type="button" data-toggle="dropdown"><i class="fa fa-align-justify"></i></button>
<ul class="dropdown-menu" role="menu">
@ -19,6 +25,7 @@
</div>
<nav class="navbar-collapse hidden-xs hidden-sm" role="navigation">
<ul class="nav navbar-nav navbar-right">
{{if gt .Member.MemberId 0}}
<li>
<div class="img user-info" data-toggle="dropdown">
<img src="{{.Member.Avatar}}" class="img-circle userbar-avatar">
@ -45,6 +52,9 @@
</li>
</ul>
</li>
{{else}}
<li><a href="{{urlfor "AccountController.Login"}}" title="用户登录">登录</a></li>
{{end}}
</ul>
</nav>
</div>