标签归档:编辑器

HTML编辑器 – UEditor

UEditor 是一个功能比较齐全的HTML编辑器,百度出品,官方网站为:http://ueditor.baidu.com/website/。这个编辑器对于文件上传方面提供了强大支持,比如可以直接粘贴(CTRL + V)图片,上传涂鸦文件(客户端画图),多文件上传等。

其中有一个叫UMeditor的产品,简称UM,它是UEditor(简称UE)的功能缩减版本。也可以使用其提供的Ubuilder来构建一个自定义的版本(主要指功能模块)。

一般直接使用UEditor这个全功能版本即可:
ueditor

目录结构:
ueditor-struct
目录dialogs对应各种弹框,目录lang对应语言包(前端展示),目录themes对应皮肤,third-party是第三方插件。文件ueditor.all.js是编辑器实现代码,可见未压缩有1M以上,min压缩后也有接近400K,问津ueditor.config.js是前端的主要配置。

前台主要配置都在ueditor.config.js中,这个文件几乎不用改变,一般需要修改的地方就是serverUrl参数,当有图片或文件需要上传时,就提交到这个指定的地址。其它配置项在这个文件中都有详细的注释(比如皮肤,语言等):

(function () {
    var URL = window.UEDITOR_HOME_URL || getUEBasePath();
    var origin = getOrigin();

    /**
     * 配置项主体。注意,此处所有涉及到路径的配置别遗漏URL变量。
     */
    window.UEDITOR_CONFIG = {

        //为编辑器实例添加一个路径,这个不能被注释
        UEDITOR_HOME_URL: URL

        // 服务器统一请求接口路径
        , serverUrl: origin + "/ueditor/server"
    };
    // ...
    // ...
    function getOrigin() {
        if (typeof location.origin === 'undefined') {
            location.origin = location.protocol + '//' + location.host;
        }
        return location.origin;
    }

    window.UE = {
        getUEBasePath: getUEBasePath,
        getOrigin: getOrigin
    };
})();

其中getOrigin()原始文件是没有的,这个用来解决原来的函数获取基本路径不准确的问题。

以上配置指定了/ueditor/serve,那么文件上传时都会POST到这个地址。所以后端需要处理文件上传的逻辑,原本下载包已经提供了相关的实现,不过为了真正可用,需要做一些改造,比如需要验证:

// 抽象类,Upload, 处理文件上传逻辑,有上个子类:UploadCatch.php 、 UploadFile.php 、 UploadScrawl.php
// 分别实现fire()方法,这个方法实际是读取配置,处理上传逻辑
<?php
namespace UEditor;

abstract class Upload
{
    //文件域名
    protected $fileField;
    //文件上传对象
    protected $file;
    //文件上传对象
    protected $base64;
    //配置信息
    protected $config;
    //原始文件名
    protected $oriName;
    //新文件名
    protected $fileName;
    //完整文件名,即从当前配置目录开始的URL
    protected $fullName;
    //完整文件名,即从当前配置目录开始的URL
    protected $filePath;
    //文件大小
    protected $fileSize;
    //文件类型
    protected $fileType;
    //上传状态信息,
    protected $stateInfo;
    //上传状态映射表,国际化用户需考虑此处数据的国际化
    protected $stateMap = array(
        //上传成功标记,在UEditor中内不可改变,否则flash判断会出错
        "SUCCESS",
        "文件大小超出 upload_max_filesize 限制",
        "文件大小超出 MAX_FILE_SIZE 限制",
        "文件未被完整上传",
        "没有文件被上传",
        "上传文件为空",
        "ERROR_TMP_FILE" => "临时文件错误",
        "ERROR_TMP_FILE_NOT_FOUND" => "找不到临时文件",
        "ERROR_SIZE_EXCEED" => "文件大小超出网站限制",
        "ERROR_TYPE_NOT_ALLOWED" => "文件类型不允许",
        "ERROR_CREATE_DIR" => "目录创建失败",
        "ERROR_DIR_NOT_WRITEABLE" => "目录没有写权限",
        "ERROR_FILE_MOVE" => "文件保存时出错",
        "ERROR_FILE_NOT_FOUND" => "找不到上传文件",
        "ERROR_WRITE_CONTENT" => "写入文件内容错误",
        "ERROR_UNKNOWN" => "未知错误",
        "ERROR_DEAD_LINK" => "链接不可用",
        "ERROR_HTTP_LINK" => "链接不是http链接",
        "ERROR_HTTP_CONTENTTYPE" => "链接contentType不正确",
        "INVALID_URL" => "非法 URL",
        "INVALID_IP" => "非法 IP"
    );

    public function __construct(array $config, $request)
    {
        $this->config = $config;
        $this->request = $request;
        $this->fileField = $this->config['fieldName'];
        if (isset($config['allowFiles'])) {
            $this->allowFiles = $config['allowFiles'];
        } else {
            $this->allowFiles = [];
        }
    }

    abstract function fire();

    public function handle()
    {
        $this->fire();
        return $this->getFileInfo();
    }

    /**
     * 上传错误检查
     * @param $errCode
     * @return string
     */
    protected function getStateInfo($errCode)
    {
        return !$this->stateMap[$errCode] ? $this->stateMap["ERROR_UNKNOWN"] : $this->stateMap[$errCode];
    }

    /**
     * 文件大小检测
     * @return bool
     */
    protected function checkSize()
    {
        return $this->fileSize <= ($this->config["maxSize"]);
    }

    /**
     * 获取文件扩展名
     * @return string
     */
    protected function getFileExt()
    {
        return '.' . $this->file->guessExtension();
    }

    /**
     * 重命名文件
     * @return string
     */
    protected function getFullName()
    {
        //替换日期事件
        $t = time();
        $d = explode('-', date("Y-y-m-d-H-i-s"));
        $format = $this->config["pathFormat"];
        $format = str_replace("{yyyy}", $d[0], $format);
        $format = str_replace("{yy}", $d[1], $format);
        $format = str_replace("{mm}", $d[2], $format);
        $format = str_replace("{dd}", $d[3], $format);
        $format = str_replace("{hh}", $d[4], $format);
        $format = str_replace("{ii}", $d[5], $format);
        $format = str_replace("{ss}", $d[6], $format);
        $format = str_replace("{time}", $t, $format);

        //过滤文件名的非法自负,并替换文件名
        $oriName = substr($this->oriName, 0, strrpos($this->oriName, '.'));
        $oriName = preg_replace("/[\|\?\"\<\>\/\*\\\\]+/", '', $oriName);
        $format = str_replace("{filename}", $oriName, $format);

        //替换随机字符串
        $randNum = rand(1, 10000000000) . rand(1, 10000000000);
        if (preg_match("/\{rand\:([\d]*)\}/i", $format, $matches)) {
            $format = preg_replace("/\{rand\:[\d]*\}/i", substr($randNum, 0, $matches[1]), $format);
        }

        $ext = $this->getFileExt();
        return $format . $ext;
    }

    /**
     * 获取文件完整路径
     * @return string
     */
    protected function getFilePath()
    {
        $fullName = $this->fullName;
        $rootPath = public_path();
        $fullName = ltrim($fullName, '/');

        return $rootPath . '/' . $fullName;
    }

    /**
     * 文件类型检测
     * @return bool
     */
    protected function checkType()
    {
        return in_array($this->getFileExt(), $this->config["allowFiles"]);
    }

    /**
     * 获取当前上传成功文件的各项信息
     * @return array
     */
    public function getFileInfo()
    {
        return array(
            "state" => $this->stateInfo,
            "url" => $this->fullName,
            "title" => $this->fileName,
            "original" => $this->oriName,
            "type" => $this->fileType,
            "size" => $this->fileSize
        );
    }
}


// 文件上传
class UploadFile extends Upload
{
    public function fire()
    {
        $file = $this->request->file($this->fileField);
        if (empty($file)) {
            $this->stateInfo = $this->getStateInfo("ERROR_FILE_NOT_FOUND");
            return false;
        }
        if (!$file->isValid()) {
            $this->stateInfo = $this->getStateInfo($file->getError());
            return false;
        }

        $this->file = $file;
        $this->oriName = $this->file->getClientOriginalName();
        $this->fileSize = $this->file->getSize();
        $this->fileType = $this->getFileExt();
        $this->fullName = $this->getFullName();
        $this->filePath = $this->getFilePath();
        $this->fileName = basename($this->filePath);

        //检查文件大小是否超出限制
        if (!$this->checkSize()) {
            $this->stateInfo = $this->getStateInfo("ERROR_SIZE_EXCEED");
            return false;
        }
        //检查是否不允许的文件格式
        if (!$this->checkType()) {
            $this->stateInfo = $this->getStateInfo("ERROR_TYPE_NOT_ALLOWED");
            return false;
        }

        if (config('ueditor.drivers.default') == 'local') {
            try {
                $this->file->move(dirname($this->filePath), $this->fileName);
                $this->stateInfo = $this->stateMap[0];
            } catch (\Symfony\Component\HttpFoundation\File\Exception\FileException $exception) {
                $this->stateInfo = $this->getStateInfo("ERROR_WRITE_CONTENT");
                return false;
            }
        } else {
            $this->stateInfo = $this->getStateInfo("ERROR_UNKNOWN");
            return false;
        }
        return true;
    }
}

