70.富文本编辑器tinyMCE提交数据到go服务端

在后台管理中,当有图文混排等各种文章(比如新闻、小说类)需要使用富文本编辑器。这次我使用了一下tinyMCE编辑器。这款编辑器是免费开源的。


提交成功效果

Download everything you need for production usage (including a jQuery integration plugin) for free. TinyMCE is open source and licensed under LGPL 2.1.

LGPL2.1可以让我们放心的使用了。
首先在https://www.tiny.cloud/download/self-hosted/ 下载tinyMCE

下载哪个都可以

安装很简单,只要在页面里引用就可以了。

    <script language="JavaScript" type="text/javascript" src="../RichText/tinymce/tinymce.js"></script>
    <script language="JavaScript" type="text/javascript" src="../RichText/tinymce/jquery.tinymce.min.js"></script>

当然,还需要init,这里给出一个设置的代码例子

<script language="JavaScript" type="text/javascript">

        var tinymceEditor;
        tinymce.init({
            selector: '#NContent',
            auto_focus: "Content",
            height: 220,
            language: "zh_CN",
            theme: "modern",
            add_unload_trigger: false,
            image_advtab: true,
            automatic_uploads: false,
            plugins: [
                "advlist autolink lists link image imagetools charmap preview",
                "searchreplace visualblocks fullscreen",
                "insertdatetime media table contextmenu paste",
                "emoticons textcolor"
            ],
            toolbar1: "undo redo | styleselect | fontselect | fontsizeselect | bold italic underline strikethrough | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent",
            toolbar2: "forecolor backcolor table emoticons link image imagetools media | fullscreen preview |  ",

            //TinyMCE 会将所有的 font 元素转换成 span 元素
            convert_fonts_to_spans: true,
            //换行符会被转换成 br 元素
            convert_newlines_to_brs: false,
            //在换行处 TinyMCE 会用 BR 元素而不是插入段落
            force_br_newlines: false,
            //当返回或进入 Mozilla/Firefox 时,这个选项可以打开/关闭段落的建立
            force_p_newlines: false,
            //这个选项控制是否将换行符从输出的 HTML 中去除。选项默认打开,因为许多服务端系统将换行转换成 <br />,因为文本是在无格式的 textarea 中输入的。使用这个选项可以让所有内容在同一行。
            remove_linebreaks: false,
            //不能把这个设置去掉,不然图片路径会出错
            relative_urls: false,
            //不允许拖动大小
            resize: true,

            font_formats: "宋体=宋体;黑体=黑体;仿宋=仿宋;楷体=楷体;隶书=隶书;幼圆=幼圆;Arial=arial,helvetica,sans-serif;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Tahoma=tahoma,arial,helvetica,sans-serif;Times New Roman=times new roman,times;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,zapf dingbats",
            fontsize_formats: "8pt 10pt 12pt 14pt 18pt 24pt 36pt",

             //////////////自己控制图片上传begin/////////////////////////////////////////
            images_upload_handler: function(blobInfo, success, failure){
                var xhr, formData;
                // var stuId = ${user.userId};
                // var maxLogId = ${maxLogId};
                var myId = "joel"
                xhr = new XMLHttpRequest();
                xhr.withCredentials = false;
                xhr.open("POST", "../upload2/?myid="+myId);
                formData = new FormData();
                formData.append("uploadfile", blobInfo.blob());
                xhr.onload = function(e){
                    var json;

                    if (xhr.status != 200) {
                        failure('HTTP Error: ' + xhr.status);
                        return;
                    }
                    json = JSON.parse(this.responseText);

                    if (!json || typeof json.location != 'string') {
                        failure('Invalid JSON: ' + xhr.responseText);
                        return;
                    }

                    success(json.location);
                };
                xhr.send(formData);
            }
            //////////////自己控制图片上传end/////////////////////////////////////////

        });
        tinymce.mceImage

    </script>

这个上传的前端代码,实现了一个文件的上传,同时还上传了一个字符串参数myid。(项目中你可以用这个参数做一些上传用户的判断之类的。)
代码中需要注意的部分

