ASP.NET HTML编辑器控件

分享于 

30分钟阅读

Web开发

  繁體

介绍

如今很多网络应用,如论坛和博客,都使用HTML编辑器作为用户发布的主要工具。 Web编辑器是一种允许在线用户创建和编辑HTML文档的控件。 用户可以查看文档的HTML代码并编辑它,并在设计模式下查看结果,反之亦然。

本文讨论如何创建一个HTML编辑器服务器控件,并考虑一些安全。

Background

四年前我在开发安全产品的公司工作,因这里我们的技术经理为我们的重要客户提供了 MODULE 文本编辑器。 所以我开发了一个HTML编辑器,它考虑了一些重要的安全问题,比如 XSS。 一般不比新版本的文本编辑器更安全,但是我创建一个有用的。用户友好的上面。 我希望这篇文章能帮助你。

系统需求

  • ASP.NET 2 +

用户界面

组件外观:

  • 顶部的三个工具栏行
  • 用于添加元素,按钮等元素的对话框。
  • 显示和/或者编辑任意模式下的文档的编辑器区域

Asp.net HTML Editor component

零部件部件

自定义控件包括两个部分:服务器端和客户端服务器端承担一些任务,例如使用AntiXSS进行安全处理,提供一些任务,例如使用html编辑器发送数据,为用户提供数据,为控件提供 WebResources,为控件提供,为控件提供。

客户端保证从html编辑器中删除一些危险的内容,将它们发送到服务器端并编码它们,并向服务器端发送数据等等。

在客户端有六个JavaScript源文件

  • 是主要的javascript源代码,所有与编辑器功能相关的事情都在这里完成。
  • Encoder.js 提供了所有编码数据的方法
  • ( 如果你想让加载程序对它的进行注释,则该部分已经被注释掉)。
  • Slider.js 提供了一个 slider 来调整编辑器文本区域的大小
  • jscolor.js 是一个开源 javascript,我刚刚使用它

在服务器端有五个类:

  • HtmlSourceInitializer.cs: 初始化编辑器 HTML
  • Registrar.cs: 将脚本列表注册到我们的控件中
  • Security.cs: 使用AntiXSS和HtmlSanitizationLibrary进行安全性的Responsibles
  • SourceActions.cs: 获取从cleint发送的数据
  • RichTextBox.cs: 建立HtmlEditor控件的可以访问方法和属性

EditorStyles。RichTextBoxIcons。Colorpicker Colorpicker are是另外一部分。

HtmlSourceInitializer.cs

组件拥有自己的HTML源,创建HTML编辑器类使用 StringBuilder 将HTML编辑器视图部分添加到 _HtmlSource 中,然后将 _HtmlSource 放入 static RichTextHtmlSource 属性。

internalstaticclass HtmlSourceInitializer
{#region fieldsprivatestatic StringBuilder _HtmlSource = null;#endregion#region getHtmlSourcepublicstatic StringBuilder RichTextHtmlSource
{
 get {
 if (_HtmlSource!= null)
 {
 return _HtmlSource;
 }
 returnnull;
 }
}#endregion#region Initialize html source///<summary>/// Initializes the Html source of editor///</summary>///<paramname="CurrentPage"></param>publicstaticvoid InitializeHtmlSource(Page CurrentPage)
{
 StringBuilder HtmlSource = new StringBuilder();
 HtmlSource.Append("<center id="centerElement" style="display:none">");
...............

Security.cs

为了防止跨站点脚本,我决定使用 AntiXSS 库。 就像你在它的概述中所看到的:

"微软反站点脚本库 V4.2 ( AntiXSS v4.2 ) 是一个编码库,旨在帮助开发人员保护基于web的应用程序免受XSS攻击。 它不同于大多数编码库,它使用白色列表技术,有时称为包含--的原则来防止对--攻击。

这种方法首先定义有效或者允许的字符集,并编码这里集合( 无效字符或者潜在攻击) 之外的任何内容。 白名单方法提供了一些优势,而不是其他编码方案。"

_SetHighLevelSecurityForHtmlTags 字段被设置为 True,这意味着从客户端发送的html文档将在两个层次解码。 例如在某些情况下,你希望得到更安全的HTML代码,并且检查它后,你可以对它进行解码以获得纯HTML源代码。

EncodeHtml 方法对创建的文档 AntiXSS HtmlEncode的字符串HTML源进行编码

方法。同时,GetSafeHtmlFragment 方法还返回标记完整的HTML Fragments。

publicstaticclass Security
{
 ///<summary>/// if this properties is true, in this case will set two steps security over exchanged data/// The default value of this property is true///</summary>internalstaticbool _SetHighLevelSecurityForHtmlTags = true;
 internalstaticstring EncodeHtml(string html)
 {
 return AntiXss.HtmlEncode(Sanitizer.GetSafeHtmlFragment(html));
 }
}

SourceActions.cs

SourceActions 类提供了从客户端发送到 SourceProvider的HTML源代码。

publicclass SourceActions : System.Web.UI.Page
{#region fieldsinternalstring _SourceCode = string.Empty;
 #endregion#region Cunstructorpublic SourceActions()
{//// TODO: Add constructor if it is requiered//}#endregion Cunstructor#region Source provider///<summary>/// provide editor html source code///</summary>///<summary>/// Sets the Current page.///</summary>///<paramname="CurrentPage">The current page.</param>///<returns>void</returns>internalvoid SourceProvider(Page CurrentPage)
{
 CurrentPage.ClientScript.GetPostBackEventReference(CurrentPage, string.Empty);
 if (CurrentPage.IsPostBack)
 {
 string eventTarget = (CurrentPage.Request["__EVENTTARGET"] == null? 
 string.Empty : CurrentPage.Request["__EVENTTARGET"]);
 string eventArgument = (CurrentPage.Request["__EVENTARGUMENT"] == null? 
 string.Empty : CurrentPage.Request["__EVENTARGUMENT"]);
 if (eventTarget == "getHtmlData")
 {
 if (SigmaToolBox.TextEditor.Security._SetHighLevelSecurityForHtmlTags)
 {
 _SourceCode = SigmaToolBox.TextEditor.Security.EncodeHtml(eventArgument);
 }
 else {
 _SourceCode = eventArgument;
 }
 }
}
}#endregion}

RichTextBox.cs

SetValue方法

这里方法将为编辑器设置一些值,如HTML文档。 如果你想使用一些存储在数据库或者它的他资源中的文档,可以使用这里方法将文档插入编辑器。

///<summary>/// This function sets a value to the text editor like some text/// or html data from database or other resources for edition activities///</summary>///<paramname="Value"></param>[Bindable(true)]
[Category("Appearance")]
[DefaultValue("")]
[Localizable(true)]publicvoid SetValue(string Value)
{
 string script = "disableElement(document.getElementById('textToolsContainer'));" + 
 "document.getElementById('textEditor').style.display='none';isOnSourceMode" + 
 "=true;isOndesignMode=false;document.getElementById('sourceTxt')." + 
 "style.display='block';document.getElementById('sourceTxt').value='" + Value + "'";
 Page.ClientScript.RegisterStartupScript(Page.GetType(), 
 "valueSetterScript", script, true);
} 

GetValue方法

这里函数获取在文本编辑器中提供的数据,例如: 在指定的页面中显示,存储在数据库中。

publicstring GetValue()
{
 returnthis.Page.Server.HtmlDecode(AntiXss.HtmlAttributeEncode(GetDecodedValue()));
}

必须修改 OnPreRender 以调用 RegisterStartupScript,如下所示:

protectedoverridevoid OnPreRender(EventArgs e)
{
 base.OnInit(e);
 Registrar.RegisterScripts(new List<string> { "SigmaToolBox.js.loading.js", 
 "SigmaToolBox.js.testJS.js", "SigmaToolBox.js.Encoder.js", 
 "SigmaToolBox.js.slider.js" }, this.Page, this.GetType());
 string InitializerJS = Page.ClientScript.GetWebResourceUrl(this.GetType(), 
 "SigmaToolBox.js.Initializer.js");
 this.Page.ClientScript.RegisterStartupScript(this.GetType(), 
 "RichText", "<script language="""javascript" src='" + 
 InitializerJS + "'></script>");
}

RichText.js

因为HTML编辑器的客户端行为相当广泛,所以客户端控件中需要相当大量的JavaScript。 其中大部分是典型的designMode 客户端编程。

这包括执行某些任务的一些重要功能,如:

  • 编辑器上的命令执行
  • 元素创建
  • 所有其他设计模式活动
  • 为handeling事件生成处理程序

文档被创建并使用这些javascript函数设计,如果有人想在服务器端使用它( 例如: 存储/解码) 应该将它发送到 sendValue(). 发生的server.This 操作

sendValue() 函数将创建的文档发送到服务器,并消除一些有风险的标签,比如 scriptiframejavascript 和文档。 Encoder.js<code>(with htmlEncode() function) 将它发送到服务器之前。 实际上,编码过程有两步,客户端第一步,第二个在服务器端。

//Sends all elements of textEditor to the serverfunction sendValue(){
 var innerHtmlData= usedFrame.innerHTML;
 var clearData=innerHtmlData.toLowerCase().replace(/<script[^>]*?>/g,"");
 var clearData = clearData.replace(/</script>/g, "");
 clearData = clearData.replace(/javascript/g, "");
 clearData = clearData.replace(/script/g, "");
 clearData = clearData.replace(/<iframe[^>]*?>/g, "");
 clearData = clearData.replace(/</iframe>/g, "");
 clearData = Encoder.htmlEncode(clearData);
 __doPostBack('getHtmlData', clearData);
}

在another编辑程序,它可以在当前文档或者给定范围内对所有请求进行响应。例如 execCommand named issues.The and reformat reformat等基本函数。

execCommand方法生成的代码在浏览器中是不同的。 IE 使用HTML标签,Firefox,Google Chrome 和Safari生成 inline 样式和 Opera,有时使用HTML标签,有时。

例如如果'粗体'命令是在非粗体文本上执行的,

如果元素周围存在元素,则 Firefox。Google Chrome 和Safari将这里元素的fontWeight样式属性设置为'粗体'。

如果对粗体文本执行'粗体'命令,浏览器将移除指定的样式属性和/或者包含文本的元素。

我在recognizing粗体or下划线和下划线的情况下遇到问题,以显示粗体,斜体和下划线,这是在其他浏览器中解决它的问题,如 Chrome。opera和 Firefox。 getActiveButtons() 方法按如下方式执行:

function getActiveButtons(){
 isColorPickerInAction=isAdvanceColorPickerInAction=false;
 var isBold=false;var isItalic=false;var isUnderline=false;
 try{
 if(document.all){
 var xmlDocument=StringtoXML(getRangeNode(window.frames["textEditor"]));
 var richTextElementsnodes=xmlDocument.documentElement.getElementsByTagName("nodeName");
 var NodesCount=richTextElementsnodes.length;
 for(var i=0;i<NodesCount;i++){
 var innerNodeValue=richTextElementsnodes[i].firstChild.nodeValue;
 //alert(innerNodeValue)switch(innerNodeValue){
 case"STRONG":
 isBold=true;
 break;
 case"EM":
 isItalic=true;
 break;
 case"U":
 isUnderline=true;
 break;
 }
 }
 }
 else{
 var retrivedData=getRangeNode(textEditorElement.contentWindow);
 for(var i=0;i<retrivedData.length;i++){
 switch(removeSpaces(retrivedData[i])){
 case"font-style:italic":
 isItalic=true;
 break;
 case"text-decoration:underline":
 isUnderline=true;
 break;
 case"font-weight:bold":
 isBold=true;
 break;
 }
 }
 }
 }
 catch(e){
 }
 setActivationStatus(isBold,isItalic,isUnderline);
} 

StringtoXML() 方法将输入字符串( 这是由 getRangeNode() 方法生成的) 转换为 XML:

function StringtoXML(text){
 var doc;
 if (window.ActiveXObject){
 doc=new ActiveXObject('Microsoft.XMLDOM');
 doc.async='false';
 doc.loadXML(text);
 } 
 else{
 parser=new DOMParser();
 doc=parser.parseFromString(text,"text/xml");
 }
 return doc;
} </span>

getRangeNode() 方法获取选定的文本父节点及它的样式以识别选定的文本样式:

function getRangeNode(win){
 var retrivedString="";
 checkCursor(usedFrame);
 if (window.getSelection){
 node = win.getSelection().anchorNode;
 var nodeStyleAttribute=node.parentNode.getAttributeNode("style").nodeValue.toString();
 var nodeStyleAttributeChildes=nodeStyleAttribute.split(";");
 retrivedString=nodeStyleAttributeChildes;
 }
 elseif (win.document.selection){
 var range = win.document.selection.createRange();
 if (range){
 node = range.parentElement();
 nodesList="";
 retrivedString= "<nodesList>"+getParentNodesList(node)+
 "<nodeName>"+node.nodeName+
 "</nodeName>"+"</nodesList>";
 }
 }
 return retrivedString;
} 

语法是:object.execCommand(cmdID, showUI, value)。execCommand功能和兼容性不同,因这里在它的他浏览器中,除 IE 之外,还有"文档保存"或者"background 颜色"。 下面你可以看到,浏览器之间存在很多不兼容的地方,这使得我们的工作变得更加困难,而且看起来比以前更有改进。

Method or propertyExplorer 6/7 Firefox 2Safari 3 Opera 9Method or propertyExplorer 6/7Firefox 2Safari 3 Opera 9Method or propertyExplorer 6/7Firefox 2Safari 3 Opera 9Method or propertyExplorer 6/7Firefox 2Safari 3 Opera 9Method or propertyExplorer 6/7Firefox 2Safari 3 Opera 9Method or propertyExplorer 6/7Firefox 2Safari 3 Opera 9Method or propertyExplorer 6/7Firefox 2Safari 3 Opera 9Method or propertyExplorer 6/7Firefox 2Safari 3 Opera 9
backcolor是的是的是的是的

mdmserver需要 #。to/actio给块级别元素选择是 IE/to选择本身的一部分。
要获得 hilitecolor 中的IE/saf效果,请使用。

粗体是的是的是的是的
便笺
contentReadOnly不是是的不是
IE 给出错误
副本是的protected是的protected
Ctrl+C始终工作
CreateBookmark
便笺
createlink是的是的是的是的
便笺
剪切是的protected是的protected
Ctrl+X始终工作
decreasefontsize不是是的不正确
Op仅允许 1减少;2nd 将文本还原为原始字体大小。
删除是的是的是的是的
便笺
fontname是的是的是的是的
便笺
fontsize不是可怕是的可怕

kp/op生成( 吞咽) <font> 标签,size 等于 parseInt(value)。 Saf创建一个普通的font-size CSS声明。

前景是的是的是的是的
kp/saf要求 #
formatblock

( 在 header 或者段落中放置选定内容)

不是是的buggy不完整
Opera 仅将选定内容中的第一个块级别元素更改为所需的block。
标题不是是的不是不是
便笺
HiliteColor不是是的不是是的

IE/saf中的bgcolor相同: 它只给选择( 而不是包含 block的) 定义定义的cmyk。

increaseFontSize不是是的不正确
Op只允许 1增加;2nd 将文本恢复为原始字体大小。
缩进不正确是的buggy更不正确

rda每缩进增加了 40px 个 margin-left。 IE/op为每个缩进添加一个( 吞咽) blockquote

应用到 <li> 时,IE/生成嵌套的<ol/ul>,但Op再次插入一个 <blockquote>

inserthorizontalrule是的是的是的是的
便笺
inserthtml不是是的不是
便笺
insertimage是的是的是的是的
IE 允许调整图像大小
InsertOrderedList几乎是的几乎是的
如果新创建的有序列表边框边框,IE 和Safari将两者合并。
insertunorderedlist几乎是的是的是的
如果新创建的无序列表与现有列表相关联,IE 将合并。
insertparagraph是的替代是的是的
Mozilla在选定的block 周围添加一个段落。 它的他浏览器 delete 选定的block 并插入用户可以填充的空段落。
italic是的是的是的是的
便笺
justifycenter是的是的是的是的
便笺
justifyfull是的是的是的是的
便笺
justifyleft是的是的是的是的
便笺
justifyright是的是的是的是的
便笺
MultipleSelection
便笺
凸出是的是的buggy是的

当应用于作为单个 <ol/ul> 子级的<li> 时,purequery/IE 将 <li> 移到 <ol/ul> 外,而operator并不React。

不幸的是,在我的测试页面中,IE 有一个额外的Bug: 它将 <li> 移到我的测试元素之外。

覆盖
便笺
粘贴是的protected是的protected
Ctrl+V始终工作
打印
便笺
重做是的是的是的是的
恢复在Safari中工作,但如果你经常撤消/恢复,则会崩溃。 在 3中解决。
如果你在编辑区域做自己的修改,撤销/恢复继续在Mozilla和 Safari (。虽然它忽略你的自定义更改) 中工作,但是 IE 和 Opera 会停止工作。
刷新
便笺
removeformat
便笺
另存为
便笺
selectall
便笺
删除线是的是的是的是的
便笺
styleWithCSS不是是的不是
提供一个通用命令,该命令应使用 CSS ( True ;默认值) 或者标记( false ) 应用样式。 在执行 execCommand("bold") 时,第一个将生成 <span style="font-weight: bold";> 第二个是 <b> 标签。
下标是的是的是的是的
IE/Moz/Op: 再次使用相同的命令删除下标。 在一起使用下标和上标会产生奇怪的效果。
上标是的是的是的是的
使用相同命令的IE/Moz/Op: 再次删除上标。 在一起使用下标和上标会产生奇怪的效果。
unbookmark
便笺
下划线是的是的是的是的
便笺
撤消是的是的是的是的
Undo在Safari中有效,但如果你经常撤消/恢复,则会崩溃。 在 3中解决。
如果你在编辑区域做自己的修改,撤销/恢复继续在Mozilla和 Safari (。虽然它忽略你的自定义更改) 中工作,但是 IE 和 Opera 会停止工作。
解除链接是的是的是的是的
便笺

另一个关键的问题是找到 cursor的最后一点。 这将导致编辑器知道当前execCommand应该影响哪些文本元素。 这个函数主要用于 IE,因为默认情况下 Firefox 和它的他人都有主题自身的能力。 当你观察时,这里任务将由 checkCursor() 方法负责:

//checks the cursor and return the carret Positionfunction checkCursor(where){
 try{
 Current=where;
 if (!isToolBoxContainerDivDisabled)
 {
 where.focus();
 if(document.all){
 CarretPosition=document.selection.createRange();
 if(CarretPosition.text==""){
 where.focus();
 }
 }
 }
 }
 catch(error){
 alert(error.name + ":" + error.message);
 }
} 

CreateEventForGeneratedButton() 是另一个重要的方法,它将指定的事件处理程序分配给自己的按钮,比如 table 创建器。

function CreateEventForGeneratedButton(ActionType){
 try{
 switch(ActionType){
 case"Link":
 (document.all)?SetCommandButton.attachEvent ("onclick",
 SetLinkCommandEventHandler):SetCommandButton.addEventListener (
 "click",SetLinkCommandEventHandler,false);
 break;
 case"Image":
 (document.all)?SetCommandImageButton.attachEvent ("onclick",
 SetImageCommandEventHandler):SetCommandImageButton.addEventListener (
 "click",SetImageCommandEventHandler,false);
 break;
 case"insertTable":
 (document.all)?SetCommandInsertTableButton.attachEvent ("onclick",
 SetInsertTableCommandEventHandler):SetCommandInsertTableButton.addEventListener (
 "click",SetInsertTableCommandEventHandler,false);
 break;
 case"insertButton":
 (document.all)?SetCommandInsertButton_Button.attachEvent ("onclick",
 SetInsertButtonCommandEventHandler): SetCommandInsertButton_Button.addEventListener (
 "click",SetInsertButtonCommandEventHandler,false);
 break;
 case"insertSWF":
 isToolBoxContainerDivDisabled=false;
 (document.all)?SetSWFCommandButton.attachEvent ("onclick",
 SetInsertSWFButtonCommandEventHandler): SetSWFCommandButton.addEventListener (
 "click",SetInsertSWFButtonCommandEventHandler,false);
 break;
 case"uploadSWF":
 (document.all)?SetSWFUploaderCommandButton.attachEvent ("onclick",
 SetSWFUploaderButtonCommandEventHandler): 
 SetSWFUploaderCommandButton.addEventListener (
 "click",SetSWFUploaderButtonCommandEventHandler,false);
 break;
 case"CancelingInsertButtton":
 (document.all)?SetCommandCancelInsertingButton_Button.attachEvent (
 "onclick",SetCancelingInsertButtonCommandEventHandler):
 SetCommandCancelInsertingButton_Button.addEventListener (
 "click",SetCancelingInsertButtonCommandEventHandler,false);
 break;
 }
 }
 catch(error){
 alert(error.name + ":" + error.message);
 }
}

Setposition() 方法设置创建的对象位置。 创建对象时,它需要位于页面上。 当拖动对话框中的对象和 page.For 实例时,这里方法设置对象的位置,它使用这里方法将 related 参数设置为"鼠标",而将该函数与"页面"一起使用,它使用的是 related 参数。

//Sets the position of object makerfunction SetPosition(e,elementName,Related){
 try{
 var ContainerElement=document.getElementById(elementName);
 switch(Related){
 case"Page":
 if(ContainerElement!=null){
 ContainerElement.style.left=(document.body.clientWidth)/2-100 +"px";
 ContainerElement.style.top=(document.body.clientHeight)/2-100 +"px";
 }
 break;
 case"Mouse":
 e=e||window.event;
 document.getElementById(elementName).style.display="block";
 document.getElementById(elementName).style.left=e.clientX+10+"px";
 document.getElementById(elementName).style.top=e.clientY +"px";
 break;
 }
 }
 catch(error){
 alert(error.name + ":" + error.message);
 } } 