要真正可用,还需要结合具体使用的框架做配置,比如在Laravel框架中,经过一些改造,就可以继承到项目中,如下是实际使用部署:
ueditor.zip

至于使用,则非常简单,需要编辑器的地方引入:

<script type="text/javascript" charset="utf-8" src="/ueditor/ueditor.config.js"></script>
<script type="text/javascript" charset="utf-8" src="/ueditor/ueditor.all.min.js"> </script>
<script type="text/javascript" charset="utf-8" src="/ueditor/lang/zh-cn/zh-cn.js"></script>

然后插入类似如下模板:

<div>
    <script id="editor" type="text/plain" style="width:1024px;height:500px;"></script>
</div>

<script type="text/javascript">

    //实例化编辑器
    //建议使用工厂方法getEditor创建和引用编辑器实例,如果在某个闭包下引用该编辑器,直接调用UE.getEditor('editor')就能拿到相关的实例
    var ue = UE.getEditor('editor');
</script>

具体的配置和使用,可以参考:http://fex.baidu.com/ueditor/

HTML编辑器 – KindEditor 基本使用

kindeditor

下载:http://www.kindsoft.net/down.php

文件说明:

asp		ASP程序
asp.net		ASP.NET程序
php		PHP程序
JSP		JSP程序
examples	例子

attached		上传的附件
plugins			插件(js)
themes			皮肤文件
kindeditor.js		编辑器主文件
kindeditor-min.js	编辑器主文件(压缩)
kindeditor-all.js	包含了插件
kindeditor-all-min.js	包含了插件(压缩
)

最简单的例子:

<!doctype html>
<html>
<head>
	<meta charset="utf-8" />
	<title>KindEditor</title>
	<script charset="utf-8" src="kindeditor.js"></script>
	<script>
	        KindEditor.ready(function(K) {
	                window.editor = K.create('#editor_id');
	        });
	</script>
</head>
<body>
<textarea id="editor_id" name="content" style="width:700px;height:300px;">
&lt;strong&gt;HTML内容&lt;/strong&gt;
</textarea>
</body>
</html>

只需要引入kindeditor.js文件,然后运行一个初始化脚本就可以。默认的样式是取themes下的default,语言取lang下的zh_CN.js,一般如果需要使用不同的themes和不一样的语言,就需要手动引入。Kindeditor提供了很多插件,放入到plugins中,如果需要使用,也要手动引入。

初始化放入textarea中内容需要装换HTML特殊字符(>,<,&,”),PHP中可以使用htmlspecialchars进行装换。

通过指定K.create()的第二参数可以对编辑器进行配置:

var options = {
        cssPath : '/css/index.css',
        filterMode : true
};
var editor = K.create('textarea[name="content"]', options);

必须认清一个事实,Kindeditor的编辑器是在一个iframe中完成可视化操作的,意思是编辑的内容实际并没有放入到textarea中,获取数据:

// 取得HTML内容
html = editor.html();

// 同步数据后可以直接取得textarea的value
editor.sync();
html = document.getElementById('editor_id').value; // 原生API
html = K('#editor_id').val(); // KindEditor Node API
html = $('#editor_id').val(); // jQuery

// 设置HTML内容
editor.html('HTML内容');

这里可以直接使用editor那是因为初始化通过K.create()赋值给了window.editor,可以直接调用editor.html()获取内容,也可以通过调用editor.sync()后(同步内容到textarea),直接去取textarea的值。

KindEditor在默认情况下自动寻找textarea所属的form元素,找到form后onsubmit事件里添加sync函数,所以用form方式提交数据,不需要手动执行sync()函数。

KindEditor默认采用白名单过滤方式,可用 htmlTags 参数定义要保留的标签和属性。当然也可以用 filterMode 参数关闭过滤模式,保留所有标签。

// 关闭过滤模式,保留所有标签
KindEditor.options.filterMode = false;

KindEditor.ready(function(K)) {
        K.create('#editor_id');
}

这个的详细配置,参考:http://kindeditor.net/docs/option.html#id67

基本了解这些就可以基本使用这个编辑器了。如果要做更多定制化的操作,需要详细阅读文档了解更多内容。