diff --git a/controllers/book.go b/controllers/book.go
index 6ad7b123..a6417c72 100644
--- a/controllers/book.go
+++ b/controllers/book.go
@@ -534,11 +534,7 @@ func (c *BookController) Release() {
}
go func(identify string) {
models.NewDocument().ReleaseContent(book_id)
- pdfpath := "cache/" + identify + ".pdf"
- if _,err := os.Stat(pdfpath); os.IsExist(err){
- os.Remove(pdfpath)
- }
}(identify)
diff --git a/controllers/document.go b/controllers/document.go
index b2107115..e9dc0760 100644
--- a/controllers/document.go
+++ b/controllers/document.go
@@ -16,7 +16,7 @@ import (
"github.com/lifei6671/godoc/conf"
"github.com/astaxie/beego"
"github.com/astaxie/beego/orm"
- "github.com/lifei6671/godoc/utils"
+ "github.com/lifei6671/godoc/utils/wkhtmltopdf"
)
//DocumentController struct.
@@ -162,11 +162,16 @@ func (c *DocumentController) Read() {
if doc.BookId != bookResult.BookId {
c.Abort("403")
}
+ attach,err := models.NewAttachment().FindListByDocumentId(doc.DocumentId)
+ if err == nil {
+ doc.AttachList = attach
+ }
+
if c.IsAjax() {
var data struct{
DocTitle string `json:"doc_title"`
Body string `json:"body"`
- Title string `json:"title"`
+ Title string `json:"title"`
}
data.DocTitle = doc.DocumentName
data.Body = doc.Release
@@ -459,8 +464,19 @@ func (c *DocumentController) Upload() {
"attach" : attachment,
}
- c.Data["json"] = result
- c.ServeJSON(true)
+ //c.Data["json"] = result
+ //c.ServeJSON(true)
+ //c.StopRun()
+ //
+ //returnJSON, err := json.Marshal(result)
+ //
+ //if err != nil {
+ // beego.Error(err)
+ //}
+ //
+ //c.Ctx.ResponseWriter.Header().Set("Content-Type", "application/json; charset=utf-8")
+ //fmt.Fprint(c.Ctx.ResponseWriter,string(returnJSON))
+ c.Ctx.Output.JSON(result,true,false)
c.StopRun()
}
@@ -746,14 +762,27 @@ func (c *DocumentController) Export() {
if _,err := os.Stat(pdfpath); os.IsNotExist(err){
paths := make([]string, len(docs))
index := 0
- for e := pathList.Front(); e != nil; e = e.Next() {
- paths[index] = e.Value.(string)
- index ++
+
+ pdfg, err := wkhtmltopdf.NewPDFGenerator()
+
+ if err != nil {
+ beego.Error(err)
+ c.Abort("500")
}
- beego.Info(paths,pdfpath)
+ for e := pathList.Front(); e != nil; e = e.Next() {
+ pdfg.AddPage(wkhtmltopdf.NewPage(paths[index]))
+ }
+ err = pdfg.Create()
+ if err != nil {
+ beego.Error(err)
+ c.Abort("500")
+ }
- utils.ConverterHtmlToPdf(paths, pdfpath)
+ err = pdfg.WriteFile(pdfpath)
+ if err != nil {
+ beego.Error(err)
+ }
}
c.Ctx.Output.Download(pdfpath, identify + ".pdf")
@@ -788,7 +817,7 @@ func RecursiveFun(parent_id int,prefix,dpath string,c *DocumentController,book
beego.Error(err)
c.Abort("500")
}
- beego.Info(fpath,html)
+ //beego.Info(fpath,html)
f.WriteString(html)
f.Close()
diff --git a/models/book.go b/models/book.go
index 8602965b..f89beac1 100644
--- a/models/book.go
+++ b/models/book.go
@@ -259,6 +259,7 @@ 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()
diff --git a/models/document.go b/models/document.go
index 271c2f5c..c9ca0180 100644
--- a/models/document.go
+++ b/models/document.go
@@ -6,6 +6,8 @@ import (
"github.com/lifei6671/godoc/conf"
"github.com/astaxie/beego/orm"
"github.com/astaxie/beego"
+ "bytes"
+ "fmt"
)
// Document struct.
@@ -121,12 +123,31 @@ 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()
+ var docs []*Document
+ _,err := o.QueryTable(m.TableNameWithPrefix()).Filter("book_id",book_id).All(&docs,"document_id","content")
if err != nil {
- beego.Error(err)
+ beego.Error("发布失败 => ",err)
+ return
}
+ for _, item := range docs {
+ item.Release = item.Content
+ attach_list ,err := NewAttachment().FindListByDocumentId(item.DocumentId)
+ if err == nil && len(attach_list) > 0 {
+ content := bytes.NewBufferString("
附件")
+ for _,attach := range attach_list {
+ li := fmt.Sprintf("- %s
",attach.HttpPath,attach.FileName,attach.FileName)
+ content.WriteString(li)
+ }
+ content.WriteString("
")
+ item.Release += content.String()
+ }
+ _,err = o.Update(item,"release")
+ if err != nil {
+ beego.Error(fmt.Sprintf("发布失败 => %+v",item),err)
+ }
+ }
}
func (m *Document) FindListByBookId(book_id int) (docs []*Document,err error) {
diff --git a/static/css/kancloud.css b/static/css/kancloud.css
index 6be58c17..147f36b7 100644
--- a/static/css/kancloud.css
+++ b/static/css/kancloud.css
@@ -332,7 +332,7 @@ h6 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
- color: #7e888b
+ color: #444
}
.manual-article .article-content{
min-width: 980px;
@@ -350,6 +350,11 @@ h6 {
min-height: 90px;
padding: 5px;
}
+.manual-article .article-content .article-body .attach-list{
+ list-style: none;
+ border-top: 1px solid #DDDDDD;
+ padding-top: 15px;
+}
.m-manual .manual-progress {
position: fixed;
top: 54px;
diff --git a/static/css/markdown.css b/static/css/markdown.css
index e8365547..2393180b 100644
--- a/static/css/markdown.css
+++ b/static/css/markdown.css
@@ -33,11 +33,12 @@ body{
width: 280px;
position: fixed;
border-top: 1px solid #DDDDDD;
- bottom: 15px;
+ bottom: 0px;
top: 40px;
background-color: #FAFAFA;
left: 0;
right: 0;
+ padding-bottom: 15px;
overflow-y:auto;
}
.manual-category .manual-nav {
diff --git a/static/js/editor.js b/static/js/editor.js
index 3a19fc9c..b1d08634 100644
--- a/static/js/editor.js
+++ b/static/js/editor.js
@@ -145,17 +145,12 @@ function pushDocumentCategory($node) {
* @param $lists
*/
function pushVueLists($lists) {
- for(var i in window.vueApp.lists){
- var item = window.vueApp.lists[i];
- window.vueApp.lists.$remove(item);
- }
+ window.vueApp.lists = [];
for(var j in $lists){
var item = $lists[j];
window.vueApp.lists.push(item);
}
- console.log(window.vueApp.lists)
- $("#attachInfo").text(" " + window.vueApp.lists.length + " 个附件")
}
//实现小提示
diff --git a/static/js/markdown.js b/static/js/markdown.js
index a06cd762..31ebaae7 100644
--- a/static/js/markdown.js
+++ b/static/js/markdown.js
@@ -52,8 +52,6 @@ $(function () {
resetEditorChanged(true);
}
});
- editormd.loadPlugin("/static/editor.md/plugins/file-dialog/file-dialog");
-
/**
* 实现标题栏操作
@@ -61,7 +59,7 @@ $(function () {
$("#editormd-tools").on("click","a[class!='disabled']",function () {
var name = $(this).find("i").attr("name");
if(name === "attachment"){
- window.editor.fileDialog();
+ $("#uploadAttachModal").modal("show");
}else if(name === "history"){
}else if(name === "save"){
@@ -145,7 +143,7 @@ $(function () {
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;
-
+ pushVueLists(res.data.attach);
}else{
layer.msg("文档加载失败");
}
diff --git a/utils/pdf_linux.go b/utils/pdf_linux.go
deleted file mode 100644
index ade0fa38..00000000
--- a/utils/pdf_linux.go
+++ /dev/null
@@ -1,77 +0,0 @@
-package utils
-
-import (
- "os/exec"
- "bufio"
- "io"
- "io/ioutil"
- "errors"
-
- "github.com/astaxie/beego"
-)
-
-
-// 使用 wkhtmltopdf 是实现 html 转 pdf.
-// 中文说明:http://www.jianshu.com/p/4d65857ffe5e#
-func ConverterHtmlToPdf(uri []string,path string) (error) {
- exe := beego.AppConfig.String("wkhtmltopdf")
-
- if exe == "" {
- return errors.New("wkhtmltopdf not exist.")
- }
- params := []string{"--margin-bottom","25"}
-
- params = append(params,uri...)
- params = append(params,path)
-
- beego.Info(params)
-
- cmd := exec.Command(exe,params...)
-
- stdout, err := cmd.StdoutPipe()
-
- if err != nil {
- return errors.New("StdoutPipe: " + err.Error())
- }
- stderr, err := cmd.StderrPipe()
- if err != nil {
-
- return errors.New("StderrPipe: " + err.Error())
- }
-
- if err := cmd.Start(); err != nil {
-
- return errors.New("Start: "+ err.Error())
- }
-
- reader := bufio.NewReader(stdout)
-
- //实时循环读取输出流中的一行内容
- for {
- line ,err2 := reader.ReadString('\n')
-
- if err2 != nil || io.EOF == err2 {
- break
- }
-
- beego.Info(line)
- }
-
- bytesErr, err := ioutil.ReadAll(stderr)
-
- if err == nil {
- beego.Info(string(bytesErr))
- }else{
- beego.Error("Error: Stderr => " + err.Error())
- return err
- }
-
- if err := cmd.Wait(); err != nil {
-
- beego.Error("Error: ", err.Error())
-
- return err
- }
-
- return nil
-}
diff --git a/utils/pdf_windows.go b/utils/pdf_windows.go
deleted file mode 100644
index b64bb0fd..00000000
--- a/utils/pdf_windows.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package utils
-
-import (
- "os/exec"
- "bufio"
- "io"
- "io/ioutil"
- "errors"
-
- "github.com/axgle/mahonia"
- "github.com/astaxie/beego"
-)
-
-// 使用 wkhtmltopdf 是实现 html 转 pdf.
-// 中文说明:http://www.jianshu.com/p/4d65857ffe5e#
-func ConverterHtmlToPdf(uri []string,path string) (error) {
-
- exe := beego.AppConfig.String("wkhtmltopdf")
-
- if exe == "" {
- return errors.New("wkhtmltopdf not exist.")
- }
-
- params := []string{"/C",exe,"--margin-bottom","25"}
-
- params = append(params,uri...)
- params = append(params,path)
-
- beego.Info(params)
-
- cmd := exec.Command("cmd",params...)
-
- stdout, err := cmd.StdoutPipe()
-
- if err != nil {
- return errors.New("StdoutPipe: " + err.Error())
- }
- stderr, err := cmd.StderrPipe()
- if err != nil {
-
- return errors.New("StderrPipe: " + err.Error())
- }
-
- if err := cmd.Start(); err != nil {
-
- return errors.New("Start: "+ err.Error())
- }
-
- reader := bufio.NewReader(stdout)
- enc := mahonia.NewDecoder("gbk")
-
- //实时循环读取输出流中的一行内容
- for {
- line ,err2 := reader.ReadString('\n')
-
- if err2 != nil || io.EOF == err2 {
- break
- }
-
- beego.Info(enc.ConvertString(line))
- }
-
- bytesErr, err := ioutil.ReadAll(stderr)
-
- if err == nil {
- beego.Info(enc.ConvertString(string(bytesErr)))
- }else{
- beego.Error("Error: Stderr => " + err.Error())
- return err
- }
-
- if err := cmd.Wait(); err != nil {
-
- beego.Error("Error: ", err.Error())
-
- return err
- }
-
- return nil
-
-}
diff --git a/utils/wkhtmltopdf/options.go b/utils/wkhtmltopdf/options.go
new file mode 100644
index 00000000..599c232b
--- /dev/null
+++ b/utils/wkhtmltopdf/options.go
@@ -0,0 +1,443 @@
+package wkhtmltopdf
+
+import (
+ "fmt"
+ "reflect"
+)
+
+//A list of options that can be set from code to make it easier to see which options are available
+type globalOptions struct {
+ CookieJar stringOption //Read and write cookies from and to the supplied cookie jar file
+ Copies uintOption //Number of copies to print into the pdf file (default 1)
+ Dpi uintOption //Change the dpi explicitly (this has no effect on X11 based systems)
+ ExtendedHelp boolOption //Display more extensive help, detailing less common command switches
+ Grayscale boolOption //PDF will be generated in grayscale
+ Help boolOption //Display help
+ HTMLDoc boolOption //Output program html help
+ ImageDpi uintOption //When embedding images scale them down to this dpi (default 600)
+ ImageQuality uintOption //When jpeg compressing images use this quality (default 94)
+ License boolOption //Output license information and exit
+ Lowquality boolOption //Generates lower quality pdf/ps. Useful to shrink the result document space
+ ManPage boolOption //Output program man page
+ MarginBottom uintOption //Set the page bottom margin
+ MarginLeft uintOption //Set the page left margin (default 10mm)
+ MarginRight uintOption //Set the page right margin (default 10mm)
+ MarginTop uintOption //Set the page top margin
+ Orientation stringOption // Set orientation to Landscape or Portrait (default Portrait)
+ NoCollate boolOption //Do not collate when printing multiple copies (default collate)
+ PageHeight uintOption //Page height
+ PageSize stringOption //Set paper size to: A4, Letter, etc. (default A4)
+ PageWidth uintOption //Page width
+ NoPdfCompression boolOption //Do not use lossless compression on pdf objects
+ Quiet boolOption //Be less verbose
+ ReadArgsFromStdin boolOption //Read command line arguments from stdin
+ Readme boolOption //Output program readme
+ Title stringOption //The title of the generated pdf file (The title of the first document is used if not specified)
+ Version boolOption //Output version information and exit
+}
+
+func (gopt *globalOptions) Args() []string {
+ return optsToArgs(gopt)
+}
+
+type outlineOptions struct {
+ DumpDefaultTocXsl boolOption //Dump the default TOC xsl style sheet to stdout
+ DumpOutline stringOption //Dump the outline to a file
+ NoOutline boolOption //Do not put an outline into the pdf
+ OutlineDepth uintOption //Set the depth of the outline (default 4)
+}
+
+func (oopt *outlineOptions) Args() []string {
+ return optsToArgs(oopt)
+}
+
+type pageOptions struct {
+ Allow sliceOption //Allow the file or files from the specified folder to be loaded (repeatable)
+ NoBackground boolOption //Do not print background
+ CacheDir stringOption //Web cache directory
+ CheckboxCheckedSvg stringOption //Use this SVG file when rendering checked checkboxes
+ CheckboxSvg stringOption //Use this SVG file when rendering unchecked checkboxes
+ Cookie mapOption //Set an additional cookie (repeatable), value should be url encoded
+ CustomHeader mapOption //Set an additional HTTP header (repeatable)
+ CustomHeaderPropagation boolOption //Add HTTP headers specified by --custom-header for each resource request
+ NoCustomHeaderPropagation boolOption //Do not add HTTP headers specified by --custom-header for each resource request
+ DebugJavascript boolOption //Show javascript debugging output
+ DefaultHeader boolOption //Add a default header, with the name of the page to the left, and the page number to the right, this is short for: --header-left='[webpage]' --header-right='[page]/[toPage]' --top 2cm --header-line
+ Encoding stringOption //Set the default text encoding, for input
+ DisableExternalLinks boolOption //Do not make links to remote web pages
+ EnableForms boolOption //Turn HTML form fields into pdf form fields
+ NoImages boolOption //Do not load or print images
+ DisableInternalLinks boolOption //Do not make local links
+ DisableJavascript boolOption //Do not allow web pages to run javascript
+ JavascriptDelay uintOption //Wait some milliseconds for javascript finish (default 200)
+ LoadErrorHandling stringOption //Specify how to handle pages that fail to load: abort, ignore or skip (default abort)
+ LoadMediaErrorHandling stringOption //Specify how to handle media files that fail to load: abort, ignore or skip (default ignore)
+ DisableLocalFileAccess boolOption //Do not allowed conversion of a local file to read in other local files, unless explicitly allowed with --allow
+ MinimumFontSize uintOption //Minimum font size
+ ExcludeFromOutline boolOption //Do not include the page in the table of contents and outlines
+ PageOffset uintOption //Set the starting page number (default 0)
+ Password stringOption //HTTP Authentication password
+ EnablePlugins boolOption //Enable installed plugins (plugins will likely not work)
+ Post mapOption //Add an additional post field (repeatable)
+ PostFile mapOption //Post an additional file (repeatable)
+ PrintMediaType boolOption //Use print media-type instead of screen
+ Proxy stringOption //Use a proxy
+ RadiobuttonCheckedSvg stringOption //Use this SVG file when rendering checked radiobuttons
+ RadiobuttonSvg stringOption //Use this SVG file when rendering unchecked radiobuttons
+ RunScript sliceOption //Run this additional javascript after the page is done loading (repeatable)
+ DisableSmartShrinking boolOption //Disable the intelligent shrinking strategy used by WebKit that makes the pixel/dpi ratio none constant
+ NoStopSlowScripts boolOption //Do not Stop slow running javascripts
+ EnableTocBackLinks boolOption //Link from section header to toc
+ UserStyleSheet stringOption //Specify a user style sheet, to load with every page
+ Username stringOption //HTTP Authentication username
+ ViewportSize stringOption //Set viewport size if you have custom scrollbars or css attribute overflow to emulate window size
+ WindowStatus stringOption //Wait until window.status is equal to this string before rendering page
+ Zoom floatOption //Use this zoom factor (default 1)
+}
+
+func (popt *pageOptions) Args() []string {
+ return optsToArgs(popt)
+}
+
+type headerAndFooterOptions struct {
+ FooterCenter stringOption //Centered footer text
+ FooterFontName stringOption //Set footer font name (default Arial)
+ FooterFontSize uintOption //Set footer font size (default 12)
+ FooterHTML stringOption //Adds a html footer
+ FooterLeft stringOption //Left aligned footer text
+ FooterLine boolOption //Display line above the footer
+ FooterRight stringOption //Right aligned footer text
+ FooterSpacing floatOption //Spacing between footer and content in mm (default 0)
+ HeaderCenter stringOption //Centered header text
+ HeaderFontName stringOption //Set header font name (default Arial)
+ HeaderFontSize uintOption //Set header font size (default 12)
+ HeaderHTML stringOption //Adds a html header
+ HeaderLeft stringOption //Left aligned header text
+ HeaderLine boolOption //Display line below the header
+ HeaderRight stringOption //Right aligned header text
+ HeaderSpacing floatOption //Spacing between header and content in mm (default 0)
+ Replace mapOption //Replace [name] with value in header and footer (repeatable)
+}
+
+func (hopt *headerAndFooterOptions) Args() []string {
+ return optsToArgs(hopt)
+}
+
+type tocOptions struct {
+ DisableDottedLines boolOption //Do not use dotted lines in the toc
+ TocHeaderText stringOption //The header text of the toc (default Table of Contents)
+ TocLevelIndentation uintOption //For each level of headings in the toc indent by this length (default 1em)
+ DisableTocLinks boolOption //Do not link from toc to sections
+ TocTextSizeShrink floatOption //For each level of headings in the toc the font is scaled by this factor
+ XslStyleSheet stringOption //Use the supplied xsl style sheet for printing the table of content
+}
+
+func (topt *tocOptions) Args() []string {
+ return optsToArgs(topt)
+}
+
+type argParser interface {
+ Parse() []string //Used in the cmd call
+}
+
+type stringOption struct {
+ option string
+ value string
+}
+
+func (so stringOption) Parse() []string {
+ args := []string{}
+ if so.value == "" {
+ return args
+ }
+ args = append(args, "--"+so.option)
+ args = append(args, so.value)
+ return args
+}
+
+func (so *stringOption) Set(value string) {
+ so.value = value
+}
+
+type sliceOption struct {
+ option string
+ value []string
+}
+
+func (so sliceOption) Parse() []string {
+ args := []string{}
+ if len(so.value) == 0 {
+ return args
+ }
+ for _, v := range so.value {
+ args = append(args, "--"+so.option)
+ args = append(args, v)
+ }
+ return args
+}
+
+func (so *sliceOption) Set(value string) {
+ so.value = append(so.value, value)
+}
+
+type mapOption struct {
+ option string
+ value map[string]string
+}
+
+func (mo mapOption) Parse() []string {
+ args := []string{}
+ if mo.value == nil || len(mo.value) == 0 {
+ return args
+ }
+ for k, v := range mo.value {
+ args = append(args, "--"+mo.option)
+ args = append(args, k)
+ args = append(args, v)
+ }
+ return args
+}
+
+func (mo *mapOption) Set(key, value string) {
+ if mo.value == nil {
+ mo.value = make(map[string]string)
+ }
+ mo.value[key] = value
+}
+
+type uintOption struct {
+ option string
+ value uint
+ isSet bool
+}
+
+func (io uintOption) Parse() []string {
+ args := []string{}
+ if io.isSet == false {
+ return args
+ }
+ args = append(args, "--"+io.option)
+ args = append(args, fmt.Sprintf("%d", io.value))
+ return args
+}
+
+func (io *uintOption) Set(value uint) {
+ io.isSet = true
+ io.value = value
+}
+
+type floatOption struct {
+ option string
+ value float64
+ isSet bool
+}
+
+func (fo floatOption) Parse() []string {
+ args := []string{}
+ if fo.isSet == false {
+ return args
+ }
+ args = append(args, "--"+fo.option)
+ args = append(args, fmt.Sprintf("%.3f", fo.value))
+ return args
+}
+
+func (fo *floatOption) Set(value float64) {
+ fo.isSet = true
+ fo.value = value
+}
+
+type boolOption struct {
+ option string
+ value bool
+}
+
+func (bo boolOption) Parse() []string {
+ if bo.value {
+ return []string{"--" + bo.option}
+ }
+ return []string{}
+}
+
+func (bo *boolOption) Set(value bool) {
+ bo.value = value
+}
+
+func newGlobalOptions() globalOptions {
+ return globalOptions{
+ CookieJar: stringOption{option: "cookie-jar"},
+ Copies: uintOption{option: "copies"},
+ Dpi: uintOption{option: "dpi"},
+ ExtendedHelp: boolOption{option: "extended-help"},
+ Grayscale: boolOption{option: "grayscale"},
+ Help: boolOption{option: "true"},
+ HTMLDoc: boolOption{option: "htmldoc"},
+ ImageDpi: uintOption{option: "image-dpi"},
+ ImageQuality: uintOption{option: "image-quality"},
+ License: boolOption{option: "license"},
+ Lowquality: boolOption{option: "lowquality"},
+ ManPage: boolOption{option: "manpage"},
+ MarginBottom: uintOption{option: "margin-bottom"},
+ MarginLeft: uintOption{option: "margin-left"},
+ MarginRight: uintOption{option: "margin-right"},
+ MarginTop: uintOption{option: "margin-top"},
+ Orientation: stringOption{option: "orientation"},
+ NoCollate: boolOption{option: "nocollate"},
+ PageHeight: uintOption{option: "page-height"},
+ PageSize: stringOption{option: "page-size"},
+ PageWidth: uintOption{option: "page-width"},
+ NoPdfCompression: boolOption{option: "no-pdf-compression"},
+ Quiet: boolOption{option: "quiet"},
+ ReadArgsFromStdin: boolOption{option: "read-args-from-stdin"},
+ Readme: boolOption{option: "readme"},
+ Title: stringOption{option: "title"},
+ Version: boolOption{option: "version"},
+ }
+}
+
+func newOutlineOptions() outlineOptions {
+ return outlineOptions{
+ DumpDefaultTocXsl: boolOption{option: "dump-default-toc-xsl"},
+ DumpOutline: stringOption{option: "dump-outline"},
+ NoOutline: boolOption{option: "no-outline"},
+ OutlineDepth: uintOption{option: "outline-depth"},
+ }
+}
+
+func newPageOptions() pageOptions {
+ return pageOptions{
+ Allow: sliceOption{option: "allow"},
+ NoBackground: boolOption{option: "no-background"},
+ CacheDir: stringOption{option: "cache-dir"},
+ CheckboxCheckedSvg: stringOption{option: "checkbox-checked-svg"},
+ CheckboxSvg: stringOption{option: "checkbox-svg"},
+ Cookie: mapOption{option: "cookie"},
+ CustomHeader: mapOption{option: "custom-header"},
+ CustomHeaderPropagation: boolOption{option: "custom-header-propagation"},
+ NoCustomHeaderPropagation: boolOption{option: "no-custom-header-propagation"},
+ DebugJavascript: boolOption{option: "debug-javascript"},
+ DefaultHeader: boolOption{option: "default-header"},
+ Encoding: stringOption{option: "encoding"},
+ DisableExternalLinks: boolOption{option: "disable-external-links"},
+ EnableForms: boolOption{option: "enable-forms"},
+ NoImages: boolOption{option: "no-images"},
+ DisableInternalLinks: boolOption{option: "disable-internal-links"},
+ DisableJavascript: boolOption{option: "disable-javascript "},
+ JavascriptDelay: uintOption{option: "javascript-delay"},
+ LoadErrorHandling: stringOption{option: "load-error-handling"},
+ LoadMediaErrorHandling: stringOption{option: "load-media-error-handling"},
+ DisableLocalFileAccess: boolOption{option: "disable-local-file-access"},
+ MinimumFontSize: uintOption{option: "minimum-font-size"},
+ ExcludeFromOutline: boolOption{option: "exclude-from-outline"},
+ PageOffset: uintOption{option: "page-offset"},
+ Password: stringOption{option: "password"},
+ EnablePlugins: boolOption{option: "enable-plugins"},
+ Post: mapOption{option: "post"},
+ PostFile: mapOption{option: "post-file"},
+ PrintMediaType: boolOption{option: "print-media-type"},
+ Proxy: stringOption{option: "proxy"},
+ RadiobuttonCheckedSvg: stringOption{option: "radiobutton-checked-svg"},
+ RadiobuttonSvg: stringOption{option: "radiobutton-svg"},
+ RunScript: sliceOption{option: "run-script"},
+ DisableSmartShrinking: boolOption{option: "disable-smart-shrinking"},
+ NoStopSlowScripts: boolOption{option: "no-stop-slow-scripts"},
+ EnableTocBackLinks: boolOption{option: "enable-toc-back-links"},
+ UserStyleSheet: stringOption{option: "user-style-sheet"},
+ Username: stringOption{option: "username"},
+ ViewportSize: stringOption{option: "viewport-size"},
+ WindowStatus: stringOption{option: "window-status"},
+ Zoom: floatOption{option: "zoom"},
+ }
+}
+
+func newHeaderAndFooterOptions() headerAndFooterOptions {
+ return headerAndFooterOptions{
+ FooterCenter: stringOption{option: "footer-center"},
+ FooterFontName: stringOption{option: "footer-font-name"},
+ FooterFontSize: uintOption{option: "footer-font-size"},
+ FooterHTML: stringOption{option: "footer-html"},
+ FooterLeft: stringOption{option: "footer-left"},
+ FooterLine: boolOption{option: "footer-line"},
+ FooterRight: stringOption{option: "footer-right"},
+ FooterSpacing: floatOption{option: "footer-spacing"},
+ HeaderCenter: stringOption{option: "header-center"},
+ HeaderFontName: stringOption{option: "header-font-name"},
+ HeaderFontSize: uintOption{option: "header-font-size"},
+ HeaderHTML: stringOption{option: "header-html"},
+ HeaderLeft: stringOption{option: "header-left"},
+ HeaderLine: boolOption{option: "header-line"},
+ HeaderRight: stringOption{option: "header-right"},
+ HeaderSpacing: floatOption{option: "header-spacing"},
+ Replace: mapOption{option: "replace"},
+ }
+}
+
+func newTocOptions() tocOptions {
+ return tocOptions{
+ DisableDottedLines: boolOption{option: "disable-dotted-lines"},
+ TocHeaderText: stringOption{option: "toc-header-text"},
+ TocLevelIndentation: uintOption{option: "toc-level-indentation"},
+ DisableTocLinks: boolOption{option: "disable-toc-links"},
+ TocTextSizeShrink: floatOption{option: "toc-text-size-shrink"},
+ XslStyleSheet: stringOption{option: "xsl-style-sheet"},
+ }
+}
+
+func optsToArgs(opts interface{}) []string {
+ args := []string{}
+ rv := reflect.Indirect(reflect.ValueOf(opts))
+ if rv.Kind() != reflect.Struct {
+ return args
+ }
+ for i := 0; i < rv.NumField(); i++ {
+ prsr, ok := rv.Field(i).Interface().(argParser)
+ if ok {
+ s := prsr.Parse()
+ if len(s) > 0 {
+ args = append(args, s...)
+ }
+ }
+ }
+ return args
+}
+
+// Constants for orientation modes
+const (
+ OrientationLandscape = "Landscape" // Landscape mode
+ OrientationPortrait = "Portrait" // Portrait mode
+)
+
+// Constants for page sizes
+const (
+ PageSizeA0 = "A0" // 841 x 1189 mm
+ PageSizeA1 = "A1" // 594 x 841 mm
+ PageSizeA2 = "A2" // 420 x 594 mm
+ PageSizeA3 = "A3" // 297 x 420 mm
+ PageSizeA4 = "A4" // 210 x 297 mm, 8.26
+ PageSizeA5 = "A5" // 148 x 210 mm
+ PageSizeA6 = "A6" // 105 x 148 mm
+ PageSizeA7 = "A7" // 74 x 105 mm
+ PageSizeA8 = "A8" // 52 x 74 mm
+ PageSizeA9 = "A9" // 37 x 52 mm
+ PageSizeB0 = "B0" // 1000 x 1414 mm
+ PageSizeB1 = "B1" // 707 x 1000 mm
+ PageSizeB2 = "B2" // 500 x 707 mm
+ PageSizeB3 = "B3" // 353 x 500 mm
+ PageSizeB4 = "B4" // 250 x 353 mm
+ PageSizeB5 = "B5" // 176 x 250 mm, 6.93
+ PageSizeB6 = "B6" // 125 x 176 mm
+ PageSizeB7 = "B7" // 88 x 125 mm
+ PageSizeB8 = "B8" // 62 x 88 mm
+ PageSizeB9 = "B9" // 33 x 62 mm
+ PageSizeB10 = "B10" // 31 x 44 mm
+ PageSizeC5E = "C5E" // 163 x 229 mm
+ PageSizeComm10E = "Comm10E" // 105 x 241 mm, U.S. Common 10 Envelope
+ PageSizeDLE = "DLE" // 110 x 220 mm
+ PageSizeExecutive = "Executive" // 7.5 x 10 inches, 190.5 x 254 mm
+ PageSizeFolio = "Folio" // 210 x 330 mm
+ PageSizeLedger = "Ledger" // 431.8 x 279.4 mm
+ PageSizeLegal = "Legal" // 8.5 x 14 inches, 215.9 x 355.6 mm
+ PageSizeLetter = "Letter" // 8.5 x 11 inches, 215.9 x 279.4 mm
+ PageSizeTabloid = "Tabloid" // 279.4 x 431.8 mm
+ PageSizeCustom = "Custom" // Unknown, or a user defined size.
+)
diff --git a/utils/wkhtmltopdf/wkhtmltopdf.go b/utils/wkhtmltopdf/wkhtmltopdf.go
new file mode 100644
index 00000000..25ae3838
--- /dev/null
+++ b/utils/wkhtmltopdf/wkhtmltopdf.go
@@ -0,0 +1,294 @@
+// Package wkhtmltopdf contains wrappers around the wkhtmltopdf commandline tool
+package wkhtmltopdf
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+)
+
+var binPath string //the cached paths as used by findPath()
+
+// SetPath sets the path to wkhtmltopdf
+func SetPath(path string) {
+ binPath = path
+}
+
+// GetPath gets the path to wkhtmltopdf
+func GetPath() string {
+ return binPath
+}
+
+// Page is the input struct for each page
+type Page struct {
+ Input string
+ PageOptions
+}
+
+// InputFile returns the input string and is part of the page interface
+func (p *Page) InputFile() string {
+ return p.Input
+}
+
+// Args returns the argument slice and is part of the page interface
+func (p *Page) Args() []string {
+ return p.PageOptions.Args()
+}
+
+// Reader returns the io.Reader and is part of the page interface
+func (p *Page) Reader() io.Reader {
+ return nil
+}
+
+// NewPage creates a new input page from a local or web resource (filepath or URL)
+func NewPage(input string) *Page {
+ return &Page{
+ Input: input,
+ PageOptions: NewPageOptions(),
+ }
+}
+
+// PageReader is one input page (a HTML document) that is read from an io.Reader
+// You can add only one Page from a reader
+type PageReader struct {
+ Input io.Reader
+ PageOptions
+}
+
+// InputFile returns the input string and is part of the page interface
+func (pr *PageReader) InputFile() string {
+ return "-"
+}
+
+// Args returns the argument slice and is part of the page interface
+func (pr *PageReader) Args() []string {
+ return pr.PageOptions.Args()
+}
+
+//Reader returns the io.Reader and is part of the page interface
+func (pr *PageReader) Reader() io.Reader {
+ return pr.Input
+}
+
+// NewPageReader creates a new PageReader from an io.Reader
+func NewPageReader(input io.Reader) *PageReader {
+ return &PageReader{
+ Input: input,
+ PageOptions: NewPageOptions(),
+ }
+}
+
+type page interface {
+ Args() []string
+ InputFile() string
+ Reader() io.Reader
+}
+
+// PageOptions are options for each input page
+type PageOptions struct {
+ pageOptions
+ headerAndFooterOptions
+}
+
+// Args returns the argument slice
+func (po *PageOptions) Args() []string {
+ return append(append([]string{}, po.pageOptions.Args()...), po.headerAndFooterOptions.Args()...)
+}
+
+// NewPageOptions returns a new PageOptions struct with all options
+func NewPageOptions() PageOptions {
+ return PageOptions{
+ pageOptions: newPageOptions(),
+ headerAndFooterOptions: newHeaderAndFooterOptions(),
+ }
+}
+
+// cover page
+type cover struct {
+ Input string
+ pageOptions
+}
+
+// table of contents
+type toc struct {
+ Include bool
+ allTocOptions
+}
+
+type allTocOptions struct {
+ pageOptions
+ tocOptions
+}
+
+// PDFGenerator is the main wkhtmltopdf struct, always use NewPDFGenerator to obtain a new PDFGenerator struct
+type PDFGenerator struct {
+ globalOptions
+ outlineOptions
+
+ Cover cover
+ TOC toc
+ OutputFile string //filename to write to, default empty (writes to internal buffer)
+
+ binPath string
+ outbuf bytes.Buffer
+ pages []page
+}
+
+//Args returns the commandline arguments as a string slice
+func (pdfg *PDFGenerator) Args() []string {
+ args := []string{}
+ args = append(args, pdfg.globalOptions.Args()...)
+ args = append(args, pdfg.outlineOptions.Args()...)
+ if pdfg.Cover.Input != "" {
+ args = append(args, "cover")
+ args = append(args, pdfg.Cover.Input)
+ args = append(args, pdfg.Cover.pageOptions.Args()...)
+ }
+ if pdfg.TOC.Include {
+ args = append(args, "toc")
+ args = append(args, pdfg.TOC.pageOptions.Args()...)
+ args = append(args, pdfg.TOC.tocOptions.Args()...)
+ }
+ for _, page := range pdfg.pages {
+ args = append(args, "page")
+ args = append(args, page.InputFile())
+ args = append(args, page.Args()...)
+ }
+ if pdfg.OutputFile != "" {
+ args = append(args, pdfg.OutputFile)
+ } else {
+ args = append(args, "-")
+ }
+ return args
+}
+
+// ArgString returns Args as a single string
+func (pdfg *PDFGenerator) ArgString() string {
+ return strings.Join(pdfg.Args(), " ")
+}
+
+// AddPage adds a new input page to the document.
+// A page is an input HTML page, it can span multiple pages in the output document.
+// It is a Page when read from file or URL or a PageReader when read from memory.
+func (pdfg *PDFGenerator) AddPage(p page) {
+ pdfg.pages = append(pdfg.pages, p)
+}
+
+// SetPages resets all pages
+func (pdfg *PDFGenerator) SetPages(p []page) {
+ pdfg.pages = p
+}
+
+// Buffer returns the embedded output buffer used if OutputFile is empty
+func (pdfg *PDFGenerator) Buffer() *bytes.Buffer {
+ return &pdfg.outbuf
+}
+
+// Bytes returns the output byte slice from the output buffer used if OutputFile is empty
+func (pdfg *PDFGenerator) Bytes() []byte {
+ return pdfg.outbuf.Bytes()
+}
+
+// WriteFile writes the contents of the output buffer to a file
+func (pdfg *PDFGenerator) WriteFile(filename string) error {
+ return ioutil.WriteFile(filename, pdfg.Bytes(), 0666)
+}
+
+//findPath finds the path to wkhtmltopdf by
+//- first looking in the current dir
+//- looking in the PATH and PATHEXT environment dirs
+//- using the WKHTMLTOPDF_PATH environment dir
+//The path is cached, meaning you can not change the location of wkhtmltopdf in
+//a running program once it has been found
+func (pdfg *PDFGenerator) findPath() error {
+ const exe = "wkhtmltopdf"
+ if binPath != "" {
+ pdfg.binPath = binPath
+ return nil
+ }
+ exeDir, err := filepath.Abs(filepath.Dir(os.Args[0]))
+ if err != nil {
+ return err
+ }
+ path, err := exec.LookPath(filepath.Join(exeDir, exe))
+ if err == nil && path != "" {
+ binPath = path
+ pdfg.binPath = path
+ return nil
+ }
+ path, err = exec.LookPath(exe)
+ if err == nil && path != "" {
+ binPath = path
+ pdfg.binPath = path
+ return nil
+ }
+ dir := os.Getenv("WKHTMLTOPDF_PATH")
+ if dir == "" {
+ return fmt.Errorf("%s not found", exe)
+ }
+ path, err = exec.LookPath(filepath.Join(dir, exe))
+ if err == nil && path != "" {
+ binPath = path
+ pdfg.binPath = path
+ return nil
+ }
+ return fmt.Errorf("%s not found", exe)
+}
+
+// Create creates the PDF document and stores it in the internal buffer if no error is returned
+func (pdfg *PDFGenerator) Create() error {
+ return pdfg.run()
+}
+
+func (pdfg *PDFGenerator) run() error {
+
+ errbuf := &bytes.Buffer{}
+
+ cmd := exec.Command(pdfg.binPath, pdfg.Args()...)
+
+ cmd.Stdout = &pdfg.outbuf
+ cmd.Stderr = errbuf
+ //if there is a pageReader page (from Stdin) we set Stdin to that reader
+ for _, page := range pdfg.pages {
+ if page.Reader() != nil {
+ cmd.Stdin = page.Reader()
+ break
+ }
+ }
+
+ err := cmd.Run()
+ if err != nil {
+ errStr := errbuf.String()
+ if strings.TrimSpace(errStr) == "" {
+ errStr = err.Error()
+ }
+ return errors.New(errStr)
+ }
+ return nil
+}
+
+// NewPDFGenerator returns a new PDFGenerator struct with all options created and
+// checks if wkhtmltopdf can be found on the system
+func NewPDFGenerator() (*PDFGenerator, error) {
+ pdfg := &PDFGenerator{
+ globalOptions: newGlobalOptions(),
+ outlineOptions: newOutlineOptions(),
+ Cover: cover{
+ pageOptions: newPageOptions(),
+ },
+ TOC: toc{
+ allTocOptions: allTocOptions{
+ tocOptions: newTocOptions(),
+ pageOptions: newPageOptions(),
+ },
+ },
+ }
+ err := pdfg.findPath()
+ return pdfg, err
+}
diff --git a/views/document/default_read.tpl b/views/document/default_read.tpl
index e22ed034..235f3851 100644
--- a/views/document/default_read.tpl
+++ b/views/document/default_read.tpl
@@ -53,7 +53,7 @@
{{if eq .Model.PrivatelyOwned 0}}
项目分享
- {{/*项目导出 */}}
+ 项目导出PDF
{{end}}
返回首页
@@ -77,7 +77,7 @@
-
diff --git a/views/document/html_edit_template.tpl b/views/document/html_edit_template.tpl
index 28dc8ae1..6067b2c0 100644
--- a/views/document/html_edit_template.tpl
+++ b/views/document/html_edit_template.tpl
@@ -84,7 +84,7 @@
@@ -153,13 +153,13 @@
-
- 上传失败
-
+ ${item.message}
+
-
-
+ ${item.file_name}
(${(item.file_size/1024/1024).toFixed(4)}MB)
${item.message}