mirror of https://github.com/mindoc-org/mindoc.git
parent
dab6f31d01
commit
e1ec6bb788
201
LICENSE
201
LICENSE
|
@ -1,201 +0,0 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -109,7 +109,15 @@ func (c *BaseController) ExecuteViewPathTemplate(tplName string,data interface{}
|
|||
}
|
||||
|
||||
func (c *BaseController) BaseUrl() string {
|
||||
return c.Ctx.Input.Scheme() + "://" + c.Ctx.Request.Host
|
||||
baseUrl := beego.AppConfig.DefaultString("baseurl","")
|
||||
if baseUrl != "" {
|
||||
if strings.HasSuffix(baseUrl,"/"){
|
||||
baseUrl = strings.TrimSuffix(baseUrl,"/")
|
||||
}
|
||||
}else{
|
||||
baseUrl = c.Ctx.Input.Scheme() + "://" + c.Ctx.Request.Host
|
||||
}
|
||||
return baseUrl
|
||||
}
|
||||
|
||||
//显示错误信息页面.
|
||||
|
|
|
@ -25,7 +25,6 @@ import (
|
|||
"github.com/lifei6671/mindoc/conf"
|
||||
"github.com/lifei6671/mindoc/models"
|
||||
"github.com/lifei6671/mindoc/utils"
|
||||
"github.com/lifei6671/mindoc/utils/wkhtmltopdf"
|
||||
"github.com/russross/blackfriday"
|
||||
)
|
||||
|
||||
|
@ -68,7 +67,7 @@ func isReadable(identify, token string, c *DocumentController) *models.BookResul
|
|||
}
|
||||
}
|
||||
|
||||
bookResult := book.ToBookResult()
|
||||
bookResult := models.NewBookResult().ToBookResult(*book)
|
||||
|
||||
if c.Member != nil {
|
||||
rel, err := models.NewRelationship().FindByBookIdAndMemberId(bookResult.BookId, c.Member.MemberId)
|
||||
|
@ -283,7 +282,7 @@ func (c *DocumentController) Edit() {
|
|||
c.JsonResult(6002, "项目不存在或权限不足")
|
||||
}
|
||||
|
||||
bookResult = book.ToBookResult()
|
||||
bookResult = models.NewBookResult().ToBookResult(*book)
|
||||
} else {
|
||||
bookResult, err = models.NewBookResult().FindByIdentify(identify, c.Member.MemberId)
|
||||
|
||||
|
@ -545,7 +544,7 @@ func (c *DocumentController) Upload() {
|
|||
}
|
||||
|
||||
if attachment.HttpPath == "" {
|
||||
attachment.HttpPath = beego.URLFor("DocumentController.DownloadAttachment", ":key", identify, ":attach_id", attachment.AttachmentId)
|
||||
attachment.HttpPath = c.BaseUrl() + beego.URLFor("DocumentController.DownloadAttachment", ":key", identify, ":attach_id", attachment.AttachmentId)
|
||||
|
||||
if err := attachment.Update(); err != nil {
|
||||
beego.Error("SaveToFile => ", err)
|
||||
|
@ -845,13 +844,6 @@ func (c *DocumentController) Content() {
|
|||
c.JsonResult(0, "ok", doc)
|
||||
}
|
||||
|
||||
func (c *DocumentController) ExportDoc() {
|
||||
c.Export(true)
|
||||
}
|
||||
|
||||
func (c *DocumentController) ExportBook() {
|
||||
c.Export(false)
|
||||
}
|
||||
|
||||
func (c *DocumentController) GetDocumentById(id string) (doc *models.Document, err error) {
|
||||
doc = models.NewDocument()
|
||||
|
@ -871,7 +863,7 @@ func (c *DocumentController) GetDocumentById(id string) (doc *models.Document, e
|
|||
}
|
||||
|
||||
// 导出
|
||||
func (c *DocumentController) Export(single_doc bool) {
|
||||
func (c *DocumentController) Export() {
|
||||
c.Prepare()
|
||||
c.TplName = "document/export.tpl"
|
||||
|
||||
|
@ -897,7 +889,7 @@ func (c *DocumentController) Export(single_doc bool) {
|
|||
c.Abort("500")
|
||||
}
|
||||
|
||||
bookResult = book.ToBookResult()
|
||||
bookResult = models.NewBookResult().ToBookResult(*book)
|
||||
} else {
|
||||
bookResult = isReadable(identify, token, c)
|
||||
}
|
||||
|
@ -906,76 +898,50 @@ func (c *DocumentController) Export(single_doc bool) {
|
|||
// TODO: 私有项目禁止导出
|
||||
}
|
||||
|
||||
docs, err := models.NewDocument().FindListByBookId(bookResult.BookId)
|
||||
if !strings.HasPrefix(bookResult.Cover,"http:://") && !strings.HasPrefix(bookResult.Cover,"https:://"){
|
||||
bookResult.Cover = c.BaseUrl() + bookResult.Cover
|
||||
}
|
||||
|
||||
eBookResult,err := bookResult.Converter(c.CruSession.SessionID())
|
||||
|
||||
if err != nil {
|
||||
beego.Error(err)
|
||||
beego.Error("转换文档失败:" + bookResult.BookName + " -> " + err.Error())
|
||||
c.Abort("500")
|
||||
}
|
||||
|
||||
|
||||
if output == "pdf" {
|
||||
exe := beego.AppConfig.String("wkhtmltopdf")
|
||||
if exe == "" {
|
||||
c.TplName = "errors/error.tpl"
|
||||
c.Data["ErrorMessage"] = "没有配置PDF导出程序"
|
||||
c.Data["ErrorCode"] = 50010
|
||||
return
|
||||
c.Ctx.Output.Download(eBookResult.PDFPath, identify + ".pdf")
|
||||
|
||||
//如果没有开启缓存,则10分钟后删除
|
||||
if !bookResult.IsCacheEBook {
|
||||
defer func(pdfpath string) {
|
||||
time.Sleep(time.Minute * 10)
|
||||
os.Remove(filepath.Dir(pdfpath))
|
||||
}(eBookResult.PDFPath)
|
||||
}
|
||||
c.StopRun()
|
||||
}else if output == "epub" {
|
||||
c.Ctx.Output.Download(eBookResult.PDFPath, identify + ".epub")
|
||||
|
||||
dpath := "cache/" + bookResult.Identify
|
||||
os.MkdirAll(dpath, 0766)
|
||||
|
||||
pathList := list.New()
|
||||
|
||||
// 增加对单页文档的导出,dandycheung, 2017-12-07
|
||||
if single_doc {
|
||||
id := c.Ctx.Input.Param(":id")
|
||||
if doc, err := c.GetDocumentById(id); err == nil {
|
||||
EachFun("", dpath, c, bookResult, doc, pathList)
|
||||
//如果没有开启缓存,则10分钟后删除
|
||||
if !bookResult.IsCacheEBook {
|
||||
defer func(pdfpath string) {
|
||||
time.Sleep(time.Minute * 10)
|
||||
os.Remove(filepath.Dir(pdfpath))
|
||||
}(eBookResult.EpubPath)
|
||||
}
|
||||
} else {
|
||||
RecursiveFun(0, "", dpath, c, bookResult, docs, pathList)
|
||||
c.StopRun()
|
||||
}else if output == "mobi" {
|
||||
c.Ctx.Output.Download(eBookResult.PDFPath, identify + ".epub")
|
||||
|
||||
//如果没有开启缓存,则10分钟后删除
|
||||
if !bookResult.IsCacheEBook {
|
||||
defer func(pdfpath string) {
|
||||
time.Sleep(time.Minute * 10)
|
||||
os.Remove(filepath.Dir(pdfpath))
|
||||
}(eBookResult.MobiPath)
|
||||
}
|
||||
|
||||
defer os.RemoveAll(dpath)
|
||||
|
||||
// TODO: check if the pathList is empty
|
||||
|
||||
os.MkdirAll("./cache", 0766)
|
||||
pdfpath := filepath.Join("cache", identify+"_"+c.CruSession.SessionID()+".pdf")
|
||||
|
||||
if _, err := os.Stat(pdfpath); os.IsNotExist(err) {
|
||||
wkhtmltopdf.SetPath(beego.AppConfig.String("wkhtmltopdf"))
|
||||
|
||||
pdfg, err := wkhtmltopdf.NewPDFGenerator()
|
||||
if err != nil {
|
||||
beego.Error(err)
|
||||
c.Abort("500")
|
||||
}
|
||||
|
||||
pdfg.MarginBottom.Set(35)
|
||||
|
||||
for e := pathList.Front(); e != nil; e = e.Next() {
|
||||
if page, ok := e.Value.(string); ok {
|
||||
pdfg.AddPage(wkhtmltopdf.NewPage(page))
|
||||
}
|
||||
}
|
||||
|
||||
err = pdfg.Create()
|
||||
if err != nil {
|
||||
beego.Error(err)
|
||||
c.Abort("500")
|
||||
}
|
||||
|
||||
err = pdfg.WriteFile(pdfpath)
|
||||
if err != nil {
|
||||
beego.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
c.Ctx.Output.Download(pdfpath, identify+".pdf")
|
||||
|
||||
defer os.Remove(pdfpath)
|
||||
|
||||
c.StopRun()
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,497 @@
|
|||
//Author:TruthHun
|
||||
//Email:TruthHun@QQ.COM
|
||||
//Date:2018-01-21
|
||||
package converter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"time"
|
||||
|
||||
"os/exec"
|
||||
|
||||
"errors"
|
||||
|
||||
"github.com/TruthHun/gotil/cryptil"
|
||||
"github.com/TruthHun/gotil/filetil"
|
||||
"github.com/TruthHun/gotil/ziptil"
|
||||
)
|
||||
|
||||
type Converter struct {
|
||||
BasePath string
|
||||
Config Config
|
||||
Debug bool
|
||||
GeneratedCover string
|
||||
}
|
||||
|
||||
//目录结构
|
||||
type Toc struct {
|
||||
Id int `json:"id"`
|
||||
Link string `json:"link"`
|
||||
Pid int `json:"pid"`
|
||||
Title string `json:"title"`
|
||||
}
|
||||
|
||||
//config.json文件解析结构
|
||||
type Config struct {
|
||||
Charset string `json:"charset"` //字符编码,默认utf-8编码
|
||||
Cover string `json:"cover"` //封面图片,或者封面html文件
|
||||
Timestamp string `json:"date"` //时间日期,如“2018-01-01 12:12:21”,其实是time.Time格式,但是直接用string就好
|
||||
Description string `json:"description"` //摘要
|
||||
Footer string `json:"footer"` //pdf的footer
|
||||
Header string `json:"header"` //pdf的header
|
||||
Identifier string `json:"identifier"` //即uuid,留空即可
|
||||
Language string `json:"language"` //语言,如zh、en、zh-CN、en-US等
|
||||
Creator string `json:"creator"` //作者,即author
|
||||
Publisher string `json:"publisher"` //出版单位
|
||||
Contributor string `json:"contributor"` //同Publisher
|
||||
Title string `json:"title"` //文档标题
|
||||
Format []string `json:"format"` //导出格式,可选值:pdf、epub、mobi
|
||||
FontSize string `json:"font_size"` //默认的pdf导出字体大小
|
||||
PaperSize string `json:"paper_size"` //页面大小
|
||||
MarginLeft string `json:"margin_left"` //PDF文档左边距,写数字即可,默认72pt
|
||||
MarginRight string `json:"margin_right"` //PDF文档左边距,写数字即可,默认72pt
|
||||
MarginTop string `json:"margin_top"` //PDF文档左边距,写数字即可,默认72pt
|
||||
MarginBottom string `json:"margin_bottom"` //PDF文档左边距,写数字即可,默认72pt
|
||||
More []string `json:"more"` //更多导出选项[PDF导出选项,具体参考:https://manual.calibre-ebook.com/generated/en/ebook-convert.html#pdf-output-options]
|
||||
Toc []Toc `json:"toc"` //目录
|
||||
///////////////////////////////////////////
|
||||
Order []string `json:"-"` //这个不需要赋值
|
||||
}
|
||||
|
||||
var (
|
||||
output = "output" //文档导出文件夹
|
||||
ebookConvert = "ebook-convert"
|
||||
)
|
||||
// 接口文档 https://manual.calibre-ebook.com/generated/en/ebook-convert.html#table-of-contents
|
||||
//根据json配置文件,创建文档转化对象
|
||||
func NewConverter(configFile string, debug ...bool) (converter *Converter, err error) {
|
||||
var (
|
||||
cfg Config
|
||||
basepath string
|
||||
db bool
|
||||
)
|
||||
if len(debug) > 0 {
|
||||
db = debug[0]
|
||||
}
|
||||
|
||||
if cfg, err = parseConfig(configFile); err == nil {
|
||||
if basepath, err = filepath.Abs(filepath.Dir(configFile)); err == nil {
|
||||
//设置默认值
|
||||
if len(cfg.Timestamp) == 0 {
|
||||
cfg.Timestamp = time.Now().Format("2006-01-02 15:04:05")
|
||||
}
|
||||
if len(cfg.Charset) == 0 {
|
||||
cfg.Charset = "utf-8"
|
||||
}
|
||||
converter = &Converter{
|
||||
Config: cfg,
|
||||
BasePath: basepath,
|
||||
Debug: db,
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//执行文档转换
|
||||
func (this *Converter) Convert() (err error) {
|
||||
if !this.Debug { //调试模式下不删除生成的文件
|
||||
defer this.converterDefer() //最后移除创建的多余而文件
|
||||
}
|
||||
|
||||
if err = this.generateMimeType(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = this.generateMetaInfo(); err != nil {
|
||||
return
|
||||
}
|
||||
if err = this.generateTocNcx(); err != nil { //生成目录
|
||||
return
|
||||
}
|
||||
if err = this.generateSummary(); err != nil { //生成文档内目录
|
||||
return
|
||||
}
|
||||
if err = this.generateTitlePage(); err != nil { //生成封面
|
||||
return
|
||||
}
|
||||
if err = this.generateContentOpf(); err != nil { //这个必须是generate*系列方法的最后一个调用
|
||||
return
|
||||
}
|
||||
|
||||
//将当前文件夹下的所有文件压缩成zip包,然后直接改名成content.epub
|
||||
f := this.BasePath + "/content.epub"
|
||||
os.Remove(f) //如果原文件存在了,则删除;
|
||||
if err = ziptil.Zip(f, this.BasePath); err == nil {
|
||||
//创建导出文件夹
|
||||
os.Mkdir(this.BasePath+"/"+output, os.ModePerm)
|
||||
if len(this.Config.Format) > 0 {
|
||||
var errs []string
|
||||
for _, v := range this.Config.Format {
|
||||
fmt.Println("convert to " + v)
|
||||
switch strings.ToLower(v) {
|
||||
case "epub":
|
||||
if err = this.convertToEpub(); err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
case "mobi":
|
||||
if err = this.convertToMobi(); err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
case "pdf":
|
||||
if err = this.convertToPdf(); err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
err = errors.New(strings.Join(errs, "\n"))
|
||||
}
|
||||
} else {
|
||||
err = this.convertToPdf()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//删除生成导出文档而创建的文件
|
||||
func (this *Converter) converterDefer() {
|
||||
//删除不必要的文件
|
||||
os.RemoveAll(this.BasePath + "/META-INF")
|
||||
os.RemoveAll(this.BasePath + "/content.epub")
|
||||
os.RemoveAll(this.BasePath + "/mimetype")
|
||||
os.RemoveAll(this.BasePath + "/toc.ncx")
|
||||
os.RemoveAll(this.BasePath + "/content.opf")
|
||||
os.RemoveAll(this.BasePath + "/titlepage.xhtml") //封面图片待优化
|
||||
os.RemoveAll(this.BasePath + "/summary.html") //文档目录
|
||||
}
|
||||
|
||||
//生成metainfo
|
||||
func (this *Converter) generateMetaInfo() (err error) {
|
||||
xml := `<?xml version="1.0"?>
|
||||
<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
|
||||
<rootfiles>
|
||||
<rootfile full-path="content.opf" media-type="application/oebps-package+xml"/>
|
||||
</rootfiles>
|
||||
</container>
|
||||
`
|
||||
folder := this.BasePath + "/META-INF"
|
||||
os.MkdirAll(folder, os.ModePerm)
|
||||
err = ioutil.WriteFile(folder+"/container.xml", []byte(xml), os.ModePerm)
|
||||
return
|
||||
}
|
||||
|
||||
//形成mimetyppe
|
||||
func (this *Converter) generateMimeType() (err error) {
|
||||
return ioutil.WriteFile(this.BasePath+"/mimetype", []byte("application/epub+zip"), os.ModePerm)
|
||||
}
|
||||
|
||||
//生成封面
|
||||
func (this *Converter) generateTitlePage() (err error) {
|
||||
if ext := strings.ToLower(filepath.Ext(this.Config.Cover)); !(ext == ".html" || ext == ".xhtml") {
|
||||
xml := `<?xml version='1.0' encoding='` + this.Config.Charset + `'?>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="` + this.Config.Language + `">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=` + this.Config.Charset + `"/>
|
||||
<meta name="calibre:cover" content="true"/>
|
||||
<title>Cover</title>
|
||||
<style type="text/css" title="override_css">
|
||||
@page {padding: 0pt; margin:0pt}
|
||||
body { text-align: center; padding:0pt; margin: 0pt; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="100%" height="100%" viewBox="0 0 800 1068" preserveAspectRatio="none">
|
||||
<image width="800" height="1068" xlink:href="` + strings.TrimPrefix(this.Config.Cover, "./") + `"/>
|
||||
</svg>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
if err = ioutil.WriteFile(this.BasePath+"/titlepage.xhtml", []byte(xml), os.ModePerm); err == nil {
|
||||
this.GeneratedCover = "titlepage.xhtml"
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//生成文档目录
|
||||
func (this *Converter) generateTocNcx() (err error) {
|
||||
ncx := `<?xml version='1.0' encoding='` + this.Config.Charset + `'?>
|
||||
<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1" xml:lang="%v">
|
||||
<head>
|
||||
<meta content="4" name="dtb:depth"/>
|
||||
<meta content="calibre (2.85.1)" name="dtb:generator"/>
|
||||
<meta content="0" name="dtb:totalPageCount"/>
|
||||
<meta content="0" name="dtb:maxPageNumber"/>
|
||||
</head>
|
||||
<docTitle>
|
||||
<text>%v</text>
|
||||
</docTitle>
|
||||
<navMap>%v</navMap>
|
||||
</ncx>
|
||||
`
|
||||
codes, _ := this.tocToXml(0, 1)
|
||||
ncx = fmt.Sprintf(ncx, this.Config.Language, this.Config.Title, strings.Join(codes, ""))
|
||||
return ioutil.WriteFile(this.BasePath+"/toc.ncx", []byte(ncx), os.ModePerm)
|
||||
}
|
||||
|
||||
//生成文档目录,即summary.html
|
||||
func (this *Converter) generateSummary() (err error) {
|
||||
//目录
|
||||
summary := `<!DOCTYPE html>
|
||||
<html lang="` + this.Config.Language + `">
|
||||
<head>
|
||||
<meta charset="` + this.Config.Charset + `">
|
||||
<title>目录</title>
|
||||
<style>
|
||||
body{margin: 0px;padding: 0px;}h1{text-align: center;padding: 0px;margin: 0px;}ul,li{list-style: none;}
|
||||
a{text-decoration: none;color: #4183c4;text-decoration: none;font-size: 16px;line-height: 28px;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>目 录</h1>
|
||||
%v
|
||||
</body>
|
||||
</html>`
|
||||
summary = fmt.Sprintf(summary, strings.Join(this.tocToSummary(0), ""))
|
||||
return ioutil.WriteFile(this.BasePath+"/summary.html", []byte(summary), os.ModePerm)
|
||||
}
|
||||
|
||||
//将toc转成toc.ncx文件
|
||||
func (this *Converter) tocToXml(pid, idx int) (codes []string, next_idx int) {
|
||||
var code string
|
||||
for _, toc := range this.Config.Toc {
|
||||
if toc.Pid == pid {
|
||||
code, idx = this.getNavPoint(toc, idx)
|
||||
codes = append(codes, code)
|
||||
for _, item := range this.Config.Toc {
|
||||
if item.Pid == toc.Id {
|
||||
code, idx = this.getNavPoint(item, idx)
|
||||
codes = append(codes, code)
|
||||
var code_arr []string
|
||||
code_arr, idx = this.tocToXml(item.Id, idx)
|
||||
codes = append(codes, code_arr...)
|
||||
codes = append(codes, `</navPoint>`)
|
||||
}
|
||||
}
|
||||
codes = append(codes, `</navPoint>`)
|
||||
}
|
||||
}
|
||||
next_idx = idx
|
||||
return
|
||||
}
|
||||
|
||||
//将toc转成toc.ncx文件
|
||||
func (this *Converter) tocToSummary(pid int) (summarys []string) {
|
||||
summarys = append(summarys, "<ul>")
|
||||
for _, toc := range this.Config.Toc {
|
||||
if toc.Pid == pid {
|
||||
summarys = append(summarys, fmt.Sprintf(`<li><a href="%v">%v</a></li>`, toc.Link, toc.Title))
|
||||
for _, item := range this.Config.Toc {
|
||||
|
||||
if item.Pid == toc.Id {
|
||||
summarys = append(summarys, fmt.Sprintf(`<li><ul><li><a href="%v">%v</a></li>`, item.Link, item.Title))
|
||||
summarys = append(summarys, "<li>")
|
||||
summarys = append(summarys, this.tocToSummary(item.Id)...)
|
||||
summarys = append(summarys, "</li></ul></li>")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
summarys = append(summarys, "</ul>")
|
||||
return
|
||||
}
|
||||
|
||||
//生成navPoint
|
||||
func (this *Converter) getNavPoint(toc Toc, idx int) (navpoint string, nextidx int) {
|
||||
navpoint = `
|
||||
<navPoint id="id%v" playOrder="%v">
|
||||
<navLabel>
|
||||
<text>%v</text>
|
||||
</navLabel>
|
||||
<content src="%v"/>`
|
||||
navpoint = fmt.Sprintf(navpoint, toc.Id, idx, toc.Title, toc.Link)
|
||||
this.Config.Order = append(this.Config.Order, toc.Link)
|
||||
nextidx = idx + 1
|
||||
return
|
||||
}
|
||||
|
||||
//生成content.opf文件
|
||||
//倒数第二步调用
|
||||
func (this *Converter) generateContentOpf() (err error) {
|
||||
var (
|
||||
guide string
|
||||
manifest string
|
||||
manifestArr []string
|
||||
spine string //注意:如果存在封面,则需要把封面放在第一个位置
|
||||
spineArr []string
|
||||
)
|
||||
|
||||
meta := `<dc:title>%v</dc:title>
|
||||
<dc:contributor opf:role="bkp">%v</dc:contributor>
|
||||
<dc:publisher>%v</dc:publisher>
|
||||
<dc:description>%v</dc:description>
|
||||
<dc:language>%v</dc:language>
|
||||
<dc:creator opf:file-as="Unknown" opf:role="aut">%v</dc:creator>
|
||||
<meta name="calibre:timestamp" content="%v"/>
|
||||
`
|
||||
meta = fmt.Sprintf(meta, this.Config.Title, this.Config.Contributor, this.Config.Publisher, this.Config.Description, this.Config.Language, this.Config.Creator, this.Config.Timestamp)
|
||||
if len(this.Config.Cover) > 0 {
|
||||
meta = meta + `<meta name="cover" content="cover"/>`
|
||||
guide = `<reference href="titlepage.xhtml" title="Cover" type="cover"/>`
|
||||
manifest = fmt.Sprintf(`<item href="%v" id="cover" media-type="%v"/>`, this.Config.Cover, GetMediaType(filepath.Ext(this.Config.Cover)))
|
||||
spineArr = append(spineArr, `<itemref idref="titlepage"/>`)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(this.BasePath + "/summary.html"); err == nil {
|
||||
spineArr = append(spineArr, `<itemref idref="summary"/>`) //目录
|
||||
|
||||
}
|
||||
|
||||
//扫描所有文件
|
||||
if files, err := filetil.ScanFiles(this.BasePath); err == nil {
|
||||
basePath := strings.Replace(this.BasePath, "\\", "/", -1)
|
||||
for _, file := range files {
|
||||
if !file.IsDir {
|
||||
ext := strings.ToLower(filepath.Ext(file.Path))
|
||||
sourcefile := strings.TrimPrefix(file.Path, basePath+"/")
|
||||
id := "ncx"
|
||||
if ext != ".ncx" {
|
||||
if file.Name == "titlepage.xhtml" { //封面
|
||||
id = "titlepage"
|
||||
} else if file.Name == "summary.html" { //目录
|
||||
id = "summary"
|
||||
} else {
|
||||
id = cryptil.Md5Crypt(sourcefile)
|
||||
}
|
||||
}
|
||||
if mt := GetMediaType(ext); mt != "" { //不是封面图片,且media-type不为空
|
||||
if sourcefile != strings.TrimLeft(this.Config.Cover, "./") { //不是封面图片,则追加进来。封面图片前面已经追加进来了
|
||||
manifestArr = append(manifestArr, fmt.Sprintf(`<item href="%v" id="%v" media-type="%v"/>`, sourcefile, id, mt))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
items := make(map[string]string)
|
||||
for _, link := range this.Config.Order {
|
||||
id := cryptil.Md5Crypt(link)
|
||||
if _, ok := items[id]; !ok { //去重
|
||||
items[id] = id
|
||||
spineArr = append(spineArr, fmt.Sprintf(`<itemref idref="%v"/>`, id))
|
||||
}
|
||||
}
|
||||
manifest = manifest + strings.Join(manifestArr, "\n")
|
||||
spine = strings.Join(spineArr, "\n")
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
|
||||
pkg := `<?xml version='1.0' encoding='` + this.Config.Charset + `'?>
|
||||
<package xmlns="http://www.idpf.org/2007/opf" unique-identifier="uuid_id" version="2.0">
|
||||
<metadata xmlns:opf="http://www.idpf.org/2007/opf" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calibre="http://calibre.kovidgoyal.net/2009/metadata">
|
||||
%v
|
||||
</metadata>
|
||||
<manifest>
|
||||
%v
|
||||
</manifest>
|
||||
<spine toc="ncx">
|
||||
%v
|
||||
</spine>
|
||||
%v
|
||||
</package>
|
||||
`
|
||||
if len(guide) > 0 {
|
||||
guide = `<guide>` + guide + `</guide>`
|
||||
}
|
||||
pkg = fmt.Sprintf(pkg, meta, manifest, spine, guide)
|
||||
return ioutil.WriteFile(this.BasePath+"/content.opf", []byte(pkg), os.ModePerm)
|
||||
}
|
||||
|
||||
//转成epub
|
||||
func (this *Converter) convertToEpub() (err error) {
|
||||
args := []string{
|
||||
this.BasePath + "/content.epub",
|
||||
this.BasePath + "/" + output + "/book.epub",
|
||||
}
|
||||
cmd := exec.Command(ebookConvert, args...)
|
||||
|
||||
if this.Debug {
|
||||
fmt.Println(cmd.Args)
|
||||
}
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
//转成mobi
|
||||
func (this *Converter) convertToMobi() (err error) {
|
||||
args := []string{
|
||||
this.BasePath + "/content.epub",
|
||||
this.BasePath + "/" + output + "/book.mobi",
|
||||
}
|
||||
cmd := exec.Command(ebookConvert, args...)
|
||||
if this.Debug {
|
||||
fmt.Println(cmd.Args)
|
||||
}
|
||||
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
//转成pdf
|
||||
func (this *Converter) convertToPdf() (err error) {
|
||||
args := []string{
|
||||
this.BasePath + "/content.epub",
|
||||
this.BasePath + "/" + output + "/book.pdf",
|
||||
}
|
||||
//页面大小
|
||||
if len(this.Config.PaperSize) > 0 {
|
||||
args = append(args, "--paper-size", this.Config.PaperSize)
|
||||
}
|
||||
//文字大小
|
||||
if len(this.Config.FontSize) > 0 {
|
||||
args = append(args, "--pdf-default-font-size", this.Config.FontSize)
|
||||
}
|
||||
|
||||
//header template
|
||||
if len(this.Config.Header) > 0 {
|
||||
args = append(args, "--pdf-header-template", this.Config.Header)
|
||||
}
|
||||
|
||||
//footer template
|
||||
if len(this.Config.Footer) > 0 {
|
||||
args = append(args, "--pdf-footer-template", this.Config.Footer)
|
||||
}
|
||||
|
||||
if len(this.Config.MarginLeft) > 0 {
|
||||
args = append(args, "--pdf-page-margin-left", this.Config.MarginLeft)
|
||||
}
|
||||
if len(this.Config.MarginTop) > 0 {
|
||||
args = append(args, "--pdf-page-margin-top", this.Config.MarginTop)
|
||||
}
|
||||
if len(this.Config.MarginRight) > 0 {
|
||||
args = append(args, "--pdf-page-margin-right", this.Config.MarginRight)
|
||||
}
|
||||
if len(this.Config.MarginBottom) > 0 {
|
||||
args = append(args, "--pdf-page-margin-bottom", this.Config.MarginBottom)
|
||||
}
|
||||
|
||||
//更多选项
|
||||
if len(this.Config.More) > 0 {
|
||||
args = append(args, this.Config.More...)
|
||||
}
|
||||
|
||||
cmd := exec.Command(ebookConvert, args...)
|
||||
if this.Debug {
|
||||
fmt.Println(cmd.Args)
|
||||
}
|
||||
return cmd.Run()
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
//Author:TruthHun
|
||||
//Email:TruthHun@QQ.COM
|
||||
//Date:2018-01-21
|
||||
package converter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//media-type
|
||||
var MediaType = map[string]string{
|
||||
".jpeg": "image/jpeg",
|
||||
".png": "image/png",
|
||||
".jpg": "image/jpeg",
|
||||
".gif": "image/gif",
|
||||
".ico": "image/x-icon",
|
||||
".bmp": "image/bmp",
|
||||
".html": "application/xhtml+xml",
|
||||
".xhtml": "application/xhtml+xml",
|
||||
".htm": "application/xhtml+xml",
|
||||
".otf": "application/x-font-opentype",
|
||||
".ttf": "application/x-font-ttf",
|
||||
".js": "application/x-javascript",
|
||||
".ncx": "x-dtbncx+xml",
|
||||
".txt": "text/plain",
|
||||
".xml": "text/xml",
|
||||
".css": "text/css",
|
||||
}
|
||||
|
||||
//根据文件扩展名,获取media-type
|
||||
func GetMediaType(ext string) string {
|
||||
if mt, ok := MediaType[strings.ToLower(ext)]; ok {
|
||||
return mt
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
//解析配置文件
|
||||
func parseConfig(configFile string) (cfg Config, err error) {
|
||||
var b []byte
|
||||
if b, err = ioutil.ReadFile(configFile); err == nil {
|
||||
err = json.Unmarshal(b, &cfg)
|
||||
}
|
||||
return
|
||||
}
|
|
@ -3,8 +3,6 @@ package models
|
|||
import (
|
||||
"time"
|
||||
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/astaxie/beego/orm"
|
||||
|
@ -24,6 +22,10 @@ type Book struct {
|
|||
OrderIndex int `orm:"column(order_index);type(int);default(0)" json:"order_index"`
|
||||
// Description 项目描述.
|
||||
Description string `orm:"column(description);size(2000)" json:"description"`
|
||||
//发行公司
|
||||
Publisher string `orm:"column(publisher);size(500)" json:"publisher"`
|
||||
//是否缓存导出的电子书,如果缓存可能会出现导出的文件不是最新的。 0 为不缓存
|
||||
IsCacheEBook int `orm:"column(is_cache_ebook);type(int);default(0)" json:"is_cache_ebook"`
|
||||
Label string `orm:"column(label);size(500)" json:"label"`
|
||||
// PrivatelyOwned 项目私有: 0 公开/ 1 私有
|
||||
PrivatelyOwned int `orm:"column(privately_owned);type(int);default(0)" json:"privately_owned"`
|
||||
|
@ -354,38 +356,6 @@ func (m *Book) FindForLabelToPager(keyword string, pageIndex, pageSize, member_i
|
|||
}
|
||||
|
||||
|
||||
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
|
||||
m.AutoRelease = book.AutoRelease == 1
|
||||
|
||||
if book.Theme == "" {
|
||||
m.Theme = "default"
|
||||
}
|
||||
if book.Editor == "" {
|
||||
m.Editor = "markdown"
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
//重置文档数量
|
||||
func (m *Book) ResetDocumentNumber(book_id int) {
|
||||
o := orm.NewOrm()
|
||||
|
|
|
@ -2,10 +2,20 @@ package models
|
|||
|
||||
import (
|
||||
"time"
|
||||
"bytes"
|
||||
|
||||
"github.com/astaxie/beego/orm"
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/lifei6671/mindoc/conf"
|
||||
"strings"
|
||||
"github.com/lifei6671/mindoc/converter"
|
||||
"strconv"
|
||||
"github.com/russross/blackfriday"
|
||||
"path/filepath"
|
||||
"github.com/astaxie/beego"
|
||||
"os"
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/lifei6671/mindoc/utils"
|
||||
)
|
||||
|
||||
type BookResult struct {
|
||||
|
@ -14,6 +24,8 @@ type BookResult struct {
|
|||
Identify string `json:"identify"`
|
||||
OrderIndex int `json:"order_index"`
|
||||
Description string `json:"description"`
|
||||
Publisher string `json:"publisher"`
|
||||
IsCacheEBook bool `json:"is_cache_ebook"`
|
||||
PrivatelyOwned int `json:"privately_owned"`
|
||||
PrivateToken string `json:"private_token"`
|
||||
DocCount int `json:"doc_count"`
|
||||
|
@ -79,13 +91,14 @@ func (m *BookResult) FindByIdentify(identify string,member_id int) (*BookResult,
|
|||
return m, err
|
||||
}
|
||||
|
||||
m = book.ToBookResult()
|
||||
m = NewBookResult().ToBookResult(*book)
|
||||
|
||||
m.CreateName = member.Account
|
||||
m.MemberId = relationship.MemberId
|
||||
m.RoleId = relationship.RoleId
|
||||
m.RelationshipId = relationship.RelationshipId
|
||||
|
||||
|
||||
if m.RoleId == conf.BookFounder {
|
||||
m.RoleName = "创始人"
|
||||
} else if m.RoleId == conf.BookAdmin {
|
||||
|
@ -134,6 +147,183 @@ func (m *BookResult) FindToPager(pageIndex, pageSize int) (books []*BookResult,t
|
|||
return
|
||||
}
|
||||
|
||||
//实体转换
|
||||
func (m *BookResult) ToBookResult(book Book) *BookResult {
|
||||
|
||||
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
|
||||
m.AutoRelease = book.AutoRelease == 1
|
||||
m.Publisher = book.Publisher
|
||||
m.IsCacheEBook = book.IsCacheEBook == 1
|
||||
|
||||
if book.Theme == "" {
|
||||
m.Theme = "default"
|
||||
}
|
||||
if book.Editor == "" {
|
||||
m.Editor = "markdown"
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *BookResult) Converter(sessionId string) (ConvertBookResult,error) {
|
||||
|
||||
convertBookResult := ConvertBookResult{}
|
||||
outputPath := filepath.Join(beego.AppConfig.DefaultString("book_output_path","cache"),sessionId,strconv.Itoa(m.BookId))
|
||||
|
||||
if m.IsCacheEBook {
|
||||
outputPath = filepath.Join(beego.AppConfig.DefaultString("book_output_path","cache"),strconv.Itoa(m.BookId))
|
||||
}
|
||||
|
||||
if m.IsCacheEBook {
|
||||
pdfpath := filepath.Join(outputPath,"output","book.pdf")
|
||||
epubpath := filepath.Join(outputPath,"output","book.epub")
|
||||
mobipath := filepath.Join(outputPath,"output","book.mobi")
|
||||
|
||||
if utils.FileExists(pdfpath) && utils.FileExists(epubpath) && utils.FileExists(mobipath){
|
||||
convertBookResult.EpubPath = epubpath
|
||||
convertBookResult.MobiPath = mobipath
|
||||
convertBookResult.PDFPath = pdfpath
|
||||
return convertBookResult,nil
|
||||
}
|
||||
}
|
||||
docs, err := NewDocument().FindListByBookId(m.BookId)
|
||||
if err != nil {
|
||||
return convertBookResult,err
|
||||
}
|
||||
|
||||
tocList := make([]converter.Toc,0)
|
||||
|
||||
for _, item := range docs {
|
||||
if item.ParentId == 0 {
|
||||
toc := converter.Toc{
|
||||
Id: item.DocumentId,
|
||||
Link: strconv.Itoa(item.DocumentId) + ".html",
|
||||
Pid: item.ParentId,
|
||||
Title: item.DocumentName,
|
||||
}
|
||||
|
||||
tocList = append(tocList,toc)
|
||||
}
|
||||
}
|
||||
for _, item := range docs {
|
||||
if item.ParentId != 0 {
|
||||
toc := converter.Toc{
|
||||
Id: item.DocumentId,
|
||||
Link: strconv.Itoa(item.DocumentId) + ".html",
|
||||
Pid: item.ParentId,
|
||||
Title: item.DocumentName,
|
||||
}
|
||||
tocList = append(tocList,toc)
|
||||
}
|
||||
}
|
||||
|
||||
ebookConfig := converter.Config{
|
||||
Charset : "utf-8",
|
||||
Cover : m.Cover,
|
||||
Timestamp : time.Now().Format("2006-01-02 15:04:05"),
|
||||
Description : string(blackfriday.MarkdownBasic([]byte(m.Description))),
|
||||
Footer : "<p style='color:#8E8E8E;font-size:12px;'>本文档使用 <a href='https://www.iminho.me' style='text-decoration:none;color:#1abc9c;font-weight:bold;'>MinDoc</a> 构建 <span style='float:right'>- _PAGENUM_ -</span></p>",
|
||||
Header : "<p style='color:#8E8E8E;font-size:12px;'>_SECTION_</p>",
|
||||
Identifier : "",
|
||||
Language : "zh-CN",
|
||||
Creator : m.CreateName,
|
||||
Publisher : m.Publisher,
|
||||
Contributor : m.Publisher,
|
||||
Title : m.BookName,
|
||||
Format: []string{"epub", "mobi", "pdf"},
|
||||
FontSize : "14",
|
||||
PaperSize : "a4",
|
||||
MarginLeft : "72",
|
||||
MarginRight : "72",
|
||||
MarginTop : "72",
|
||||
MarginBottom : "72",
|
||||
Toc : tocList,
|
||||
More : []string{},
|
||||
|
||||
}
|
||||
|
||||
|
||||
os.MkdirAll(outputPath, 0766)
|
||||
if outputPath, err = filepath.Abs(outputPath); err != nil {
|
||||
beego.Error("导出目录配置错误:" + err.Error())
|
||||
return convertBookResult,err
|
||||
}
|
||||
|
||||
viewPath := beego.BConfig.WebConfig.ViewsPath
|
||||
baseUrl := beego.AppConfig.DefaultString("baseurl","")
|
||||
|
||||
for _,item := range docs {
|
||||
name := strconv.Itoa(item.DocumentId)
|
||||
fpath := filepath.Join(outputPath,name + ".html")
|
||||
|
||||
f, err := os.OpenFile(fpath, os.O_CREATE|os.O_RDWR, 0777)
|
||||
if err != nil {
|
||||
return convertBookResult,err
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
|
||||
if err := beego.ExecuteViewPathTemplate(&buf,"document/export.tpl",viewPath,map[string]interface{}{"Model": m, "Lists": item, "BaseUrl": baseUrl}); err != nil {
|
||||
return convertBookResult,err
|
||||
}
|
||||
html := buf.String()
|
||||
|
||||
|
||||
if err != nil {
|
||||
|
||||
f.Close()
|
||||
return convertBookResult,err
|
||||
}
|
||||
|
||||
bufio := bytes.NewReader(buf.Bytes())
|
||||
|
||||
doc, err := goquery.NewDocumentFromReader(bufio)
|
||||
doc.Find("img").Each(func(i int, contentSelection *goquery.Selection) {
|
||||
if src, ok := contentSelection.Attr("src"); ok && strings.HasPrefix(src, "/uploads/") {
|
||||
contentSelection.SetAttr("src", baseUrl + src)
|
||||
}
|
||||
})
|
||||
|
||||
html, err = doc.Html()
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return convertBookResult,err
|
||||
}
|
||||
|
||||
// html = strings.Replace(html, "<img src=\"/uploads", "<img src=\"" + c.BaseUrl() + "/uploads", -1)
|
||||
|
||||
f.WriteString(html)
|
||||
f.Close()
|
||||
}
|
||||
eBookConverter := &converter.Converter{
|
||||
BasePath : outputPath,
|
||||
Config : ebookConfig,
|
||||
Debug : false,
|
||||
}
|
||||
|
||||
if err := eBookConverter.Convert();err != nil {
|
||||
beego.Error("转换文件错误:" + m.BookName +" => "+ err.Error())
|
||||
return convertBookResult,err
|
||||
}
|
||||
convertBookResult.MobiPath = filepath.Join(outputPath,"output","book.mobi")
|
||||
convertBookResult.PDFPath = filepath.Join(outputPath,"output","book.pdf")
|
||||
convertBookResult.EpubPath = filepath.Join(outputPath,"output","book.epub")
|
||||
return convertBookResult,nil
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package models
|
||||
|
||||
// 转换结果
|
||||
type ConvertBookResult struct {
|
||||
PDFPath string
|
||||
EpubPath string
|
||||
MobiPath string
|
||||
}
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/astaxie/beego"
|
||||
"github.com/astaxie/beego/orm"
|
||||
"github.com/lifei6671/mindoc/conf"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Document struct.
|
||||
|
@ -137,6 +138,9 @@ func (m *Document) ReleaseContent(book_id int) {
|
|||
if err == nil && len(attach_list) > 0 {
|
||||
content := bytes.NewBufferString("<div class=\"attach-list\"><strong>附件</strong><ul>")
|
||||
for _, attach := range attach_list {
|
||||
if strings.HasPrefix(attach.HttpPath,"/"){
|
||||
attach.HttpPath = strings.TrimSuffix(beego.AppConfig.DefaultString("baseurl",""),"/") + attach.HttpPath
|
||||
}
|
||||
li := fmt.Sprintf("<li><a href=\"%s\" target=\"_blank\" title=\"%s\">%s</a></li>", attach.HttpPath, attach.FileName, attach.FileName)
|
||||
|
||||
content.WriteString(li)
|
||||
|
|
|
@ -72,8 +72,7 @@ func init() {
|
|||
beego.Router("/docs/:key", &controllers.DocumentController{}, "*:Index")
|
||||
beego.Router("/docs/:key/:id", &controllers.DocumentController{}, "*:Read")
|
||||
beego.Router("/docs/:key/search", &controllers.DocumentController{}, "post:Search")
|
||||
beego.Router("/export/:key", &controllers.DocumentController{}, "*:ExportBook")
|
||||
beego.Router("/export/:key/:id", &controllers.DocumentController{}, "*:ExportDoc")
|
||||
beego.Router("/export/:key", &controllers.DocumentController{}, "*:Export")
|
||||
beego.Router("/qrcode/:key.png", &controllers.DocumentController{}, "get:QrCode")
|
||||
|
||||
beego.Router("/attach_files/:key/:attach_id", &controllers.DocumentController{}, "get:DownloadAttachment")
|
||||
|
|
|
@ -88,7 +88,6 @@ function openDeleteDocumentDialog($node) {
|
|||
layer.close(index);
|
||||
if(res.errcode === 0){
|
||||
window.treeCatalog.delete_node($node);
|
||||
resetEditor($node);
|
||||
}else{
|
||||
layer.msg("删除失败",{icon : 2})
|
||||
}
|
||||
|
@ -153,6 +152,24 @@ function pushVueLists($lists) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布项目
|
||||
*/
|
||||
function releaseBook() {
|
||||
$.ajax({
|
||||
url: window.releaseURL,
|
||||
data: { "identify": window.book.identify },
|
||||
type: "post",
|
||||
dataType: "json",
|
||||
success: function (res) {
|
||||
if (res.errcode === 0) {
|
||||
layer.msg("发布任务已推送到任务队列,稍后将在后台执行。");
|
||||
} else {
|
||||
layer.msg(res.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
//实现小提示
|
||||
$("[data-toggle='tooltip']").hover(function () {
|
||||
var title = $(this).attr('data-title');
|
||||
|
@ -238,7 +255,6 @@ function uploadImage($id,$callback) {
|
|||
|
||||
var imageFile = clipboard.items[i].getAsFile();
|
||||
|
||||
console.log(imageFile)
|
||||
var fileName = Date.parse(new Date());
|
||||
|
||||
switch (imageFile.type){
|
||||
|
|
|
@ -228,24 +228,6 @@ $(function () {
|
|||
});
|
||||
}
|
||||
|
||||
function releaseBook() {
|
||||
$.ajax({
|
||||
url: window.releaseURL,
|
||||
data: { "identify": window.book.identify },
|
||||
type: "post",
|
||||
dataType: "json",
|
||||
success: function (res) {
|
||||
if (res.errcode === 0) {
|
||||
layer.msg("发布任务已推送到任务队列,稍后将在后台执行。");
|
||||
} else {
|
||||
layer.msg(res.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function resetEditor($node) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置编辑器变更状态
|
||||
|
|
|
@ -8,14 +8,83 @@ $(function () {
|
|||
toolbar :"#editormd-tools"
|
||||
}
|
||||
});
|
||||
window.editor.on("editor-change",function () {
|
||||
window.editor.on("text-change",function () {
|
||||
resetEditorChanged(true);
|
||||
});
|
||||
window.menu_save.on("click",function () {
|
||||
if($(this).hasClass('change')){
|
||||
saveDocument();
|
||||
var $editorEle = $("#editormd-tools");
|
||||
|
||||
$editorEle.find(".ql-undo").on("click",function () {
|
||||
window.editor.history.undo();
|
||||
});
|
||||
$editorEle.find(".ql-redo").on("click",function () {
|
||||
window.editor.history.redo();
|
||||
});
|
||||
|
||||
$("#btnRelease").on("click",function () {
|
||||
if (Object.prototype.toString.call(window.documentCategory) === '[object Array]' && window.documentCategory.length > 0) {
|
||||
if ($("#markdown-save").hasClass('change')) {
|
||||
var comfirm_result = confirm("编辑内容未保存,需要保存吗?")
|
||||
if (comfirm_result) {
|
||||
saveDocument(false, releaseBook);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
releaseBook();
|
||||
} else {
|
||||
layer.msg("没有需要发布的文档")
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 实现自定义图片上传
|
||||
*/
|
||||
window.editor.getModule('toolbar').addHandler('image',function () {
|
||||
var input = document.createElement('input');
|
||||
input.setAttribute('type', 'file');
|
||||
input.click();
|
||||
|
||||
// Listen upload local image and save to server
|
||||
input.onchange = function () {
|
||||
var file = input.files[0];
|
||||
|
||||
// file type is only image.
|
||||
if (/^image\//.test(file.type)) {
|
||||
var form = new FormData();
|
||||
form.append('editormd-image-file', file, file.name);
|
||||
|
||||
var layerIndex = 0;
|
||||
|
||||
$.ajax({
|
||||
url: window.imageUploadURL,
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
data: form,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
error: function() {
|
||||
layer.close(layerIndex);
|
||||
layer.msg("图片上传失败");
|
||||
},
|
||||
success: function(data) {
|
||||
layer.close(layerIndex);
|
||||
if(data.errcode !== 0){
|
||||
layer.msg(data.message);
|
||||
}else{
|
||||
var range = window.editor.getSelection();
|
||||
editor.insertEmbed(range.index, 'image', data.url);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.warn('You could only upload images.');
|
||||
}
|
||||
};
|
||||
});
|
||||
/**
|
||||
* 实现保存
|
||||
*/
|
||||
window.menu_save.on("click",function () {if($(this).hasClass('change')){saveDocument();}});
|
||||
/**
|
||||
* 设置编辑器变更状态
|
||||
* @param $is_change
|
||||
|
@ -43,13 +112,14 @@ $(function () {
|
|||
|
||||
if(res.errcode === 0){
|
||||
window.isLoad = true;
|
||||
window.editor.setContents([{ insert: res.data.content }]);
|
||||
window.editor.root.innerHTML = res.data.content;
|
||||
|
||||
// 将原始内容备份
|
||||
window.source = res.data.content;
|
||||
var node = { "id" : res.data.doc_id,'parent' : res.data.parent_id === 0 ? '#' : res.data.parent_id ,"text" : res.data.doc_name,"identify" : res.data.identify,"version" : res.data.version};
|
||||
pushDocumentCategory(node);
|
||||
window.selectNode = node;
|
||||
window.isLoad = true;
|
||||
|
||||
pushVueLists(res.data.attach);
|
||||
|
||||
|
@ -70,8 +140,9 @@ $(function () {
|
|||
var index = null;
|
||||
var node = window.selectNode;
|
||||
|
||||
var html = window.editor.getContents();
|
||||
var html = window.editor.root.innerHTML;
|
||||
|
||||
console.log(html)
|
||||
var content = "";
|
||||
if($.trim(html) !== ""){
|
||||
content = toMarkdown(html, { gfm: true });
|
||||
|
@ -111,10 +182,9 @@ $(function () {
|
|||
break;
|
||||
}
|
||||
}
|
||||
resetEditorChanged(false);
|
||||
// 更新内容备份
|
||||
window.source = res.data.content;
|
||||
// 触发编辑器 onchange 回调函数
|
||||
window.editor.onchange();
|
||||
if(typeof callback === "function"){
|
||||
callback();
|
||||
}
|
||||
|
@ -231,8 +301,20 @@ $(function () {
|
|||
}
|
||||
}).on('loaded.jstree', function () {
|
||||
window.treeCatalog = $(this).jstree();
|
||||
var $select_node_id = window.treeCatalog.get_selected();
|
||||
if ($select_node_id) {
|
||||
var $select_node = window.treeCatalog.get_node($select_node_id[0])
|
||||
if ($select_node) {
|
||||
$select_node.node = {
|
||||
id: $select_node.id
|
||||
};
|
||||
|
||||
loadDocument($select_node);
|
||||
}
|
||||
}
|
||||
|
||||
}).on('select_node.jstree', function (node, selected, event) {
|
||||
if(window.menu_save.hasClass('selected')) {
|
||||
if(window.menu_save.hasClass('change')) {
|
||||
if(confirm("编辑内容未保存,需要保存吗?")){
|
||||
saveDocument(false,function () {
|
||||
loadDocument(selected);
|
||||
|
@ -242,7 +324,11 @@ $(function () {
|
|||
}
|
||||
loadDocument(selected);
|
||||
|
||||
}).on("move_node.jstree", jstree_save);
|
||||
}).on("move_node.jstree", jstree_save)
|
||||
.on("delete_node.jstree",function (node,parent) {
|
||||
window.isLoad = true;
|
||||
window.editor.root.innerHTML ='';
|
||||
});
|
||||
|
||||
window.saveDocument = saveDocument;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
(function () {
|
||||
var icons = Quill.import('ui/icons');
|
||||
icons.header[3] = '<svg viewBox="0 0 18 18">\n' +
|
||||
' <path class="ql-fill" d="M14.51758,9.64453a1.85627,1.85627,0,0,0-1.24316.38477H13.252a1.73532,1.73532,0,0,1,1.72754-1.4082,2.66491,2.66491,0,0,1,.5498.06641c.35254.05469.57227.01074.70508-.40723l.16406-.5166a.53393.53393,0,0,0-.373-.75977,4.83723,4.83723,0,0,0-1.17773-.14258c-2.43164,0-3.7627,2.17773-3.7627,4.43359,0,2.47559,1.60645,3.69629,3.19043,3.69629A2.70585,2.70585,0,0,0,16.96,12.19727,2.43861,2.43861,0,0,0,14.51758,9.64453Zm-.23047,3.58691c-.67187,0-1.22168-.81445-1.22168-1.45215,0-.47363.30762-.583.72559-.583.96875,0,1.27734.59375,1.27734,1.12207A.82182.82182,0,0,1,14.28711,13.23145ZM10,4V14a1,1,0,0,1-2,0V10H3v4a1,1,0,0,1-2,0V4A1,1,0,0,1,3,4V8H8V4a1,1,0,0,1,2,0Z"/>\n' +
|
||||
' <path class="ql-fill" d="M16.65186,12.30664a2.6742,2.6742,0,0,1-2.915,2.68457,3.96592,3.96592,0,0,1-2.25537-.6709.56007.56007,0,0,1-.13232-.83594L11.64648,13c.209-.34082.48389-.36328.82471-.1543a2.32654,2.32654,0,0,0,1.12256.33008c.71484,0,1.12207-.35156,1.12207-.78125,0-.61523-.61621-.86816-1.46338-.86816H13.2085a.65159.65159,0,0,1-.68213-.41895l-.05518-.10937a.67114.67114,0,0,1,.14307-.78125l.71533-.86914a8.55289,8.55289,0,0,1,.68213-.7373V8.58887a3.93913,3.93913,0,0,1-.748.05469H11.9873a.54085.54085,0,0,1-.605-.60547V7.59863a.54085.54085,0,0,1,.605-.60547h3.75146a.53773.53773,0,0,1,.60547.59375v.17676a1.03723,1.03723,0,0,1-.27539.748L14.74854,10.0293A2.31132,2.31132,0,0,1,16.65186,12.30664ZM9,3A.99974.99974,0,0,0,8,4V8H3V4A1,1,0,0,0,1,4V14a1,1,0,0,0,2,0V10H8v4a1,1,0,0,0,2,0V4A.99974.99974,0,0,0,9,3Z"/>\n' +
|
||||
'</svg>';
|
||||
icons.header[4] = '<svg viewBox="0 0 18 18">\n' +
|
||||
' <path class="ql-fill" d="M10,4V14a1,1,0,0,1-2,0V10H3v4a1,1,0,0,1-2,0V4A1,1,0,0,1,3,4V8H8V4a1,1,0,0,1,2,0Zm7.05371,7.96582v.38477c0,.39648-.165.60547-.46191.60547h-.47314v1.29785a.54085.54085,0,0,1-.605.60547h-.69336a.54085.54085,0,0,1-.605-.60547V12.95605H11.333a.5412.5412,0,0,1-.60547-.60547v-.15332a1.199,1.199,0,0,1,.22021-.748l2.56348-4.05957a.7819.7819,0,0,1,.72607-.39648h1.27637a.54085.54085,0,0,1,.605.60547v3.7627h.33008A.54055.54055,0,0,1,17.05371,11.96582ZM14.28125,8.7207h-.022a4.18969,4.18969,0,0,1-.38525.81348l-1.188,1.80469v.02246h1.5293V9.60059A7.04058,7.04058,0,0,1,14.28125,8.7207Z"/>\n' +
|
||||
|
|
|
@ -40,31 +40,34 @@
|
|||
<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 gt .Model.RelationshipId 0}}
|
||||
{{if eq .Model.RoleId 0 1 2}}
|
||||
<li><a href="{{urlfor "DocumentController.Edit" ":key" .Model.Identify ":id" ""}}">返回编辑</a> </li>
|
||||
<div class="dropdown pull-right">
|
||||
<a href="{{urlfor "DocumentController.Edit" ":key" .Model.Identify ":id" ""}}" class="btn btn-default">编辑</a>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
<li><a href="{{urlfor "BookController.Index"}}">我的项目</a> </li>
|
||||
<li role="presentation" class="divider"></li>
|
||||
{{end}}
|
||||
<div class="dropdown pull-right" style="margin-right: 10px;">
|
||||
<a href="{{urlfor "HomeController.Index"}}" class="btn btn-default"><i class="fa fa-home" aria-hidden="true"></i> 首页</a>
|
||||
</div>
|
||||
<div class="dropdown pull-right" style="margin-right: 10px;">
|
||||
{{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="javascript:void(0);" onclick="ExportPdfDoc()">文档导出为 PDF</a> </li>
|
||||
<li><a href="{{urlfor "DocumentController.ExportBook" ":key" .Model.Identify "output" "pdf"}}" target="_blank">项目导出为 PDF</a> </li>
|
||||
<button type="button" class="btn btn-success" data-toggle="modal" data-target="#shareProject"><i class="fa fa-share-alt" aria-hidden="true"></i> 分享</button>
|
||||
{{end}}
|
||||
|
||||
<li><a href="{{urlfor "HomeController.Index"}}" title="返回首页">返回首页</a> </li>
|
||||
</div>
|
||||
<div class="dropdown pull-right" style="margin-right: 10px;">
|
||||
<button type="button" class="btn btn-primary" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
下载 <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu" aria-labelledby="dLabel" style="margin-top: -5px;">
|
||||
<li><a href="{{urlfor "DocumentController.Export" ":key" .Model.Identify "output" "pdf"}}" target="_blank">PDF</a> </li>
|
||||
<li><a href="{{urlfor "DocumentController.Export" ":key" .Model.Identify "output" "epub"}}" target="_blank">EPUB</a> </li>
|
||||
<li><a href="{{urlfor "DocumentController.Export" ":key" .Model.Identify "output" "mobi"}}" target="_blank">MOBI</a> </li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
@ -128,7 +131,7 @@
|
|||
</div>
|
||||
<div class="col-md-8 text-center">
|
||||
<h1 id="article-title">{{.Title}}</h1>
|
||||
<h3 id="article-info" class="article-info">{{.Info}}</h3>
|
||||
{{/*<h3 id="article-info" class="article-info">{{.Info}}</h3>*/}}
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
</div>
|
||||
|
@ -198,7 +201,7 @@
|
|||
<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">
|
||||
|
@ -226,10 +229,39 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 下载项目 -->
|
||||
<div class="modal fade" id="downloadBookModal" 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">×</span><span class="sr-only">Close</span></button>
|
||||
<h4 class="modal-title" id="myModalLabel">项目分享</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 text-center" style="padding-bottom: 15px;">
|
||||
<img src="{{urlfor "DocumentController.QrCode" ":key" .Model.Identify}}" alt="扫一扫手机阅读" />
|
||||
</div>
|
||||
</div>
|
||||
<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="{{cdnjs "/static/jquery/1.12.4/jquery.min.js"}}"></script>
|
||||
<script src="{{cdnjs "/static/bootstrap/js/bootstrap.min.js"}}"></script>
|
||||
<script src="{{cdnjs "/static/js/jquery.form.js"}}" type="text/javascript"></script>
|
||||
<script src="/static/layer/layer.js" type="text/javascript"></script>
|
||||
<script src="{{cdnjs "/static/layer/layer.js"}}" type="text/javascript"></script>
|
||||
<script src="{{cdnjs "/static/jstree/3.3.4/jstree.min.js"}}" type="text/javascript"></script>
|
||||
<script src="{{cdnjs "/static/nprogress/nprogress.js"}}" type="text/javascript"></script>
|
||||
<script src="{{cdnjs "/static/highlight/highlight.js"}}" type="text/javascript"></script>
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
border-left: none;
|
||||
height: 100%;
|
||||
outline:none;
|
||||
padding: 5px;
|
||||
padding: 5px 5px 30px 5px;
|
||||
}
|
||||
.btn-info{background-color: #ffffff !important;}
|
||||
.btn-info>i{background-color: #cacbcd !important; color: #393939 !important; box-shadow: inset 0 0 0 1px transparent,inset 0 0 0 0 rgba(34,36,38,.15);}
|
||||
|
@ -182,12 +182,13 @@
|
|||
<body>
|
||||
|
||||
<div class="m-manual manual-editor">
|
||||
<div class="manual-head btn-toolbar" id="editormd-tools" style="min-width: 1600px;" data-role="editor-toolbar" data-target="#editor">
|
||||
<div class="manual-head btn-toolbar" id="editormd-tools" style="min-width: 1360px;" data-role="editor-toolbar" data-target="#editor">
|
||||
<div class="editor-group">
|
||||
<a href="{{urlfor "BookController.Index"}}" data-toggle="tooltip" data-title="返回"><i class="fa fa-chevron-left" aria-hidden="true"></i></a>
|
||||
</div>
|
||||
<div class="editor-group">
|
||||
<a href="javascript:;" id="markdown-save" data-toggle="tooltip" data-title="保存" class="disabled save"><i class="fa fa-save" aria-hidden="true" name="save"></i></a>
|
||||
<a href="javascript:;" id="markdown-save" data-toggle="tooltip" data-title="保存" class="disabled save"><i class="fa fa-save first" aria-hidden="true" name="save"></i></a>
|
||||
<a href="javascript:;" data-toggle="tooltip" data-title="发布" id="btnRelease"><i class="fa fa-cloud-upload last" name="release" aria-hidden="true"></i></a>
|
||||
</div>
|
||||
<div class="editor-group">
|
||||
<a href="javascript:;" data-toggle="tooltip" data-title="撤销 (Ctrl-Z)" class="ql-undo"><i class="fa fa-undo first" name="undo" unselectable="on"></i></a>
|
||||
|
@ -231,30 +232,13 @@
|
|||
<button data-toggle="tooltip" data-title="公式" class="ql-formula editor-item"><i class="fa fa-tasks item" name="tasks" aria-hidden="true"></i></button>
|
||||
<select data-toggle="tooltip" data-title="字体颜色" class="ql-color ql-picker ql-color-picker editor-item-select" ></select>
|
||||
<select data-toggle="tooltip" data-title="背景颜色" class="ql-background editor-item-select"></select>
|
||||
<a href="javascript:;" data-toggle="tooltip" data-title="附件"><i class="fa fa-paperclip item" aria-hidden="true" name="attachment"></i></a>
|
||||
<a href="javascript:;" data-toggle="tooltip" data-title="模板"><i class="fa fa-tachometer last" name="template"></i></a>
|
||||
<a href="javascript:;" data-toggle="tooltip" data-title="附件" id="btnUploadFile"><i class="fa fa-paperclip last" aria-hidden="true" name="attachment"></i></a>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="editormd-group pull-right">
|
||||
<a href="javascript:;" data-toggle="tooltip" data-title="关闭实时预览"><i class="fa fa-eye-slash first" name="watch" unselectable="on"></i></a>
|
||||
<a href="javascript:;" data-toggle="tooltip" data-title="修改历史"><i class="fa fa-history item" name="history" aria-hidden="true"></i></a>
|
||||
<a href="javascript:;" data-toggle="tooltip" data-title="边栏"><i class="fa fa-columns item" aria-hidden="true" name="sidebar"></i></a>
|
||||
<a href="javascript:;" data-toggle="tooltip" data-title="使用帮助"><i class="fa fa-question-circle-o last" aria-hidden="true" name="help"></i></a>
|
||||
</div>
|
||||
|
||||
<div class="editormd-group pull-right">
|
||||
<a href="javascript:;" data-toggle="tooltip" data-title="发布"><i class="fa fa-cloud-upload" name="release" aria-hidden="true"></i></a>
|
||||
</div>
|
||||
|
||||
<div class="editor-group">
|
||||
<a href="javascript:;" data-toggle="tooltip" data-title=""></a>
|
||||
<a href="javascript:;" data-toggle="tooltip" data-title=""></a>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div class="manual-body" style="min-width: 1600px;right: inherit">
|
||||
<div class="manual-body">
|
||||
<div class="manual-category" id="manualCategory" style=" border-right: 1px solid #DDDDDD;width: 281px;position: absolute;">
|
||||
<div class="manual-nav">
|
||||
<div class="nav-item active"><i class="fa fa-bars" aria-hidden="true"></i> 文档</div>
|
||||
|
@ -263,26 +247,14 @@
|
|||
</div>
|
||||
<div class="manual-tree" id="sidebar"> </div>
|
||||
</div>
|
||||
<div class="manual-editor-container" id="manualEditorContainer" style="min-width: 1319px;">
|
||||
<div class="manual-editormd">
|
||||
<div id="docEditor" class="manual-editormd-active ql-editor ql-blank">
|
||||
MinDoc 是一款针对IT团队开发的简单好用的文档管理系统。
|
||||
|
||||
|
||||
MinDoc 的前身是 SmartWiki 文档系统。SmartWiki 是基于 PHP 框架 laravel 开发的一款文档管理系统。因 PHP 的部署对普通用户来说太复杂,所以改用 Golang 开发。可以方便用户部署和实用。
|
||||
|
||||
开发缘起是公司IT部门需要一款简单实用的项目接口文档管理和分享的系统。其功能和界面源于 kancloud 。
|
||||
|
||||
可以用来储存日常接口文档,数据库字典,手册说明等文档。内置项目管理,用户管理,权限管理等功能,能够满足大部分中小团队的文档管理需求。
|
||||
<div contenteditable="false" class="editor-wrapper"><pre><code class="editor-code">f</code></pre></div>
|
||||
<div><br/></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="manual-editor-status">
|
||||
<div class="manual-editor-container" id="manualEditorContainer" style="min-width: 1060px;">
|
||||
<div class="manual-editormd" style="bottom: 0;">
|
||||
<div id="docEditor" class="manual-editormd-active ql-editor ql-blank"></div>
|
||||
<div class="manual-editor-status" style="border-top: 1px solid #DDDDDD;">
|
||||
<div id="attachInfo" class="item">0 个附件</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 添加文档 -->
|
||||
|
@ -334,11 +306,9 @@
|
|||
<div class="modal-body">
|
||||
<div class="attach-drop-panel">
|
||||
<div class="upload-container" id="filePicker">
|
||||
<div class="webuploader-pick">
|
||||
<i class="fa fa-upload" aria-hidden="true"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="attach-list" id="attachList">
|
||||
<template v-for="item in lists">
|
||||
<div class="attach-item" :id="item.attachment_id">
|
||||
|
@ -395,57 +365,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="documentTemplateModal" tabindex="-1" role="dialog" aria-labelledby="请选择模板类型" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-title">请选择模板类型</h4>
|
||||
</div>
|
||||
<div class="modal-body template-list">
|
||||
<div class="container">
|
||||
<div class="section">
|
||||
<a data-type="normal" href="javascript:;"><i class="fa fa-file-o"></i></a>
|
||||
<h3><a data-type="normal" href="javascript:;">普通文档</a></h3>
|
||||
<ul>
|
||||
<li>默认类型</li>
|
||||
<li>简单的文本文档</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<a data-type="api" href="javascript:;"><i class="fa fa-file-code-o"></i></a>
|
||||
<h3><a data-type="api" href="javascript:;">API文档</a></h3>
|
||||
<ul>
|
||||
<li>用于API文档速写</li>
|
||||
<li>支持代码高亮</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section">
|
||||
<a data-type="code" href="javascript:;"><i class="fa fa-book"></i></a>
|
||||
|
||||
<h3><a data-type="code" href="javascript:;">数据字典</a></h3>
|
||||
<ul>
|
||||
<li>用于数据字典显示</li>
|
||||
<li>表格支持</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template id="template-normal">
|
||||
{{template "document/template_normal.tpl"}}
|
||||
</template>
|
||||
<template id="template-api">
|
||||
{{template "document/template_api.tpl"}}
|
||||
</template>
|
||||
<template id="template-code">
|
||||
{{template "document/template_code.tpl"}}
|
||||
</template>
|
||||
<script src="{{cdnjs "/static/jquery/1.12.4/jquery.min.js"}}"></script>
|
||||
<script src="{{cdnjs "/static/vuejs/vue.min.js"}}" type="text/javascript"></script>
|
||||
<script src="{{cdnjs "/static/bootstrap/js/bootstrap.min.js"}}"></script>
|
||||
|
@ -455,6 +374,7 @@
|
|||
{{/*<script src="/static/bootstrap/plugins/bootstrap-wysiwyg/bootstrap-wysiwyg.js" type="text/javascript"></script>*/}}
|
||||
{{/*<script src="/static/bootstrap/plugins/bootstrap-wysiwyg/external/google-code-prettify/prettify.js"></script>*/}}
|
||||
<script src="/static/katex/katex.min.js" type="text/javascript"></script>
|
||||
<script src="/static/to-markdown/dist/to-markdown.js" type="text/javascript"></script>
|
||||
<script src="/static/quill/quill.js" type="text/javascript"></script>
|
||||
<script src="/static/quill/quill.icons.js"></script>
|
||||
<script src="{{cdnjs "/static/layer/layer.js"}}" type="text/javascript" ></script>
|
||||
|
@ -465,14 +385,7 @@
|
|||
|
||||
|
||||
$(function () {
|
||||
var $editorEle = $("#editormd-tools");
|
||||
|
||||
$editorEle.find(".ql-undo").on("click",function () {
|
||||
quill.history.undo();
|
||||
});
|
||||
$editorEle.find(".ql-redo").on("click",function () {
|
||||
quill.history.redo();
|
||||
});
|
||||
|
||||
$(".editor-code").on("dblclick",function () {
|
||||
var code = $(this).html();
|
||||
|
@ -485,7 +398,7 @@
|
|||
$(this).parents(".editor-wrapper").addClass("editor-wrapper-selected");
|
||||
});
|
||||
|
||||
$("#attachInfo").on("click",function () {
|
||||
$("#attachInfo,#btnUploadFile").on("click",function () {
|
||||
$("#uploadAttachModal").modal("show");
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue