mirror of https://github.com/mindoc-org/mindoc.git
优化pdf生成逻辑
parent
0f0b110a49
commit
837d0da991
|
@ -534,11 +534,7 @@ func (c *BookController) Release() {
|
||||||
}
|
}
|
||||||
go func(identify string) {
|
go func(identify string) {
|
||||||
models.NewDocument().ReleaseContent(book_id)
|
models.NewDocument().ReleaseContent(book_id)
|
||||||
pdfpath := "cache/" + identify + ".pdf"
|
|
||||||
|
|
||||||
if _,err := os.Stat(pdfpath); os.IsExist(err){
|
|
||||||
os.Remove(pdfpath)
|
|
||||||
}
|
|
||||||
|
|
||||||
}(identify)
|
}(identify)
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ import (
|
||||||
"github.com/lifei6671/godoc/conf"
|
"github.com/lifei6671/godoc/conf"
|
||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
"github.com/astaxie/beego/orm"
|
"github.com/astaxie/beego/orm"
|
||||||
"github.com/lifei6671/godoc/utils"
|
"github.com/lifei6671/godoc/utils/wkhtmltopdf"
|
||||||
)
|
)
|
||||||
|
|
||||||
//DocumentController struct.
|
//DocumentController struct.
|
||||||
|
@ -162,11 +162,16 @@ func (c *DocumentController) Read() {
|
||||||
if doc.BookId != bookResult.BookId {
|
if doc.BookId != bookResult.BookId {
|
||||||
c.Abort("403")
|
c.Abort("403")
|
||||||
}
|
}
|
||||||
|
attach,err := models.NewAttachment().FindListByDocumentId(doc.DocumentId)
|
||||||
|
if err == nil {
|
||||||
|
doc.AttachList = attach
|
||||||
|
}
|
||||||
|
|
||||||
if c.IsAjax() {
|
if c.IsAjax() {
|
||||||
var data struct{
|
var data struct{
|
||||||
DocTitle string `json:"doc_title"`
|
DocTitle string `json:"doc_title"`
|
||||||
Body string `json:"body"`
|
Body string `json:"body"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
}
|
}
|
||||||
data.DocTitle = doc.DocumentName
|
data.DocTitle = doc.DocumentName
|
||||||
data.Body = doc.Release
|
data.Body = doc.Release
|
||||||
|
@ -459,8 +464,19 @@ func (c *DocumentController) Upload() {
|
||||||
"attach" : attachment,
|
"attach" : attachment,
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["json"] = result
|
//c.Data["json"] = result
|
||||||
c.ServeJSON(true)
|
//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()
|
c.StopRun()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -746,14 +762,27 @@ func (c *DocumentController) Export() {
|
||||||
if _,err := os.Stat(pdfpath); os.IsNotExist(err){
|
if _,err := os.Stat(pdfpath); os.IsNotExist(err){
|
||||||
paths := make([]string, len(docs))
|
paths := make([]string, len(docs))
|
||||||
index := 0
|
index := 0
|
||||||
for e := pathList.Front(); e != nil; e = e.Next() {
|
|
||||||
paths[index] = e.Value.(string)
|
pdfg, err := wkhtmltopdf.NewPDFGenerator()
|
||||||
index ++
|
|
||||||
|
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")
|
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)
|
beego.Error(err)
|
||||||
c.Abort("500")
|
c.Abort("500")
|
||||||
}
|
}
|
||||||
beego.Info(fpath,html)
|
//beego.Info(fpath,html)
|
||||||
f.WriteString(html)
|
f.WriteString(html)
|
||||||
f.Close()
|
f.Close()
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
func (m *Book) FindForHomeToPager(pageIndex, pageSize ,member_id int) (books []*BookResult,totalCount int,err error) {
|
||||||
o := orm.NewOrm()
|
o := orm.NewOrm()
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"github.com/lifei6671/godoc/conf"
|
"github.com/lifei6671/godoc/conf"
|
||||||
"github.com/astaxie/beego/orm"
|
"github.com/astaxie/beego/orm"
|
||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Document struct.
|
// Document struct.
|
||||||
|
@ -121,12 +123,31 @@ func (m *Document) ReleaseContent(book_id int) {
|
||||||
|
|
||||||
o := orm.NewOrm()
|
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 {
|
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("<div class=\"attach-list\"><strong>附件</strong><ul>")
|
||||||
|
for _,attach := range attach_list {
|
||||||
|
li := fmt.Sprintf("<li><a href=\"%s\" target=\"_blank\" title=\"%s\">%s</a></li>",attach.HttpPath,attach.FileName,attach.FileName)
|
||||||
|
|
||||||
|
content.WriteString(li)
|
||||||
|
}
|
||||||
|
content.WriteString("</ul></div>")
|
||||||
|
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) {
|
func (m *Document) FindListByBookId(book_id int) (docs []*Document,err error) {
|
||||||
|
|
|
@ -332,7 +332,7 @@ h6 {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
color: #7e888b
|
color: #444
|
||||||
}
|
}
|
||||||
.manual-article .article-content{
|
.manual-article .article-content{
|
||||||
min-width: 980px;
|
min-width: 980px;
|
||||||
|
@ -350,6 +350,11 @@ h6 {
|
||||||
min-height: 90px;
|
min-height: 90px;
|
||||||
padding: 5px;
|
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 {
|
.m-manual .manual-progress {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 54px;
|
top: 54px;
|
||||||
|
|
|
@ -33,11 +33,12 @@ body{
|
||||||
width: 280px;
|
width: 280px;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
border-top: 1px solid #DDDDDD;
|
border-top: 1px solid #DDDDDD;
|
||||||
bottom: 15px;
|
bottom: 0px;
|
||||||
top: 40px;
|
top: 40px;
|
||||||
background-color: #FAFAFA;
|
background-color: #FAFAFA;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
padding-bottom: 15px;
|
||||||
overflow-y:auto;
|
overflow-y:auto;
|
||||||
}
|
}
|
||||||
.manual-category .manual-nav {
|
.manual-category .manual-nav {
|
||||||
|
|
|
@ -145,17 +145,12 @@ function pushDocumentCategory($node) {
|
||||||
* @param $lists
|
* @param $lists
|
||||||
*/
|
*/
|
||||||
function pushVueLists($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){
|
for(var j in $lists){
|
||||||
var item = $lists[j];
|
var item = $lists[j];
|
||||||
window.vueApp.lists.push(item);
|
window.vueApp.lists.push(item);
|
||||||
}
|
}
|
||||||
console.log(window.vueApp.lists)
|
|
||||||
$("#attachInfo").text(" " + window.vueApp.lists.length + " 个附件")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//实现小提示
|
//实现小提示
|
||||||
|
|
|
@ -52,8 +52,6 @@ $(function () {
|
||||||
resetEditorChanged(true);
|
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 () {
|
$("#editormd-tools").on("click","a[class!='disabled']",function () {
|
||||||
var name = $(this).find("i").attr("name");
|
var name = $(this).find("i").attr("name");
|
||||||
if(name === "attachment"){
|
if(name === "attachment"){
|
||||||
window.editor.fileDialog();
|
$("#uploadAttachModal").modal("show");
|
||||||
}else if(name === "history"){
|
}else if(name === "history"){
|
||||||
|
|
||||||
}else if(name === "save"){
|
}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};
|
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);
|
pushDocumentCategory(node);
|
||||||
window.selectNode = node;
|
window.selectNode = node;
|
||||||
|
pushVueLists(res.data.attach);
|
||||||
}else{
|
}else{
|
||||||
layer.msg("文档加载失败");
|
layer.msg("文档加载失败");
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
|
|
||||||
}
|
|
|
@ -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.
|
||||||
|
)
|
|
@ -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
|
||||||
|
}
|
|
@ -53,7 +53,7 @@
|
||||||
{{if eq .Model.PrivatelyOwned 0}}
|
{{if eq .Model.PrivatelyOwned 0}}
|
||||||
<li><a href="javascript:" data-toggle="modal" data-target="#shareProject">项目分享</a> </li>
|
<li><a href="javascript:" data-toggle="modal" data-target="#shareProject">项目分享</a> </li>
|
||||||
<li role="presentation" class="divider"></li>
|
<li role="presentation" class="divider"></li>
|
||||||
{{/*<li><a href="https://wiki.iminho.me/export/1" target="_blank">项目导出</a> </li>*/}}
|
<li><a href="{{urlfor "DocumentController.Export" ":key" .Model.Identify "output" "pdf"}}" target="_blank">项目导出PDF</a> </li>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<li><a href="{{urlfor "HomeController.Index"}}" title="返回首页">返回首页</a> </li>
|
<li><a href="{{urlfor "HomeController.Index"}}" title="返回首页">返回首页</a> </li>
|
||||||
|
@ -77,7 +77,7 @@
|
||||||
<div class="tab-wrap">
|
<div class="tab-wrap">
|
||||||
<div class="tab-item manual-catalog">
|
<div class="tab-item manual-catalog">
|
||||||
<div class="catalog-list read-book-preview" id="sidebar">
|
<div class="catalog-list read-book-preview" id="sidebar">
|
||||||
{{.Result}}
|
{{.Result}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -104,12 +104,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="article-content">
|
<div class="article-content">
|
||||||
<div class="article-body {{if eq .Model.Editor "markdown"}}markdown-body editormd-preview-container{{else}}editor-content{{end}}" id="page-content">
|
<div class="article-body {{if eq .Model.Editor "markdown"}}markdown-body editormd-preview-container{{else}}editor-content{{end}}" id="page-content">
|
||||||
{{.Content}}
|
{{.Content}}
|
||||||
</div>
|
</div>
|
||||||
|
<!--
|
||||||
{{/*
|
{{/*
|
||||||
{{if .Model.IsDisplayComment}}
|
{{if .Model.IsDisplayComment}}
|
||||||
<div id="articleComment" class="m-comment">
|
<div id="articleComment" class="m-comment">
|
||||||
|
@ -155,7 +155,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
*/}}
|
*/}}-->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -84,7 +84,7 @@
|
||||||
<div id="htmlEditor" class="manual-editormd-active" style="height: 100%"></div>
|
<div id="htmlEditor" class="manual-editormd-active" style="height: 100%"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="manual-editor-status">
|
<div class="manual-editor-status">
|
||||||
<div id="attachInfo" class="item"></div>
|
<div id="attachInfo" class="item">0 个附件</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -153,13 +153,13 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="item.state == 'error'">
|
<template v-else-if="item.state == 'error'">
|
||||||
<div class="error-message text">
|
<span class="error-message">${item.message}</span>
|
||||||
上传失败
|
<button type="button" class="btn btn-sm close" @click="removeAttach(item.attachment_id)">
|
||||||
</div>
|
<i class="fa fa-remove" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<input type="hidden" name="attach_id[0]" :value="item.attach_id">
|
<a :href="item.http_path" target="_blank" :title="item.file_name">${item.file_name}</a>
|
||||||
<input type="text" class="form-control" placeholder="附件名称" :value="item.file_name">
|
|
||||||
<span class="text">(${(item.file_size/1024/1024).toFixed(4)}MB)</span>
|
<span class="text">(${(item.file_size/1024/1024).toFixed(4)}MB)</span>
|
||||||
<span class="error-message">${item.message}</span>
|
<span class="error-message">${item.message}</span>
|
||||||
<button type="button" class="btn btn-sm close" @click="removeAttach(item.attachment_id)">
|
<button type="button" class="btn btn-sm close" @click="removeAttach(item.attachment_id)">
|
||||||
|
@ -174,7 +174,7 @@
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<span id="add-error-message" class="error-message"></span>
|
<span id="add-error-message" class="error-message"></span>
|
||||||
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
|
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
|
||||||
<button type="button" class="btn btn-primary" id="btnUploadAttachFile">确定</button>
|
<button type="button" class="btn btn-primary" id="btnUploadAttachFile" data-dismiss="modal">确定</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -202,8 +202,17 @@
|
||||||
delimiters : ['${','}'],
|
delimiters : ['${','}'],
|
||||||
methods : {
|
methods : {
|
||||||
removeAttach : function ($attach_id) {
|
removeAttach : function ($attach_id) {
|
||||||
console.log($attach_id);
|
|
||||||
var $this = this;
|
var $this = this;
|
||||||
|
var item = $this.lists.filter(function ($item) {
|
||||||
|
return $item.attachment_id == $attach_id;
|
||||||
|
});
|
||||||
|
|
||||||
|
if(item && item[0].hasOwnProperty("state")){
|
||||||
|
$this.lists = $this.lists.filter(function ($item) {
|
||||||
|
return $item.attachment_id != $attach_id;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url : "{{urlfor "DocumentController.RemoveAttachment"}}",
|
url : "{{urlfor "DocumentController.RemoveAttachment"}}",
|
||||||
type : "post",
|
type : "post",
|
||||||
|
@ -211,21 +220,23 @@
|
||||||
success : function (res) {
|
success : function (res) {
|
||||||
console.log(res);
|
console.log(res);
|
||||||
if(res.errcode === 0){
|
if(res.errcode === 0){
|
||||||
for(var i in $this.lists){
|
$this.lists = $this.lists.filter(function ($item) {
|
||||||
var item = $this.lists[i];
|
return $item.attachment_id != $attach_id;
|
||||||
if (item.attachment_id == res.data.attachment_id){
|
});
|
||||||
$this.lists.$remove(item);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}else{
|
}else{
|
||||||
layer.msg(res.message);
|
layer.msg(res.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
watch : {
|
||||||
|
lists : function ($lists) {
|
||||||
|
$("#attachInfo").text(" " + $lists.length + " 个附件")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<script src="/static/js/editor.js" type="text/javascript"></script>
|
<script src="/static/js/editor.js" type="text/javascript"></script>
|
||||||
<script src="/static/js/html-editor.js" type="text/javascript"></script>
|
<script src="/static/js/html-editor.js" type="text/javascript"></script>
|
||||||
|
@ -267,16 +278,22 @@
|
||||||
var item = window.vueApp.lists[i];
|
var item = window.vueApp.lists[i];
|
||||||
if(item.attachment_id == file.id){
|
if(item.attachment_id == file.id){
|
||||||
item.state = "error";
|
item.state = "error";
|
||||||
|
item.message = "上传失败";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}).on("uploadSuccess",function (file, res) {
|
}).on("uploadSuccess",function (file, res) {
|
||||||
console.log(file);
|
|
||||||
|
|
||||||
for(var index in window.vueApp.lists){
|
for(var index in window.vueApp.lists){
|
||||||
var item = window.vueApp.lists[index];
|
var item = window.vueApp.lists[index];
|
||||||
if(item.id === file.id){
|
if(item.attachment_id === file.id){
|
||||||
window.vueApp.lists.splice(index,1,res.attach);
|
if(res.errcode === 0) {
|
||||||
|
window.vueApp.lists.splice(index, 1, res.attach);
|
||||||
|
}else{
|
||||||
|
item.message = res.message;
|
||||||
|
item.state = "error";
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,8 @@
|
||||||
<link href="{{cdncss "/static/editor.md/css/editormd.css"}}" rel="stylesheet">
|
<link href="{{cdncss "/static/editor.md/css/editormd.css"}}" rel="stylesheet">
|
||||||
<link href="{{cdncss "/static/css/jstree.css"}}" rel="stylesheet">
|
<link href="{{cdncss "/static/css/jstree.css"}}" rel="stylesheet">
|
||||||
<link href="{{cdncss "/static/highlight/styles/zenburn.css"}}" rel="stylesheet">
|
<link href="{{cdncss "/static/highlight/styles/zenburn.css"}}" rel="stylesheet">
|
||||||
<link href="{{cdncss "/static/css/markdown.css"}}" rel="stylesheet">
|
<link href="{{cdncss "/static/webuploader/webuploader.css"}}" rel="stylesheet">
|
||||||
|
<link href="/static/css/markdown.css" rel="stylesheet">
|
||||||
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
|
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
|
||||||
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
||||||
<!--[if lt IE 9]>
|
<!--[if lt IE 9]>
|
||||||
|
@ -111,7 +112,7 @@
|
||||||
<div id="docEditor" class="manual-editormd-active"></div>
|
<div id="docEditor" class="manual-editormd-active"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="manual-editor-status">
|
<div class="manual-editor-status">
|
||||||
|
<div id="attachInfo" class="item">0 个附件</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -154,13 +155,186 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="modal fade" id="uploadAttachModal" tabindex="-1" role="dialog" aria-labelledby="uploadAttachModalLabel">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<form method="post" id="uploadAttachModalForm" class="form-horizontal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
<h4 class="modal-title" id="myModalLabel">上传附件</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="attach-drop-panel">
|
||||||
|
<div class="upload-container" id="filePicker"><i class="fa fa-upload" aria-hidden="true"></i></div>
|
||||||
|
</div>
|
||||||
|
<div class="attach-list" id="attachList">
|
||||||
|
<template v-for="item in lists">
|
||||||
|
<div class="attach-item" :id="item.attachment_id">
|
||||||
|
<template v-if="item.state == 'wait'">
|
||||||
|
<div class="progress">
|
||||||
|
<div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100">
|
||||||
|
<span class="sr-only">0% Complete (success)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="item.state == 'error'">
|
||||||
|
<span class="error-message">${item.message}</span>
|
||||||
|
<button type="button" class="btn btn-sm close" @click="removeAttach(item.attachment_id)">
|
||||||
|
<i class="fa fa-remove" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a :href="item.http_path" target="_blank" :title="item.file_name">${item.file_name}</a>
|
||||||
|
<span class="text">(${(item.file_size/1024/1024).toFixed(4)}MB)</span>
|
||||||
|
<span class="error-message">${item.message}</span>
|
||||||
|
<button type="button" class="btn btn-sm close" @click="removeAttach(item.attachment_id)">
|
||||||
|
<i class="fa fa-remove" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<span id="add-error-message" class="error-message"></span>
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
|
||||||
|
<button type="button" class="btn btn-primary" id="btnUploadAttachFile" data-dismiss="modal">确定</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="{{cdnjs "/static/jquery/1.12.4/jquery.min.js"}}"></script>
|
<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>
|
<script src="{{cdnjs "/static/bootstrap/js/bootstrap.min.js"}}"></script>
|
||||||
|
<script src="{{cdnjs "/static/webuploader/webuploader.min.js"}}" type="text/javascript"></script>
|
||||||
<script src="{{cdnjs "/static/jstree/3.3.4/jstree.min.js"}}" type="text/javascript"></script>
|
<script src="{{cdnjs "/static/jstree/3.3.4/jstree.min.js"}}" type="text/javascript"></script>
|
||||||
<script src="{{cdnjs "/static/editor.md/editormd.js"}}" type="text/javascript"></script>
|
<script src="{{cdnjs "/static/editor.md/editormd.js"}}" type="text/javascript"></script>
|
||||||
<script type="text/javascript" src="{{cdnjs "/static/layer/layer.js"}}"></script>
|
<script type="text/javascript" src="{{cdnjs "/static/layer/layer.js"}}"></script>
|
||||||
<script src="{{cdnjs "/static/js/jquery.form.js"}}" type="text/javascript"></script>
|
<script src="{{cdnjs "/static/js/jquery.form.js"}}" type="text/javascript"></script>
|
||||||
<script src="{{cdnjs "/static/js/editor.js"}}" type="text/javascript"></script>
|
<script type="text/javascript">
|
||||||
<script src="{{cdnjs "/static/js/markdown.js"}}" type="text/javascript"></script>
|
window.vueApp = new Vue({
|
||||||
|
el : "#attachList",
|
||||||
|
data : {
|
||||||
|
lists : []
|
||||||
|
},
|
||||||
|
delimiters : ['${','}'],
|
||||||
|
methods : {
|
||||||
|
removeAttach : function ($attach_id) {
|
||||||
|
var $this = this;
|
||||||
|
var item = $this.lists.filter(function ($item) {
|
||||||
|
return $item.attachment_id == $attach_id;
|
||||||
|
});
|
||||||
|
|
||||||
|
if(item && item[0].hasOwnProperty("state")){
|
||||||
|
$this.lists = $this.lists.filter(function ($item) {
|
||||||
|
return $item.attachment_id != $attach_id;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$.ajax({
|
||||||
|
url : "{{urlfor "DocumentController.RemoveAttachment"}}",
|
||||||
|
type : "post",
|
||||||
|
data : { "attach_id" : $attach_id},
|
||||||
|
success : function (res) {
|
||||||
|
console.log(res);
|
||||||
|
if(res.errcode === 0){
|
||||||
|
$this.lists = $this.lists.filter(function ($item) {
|
||||||
|
return $item.attachment_id != $attach_id;
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
layer.msg(res.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch : {
|
||||||
|
lists : function ($lists) {
|
||||||
|
$("#attachInfo").text(" " + $lists.length + " 个附件")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<script src="/static/js/editor.js" type="text/javascript"></script>
|
||||||
|
<script src="/static/js/markdown.js" type="text/javascript"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(function () {
|
||||||
|
$("#attachInfo").on("click",function () {
|
||||||
|
$("#uploadAttachModal").modal("show");
|
||||||
|
});
|
||||||
|
window.uploader = null;
|
||||||
|
|
||||||
|
$("#uploadAttachModal").on("shown.bs.modal",function () {
|
||||||
|
if(window.uploader === null){
|
||||||
|
try {
|
||||||
|
window.uploader = WebUploader.create({
|
||||||
|
auto: true,
|
||||||
|
dnd : true,
|
||||||
|
swf: '/static/webuploader/Uploader.swf',
|
||||||
|
server: '{{urlfor "DocumentController.Upload"}}',
|
||||||
|
formData : { "identify" : {{.Model.Identify}},"doc_id" : window.selectNode.id },
|
||||||
|
pick: "#filePicker",
|
||||||
|
fileVal : "editormd-file-file",
|
||||||
|
fileNumLimit : 1,
|
||||||
|
compress : false
|
||||||
|
}).on("beforeFileQueued",function (file) {
|
||||||
|
uploader.reset();
|
||||||
|
}).on( 'fileQueued', function( file ) {
|
||||||
|
var item = {
|
||||||
|
state : "wait",
|
||||||
|
attachment_id : file.id,
|
||||||
|
file_size : file.size,
|
||||||
|
file_name : file.name,
|
||||||
|
message : "正在上传"
|
||||||
|
};
|
||||||
|
window.vueApp.lists.splice(0,0,item);
|
||||||
|
|
||||||
|
}).on("uploadError",function (file,reason) {
|
||||||
|
for(var i in window.vueApp.lists){
|
||||||
|
var item = window.vueApp.lists[i];
|
||||||
|
if(item.attachment_id == file.id){
|
||||||
|
item.state = "error";
|
||||||
|
item.message = "上传失败";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}).on("uploadSuccess",function (file, res) {
|
||||||
|
|
||||||
|
for(var index in window.vueApp.lists){
|
||||||
|
var item = window.vueApp.lists[index];
|
||||||
|
if(item.attachment_id === file.id){
|
||||||
|
if(res.errcode === 0) {
|
||||||
|
window.vueApp.lists.splice(index, 1, res.attach);
|
||||||
|
|
||||||
|
}else{
|
||||||
|
item.message = res.message;
|
||||||
|
item.state = "error";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}).on("beforeFileQueued",function (file) {
|
||||||
|
|
||||||
|
}).on("uploadComplete",function () {
|
||||||
|
|
||||||
|
}).on("uploadProgress",function (file, percentage) {
|
||||||
|
var $li = $( '#'+file.id ),
|
||||||
|
$percent = $li.find('.progress .progress-bar');
|
||||||
|
|
||||||
|
$percent.css( 'width', percentage * 100 + '%' );
|
||||||
|
});
|
||||||
|
}catch(e){
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
Loading…
Reference in New Issue