但是,我并不基于面向对象编程,而是试图让它清晰有效。

使用这里代码

要使用这里控件,应在 Visual Studio 工具箱中将控件的DLL添加到 RichTextBoxControl_dll 文件夹中,然后将它的拖动到ASPX页上。 你将看到类似 below的内容:

<%@PageLanguage="C#"AutoEventWireup="true"CodeBehind="Default.aspx.cs"Inherits="testCustom._Default"%><%@RegisterAssembly="SigmaToolBox"Namespace="SigmaToolBox.TextEditor"TagPrefix="sigma"%><!DOCTYPEhtmlPUBLIC"-//W3C//DTDXHTML1.0Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><htmlxmlns="http://www.w3.org/1999/xhtml"><headrunat="server"><title>Untitled Page</title></head><bodyid="body1"><%=htmlCode %><formid="form1"runat="server"><div><sigma:RichTextBoxID="RichTextBox1"runat="server"/></div><divid="div1"><asp:ButtonStyle="border-style: groove"ID="Button1"runat="server"OnClientClick="sendValue()"Text="Get Data"OnClick="Button1_Click"/></div></form></body></html>

如果要获取创建的文档,首先应将它与服务器一起发送到服务器,然后在事件集中设置事件,如按钮和编写你的代码,这样就可以获得安全数据:

publicpartialclass _Default : System.Web.UI.Page
{
 publicstring htmlCode = string.Empty;
 protectedvoid Page_Load(object sender, EventArgs e)
 {
 //To DO }
 protectedvoid Button1_Click(object sender, EventArgs e)
 {
 RichTextBox1.SetHighLevelSecurityForHtmlTags = false;
 htmlCode =RichTextBox1.GetValue();
 }
} 

浏览器测试

本文中讨论的控件在 Firefox 5 +。IE 8.Opera。Safari和 Chrome 上进行了测试。

IE 9: IE 9中没有错误,但存在一些缺少它的外观的错误:

  • 两个过多的滚动条
  • 对话框或者文本的behind 处消失

如何使用 Visual Studio 2010或者更低版本打开这里项目

自从上一次使用 Visual Studio 2012来检查它的可靠性时,也许你在打开这个项目时遇到了一些问题,比VS2012更为。 要修复这里问题,请按 below 步骤操作:

  • 使用记事本等文本编辑器打开项目SLN文件。 在第二行中,你将看到此行: Microsoft Visual Studio 解决方案文件,格式版本 12.00. 将版本更改为 11.00或者更低
  • 保存文本并使用 vs 打开项目以享受它 !

结束语

我试图让这个控件成为用户友好的,我相信使用它是如此简单。 entirely我已经开发了自定义控件和基于web的HTML编辑器,我希望这是一个很有价值的经验,但是我希望这个源代码能为你提供一些宝贵的信息。

你的学习将有很多微小的点: 将CSS文件中的图像寻址到自定义控件项目和Javascript源中的大量它的他内容。 希望是有用的。

历史记录

,5,2012

  • 修复了颜色选择器操作的Firefox的onclick javascript错误
  • 在 Chrome 和 Opera 上创建 table 外观,改进了

,年 26月,2012

  • 修复Safari浏览器

,23,2009

  • 已经添加GetValue和SetValue方法

,10,2009

添加的功能:

  • SWF支持
  • 特殊字符支持
  • 预览支持

相关文章