tinymce.init({
selector: '#NContent',
auto_focus: "NContent",

这里的NContent是对应界面模板中的 textarea 组件id

xhr.open("POST", "../upload2/?myid="+myId);
formData = new FormData();
formData.append("uploadfile", blobInfo.blob());

这里面的 upload2 是接收数据的路径,uploadfile是传递文件的组件名称name,都是需要在服务端对应的。

if (!json || typeof json.location != 'string') {
failure('Invalid JSON: ' + xhr.responseText);
return;
}

这段代码中 json.location 中的 location 是服务端接收到文件后,返回给客户端的json串中的一个键。
html模板完整代码

{{define "news"}}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Joel News</title>
    <script language="JavaScript" type="text/javascript" src="../RichText/tinymce/tinymce.js"></script>
    <script language="JavaScript" type="text/javascript" src="../RichText/tinymce/jquery.tinymce.min.js"></script>

    <script language="JavaScript" type="text/javascript">

        var tinymceEditor;
        tinymce.init({
            selector: '#NContent',
            auto_focus: "NContent",
            height: 220,
            language: "zh_CN",
            theme: "modern",
            add_unload_trigger: false,
            image_advtab: true,
            automatic_uploads: false,
            plugins: [
                "advlist autolink lists link image imagetools charmap preview",
                "searchreplace visualblocks fullscreen",
                "insertdatetime media table contextmenu paste",
                "emoticons textcolor"
            ],
            toolbar1: "undo redo | styleselect | fontselect | fontsizeselect | bold italic underline strikethrough | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent",
            toolbar2: "forecolor backcolor table emoticons link image imagetools media | fullscreen preview |  ",

            //TinyMCE 会将所有的 font 元素转换成 span 元素
            convert_fonts_to_spans: true,
            //换行符会被转换成 br 元素
            convert_newlines_to_brs: false,
            //在换行处 TinyMCE 会用 BR 元素而不是插入段落
            force_br_newlines: false,
            //当返回或进入 Mozilla/Firefox 时,这个选项可以打开/关闭段落的建立
            force_p_newlines: false,
            //这个选项控制是否将换行符从输出的 HTML 中去除。选项默认打开,因为许多服务端系统将换行转换成 <br />,因为文本是在无格式的 textarea 中输入的。使用这个选项可以让所有内容在同一行。
            remove_linebreaks: false,
            //不能把这个设置去掉,不然图片路径会出错
            relative_urls: false,
            //不允许拖动大小
            resize: true,

            font_formats: "宋体=宋体;黑体=黑体;仿宋=仿宋;楷体=楷体;隶书=隶书;幼圆=幼圆;Arial=arial,helvetica,sans-serif;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Tahoma=tahoma,arial,helvetica,sans-serif;Times New Roman=times new roman,times;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,zapf dingbats",
            fontsize_formats: "8pt 10pt 12pt 14pt 18pt 24pt 36pt",

             //////////////自己控制图片上传begin/////////////////////////////////////////
            images_upload_handler: function(blobInfo, success, failure){
                var xhr, formData;
                // var stuId = ${user.userId};
                // var maxLogId = ${maxLogId};
                var myId = "joel"
                xhr = new XMLHttpRequest();
                xhr.withCredentials = false;
                xhr.open("POST", "../upload2/?myid="+myId);
                formData = new FormData();
                formData.append("uploadfile", blobInfo.blob());
                xhr.onload = function(e){
                    var json;

                    if (xhr.status != 200) {
                        failure('HTTP Error: ' + xhr.status);
                        return;
                    }
                    json = JSON.parse(this.responseText);

                    if (!json || typeof json.location != 'string') {
                        failure('Invalid JSON: ' + xhr.responseText);
                        return;
                    }

                    success(json.location);
                };
                xhr.send(formData);
            }
            //////////////自己控制图片上传end/////////////////////////////////////////

        });
        tinymce.mceImage

    </script>


</head>
<body>

News
<hr style="border: 1px;height: 1px;" color="#e8e8e8">
标题:{{.NTitle}}
作者:{{.NAuthor}}
发布时间:{{.NPublish}}<br>
内容:{{.NContent }}<br>
附件:{{.NAttachment}}
关键字:{{.NKeyword}}
标签:{{.NTab}}
分类:{{.NClass}}
<hr style="border: 1px;height: 1px;" color="#e3e3e3">
<form method="post" action="" enctype="multipart/form-data">
    <table>

        <tr>
            <td>标题:</td>
            <td><input id="NTitle" name="NTitle" value={{.NTitle}}></td>

            <td>作者:</td>
            <td><input id="NAuthor" name="NAuthor" value={{.NAuthor}}></td>

            <td>发布时间:</td>
            <td><input id="NPublish" name="NPublish" value=""></td>
        </tr>
        <tr>
            <td>内容:</td>
            <td colspan="5"><textarea id="NContent" name="NContent" rows="5" cols="80"
                                      class="editor">{{.NContent }}</textarea></td>
        </tr>
        <tr>
            <td>附件:</td>
            <td><input id="NAttachment" name="NAttachment" value=""></td>

            <td>关键字:</td>
            <td><input id="NKeyword" name="NKeyword" value=""></td>

            <td>标签:</td>
            <td><input id="NTab" name="NTab" value=""></td>

            <td>分类:</td>
            <td><input id="NClass" name="NClass" value=""></td>
        </tr>
        <tr>
            <td></td>
            <td colspan="5"><input id="NSubmit" type="submit" value="提交"/></td>
        </tr>

    </table>


</form>

</body>
</html>
{{end}}

这个模板命名为 news
下面来看golang代码。
此代码借用前面章节使用过的代码示例,稍作修改。
代码大体分两个部分,一个新闻的模板绑定,一个文件的服务端接收。
新闻代码


type News struct {
    NTitle  string      //标题
    NAuthor string  //作者
    NPublish    string  //发布时间
    NContent    template.HTML   //内容
    NAttachment string  //附件
    NKeyword    string  //关键字
    NTab        string  //标签
    NClass      string  //分类
}
func processNewsHandler(writer http.ResponseWriter, request *http.Request) {
    var RNTitle = request.FormValue("NTitle")
    var RNAuthor = request.FormValue("NAuthor")
    var RNPublish = request.FormValue("NPublish")
    var RNContent = request.FormValue("NContent")
    var RNAttachment = request.FormValue("NAttachment")
    var RNKeyword = request.FormValue("NKeyword")
    var RNTab = request.FormValue("NTab")
    var RNClass = request.FormValue("NClass")

    myNews := News{}
    myNews.NTitle = RNTitle
    myNews.NAuthor = RNAuthor
    myNews.NPublish = RNPublish
    myNews.NContent = template.HTML(RNContent)
    myNews.NAttachment = RNAttachment
    myNews.NKeyword = RNKeyword
    myNews.NTab = RNTab
    myNews.NClass = RNClass

    t, _ := template.ParseFiles("./JoelTempWeb/tmplNews2.html")
    t.ExecuteTemplate(writer, "news", myNews)
}

文件接收代码


//单页上传,无模板文件,只有模板代码
func index(writer http.ResponseWriter, request *http.Request) {
    writer.Write([]byte(tpl))
}
const tpl = `<html>
<head>
<title>上传文件</title>
</head>
<body>
<form enctype="multipart/form-data" action="/upload2/" method="post">
<input type="file" name="uploadfile">
<input type="hidden" name="token" value="{...{.}...}">
<input type="submit" value="upload">
</form>
</body>
</html>
`
func upload(writer http.ResponseWriter, request *http.Request) {
    request.ParseMultipartForm(32<<20)
    //接收客户端传来的文件 uploadfile 与客户端保持一致
    file, handler, err := request.FormFile("uploadfile")
    myid := request.FormValue("myid")
    fmt.Println(myid)
    if err != nil{
        fmt.Println(err)
        return
    }
    defer file.Close()
    //上传的文件保存在ppp路径下
    ext := path.Ext(handler.Filename)       //获取文件后缀
    fileNewName := string(time.Now().Format("20060102150405"))+strconv.Itoa(time.Now().Nanosecond())+ext

    f, err := os.OpenFile("./ppp/"+fileNewName, os.O_WRONLY|os.O_CREATE, 0666)
    if err != nil{
        fmt.Println(err)
        return
    }
    defer f.Close()

    io.Copy(f, file)

    back := "{\"error\":false,\"location\":\"../cdn/image/"+fileNewName+"\"}"
    fmt.Fprintln(writer, string(back))

    //location33 := "../cdn/image/"+fileNewName
    //back := make(map[string]interface{})
    //back["error"] = false
    //back["location"] = location33
    //result, _:= json.Marshal(back)
    //fmt.Fprintln(writer, string(result))
}

接收代码里,包含了一段内嵌的页面提交代码。用来测试上传功能。就是这部分

//单页上传,无模板文件,只有模板代码
func index(writer http.ResponseWriter, request *http.Request) {
    writer.Write([]byte(tpl))
}
const tpl = `<html>
<head>
<title>上传文件</title>
</head>
<body>
<form enctype="multipart/form-data" action="/upload2/" method="post">
<input type="file" name="uploadfile">
<input type="hidden" name="token" value="{...{.}...}">
<input type="submit" value="upload">
</form>
</body>
</html>
`

如果不必要,可是删除。
main里的代码部分

    http.HandleFunc("/news/", processNewsHandler)
    http.HandleFunc("/upload2/", upload)
    http.HandleFunc("/index/", index)

    http.Handle("/cdn/image/", http.StripPrefix("/cdn/image/", http.FileServer(http.Dir("ppp"))))
    http.Handle("/RichText/", http.StripPrefix("/RichText/", http.FileServer(http.Dir("JoelTempWeb/JoelRichText"))))        //富文本编辑器

server := http.Server{
        Addr: ":8090",
    }
    server.ListenAndServe()

在接收文件的代码中,返回给客户端值给出了2种写法

    back := "{\"error\":false,\"location\":\"../cdn/image/"+fileNewName+"\"}"
    fmt.Fprintln(writer, string(back))

    //location33 := "../cdn/image/"+fileNewName
    //back := make(map[string]interface{})
    //back["error"] = false
    //back["location"] = location33
    //result, _:= json.Marshal(back)
    //fmt.Fprintln(writer, string(result))

看你喜欢那种。


准备上传图片

选择图片

自动插入图片服务器路径

插入编辑器

提交成功
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,753评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,668评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,090评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,010评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,054评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,806评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,484评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,380评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,873评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,021评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,158评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,838评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,499评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,044评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,159评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,449评论 3 374
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,136评论 2 356

推荐阅读更多精彩内容