PDA

View Full Version : Ext.ux.form.HtmlEditor.Plugins



VinylFox
22 Jun 2009, 1:55 PM
This is a set of plugins that add to the HTML editing functionality of the HtmlEditor Component. There is also a base class thats easily extended or configured to add standard Midas commands that don't require a 3rd argument.

http://www.vinylfox.com/wp-content/uploads/2009/06/htmleditor-plugin-set.png

Currently, these are the plugins that have been created for this set:


SpecialCharacters (Character Map)
WordPaste
Divider
Table
HR
IndentOutdent
SubSuperScript
RemoveFormat
MidasCommand
Heading
Font (experimental)


For more info, see the blog post(s) about it.
http://www.vinylfox.com/plugin-set-for-additional-extjs-htmleditor-buttons/
http://www.vinylfox.com/plugin-set-for-htmleditor-gets-a-charactermap/
All the code can be found on Github. Also check out the README on Github.
http://github.com/VinylFox/ExtJS.ux.HtmlEditor.Plugins
Limited mirroring of updates on Google Code's SVN.
http://code.google.com/p/ext-ux-htmleditor-plugins/

Enjoy! and please feel free to suggest changes and fixes here.

mprice
22 Jun 2009, 6:06 PM
Shea - this is great! I was also looking at TinyMCE as a more robust solution than the out-of-the-box html editor, but your plugins meet most of the needs. Love the MidasCommand "base class" concept. I pulled down the .js files and dropped them in to my 2.2.1 test area and included them in the dynamic form example per your instructions on your blog. But I got some strange results when loading up in FF. The editor appears to duplicate:

14481

I included the .js files in examples/form/dynamic.html as follows:



<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title>Forms</title>
<link rel="stylesheet" type="text/css" href="../../resources/css/ext-all.css"/>

<!-- GC -->
<!-- LIBS -->
<script type="text/javascript" src="../../adapter/ext/ext-base.js"></script>
<!-- ENDLIBS -->

<script type="text/javascript" src="../../ext-all.js"></script>

<script type="text/javascript" src="states.js"></script>
<script type="text/javascript" src="dynamic.js"></script>
<link rel="stylesheet" type="text/css" href="forms.css"/>

<!-- Common Styles for the examples -->
<link rel="stylesheet" type="text/css" href="../shared/examples.css"/>

<script type="text/javascript" src="plugins/Ext.ux.form.HtmlEditor.MidasCommand.js"></script>
<script type="text/javascript" src="plugins/Ext.ux.form.HtmlEditor.Divider.js"></script>
<script type="text/javascript" src="plugins/Ext.ux.form.HtmlEditor.HR.js"></script>
<script type="text/javascript" src="plugins/Ext.ux.form.HtmlEditor.IndentOutdent.js"></script>
<script type="text/javascript" src="plugins/Ext.ux.form.HtmlEditor.RemoveFormat.js"></script>
<script type="text/javascript" src="plugins/Ext.ux.form.HtmlEditor.SubSuperScript.js"></script>
<script type="text/javascript" src="plugins/Ext.ux.form.HtmlEditor.Table.js"></script>
<script type="text/javascript" src="plugins/Ext.ux.form.HtmlEditor.Word.js"></script>

</head>


and added the plugins to the examples/form/dynamic.js file as follows:



xtype:'htmleditor',
id:'bio',
fieldLabel:'Biography',
height:200,
anchor:'98%',
plugins: [
new Ext.ux.form.HtmlEditor.Divider(),
new Ext.ux.form.HtmlEditor.Word(),
new Ext.ux.form.HtmlEditor.Table(),
new Ext.ux.form.HtmlEditor.HR(),
new Ext.ux.form.HtmlEditor.IndentOutdent(),
new Ext.ux.form.HtmlEditor.SubSuperScript(),
new Ext.ux.form.HtmlEditor.RemoveFormat()
]

VinylFox
22 Jun 2009, 6:18 PM
... pulled down the .js files and dropped them in to my 2.2.1 test area ...

These were developed on the 3.x branch.

If I remember right, 2.2.1 does not have Ext.isObject, which is used in my code. That is one area these plugins will fail at. Not sure what else might be specific to 3.x.

mprice
22 Jun 2009, 6:38 PM
Gotcha. Ran in 3.0 RC2, and only had one issue with the following line in the Table and HR classes:



animateTarget: btn.getEl(),


Firebug says:



btn is undefined
handler()()
DomHelper()(Object browserEvent=Event blur button=-1 type=blur)
A()
animateTarget: btn.getEl(),


If I commented this line out, worked just fine.

VinylFox
22 Jun 2009, 7:03 PM
...If I commented this line out, worked just fine.

I didn't really like the window animation anyway, so I just removed that from my code and committed. Not sure why it would fail like that though. Thanks for the testing and feedback. Let me know if you find anything else.

VinylFox
13 Jul 2009, 3:28 AM
I have added a character map button (http://www.vinylfox.com/plugin-set-for-htmleditor-gets-a-charactermap/), which pops up a window allowing you to select special characters (http://www.vinylfox.com/plugin-set-for-htmleditor-gets-a-charactermap/) from a view.

http://content.screencast.com/users/VinylFox/folders/Jing/media/79425b8f-788a-4360-9abb-9ffa48b30304/2009-07-13_0727.png (http://www.vinylfox.com/plugin-set-for-htmleditor-gets-a-charactermap/)

Updated the google code SVN with this new code.

irina
13 Jul 2009, 6:54 AM
Hi,
I got this plugin form SVN, but there are only .js files. It works for me but there are no icons. How could I get images for buttons?
Thank you very much,
Irina

VinylFox
13 Jul 2009, 7:06 AM
The icons are up to you to implement, I typically snag a few I like from the many open source icon sets that are out there...

http://www.famfamfam.com/
http://code.google.com/p/fugue-icons/
http://www.everaldo.com/crystal/

Enjoy

irina
13 Jul 2009, 7:08 AM
Ok, got it, thanks a lot for good icons resources.

VinylFox
13 Jul 2009, 7:16 AM
Also, I should mention that the 'style.css' file that is up on the code repo has some examples. The styles basically use the midas command name as the css name with '.x-edit-' pre-pended to it. For example:


.x-edit-outdent {background: url(../images/edit-outdent.png) 0 0 no-repeat !important;}
.x-edit-subscript {background: url(../images/text_sub.png) 0 0 no-repeat !important;}
.x-edit-superscript {background: url(../images/text_super.png) 0 0 no-repeat !important;}
.x-edit-removeFormat {background: url(../images/exclamation.png) 0 0 no-repeat !important;}

In that first style 'outdent' is the midas command, so that button will use '.x-edit-outdent' as the style name. Etc.

galdaka
13 Jul 2009, 11:10 AM
Hi,

Good work!! Is posible live example?

Greetings,

imm
15 Jul 2009, 8:38 AM
Hi.
A little bug report. =)
Cancel button not working in plugin "HR" and "SpecialCharacters".

VinylFox
15 Jul 2009, 9:00 AM
Hi.
A little bug report. =)
Cancel button not working in plugin "HR" and "SpecialCharacters".

Thanks man, good catch.

Fixed.

http://code.google.com/p/ext-ux-htmleditor-plugins/source/detail?r=5

imm
15 Jul 2009, 12:39 PM
I wrote some code that upload and insert picture in editor.
My employer uses a web browser Google Chrome. But it works wrong with the method insertAtCursor(). Google Chrome insert
"&lt;img src='...' />" instead
"<img src='...' />". Other browsers works fine. How can i fix it?

VinylFox
15 Jul 2009, 1:00 PM
...web browser Google Chrome. But it works wrong with the method insertAtCursor() .... Other browsers works fine. How can i fix it?

I see - it appears that Google Chome htmlencodes the inserted value. Ill take a look into this and let you know what I find.

imm
16 Jul 2009, 3:25 AM
I found a temporary solution.
If replace

// file: src/widgets/form/HtmlEditor.js
insertAtCursor : function(text){
if(!this.activated){
return;
}
if(Ext.isIE){
this.win.focus();
var r = this.doc.selection.createRange();
if(r){
r.collapse(true);
r.pasteHTML(text);
this.syncValue();
this.deferFocus();
}
}else if(Ext.isGecko || Ext.isOpera){
this.win.focus();
this.execCmd('InsertHTML', text);
this.deferFocus();
}else if(Ext.isWebKit){
this.execCmd('InsertText', text);
this.deferFocus();
}
},to
// file: src/widgets/form/HtmlEditor.js
insertAtCursor : function(text){
if(!this.activated){
return;
}
if(Ext.isIE){
this.win.focus();
var r = this.doc.selection.createRange();
if(r){
r.collapse(true);
r.pasteHTML(text);
this.syncValue();
this.deferFocus();
}
}else if(Ext.isGecko || Ext.isOpera || Ext.isChrome){
this.win.focus();
this.execCmd('InsertHTML', text);
this.deferFocus();
}else if(Ext.isWebKit){
this.execCmd('InsertText', text);
this.deferFocus();
}
}, then everything works.
Maybe if Ext.isWebKit == true need to write 'InsertHTML' instead 'InsertText'?
What do you think?

imm
16 Jul 2009, 3:36 AM
And I have another stupid question. Why resize of the picture works in Firefox and IE, but does not work in Opera and Google Chrome?
http://extjs.com/forum/attachment.php?attachmentid=15047&stc=1&d=1247743986

VinylFox
16 Jul 2009, 8:34 AM
I found a temporary solution....

FYI: I posted this as a bug, so we will wait to see what the consensus is.

http://extjs.com/forum/showthread.php?p=359333#post359333

zombeerose
24 Aug 2009, 2:54 PM
Have you tested your Word plugin against M$ Office 2007? It seems to be missing some regex's to clean up the M$ trash.

VinylFox
24 Aug 2009, 3:04 PM
Have you tested your Word plugin against M$ Office 2007? It seems to be missing some regex's to clean up the M$ trash.

If you let me know exactly what 'trash' remains, I will gladly add it to the plugins cleanup code.

zombeerose
24 Aug 2009, 3:08 PM
Here is a super simple sample of a 2007 file - run it thru your regex...



<meta http-equiv="Content-Type" content="text/html; charset=utf-8"><meta name="ProgId" content="Word.Document"><meta name="Generator" content="Microsoft Word 12"><meta name="Originator" content="Microsoft Word 12"><link rel="File-List" href="file:///C:%5CDOCUME%7E1%5CP%7E1%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_filelist.xml"><link rel="themeData" href="file:///C:%5CDOCUME%7E1%5CP%7E1%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_themedata.thmx"><link rel="colorSchemeMapping" href="file:///C:%5CDOCUME%7E1%5CP%7E1%5CLOCALS%7E1%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_colorschememapping.xml"><!--[if gte mso 9]><xml>
<w:WordDocument>
<w:View>Normal</w:View>
<w:Zoom>0</w:Zoom>
<w:TrackMoves/>
<w:TrackFormatting/>
<w:PunctuationKerning/>
<w:ValidateAgainstSchemas/>
<w:SaveIfXMLInvalid>false</w:SaveIfXMLInvalid>
<w:IgnoreMixedContent>false</w:IgnoreMixedContent>
<w:AlwaysShowPlaceholderText>false</w:AlwaysShowPlaceholderText>
<w:DoNotPromoteQF/>
<w:LidThemeOther>EN-US</w:LidThemeOther>
<w:LidThemeAsian>X-NONE</w:LidThemeAsian>
<w:LidThemeComplexScript>X-NONE</w:LidThemeComplexScript>
<w:Compatibility>
<w:BreakWrappedTables/>
<w:SnapToGridInCell/>
<w:WrapTextWithPunct/>
<w:UseAsianBreakRules/>
<w:DontGrowAutofit/>
<w:SplitPgBreakAndParaMark/>
<w:DontVertAlignCellWithSp/>
<w:DontBreakConstrainedForcedTables/>
<w:DontVertAlignInTxbx/>
<w:Word11KerningPairs/>
<w:CachedColBalance/>
</w:Compatibility>
<w:BrowserLevel>MicrosoftInternetExplorer4</w:BrowserLevel>
<m:mathPr>
<m:mathFont m:val="Cambria Math"/>
<m:brkBin m:val="before"/>
<m:brkBinSub m:val="--"/>
<m:smallFrac m:val="off"/>
<m:dispDef/>
<m:lMargin m:val="0"/>
<m:rMargin m:val="0"/>
<m:defJc m:val="centerGroup"/>
<m:wrapIndent m:val="1440"/>
<m:intLim m:val="subSup"/>
<m:naryLim m:val="undOvr"/>
</m:mathPr></w:WordDocument>
</xml><![endif]--><!--[if gte mso 9]><xml>
<w:LatentStyles DefLockedState="false" DefUnhideWhenUsed="true"
DefSemiHidden="true" DefQFormat="false" DefPriority="99"
LatentStyleCount="267">
<w:LsdException Locked="false" Priority="0" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Normal"/>
<w:LsdException Locked="false" Priority="9" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="heading 1"/>
<w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 2"/>
<w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 3"/>
<w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 4"/>
<w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 5"/>
<w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 6"/>
<w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 7"/>
<w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 8"/>
<w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 9"/>
<w:LsdException Locked="false" Priority="39" Name="toc 1"/>
<w:LsdException Locked="false" Priority="39" Name="toc 2"/>
<w:LsdException Locked="false" Priority="39" Name="toc 3"/>
<w:LsdException Locked="false" Priority="39" Name="toc 4"/>
<w:LsdException Locked="false" Priority="39" Name="toc 5"/>
<w:LsdException Locked="false" Priority="39" Name="toc 6"/>
<w:LsdException Locked="false" Priority="39" Name="toc 7"/>
<w:LsdException Locked="false" Priority="39" Name="toc 8"/>
<w:LsdException Locked="false" Priority="39" Name="toc 9"/>
<w:LsdException Locked="false" Priority="35" QFormat="true" Name="caption"/>
<w:LsdException Locked="false" Priority="10" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Title"/>
<w:LsdException Locked="false" Priority="1" Name="Default Paragraph Font"/>
<w:LsdException Locked="false" Priority="11" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Subtitle"/>
<w:LsdException Locked="false" Priority="22" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Strong"/>
<w:LsdException Locked="false" Priority="20" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Emphasis"/>
<w:LsdException Locked="false" Priority="59" SemiHidden="false"
UnhideWhenUsed="false" Name="Table Grid"/>
<w:LsdException Locked="false" UnhideWhenUsed="false" Name="Placeholder Text"/>
<w:LsdException Locked="false" Priority="1" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="No Spacing"/>
<w:LsdException Locked="false" Priority="60" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Shading"/>
<w:LsdException Locked="false" Priority="61" SemiHidden="false"
UnhideWhenUsed="false" Name="Light List"/>
<w:LsdException Locked="false" Priority="62" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Grid"/>
<w:LsdException Locked="false" Priority="63" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 1"/>
<w:LsdException Locked="false" Priority="64" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 2"/>
<w:LsdException Locked="false" Priority="65" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 1"/>
<w:LsdException Locked="false" Priority="66" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 2"/>
<w:LsdException Locked="false" Priority="67" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 1"/>
<w:LsdException Locked="false" Priority="68" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 2"/>
<w:LsdException Locked="false" Priority="69" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 3"/>
<w:LsdException Locked="false" Priority="70" SemiHidden="false"
UnhideWhenUsed="false" Name="Dark List"/>
<w:LsdException Locked="false" Priority="71" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Shading"/>
<w:LsdException Locked="false" Priority="72" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful List"/>
<w:LsdException Locked="false" Priority="73" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Grid"/>
<w:LsdException Locked="false" Priority="60" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Shading Accent 1"/>
<w:LsdException Locked="false" Priority="61" SemiHidden="false"
UnhideWhenUsed="false" Name="Light List Accent 1"/>
<w:LsdException Locked="false" Priority="62" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Grid Accent 1"/>
<w:LsdException Locked="false" Priority="63" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 1 Accent 1"/>
<w:LsdException Locked="false" Priority="64" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 2 Accent 1"/>
<w:LsdException Locked="false" Priority="65" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 1 Accent 1"/>
<w:LsdException Locked="false" UnhideWhenUsed="false" Name="Revision"/>
<w:LsdException Locked="false" Priority="34" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="List Paragraph"/>
<w:LsdException Locked="false" Priority="29" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Quote"/>
<w:LsdException Locked="false" Priority="30" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Intense Quote"/>
<w:LsdException Locked="false" Priority="66" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 2 Accent 1"/>
<w:LsdException Locked="false" Priority="67" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 1 Accent 1"/>
<w:LsdException Locked="false" Priority="68" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 2 Accent 1"/>
<w:LsdException Locked="false" Priority="69" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 3 Accent 1"/>
<w:LsdException Locked="false" Priority="70" SemiHidden="false"
UnhideWhenUsed="false" Name="Dark List Accent 1"/>
<w:LsdException Locked="false" Priority="71" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Shading Accent 1"/>
<w:LsdException Locked="false" Priority="72" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful List Accent 1"/>
<w:LsdException Locked="false" Priority="73" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Grid Accent 1"/>
<w:LsdException Locked="false" Priority="60" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Shading Accent 2"/>
<w:LsdException Locked="false" Priority="61" SemiHidden="false"
UnhideWhenUsed="false" Name="Light List Accent 2"/>
<w:LsdException Locked="false" Priority="62" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Grid Accent 2"/>
<w:LsdException Locked="false" Priority="63" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 1 Accent 2"/>
<w:LsdException Locked="false" Priority="64" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 2 Accent 2"/>
<w:LsdException Locked="false" Priority="65" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 1 Accent 2"/>
<w:LsdException Locked="false" Priority="66" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 2 Accent 2"/>
<w:LsdException Locked="false" Priority="67" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 1 Accent 2"/>
<w:LsdException Locked="false" Priority="68" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 2 Accent 2"/>
<w:LsdException Locked="false" Priority="69" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 3 Accent 2"/>
<w:LsdException Locked="false" Priority="70" SemiHidden="false"
UnhideWhenUsed="false" Name="Dark List Accent 2"/>
<w:LsdException Locked="false" Priority="71" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Shading Accent 2"/>
<w:LsdException Locked="false" Priority="72" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful List Accent 2"/>
<w:LsdException Locked="false" Priority="73" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Grid Accent 2"/>
<w:LsdException Locked="false" Priority="60" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Shading Accent 3"/>
<w:LsdException Locked="false" Priority="61" SemiHidden="false"
UnhideWhenUsed="false" Name="Light List Accent 3"/>
<w:LsdException Locked="false" Priority="62" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Grid Accent 3"/>
<w:LsdException Locked="false" Priority="63" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 1 Accent 3"/>
<w:LsdException Locked="false" Priority="64" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 2 Accent 3"/>
<w:LsdException Locked="false" Priority="65" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 1 Accent 3"/>
<w:LsdException Locked="false" Priority="66" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 2 Accent 3"/>
<w:LsdException Locked="false" Priority="67" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 1 Accent 3"/>
<w:LsdException Locked="false" Priority="68" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 2 Accent 3"/>
<w:LsdException Locked="false" Priority="69" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 3 Accent 3"/>
<w:LsdException Locked="false" Priority="70" SemiHidden="false"
UnhideWhenUsed="false" Name="Dark List Accent 3"/>
<w:LsdException Locked="false" Priority="71" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Shading Accent 3"/>
<w:LsdException Locked="false" Priority="72" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful List Accent 3"/>
<w:LsdException Locked="false" Priority="73" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Grid Accent 3"/>
<w:LsdException Locked="false" Priority="60" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Shading Accent 4"/>
<w:LsdException Locked="false" Priority="61" SemiHidden="false"
UnhideWhenUsed="false" Name="Light List Accent 4"/>
<w:LsdException Locked="false" Priority="62" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Grid Accent 4"/>
<w:LsdException Locked="false" Priority="63" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 1 Accent 4"/>
<w:LsdException Locked="false" Priority="64" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 2 Accent 4"/>
<w:LsdException Locked="false" Priority="65" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 1 Accent 4"/>
<w:LsdException Locked="false" Priority="66" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 2 Accent 4"/>
<w:LsdException Locked="false" Priority="67" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 1 Accent 4"/>
<w:LsdException Locked="false" Priority="68" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 2 Accent 4"/>
<w:LsdException Locked="false" Priority="69" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 3 Accent 4"/>
<w:LsdException Locked="false" Priority="70" SemiHidden="false"
UnhideWhenUsed="false" Name="Dark List Accent 4"/>
<w:LsdException Locked="false" Priority="71" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Shading Accent 4"/>
<w:LsdException Locked="false" Priority="72" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful List Accent 4"/>
<w:LsdException Locked="false" Priority="73" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Grid Accent 4"/>
<w:LsdException Locked="false" Priority="60" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Shading Accent 5"/>
<w:LsdException Locked="false" Priority="61" SemiHidden="false"
UnhideWhenUsed="false" Name="Light List Accent 5"/>
<w:LsdException Locked="false" Priority="62" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Grid Accent 5"/>
<w:LsdException Locked="false" Priority="63" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 1 Accent 5"/>
<w:LsdException Locked="false" Priority="64" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 2 Accent 5"/>
<w:LsdException Locked="false" Priority="65" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 1 Accent 5"/>
<w:LsdException Locked="false" Priority="66" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 2 Accent 5"/>
<w:LsdException Locked="false" Priority="67" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 1 Accent 5"/>
<w:LsdException Locked="false" Priority="68" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 2 Accent 5"/>
<w:LsdException Locked="false" Priority="69" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 3 Accent 5"/>
<w:LsdException Locked="false" Priority="70" SemiHidden="false"
UnhideWhenUsed="false" Name="Dark List Accent 5"/>
<w:LsdException Locked="false" Priority="71" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Shading Accent 5"/>
<w:LsdException Locked="false" Priority="72" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful List Accent 5"/>
<w:LsdException Locked="false" Priority="73" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Grid Accent 5"/>
<w:LsdException Locked="false" Priority="60" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Shading Accent 6"/>
<w:LsdException Locked="false" Priority="61" SemiHidden="false"
UnhideWhenUsed="false" Name="Light List Accent 6"/>
<w:LsdException Locked="false" Priority="62" SemiHidden="false"
UnhideWhenUsed="false" Name="Light Grid Accent 6"/>
<w:LsdException Locked="false" Priority="63" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 1 Accent 6"/>
<w:LsdException Locked="false" Priority="64" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Shading 2 Accent 6"/>
<w:LsdException Locked="false" Priority="65" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 1 Accent 6"/>
<w:LsdException Locked="false" Priority="66" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium List 2 Accent 6"/>
<w:LsdException Locked="false" Priority="67" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 1 Accent 6"/>
<w:LsdException Locked="false" Priority="68" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 2 Accent 6"/>
<w:LsdException Locked="false" Priority="69" SemiHidden="false"
UnhideWhenUsed="false" Name="Medium Grid 3 Accent 6"/>
<w:LsdException Locked="false" Priority="70" SemiHidden="false"
UnhideWhenUsed="false" Name="Dark List Accent 6"/>
<w:LsdException Locked="false" Priority="71" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Shading Accent 6"/>
<w:LsdException Locked="false" Priority="72" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful List Accent 6"/>
<w:LsdException Locked="false" Priority="73" SemiHidden="false"
UnhideWhenUsed="false" Name="Colorful Grid Accent 6"/>
<w:LsdException Locked="false" Priority="19" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Subtle Emphasis"/>
<w:LsdException Locked="false" Priority="21" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Intense Emphasis"/>
<w:LsdException Locked="false" Priority="31" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Subtle Reference"/>
<w:LsdException Locked="false" Priority="32" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Intense Reference"/>
<w:LsdException Locked="false" Priority="33" SemiHidden="false"
UnhideWhenUsed="false" QFormat="true" Name="Book Title"/>
<w:LsdException Locked="false" Priority="37" Name="Bibliography"/>
<w:LsdException Locked="false" Priority="39" QFormat="true" Name="TOC Heading"/>
</w:LatentStyles>
</xml><![endif]--><style>
<!--
/* Font Definitions */
@font-face
{font-family:"Cambria Math";
panose-1:2 4 5 3 5 4 6 3 2 4;
mso-font-charset:1;
mso-generic-font-family:roman;
mso-font-format:other;
mso-font-pitch:variable;
mso-font-signature:0 0 0 0 0 0;}
@font-face
{font-family:Calibri;
panose-1:2 15 5 2 2 2 4 3 2 4;
mso-font-charset:0;
mso-generic-font-family:swiss;
mso-font-pitch:variable;
mso-font-signature:-1610611985 1073750139 0 0 159 0;}
/* Style Definitions */
p.MsoNormal, li.MsoNormal, div.MsoNormal
{mso-style-unhide:no;
mso-style-qformat:yes;
mso-style-parent:"";
margin-top:0in;
margin-right:0in;
margin-bottom:10.0pt;
margin-left:0in;
line-height:115%;
mso-pagination:widow-orphan;
font-size:11.0pt;
font-family:"Calibri","sans-serif";
mso-ascii-font-family:Calibri;
mso-ascii-theme-font:minor-latin;
mso-fareast-font-family:Calibri;
mso-fareast-theme-font:minor-latin;
mso-hansi-font-family:Calibri;
mso-hansi-theme-font:minor-latin;
mso-bidi-font-family:"Times New Roman";
mso-bidi-theme-font:minor-bidi;}
.MsoChpDefault
{mso-style-type:export-only;
mso-default-props:yes;
mso-ascii-font-family:Calibri;
mso-ascii-theme-font:minor-latin;
mso-fareast-font-family:Calibri;
mso-fareast-theme-font:minor-latin;
mso-hansi-font-family:Calibri;
mso-hansi-theme-font:minor-latin;
mso-bidi-font-family:"Times New Roman";
mso-bidi-theme-font:minor-bidi;}
.MsoPapDefault
{mso-style-type:export-only;
margin-bottom:10.0pt;
line-height:115%;}
@page Section1
{size:8.5in 11.0in;
margin:1.0in 1.0in 1.0in 1.0in;
mso-header-margin:.5in;
mso-footer-margin:.5in;
mso-paper-source:0;}
div.Section1
{page:Section1;}
-->
</style><!--[if gte mso 10]>
<style>
/* Style Definitions */
table.MsoNormalTable
{mso-style-name:"Table Normal";
mso-tstyle-rowband-size:0;
mso-tstyle-colband-size:0;
mso-style-noshow:yes;
mso-style-priority:99;
mso-style-qformat:yes;
mso-style-parent:"";
mso-padding-alt:0in 5.4pt 0in 5.4pt;
mso-para-margin-top:0in;
mso-para-margin-right:0in;
mso-para-margin-bottom:10.0pt;
mso-para-margin-left:0in;
line-height:115%;
mso-pagination:widow-orphan;
font-size:11.0pt;
font-family:"Calibri","sans-serif";
mso-ascii-font-family:Calibri;
mso-ascii-theme-font:minor-latin;
mso-fareast-font-family:"Times New Roman";
mso-fareast-theme-font:minor-fareast;
mso-hansi-font-family:Calibri;
mso-hansi-theme-font:minor-latin;}
</style>
<![endif]-->

<p class="MsoNormal">WORD 2007</p>

zombeerose
25 Aug 2009, 8:09 AM
After much pain and suffering, here are the main patterns I had to add just to clean up my sample page. Note the order because the new lines have to be removed first otherwise the white-space characters will cause the xml/style patterns to fail.



var removals = [
/&nbsp;/ig,
/\n/g,
/\r/g,
/<xml[^>]*>.*?<\/xml>/ig,
/<style[^>]*>.*?<\/style>/ig,
/<\/?meta[^>]*>/ig,
/<\/?link[^>]*>/ig,
...


This page is awesome -> http://www.regular-expressions.info

zombeerose
25 Aug 2009, 10:04 AM
Ok - here are the patterns I am using for Office 2007 processing...



var removals = [
/&nbsp;/ig,
/[\r\n]/g, /* white-space must be removed first */
/<(xml|style)[^>]*>.*?<\/\1>/ig, /* remove everything between these tags */
/<\/?(meta|link|object|span|font|strong|img)[^>]*>/ig, /* remove specific tags - images are included since they would have local drive paths */
/<\/?[A-Z0-9]*:[A-Z]*[^>]*>/ig, /* remove all tags with a colon */
/(lang|class|type|href|name|title|id|clear)=\"[^\"]*\"/ig, /* attributes with double quoted values */
/(lang|class|type|href|name|title|id|clear)=\'[^\']*\'/ig, /* attributes with single quoted values */
/style=(\'\'|\"\")/ig, /* empty style tags */
/<![\[-].*?-*>/g /* comments */
];


I hope that helps.

VinylFox
26 Aug 2009, 9:05 AM
Thanks for figuring out the patters, that's tough work, so I really appreciate your help.

I am trying to figure out how to handle the paste at this point, which is a pain. There is a 'paste' event that works in IE only, however it does not seem to work when attached to the midas editor, only on textarea, input fields and forms, which the midas edit did not relay events to. So im taking a different approach.

My best bet was to monitor the htmleditor value length, figuring out what changed, and running the word garbage scrubber against that portion of the text. This mostly works, but seems to be temperamental still - please take a look and let me know if you find anything that can be fixed to make it work better.

http://code.google.com/p/ext-ux-htmleditor-plugins/source/detail?r=8

Nom4d3
26 Aug 2009, 12:59 PM
I'm testing on Ext 2.3 and found these issues:
- When you paste from Word XP to Firefox 3.5, the editor brings this text at the begining:
Normal 0 MicrosoftInternetExplorer4

- Sometimes the text don't appear on the original editor after copy from word and insert it. It's happens because the htmlEditor is not activated yet. so i solved this issue with this code:



...
onRender: function() {
this.cmp.getToolbar().add({
iconCls: 'x-edit-wordpaste',
handler: function() {

//Enable and Focus to Editor
this.cmp.onFirstFocus(); //yes... i know... it's an event :P

var wordPaste;
var wordPasteEditor = new Ext.form.HtmlEditor({
...


- The "Removing Formatting" button works differently for each browser. On Chrome it is always enabled. On Opera and IE, the button enables only when you select the text, but sometimes it stays disabled. FF works like a charm... you select the text and the button enables.

- Insert Tables don't work
Ext.data.ArrayStore is not a constructor
[Break on this error] data: this.tableBorderOptions\r\n

- HR don't work
this.hrWindow is undefined
[Break on this error] var frm = this.hrWindow.getComponent('insert-hr').getForm();\r\n

- Subscript, Superscript, Indent, Outdent and Divider work fine.

i'm using this fixes on 2.x, but i still have the troubles above:


//It's necessary to works on 2.x
Ext.isObject = function(v){ return v && typeof v == "object"; }

//Fix a bug of insertAtCursor (fixed on Ext SVN)
Ext.override(Ext.form.HtmlEditor, {
insertAtCursor : function(text){
if(!this.activated){
return;
}
if(Ext.isIE){
this.win.focus();
var r = this.doc.selection.createRange();
if(r){
r.collapse(true);
r.pasteHTML(text);
this.syncValue();
this.deferFocus();
}
}else if(Ext.isGecko || Ext.isOpera || Ext.isWebKit){
this.win.focus();
this.execCmd('InsertHTML', text);
this.deferFocus();
}
}
});

Thanks for this plugin and sorry for my bad English :P

zombeerose
26 Aug 2009, 6:14 PM
@VinylFox - thanks for the ideas. I will keep that in mind. I ended up overriding/extending the cleanHtml method of the html editor and performing the regex replacements during that method. That way, the value will always be stripped regardless if the user is pasting or typing.

VinylFox
27 Aug 2009, 6:55 AM
@zombeerose - are you sure thats what you really want? That will scrub all the html in your htmleditor, including valid tags inserted using the htmleditors buttons.

PS, they are not just ideas, I also updated the code - http://code.google.com/p/ext-ux-html...rce/detail?r=8 - did you see this link?

VinylFox
27 Aug 2009, 7:02 AM
@Nom4d3 - Thank you for your thorough feedback, I will try to address the problems you found, however I don't have any active 2.x projects at this time - only 3.x - so testing with 2.x is a bit of a chore.

rwankar
31 Aug 2009, 2:14 AM
In both browsers the pop-up that prompts for rows and columns for the table shows up as a wide panel fully across the screen with the text boxes invisible. Can these browsers be supported?

Thanks.

VinylFox
3 Sep 2009, 4:44 AM
In both browsers the pop-up that prompts for rows and columns for the table shows up as a wide panel fully across the screen with the text boxes invisible. Can these browsers be supported?

What version of ExtJS are you using? (be specific)

rwankar
3 Sep 2009, 6:08 AM
3.0. Sorry, I didn't realize I had not mentioned it.

feyyaz
7 Sep 2009, 6:45 AM
for 3.x users i would suggest to add/change following code lines inside each plugin to get it work also with the new feature: where out of screen/panel buttons will be put into a menu

here a code example from the 'table' plugin. the option 'tooltip' has been moved from the bottom of the code to the top to make the code-snippet shorter.



...
onRender: function() {
var cmp = this.cmp;
var btn = this.cmp.getToolbar().addButton({
iconCls: 'x-edit-table',
tooltip: {title:'Insert table',text:'Inserts an table into the editor.'},
overflowText:'Insert table',
handler: function() {
...


the config option overflowText is used as the label for the menuentry in the menu for the out of screen/panel buttons.

VinylFox
13 Sep 2009, 6:28 AM
...i would suggest to add/change following code lines inside each plugin to get it work also with the new feature: where out of screen/panel buttons will be put into a menu ... the config option overflowText is used as the label for the menu entry in the menu for the out of screen/panel buttons.

Thanks for the suggestions, I have made these changes and update the Google code repo. I additionally setup validation on both the Table and HR forms.

http://code.google.com/p/ext-ux-htmleditor-plugins/source/detail?r=12

Nom4d3
16 Sep 2009, 8:51 AM
some rules to clear the paste from Word2003 to Firefox 3.5 (i didn't test another Word versions)

/<meta[^>]*>/g, /<link[^>]*>/g, /<style>(.*)<\/style>/g, /<w:[^>]*>(.*)<\/w:[^>]*>/g

before these new rules:

<meta http-equiv="Content-Type" content="text/html; charset=utf-8"><meta content="Word.Document"><meta content="Microsoft Word 10"><meta content="Microsoft Word 10"><link rel="File-List" href="file:///C:%5CDOCUME%7E1%5CADMINI%7E1%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C05%5Cclip_filelist.xml"> <w:worddocument> <w:view>Normal</w:view> <w:zoom>0</w:zoom> <w:compatibility> <w:breakwrappedtables> <w:snaptogridincell> <w:wraptextwithpunct> <w:useasianbreakrules> </w:useasianbreakrules> <w:browserlevel>MicrosoftInternetExplorer4</w:browserlevel> </w:wraptextwithpunct></w:snaptogridincell><style><!-- /* Style Definitions */ p., li., div. {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; mso-pagination:widow-orphan; font-size:12.0pt; font-family:"Times New Roman"; mso-fareast-font-family:"Times New Roman";}h1 {mso-style-next:Normal; margin-top:12.0pt; margin-right:0cm; margin-bottom:3.0pt; margin-left:0cm; mso-pagination:widow-orphan; page-break-after:avoid; mso-outline-level:1; font-size:16.0pt; font-family:Arial; mso-font-kerning:16.0pt;}@page Section1 {size:612.0pt 792.0pt; margin:72.0pt 90.0pt 72.0pt 90.0pt; mso-header-margin:35.4pt; mso-footer-margin:35.4pt; mso-paper-source:0;}div.Section1 {page:Section1;}--></style><style> /* Style Definitions */ table.Table {mso-style-name:"Tabela normal"; mso-tstyle-rowband-size:0; mso-tstyle-colband-size:0; mso-style-noshow:yes; mso-style-parent:""; mso-padding-alt:0cm 5.4pt 0cm 5.4pt; mso-para-margin:0cm; mso-para-margin-bottom:.0001pt; mso-pagination:widow-orphan; font-size:10.0pt; font-family:"Times New Roman";}</style>

<p>test</p><p>test 22 2 2 2 22</p><p>test 3 3 3 3 3</p><p></p><p><b>test</b> 4 44 4 4 4</p><p></p><p></p><h1>test 5 5 5 5 5 5 5 5</h1><p></p><p><b><i><u>test</u></i></b></p><p></p>

</w:breakwrappedtables></w:compatibility></w:worddocument>

After:

<p>test</p><p>test 22 2 2 2 22</p><p>test 3 3 3 3 3</p><p></p><p><b>test</b> 4 44 4 4 4</p><p></p><p></p><h1>test 5 5 5 5 5 5 5 5</h1><p></p><p><b><i><u>test</u></i></b></p><p></p>

Nom4d3
16 Sep 2009, 10:06 AM
i'm testing on Word XP and Word 2007 and no junk for while.
Tested on Opera10, IE8, IE7, Fireforx 3.5 and Chrome 4.

:)

VinylFox
16 Sep 2009, 10:19 AM
Your regexes have been added. Thanks again for all your help.

http://code.google.com/p/ext-ux-htmleditor-plugins/source/detail?r=13
http://code.google.com/p/ext-ux-htmleditor-plugins/source/detail?r=14

Im seeing this meta tag sticking around when I paste from OpenOffice, have any ideas why?


<meta http-equiv="CONTENT-TYPE" content="text/html; charset=utf-8">

aktolman
5 Oct 2009, 12:26 PM
This looks great, but i cant get it to work. Is there a working example anywhere?

VinylFox
5 Oct 2009, 12:33 PM
This looks great, but i cant get it to work. Is there a working example anywhere?

A great way to get help would be to explain what your doing in more detail, how you "cant get it to work", and provide a sample of your relevant code.

aktolman
6 Oct 2009, 12:05 AM
A great way to get help would be to explain what your doing in more detail, how you "cant get it to work", and provide a sample of your relevant code.

I take it thats a no then? If i had wanted help with the code I would have stated the problem.

I just wanted to see a working example before taking the time to fix it as I have some other options. But dont worry, I got it working.

lassaad
6 Oct 2009, 1:32 AM
I want to use this plugin with my Ext 2.1 application. Is't possible ?

VinylFox
6 Oct 2009, 3:32 AM
I take it thats a no then? If i had wanted help with the code I would have stated the problem.

I just wanted to see a working example before taking the time to fix it as I have some other options. But dont worry, I got it working.

good luck getting help with an attitude like that.



I want to use this plugin with my Ext 2.1 application. Is't possible ?

Yes, its possible. Just add this chunk of code somewhere before you use the plugin.


Ext.isObject = function(v){
return v && typeof v == “object”;
}

I believe that will be all you need - let me know how it works.

lassaad
6 Oct 2009, 5:18 AM
It does not work and give me this error:
this.cmp.getDoc() Object doesn't support this property or method

this is portion of my code


Ext.isObject = function(v){
return v && typeof v == "object";
}

var pp= new Ext.Panel({
width: "100%",
id:_id,
closable:true,
iconCls : 'form',
layout: 'form',
tbar:tbarFormulaire,
items:[
{
xtype:"htmleditor",
hideLabel : true,
name:"content",
id:"content"+_id,
anchor:"100%",
autoHeight:true,
allowBlank:true,
plugins: [
new Ext.ux.form.HtmlEditor.Divider(),
new Ext.ux.form.HtmlEditor.Word(),
new Ext.ux.form.HtmlEditor.Table(),
new Ext.ux.form.HtmlEditor.HR(),
new Ext.ux.form.HtmlEditor.IndentOutdent(),
new Ext.ux.form.HtmlEditor.SubSuperScript(),
new Ext.ux.form.HtmlEditor.RemoveFormat()
]
}
]
});

VinylFox
6 Oct 2009, 5:31 AM
Interesting.

Can you add just one plugin at a time to the htmleditor and let me know which one breaks it?

lassaad
6 Oct 2009, 5:57 AM
only this plugins work:

new Ext.ux.form.HtmlEditor.Divider(),
// new Ext.ux.form.HtmlEditor.Word() (crash)
// new Ext.ux.form.HtmlEditor.Table(), (crash)
new Ext.ux.form.HtmlEditor.HR(),
// new Ext.ux.form.HtmlEditor.IndentOutdent(), (crash)
// new Ext.ux.form.HtmlEditor.SubSuperScript(), (crash)
// new Ext.ux.form.HtmlEditor.RemoveFormat() (crash)

VinylFox
6 Oct 2009, 6:10 AM
Are you sure Table crashes with that same error? That would be odd, since it does not use getDoc anywhere.

I guess ill have to setup a 2.1 test environment and see whats going on.

lassaad
6 Oct 2009, 6:33 AM
Yes, when I clic to insert new table, it crashes:
this.tableWindow is undefined


this.tableWindow=new Ext.Window({title:"Insert Table",closeAction:"hide",items:[{itemId:"insert-table",xtype:"form",border:false,plain:true,bodyStyle:"padding: 10px;",labelWidth:60,labelAlign:"right",items:[{xtype:"numberfield",allowBlank:false,allowDecimals:false,fieldLabel:"Rows",name:"row",width:60},{xtype:"numberfield",allowBlank:false,allowDecimals:false,fieldLabel:"Columns",name:"col",width:60},{xtype:"combo",fieldLabel:"Border",name:"border",forceSelection:true,mode:"local",store:new Ext.data.ArrayStore({autoDestroy:true,fields:["spec","val"],data:this.tableBorderOptions}),triggerAction:"all",value:"none",displayField:"val",valueField:"spec",width:90}]}],buttons:[{text:"Insert",handler:function(){var g=this.tableWindow.getComponent("insert-table").getForm();if(g.isValid()){var e=g.findField("border").getValue();var c=[g.findField("row").getValue(),g.findField("col").getValue()];if(c.length==2&&c[0]>0&&c[0]<10&&c[1]>0&&c[1]<10){var f="<table>";for(var h=0;h<c[0];h++){f+="<tr>";for(var d=0;d<c[1];d++){f+="<td width='20%' style='border: "+e+";'>"+h+"-"+d+"</td>"}f+="</tr>"}f+="</table>";this.cmp.insertAtCursor(f)}this.tableWindow.hide()}else{if(!g.findField("row").isValid()){g.findField("row").getEl().frame()}else{if(!g.findField("col").isValid()){g.findField("col").getEl().frame()}}}},scope:this},{text:"Cancel",handler:function(){this.tableWindow.hide()},scope:this}]})

Fabyo
9 Oct 2009, 5:07 AM
You do not have an example online?
I would like to test, but I can not run this plugin

vizcano
29 Oct 2009, 2:13 PM
I can't make it work, the only component i can use is Divider with the rest, firebug says

b is undefined
Ext.DomHelper=function(){var s=null,j=/^...b.stopEvent();this.completeEdit()}}}});\n

Can u help me¿

Stephan Schrade
20 Nov 2009, 2:09 PM
Hi VinylFox,
thanks very much for this very good plugin.
I' ve studied your code and do partial understand it.
But it makes me wonder how you manage to insert the html code via the midascommand.
Where is the necessary <sub></sub> for example?
Can you please briefly explain how this works?

I want to make an additional button to change the color of the selected text.

TIA Stephan

VinylFox
20 Nov 2009, 2:28 PM
Your welcome.

The Mozilla page for the Midas spec has some good info on it - just the basics.

http://www.mozilla.org/editor/midas-spec.html

So in the case of the 'sub' tag, my code for SubSuperScript has the following config:

(line 11 of Ext.ux.form.HtmlEditor.SubSuperScript)

cmd: 'subscript'

This config is read by my MidasCommand code, and passed to the HtmlEditor's relayCmd method:

(line 39 of Ext.ux.form.HtmlEditor.MidasCommand)

this.cmp.relayCmd(b.cmd);

From there, the HtmlEditor calls execCmd, which in turn calls the actual Midas spec method execCommand passing in my 'cmd' config value of 'subscript'.


this.doc.execCommand(cmd, false, value === undefined ? null : value);

The commands are pre-defined by the spec i linked to in the "Supported Commands" section. Anything outside of that set of commands can use the 'inserthtml' command to insert a fragment of html you specify.

Hope that helps.

Ronaldo
21 Nov 2009, 11:37 AM
Hi,

I *love* these plugins! Thanks!

One tiny problem: When I open, close (cancel or insert) and reopen the special characters selection window, I get duplicate characters.
(ext 3.0.3 public release from July 6th, 2009)

Keep going!
Ronaldo

VinylFox
21 Nov 2009, 3:03 PM
Yeah, you should update your copy. This was fixed in revision 19.

http://code.google.com/p/ext-ux-htmleditor-plugins/source/detail?r=19

I have also added an image button, that is used as a base for your own method of choosing images.

http://code.google.com/p/ext-ux-htmleditor-plugins/source/detail?r=20

The 0.2 version on the downloads page does not have these changes yet, they are only in SVN. Ill update the download later this week after some testing.

vizcano
23 Nov 2009, 5:02 AM
I'm still having problems with this plugin, and do not why... Now i don't have this error

b is undefined
Ext.DomHelper=function(){var s=null,j=/^...b.stopEvent();this.completeEdit()}}}});\n

But anyway, nothing works, i've tried with word paste, its shown at the tool bar but it does nothing :(

Please help me, i would like to use this fantastic plugin but i can't

VinylFox
23 Nov 2009, 5:07 AM
...have this error

b is undefined
Ext.DomHelper=function(){var s=null,j=/^...b.stopEvent();this.completeEdit()}}}});\n
...

Use the debug version of Ext to get a useful error message.

vizcano
23 Nov 2009, 7:34 AM
I did it, and now i dont get that error, but the Paste Word functionality doesnt work properly, sometimes it works and sometimes (the major part of the time) it does nothing, it paste the text with all those annoying tags

vizcano
24 Nov 2009, 12:17 AM
I've discovered that the problem i'm stuck on, is on the line 420

if (e.V == e.getKey() && e.ctrlKey && this.wordPasteEnabled)

It works fine on IE but not Firefox, most of the times i got e.ctrlKey as false so the lines inside the if arent executed and the MSWord tags arent removed.

Any ideas to solve this?

Ronaldo
25 Nov 2009, 3:17 AM
Hi,

Here's a first attempt at a find and replace plugin, based on code by Shea Frederick. There are still a few (ahum) issues with it, but I don't have the time to make this perfect right now.

Issues


It's only possible to search in sourceEdit mode, that is, only the content of the textarea content will be searched . You should switch to source edit mode and then use the find functionality.
I've created a (crappy) scrollIntoView method for the textareathat works partially (good enough for me but not for real customers), only tested in FF3.5 and it only works in FF (3.5?). You need to extend/override the htmleditor class. see below.
In order to enable the find button in source edit mode, I had to override the texteditor method 'disableItems'. I've introduced a new toolbar button 'sourceEditEnabled' property, so the htmleditor won't disable this button when in source edit mode (in the 'disableItems' method).
I'm working in 3.0RC2, because of some issues elsewhere in my app. When working with 3.0.3, you need to replace itemId by 'getItemId()'. See code.
I'm using a regexp to search. The window should have a 'is regex' checkbox, and if that's not checked, special regex characters entered as value to find should be escaped. A find/replace and a replace all button would be nice to.
The buttons should be disabled when approprate (If nothings is searched/found yet, the replace button should be disabled).




Ext.form.HtmlEditor.override({
disableItems: function(disabled){
if(this.fontSelect){
this.fontSelect.dom.disabled = disabled;
}
this.tb.items.each(function(item){
// Note: item.itemId should be item.getItemId() in version 3.0.3
if(item.itemId != 'sourceedit' && item.sourceEditEnabled !== true){
item.setDisabled(disabled);
}
});
},
/**
* Simple first attempt to create a method that scrolls the startPos into view
*/
scrollIntoView: function(startPos) {
if(this.sourceEditMode) {
// Count the number of '\n' returns before startPos
var textarea = this.el.dom;

if(Ext.isGecko) {
var scrollPos = textarea.scrollTop;
var lineCount = textarea.value.substring(0,startPos).split(/\r?\n|\r/).length + 1;
var totalLineCount = textarea.value.split(/\r?\n|\r/).length + 1;
textarea.scrollTop = (lineCount>2 ? lineCount-2 : 0) * (textarea.scrollHeight/totalLineCount);
}
}
}
});






/**
* @author Ronald van Raaphorst - Twensoc - Based on code by Shea Frederick - http://www.vinylfox.com
* @class Ext.ux.form.HtmlEditor.FindAndReplace
* @extends Ext.util.Observable
* <p>A plugin that provides search and replace functionality in source edit mode.</p>
*/
Ext.ux.form.HtmlEditor.FindAndReplace = Ext.extend(Ext.util.Observable, {
// private
cmd: 'table',
// private
init: function(cmp){
this.cmp = cmp;
this.cmp.on('render', this.onRender, this);
this.lastSelectionStart=-1;
},
// private
onRender: function(){
var cmp = this.cmp;
var btn = this.cmp.getToolbar().addButton({
iconCls: 'x-edit-findandreplace',
sourceEditEnabled:true,
handler: function(){
if (!this.farWindow){
this.farWindow = new Ext.Window({
title: 'Find and replace',
closeAction: 'hide',
items: [{
itemId: 'findandreplace',
xtype: 'form',
border: false,
plain: true,
bodyStyle: 'padding: 10px;',
labelWidth: 60,
labelAlign: 'right',
items: [{
xtype: 'textfield',
allowBlank: false,
fieldLabel: 'Find',
name: 'find',
width: 200
}, {
xtype: 'textfield',
allowBlank: true,
fieldLabel: 'Replace',
name: 'replace',
width: 200
}]
}],
buttons: [{
text: 'Find',
handler: function(){
var frm = this.farWindow.getComponent('findandreplace').getForm();
if (!frm.isValid()) return;

var findValue = frm.findField('find').getValue();
var replaceValue = frm.findField('replace').getValue();
if(cmp.sourceEditMode) {
// source edit mode
var textarea = cmp.el.dom;
var startPos = textarea.selectionStart===this.lastSelectionStart ? textarea.selectionStart+1 : textarea.selectionStart;
var txt = textarea.value.substring(startPos);

var regexp = new RegExp(findValue);
var r = txt.search(regexp);
if(r==-1) {
return;
}
this.lastSelectionStart = startPos + r;
if(Ext.isGecko) {
textarea.setSelectionRange(this.lastSelectionStart , this.lastSelectionStart + findValue.length);
cmp.scrollIntoView(startPos);
cmp.focus(false, true);
}
return;
}
// design mode
//var doc = this.cmp.getEditorBody();
//var txt = doc.innerHTML;
// Should we search/replace in the source, and push the result back to the design?
},
scope: this
}, {
text: 'Replace',
handler: function(){
var frm = this.farWindow.getComponent('findandreplace').getForm();
if (!frm.isValid()) return;

var findValue = frm.findField('find').getValue();
var replaceValue = frm.findField('replace').getValue();
if(cmp.sourceEditMode) {
var textarea = cmp.el.dom;
var startPos = textarea.selectionStart;
var endPos = textarea.selectionEnd;
var txt = textarea.value;

//cmp.execCmd('delete', null);
//cmp.focus(false, false);
//cmp.insertAtCursor(replaceValue);

if(Ext.isGecko) {
// TODO: Scroll into view
var scrollPosition = textarea.scrollTop;
textarea.value = txt.substring(0,startPos) + replaceValue + txt.substring(endPos);
textarea.setSelectionRange(startPos,startPos + replaceValue.length);
textarea.scrollTop = scrollPosition;
cmp.focus(false, true);
}
return;
}
return;
},
scope: this
}, {
text: 'Close',
handler: function(){
this.farWindow.hide();
},
scope: this
}]
});

}else{
this.farWindow.getEl().frame();
}
this.farWindow.show();
},
scope: this,
tooltip: {
title: 'Find and replace'
},
overflowText: 'Find and replace'
});
}
});


Ronaldo

VinylFox
29 Nov 2009, 7:12 PM
Thanks a bunch Ronaldo, ill fix your code up and add it to my code repo.

langtul
15 Dec 2009, 3:36 AM
I found something strange(or not) when i use the word paste plugin.
in the function below:


findValueDiffAt: function(val){

for (i=0;i<this.curLength;i++){
if (this.lastValue[i] != val[i]){
return i;
}
}

}
It always returns a 'undefined', And i alert the process, it appeared that 'this.lastValue[i]' does not work well. replace it with 'this.lastValue.charAt(i)' may be ok.
I don't know why others can work well.

Also, sometimes paste doe's not work well, When i press 'ctrl + v' on, it doesn't work (not call the method fixWordPaste because not alert what i added) occasionally.

Still thanks a lot for the good tools...

langtul
15 Dec 2009, 3:45 AM
By the way, there is also a problem when i use the plugin of table inserting.


this.tableWindow = new Ext.Window({
title : "Insert Table",
closeAction : "hide",
items : [{
itemId : "insert-table",
xtype : "form",
border : false,
plain : true,

It does not work when i click the button(insert) in the toolbar by using the code above. A window appeared without anything(input box or combo or button) and it is so wide.

After I add the width property, it works well....

I also don't know why!

langtul
15 Dec 2009, 4:09 AM
I think the style of the table would be better.

I changed the table code like below:

var f = "<table style='border-collapse: collapse'>";
for (var h = 0; h < c[0]; h++) {
f += "<tr>";
for (var d = 0; d < c[1]; d++) {
f += "<td width='20%' style='border: "
+ e
+ ";'></td>"
}
f += "</tr>"
}

I think it looks better! [and also delete the code for the rows and cols info]
https://extjs.net/forum/attachment.php?attachmentid=17792&stc=1&d=1260878918
hope it useful.

vizcano
15 Dec 2009, 6:19 AM
Because the Word Paste functionality was only available if you push crtl+v, i've rewritten it to launch a window where you can paste the text.

Here is my code, feel free to commet :)



Ext.ux.form.HtmlEditor.Word = Ext.extend(Ext.util.Observable, {
// private
cmd: 'word',
// private
init: function(cmp){
this.cmp = cmp;
this.cmp.on('render', this.onRender, this);
},
// private
onRender: function(){
var cmp = this.cmp;
var btn = this.cmp.getToolbar().addButton({
iconCls: 'x-edit-wordpaste',
handler: function(){
var wordPasteEditor = new Ext.form.HtmlEditor({
id:'wordpaste',
width: 520,
height: 150
});
if (!this.wordPasteWindow){
this.wordPasteWindow = new Ext.Window({
title: "Pegar texto de Microsoft Word",
closeAction: 'hide',
width: 540,
items: [new Ext.FormPanel({
id: 'insert-word',
labelWidth :1,
labelPad:1,
items: wordPasteEditor
})
],
buttons: [{
text: 'Insertar',
handler: function(){
//var frm= this.wordPasteWindow.formPanel.getForm();
var frm= this.wordPasteWindow.getComponent('insert-word').getForm();
if (frm.isValid()){
this.fixWordPaste();
}else{
frm.findField('wordpaste').getEl().frame();
}
},
scope: this
}, {
text: 'Cancelar',
handler: function(){
this.wordPasteWindow.hide();
},
scope: this
}]
});
}else{
this.wordPasteWindow.getEl().frame();
}
this.wordPasteWindow.on('show', function(){
if(wordPasteEditor.getToolbar())
wordPasteEditor.getToolbar().hide();
});
this.wordPasteWindow.show();
},
scope: this,
tooltip: {
title: 'Pegar desde MS Word'
},
overflowText: 'Pegar desde MS Word'
});
btn.disable();
},
// private
fixWordPaste: function(){
var frm = this.wordPasteWindow.getComponent('insert-word').getForm();
if (frm.isValid()) {
var wordPaste = frm.findField('wordpaste').getValue();

wordPaste = wordPaste.replace(/<\!--[\s\S]*?-->/g, '' ) ;
wordPaste = wordPaste.replace(/<o:p>\s*<\/o:p>/g, '') ;
wordPaste = wordPaste.replace(/<o:p>[\s\S]*?<\/o:p>/g, '&nbsp;') ;
wordPaste = wordPaste.replace( /\s*mso-[^:]+:[^;"]+;?/gi, '' ) ;
wordPaste = wordPaste.replace( /\s*MARGIN: 0(?:cm|in) 0(?:cm|in) 0pt\s*;/gi, '' ) ;
wordPaste = wordPaste.replace( /\s*MARGIN: 0(?:cm|in) 0(?:cm|in) 0pt\s*"/gi, "\"" ) ;
wordPaste = wordPaste.replace( /\s*TEXT-INDENT: 0cm\s*;/gi, '' ) ;
wordPaste = wordPaste.replace( /\s*TEXT-INDENT: 0cm\s*"/gi, "\"" ) ;
wordPaste = wordPaste.replace( /\s*TEXT-ALIGN: [^\s;]+;?"/gi, "\"" ) ;
wordPaste = wordPaste.replace( /\s*PAGE-BREAK-BEFORE: [^\s;]+;?"/gi, "\"" ) ;
wordPaste = wordPaste.replace( /\s*FONT-VARIANT: [^\s;]+;?"/gi, "\"" ) ;
wordPaste = wordPaste.replace( /\s*tab-stops:[^;"]*;?/gi, '' ) ;
wordPaste = wordPaste.replace( /\s*tab-stops:[^"]*/gi, '' ) ;
wordPaste = wordPaste.replace(/<(\w[^>]*) class=([^ |>]*)([^>]*)/gi, "<$1$3") ;
wordPaste = wordPaste.replace( /<STYLE[^>]*>[\s\S]*?<\/STYLE[^>]*>/gi, '' ) ;
wordPaste = wordPaste.replace( /<(?:META|LINK)[^>]*>\s*/gi, '' ) ;
wordPaste = wordPaste.replace( /\s*style="\s*"/gi, '' ) ;
wordPaste = wordPaste.replace( /<SPAN\s*[^>]*>\s*&nbsp;\s*<\/SPAN>/gi, '&nbsp;' ) ;
wordPaste = wordPaste.replace( /<SPAN\s*[^>]*><\/SPAN>/gi, '' ) ;
wordPaste = wordPaste.replace(/<(\w[^>]*) lang=([^ |>]*)([^>]*)/gi, "<$1$3") ;
wordPaste = wordPaste.replace( /<SPAN\s*>([\s\S]*?)<\/SPAN>/gi, '$1' ) ;
wordPaste = wordPaste.replace( /<FONT\s*>([\s\S]*?)<\/FONT>/gi, '$1' ) ;
wordPaste = wordPaste.replace(/<\\?\?xml[^>]*>/gi, '' ) ;
wordPaste = wordPaste.replace( /<w:[^>]*>[\s\S]*?<\/w:[^>]*>/gi, '' ) ;
wordPaste = wordPaste.replace(/<\/?\w+:[^>]*>/gi, '' ) ;
wordPaste = wordPaste.replace( /<(U|I|STRIKE)>&nbsp;<\/\1>/g, '&nbsp;' ) ;
wordPaste = wordPaste.replace( /<H\d>\s*<\/H\d>/gi, '' ) ;
wordPaste = wordPaste.replace( /<(\w+)[^>]*\sstyle="[^"]*DISPLAY\s?:\s?none[\s\S]*?<\/\1>/ig, '' ) ;
wordPaste = wordPaste.replace( /<(\w[^>]*) language=([^ |>]*)([^>]*)/gi, "<$1$3") ;
wordPaste = wordPaste.replace( /<(\w[^>]*) onmouseover="([^\"]*)"([^>]*)/gi, "<$1$3") ;
wordPaste = wordPaste.replace( /<(\w[^>]*) onmouseout="([^\"]*)"([^>]*)/gi, "<$1$3") ;
wordPaste = wordPaste.replace( /<H(\d)([^>]*)>/gi, '<h$1>' ) ;
wordPaste = wordPaste.replace( /<(H\d)><FONT[^>]*>([\s\S]*?)<\/FONT><\/\1>/gi, '<$1>$2<\/$1>' );
wordPaste = wordPaste.replace( /<(H\d)><EM>([\s\S]*?)<\/EM><\/\1>/gi, '<$1>$2<\/$1>' );
wordPaste = wordPaste.replace( /<\/?IMG[^>]*>/gi, '' ) ;
wordPaste = wordPaste.replace( /<\/?PERSONNAME[^>]*>/gi, '' ) ;
wordPaste = wordPaste.replace(/<[pP]><\/[pP]>/g, ""); // delete all empty paragraph tags

this.insertWordPaste(wordPaste);

frm.reset();
this.wordPasteWindow.hide();
}
},

insertWordPaste: function(w){
this.cmp.insertAtCursor(w);
}
});

vizcano
15 Dec 2009, 7:51 AM
I've got a question, i've seen that some features work with Midas Command, and they also work on IE, but i've tried to do Undo/Redo and it doesn't work, could anyone help me?

This is my code



Ext.ux.form.HtmlEditor.UndoRedo = Ext.extend(Ext.ux.form.HtmlEditor.MidasCommand, {
// private
midasBtns: ['|', {
cmd: 'undo',
tooltip: {
title: 'Undo'
},
overflowText: 'Redo'
}, {
cmd: 'redo',
tooltip: {
title: 'Redo'
},
overflowText: 'Redo'
}]
});

VinylFox
15 Dec 2009, 9:30 AM
I've got a question, i've seen that some features work with Midas Command, and they also work on IE, but i've tried to do Undo/Redo and it doesn't work, could anyone help me?...

The MS site says (http://msdn.microsoft.com/en-us/library/ms533049(VS.85).aspx) that Redo is not supported...


Redo
Not currently supported.


However it appears that Undo is supported...


Undo
Undo the previous command.


But I was not able to get it to work in IE 7. Seems like this is another Microsoft 'supported' while unsupported feature.

walldorff
15 Dec 2009, 6:03 PM
Great plugins! Thanks Shea. :)
I changed the code in all of the files a bit, in order to be able to translate the literals (the texts for the titles and buttons etc) into variables. Those variables I added to the top of Ext.ux.form.HtmlEditor.MidasCommand.js; maybe it's a better idea in the future to add a translation file to the package. As an example:
/* button text */
var btnInsertText = 'Invoegen'; // Insert
var btnCancelText = 'Annuleren'; // Cancel

/* tooltips and overflow text */
var HR_ttTitle = 'Horizontale lijn'; // Horizontal Line
var HR_ttText = 'Voeg horizontale lijn in de tekst'; // Insert Horizontal Line in text
var HR_ofText = 'Horizontale lijn'; // Horizontal Line

var Image_ttTitle = 'Afbeelding'; // Image
var Image_ttText = 'Afbeelding in de tekst invoegen'; // Insert image in text
So here it is :)

VinylFox
15 Dec 2009, 6:28 PM
..I changed the code in all of the files a bit, in order to be able to translate the literals (the texts for the titles and buttons etc) into variables...

Cool man! I was actually starting to do that myself, so thank you for your help.

Is that Flemish you've translated to?

walldorff
15 Dec 2009, 6:38 PM
Is that Flemish you've translated to?
Actually it's Dutch. Flemish is the degenerated form of the Dutch lingo ;)

At the moment I'm trying to figure out how to get htmlDecoded text into the editor. Chars with accents like é don't appear as their proper html entities (&eacute; etc).
Have you got any ideas out of the sleeve? :)
Strike that. Must do encoding serverside, right after the post. What was I thinking...

vizcano
15 Dec 2009, 11:41 PM
The MS site says (http://msdn.microsoft.com/en-us/library/ms533049(VS.85).aspx) that Redo is not supported...



However it appears that Undo is supported...



But I was not able to get it to work in IE 7. Seems like this is another Microsoft 'supported' while unsupported feature.


Crappy IE, but anyway, i got Midas Demo http://www.mozilla.org/editor/midasdemo/ running on IE and undo and redo work perfect...

vizcano
16 Dec 2009, 3:04 AM
Finally i've adapted dangreenfield 's plugin for Ext.ux.HTMLEditor http://www.extjs.com/forum/showthread.php?t=19754

The only thing is that i'm not able to implement the keymap, so the keycontrols are missed...

Please take a look and feel free to coment :)



Ext.ux.form.HtmlEditor.UndoRedo =Ext.extend(Ext.util.Observable, {
// private
cmd: 'undoredo',
// IE only: variables
// size parameter limits the rollback history
volume: -1,
history: new Array(),
index: 0,
placeholder: 0,
count: 0,
ignore: false,

// private
init: function(cmp){
this.cmp = cmp;
this.cmp.on('render', this.onRender, this);
if (Ext.isIE) {
this.cmp.on('sync', this.onSync, this);
this.cmp.on('editmodechange', this.onEditmodechange, this);
}

},
// private
onRender: function(){
var cmp = this.cmp;
var btn_undo = this.cmp.getToolbar().addButton({
itemId: 'undo',
iconCls: 'x-edit-undo',
handler: this.undo,
scope: this,
tooltip: {
title: 'Deshacer'
},
overflowText: 'Deshacer'
});
var btn_redo = this.cmp.getToolbar().addButton({
itemId: 'redo',
iconCls: 'x-edit-redo',
handler: this.redo,
scope: this,
tooltip: {
title: 'Rehacer'
},
overflowText: 'Rehacer'
});
if (Ext.isIE) {
/*// monitor for ctrl-z (undo) and ctrl-y (redo) keys
var keyCommands = [{
key: 'z',
ctrlKey: true,
fn: this.undo,
scope: this
}, {
key: 'y',
ctrlKey: true,
fn: this.redo,
scope: this
}];
new Ext.KeyMap(this.cmp.getEditorBody(), keyCommands);*/

// record changed data when in source edit mode
this.cmp.el.on('keyup', this.record, this);
}

},
onSync: function(){
if (this.ignore)
this.ignore = false;
else
this.record();
},
onEditmodechange: function(){
// set a placeholder when source edit mode is selected
if (this.cmp.sourceEditMode) {
this.placeholder = this.index;
}

// else record all changes made in source edit mode as a
// single historic entry.
// note: undo/redo functions continue to work while in
// source edit mode (even when undoing changes made before
// the mode was changed), but those made while in source
// edit mode are no longer available once source edit mode
// is exited as they can appear undesirable or meaningless
// when in normal edit mode, so they are rolled together
// to form a single historic change
else {

// if changes were made while in source edit mode then
if (this.index > this.placeholder) {

// if starting point was lost to history then
if (this.placeholder < 0) {

// record all source edit mode changes as first
// historic record
this.placeholder == 0;
this.history[this.placeholder] = this.history[this.index];
}

// else check to see if data has actually changed
// while in source edit mode then
else if (this.history[this.placeholder].content != this.history[this.index].content) {

// record all source edit mode changes as single
// historic record, to follow last record change
// made in normal edit mode
this.placeholder++;
this.history[this.placeholder] = this.history[this.index];
}

// reset index and count to placeholder
this.index = this.placeholder;
this.count = this.index;
}

// if no changes were made then reset count as it
// may have grown if changes were made and reversed
else {
this.count = this.placeholder;
}

// update the undo/redo buttons on the toolbar
this.updateToolbar();
}
},
// private
undo: function(){
if (Ext.isIE) {
// ensure that there is data to undo
if (this.index > 1) {

// decrement the index
this.index--;

// if in source edit mode then update the element directly
if (this.cmp.sourceEditMode) {
this.resetElement();
}

// else update the editor body
else {
this.reset();

// ignore next record request as syncValue is called
// by Ext.form.HtmlEditor.updateToolBar and we don't
// want our undo reversed again
this.ignore = true;

// update the editor toolbar and return focus
this.cmp.updateToolbar();
this.cmp.deferFocus();
}

// update the undo/redo buttons on the toolbar
this.updateToolbar();
}

}else
this.cmp.relayCmd('undo');
},

redo: function(){
if (Ext.isIE) {
// ensure that there is data to redo
if (this.index < this.count) {

// increment the index
this.index++;

// if in source edit mode then update the element directly
if (this.cmp.sourceEditMode) {
this.resetElement();
}

// else update the editor body
else {
this.reset();

// ignore next record request as syncValue is called
// by Ext.form.HtmlEditor.updateToolBar and we don't
// want our redo reversed again
this.ignore = true;

// update the editor toolbar and return focus
this.cmp.updateToolbar();
this.cmp.deferFocus();
}

// update the undo/redo buttons on the toolbar
this.updateToolbar();
}

}else
this.cmp.relayCmd('redo');
},
// IE only: record changes to data
record: function() {

// get the current html content from the element
var content = this.cmp.el.dom.value;

// if no historic records exist yet or content has
// changed since the last record then
if (this.index == 0 || this.history[this.index].content != content) {

// if size of rollbacks has been reached then drop
// the oldest record from the array
if (this.count == this.volume) {
this.history.shift();
this.placeholder--;
}

// else increment the index
else {
this.index++;
}

// record the changed content and cursor position
this.history[this.index] = {
content: content,
bookmark: this.cmp.doc.selection.createRange().getBookmark()
};
this.count = this.index;
}

// update the undo/redo buttons on the toolbar
this.updateToolbar();
},
// IE only: updates the toolbar buttons
updateToolbar: function() {
this.cmp.tb.items.map.undo.setDisabled(this.index < 2);
this.cmp.tb.items.map.redo.setDisabled(this.index == this.count);
},

// IE only: updates the editor body
reset: function() {
this.cmp.getEditorBody().innerHTML = this.history[this.index].content;
this.resetBookmark();
},
// IE only: updates the element (when in source edit mode)
resetElement: function() {
this.cmp.el.dom.value = this.history[this.index].content;
this.resetBookmark();
},
// IE only: reposition the cursor
resetBookmark: function() {
var range = this.cmp.doc.selection.createRange();
range.moveToBookmark(this.history[this.index].bookmark);
range.select();
}
});

Scorpie
16 Dec 2009, 6:41 AM
Actually it's Dutch. Flemish is the degenerated form of the Dutch lingo ;)

At the moment I'm trying to figure out how to get htmlDecoded text into the editor. Chars with accents like é don't appear as their proper html entities (&eacute; etc).
Have you got any ideas out of the sleeve? :)
Strike that. Must do encoding serverside, right after the post. What was I thinking...

A fellow dutchie, nice to meet you at last, lol :D

langtul
17 Dec 2009, 6:05 AM
I have a problem.

No matter what i tried to insert(a table, a special char or a rule), it always appeared at the beginning of the text, or the first line.

Anybody other got it? or any way to fix? It seemed the function insertAtCursor() doesnot work well...

VinylFox
17 Dec 2009, 6:32 AM
@langtul - your never going to get a response with such a vague report.

Please see this: http://www.extjs.com/forum/showthread.php?t=66423

Ronaldo
17 Dec 2009, 11:40 PM
Actually it's Dutch. Flemish is the degenerated form of the Dutch lingo ;)
ROFL - I think the belgians think otherwise, at least half of them :)

dearsina
22 Dec 2009, 9:48 AM
VinylFox, this plugin was a breeze to install and very, very cool. Thanks for your hard work.

For those of you who are using the famfamfam family of icons, copy and paste the below inot the style.css to make all the icons appear (assuming that you are storing them in /icons):



.x-edit-wordpaste {background: url(icons/page_white_word.png) 0 0 no-repeat !important;}
.x-edit-table {background: url(icons/table.png) 0 0 no-repeat !important;}
.x-edit-hr {background: url(icons/text_horizontalrule.png) 0 0 no-repeat !important;}
.x-edit-indent {background: url(icons/text_indent.png) 0 0 no-repeat !important;}
.x-edit-char {background: url(icons/text_letter_omega.png) 0 0 no-repeat !important;}
.x-edit-outdent {background: url(icons/text_indent_remove.png) 0 0 no-repeat !important;}
.x-edit-subscript {background: url(icons/text_subscript.png) 0 0 no-repeat !important;}
.x-edit-superscript {background: url(icons/text_superscript.png) 0 0 no-repeat !important;}
.x-edit-removeFormat {background: url(icons/exclamation.png) 0 0 no-repeat !important;}

jack sparrow
24 Jan 2010, 4:06 AM
Because the Word Paste functionality was only available if you push crtl+v, i've rewritten it to launch a window where you can paste the text.

Here is my code, feel free to commet :)
........................



Hi Vizcano, I had your same problem, but I solved in another way...more simple I think, so I want to share with you and others guys my solution....feel free to comment TOO ;)

I used "paste" event on EditorBody, so cleaning function will be called anytime a text is pasted in the editor, whether you use CTRL+V or you use mouse right click!!

Tested succesfully with IE7+, FF3+, Chrome.

This is my FULL CODE: (I use the Word Paste functionality automatically in my projects, so I don't have "paste button" in the editor toolbar)



Ext.ux.form.HtmlEditor.Word = Ext.extend(Ext.util.Observable, {

init: function(cmp){

this.cmp = cmp;
this.cmp.on('initialize', this.onInit, this, {delay:100, single: true});
},

onInit: function(){

this.cel = Ext.get(this.cmp.getEditorBody()) ;
this.cel.on('paste', this.checkIfPaste , this, {buffer:100});
},

checkIfPaste: function(e){

var filtered = this.fixWordPaste(this.cmp.getValue());
this.cmp.setValue(filtered);
},

fixWordPaste: function (wordPaste) {

wordPaste = wordPaste.replace(/MsoNormal/gi,"");
wordPaste = wordPaste.replace(/<\/?link[^>]*>/gi,"");
wordPaste = wordPaste.replace(/<\/?meta[^>]*>/gi,"");
wordPaste = wordPaste.replace(/<\/?xml[^>]*>/gi,"");
wordPaste = wordPaste.replace(/<\?xml[^>]*\/>/gi,"");
wordPaste = wordPaste.replace(/<!--(.*)-->/gi, "");
wordPaste = wordPaste.replace(/<!--(.*)>/gi, "");
wordPaste = wordPaste.replace(/<!(.*)-->/gi, "");
wordPaste = wordPaste.replace(/<w:[^>]*>(.*)<\/w:[^>]*>/gi,'');
wordPaste = wordPaste.replace(/<w:[^>]*\/>/gi,'');
wordPaste = wordPaste.replace(/<\/?w:[^>]*>/gi,"");
wordPaste = wordPaste.replace(/<m:[^>]*\/>/gi,'');
wordPaste = wordPaste.replace(/<m:[^>]>(.*)<\/m:[^>]*>/gi,'');
wordPaste = wordPaste.replace(/<o:[^>]*>(.*)<\/o:[^>]*>/gi,'');
wordPaste = wordPaste.replace(/<o:[^>]*\/>/gi,'');
wordPaste = wordPaste.replace(/<\/?m:[^>]*>/gi,"");
wordPaste = wordPaste.replace(/style=\"([^>]*)\"/gi,"");
wordPaste = wordPaste.replace(/style=\'([^>]*)\'/gi,"");
wordPaste = wordPaste.replace(/class=\"(.*)\"/gi,"");
wordPaste = wordPaste.replace(/class=\'(.*)\'/gi,"");
wordPaste = wordPaste.replace(/<p[^>]*>/gi,'<p>');
wordPaste = wordPaste.replace(/<\/p[^>]*>/gi,'<\/p>');
wordPaste = wordPaste.replace(/<span[^>]*>/gi,'');
wordPaste = wordPaste.replace(/<\/span[^>]*>/gi,'');
wordPaste = wordPaste.replace(/<st1:[^>]*>/gi,'');
wordPaste = wordPaste.replace(/<\/st1:[^>]*>/gi,'');
wordPaste = wordPaste.replace(/<font[^>]*>/gi,'');
wordPaste = wordPaste.replace(/<\/font[^>]*>/gi,'');
wordPaste = wordPaste.replace(/[\r\n]/g,' ');
wordPaste = wordPaste.replace(/<wordPasteong><\/wordPasteong>/gi,'');
wordPaste = wordPaste.replace(/<p><\/p>/gi,'');
wordPaste = wordPaste.replace(/\/\*(.*)\*\//gi,'');
wordPaste = wordPaste.replace(/<!--/gi, "");
wordPaste = wordPaste.replace(/-->/gi, "");
wordPaste = wordPaste.replace(/<style[^>]*>[^<]*<\/style[^>]*>/gi,'');
wordPaste = wordPaste.replace(/<hr>/gi,'');

return wordPaste;
}

});

mystix
24 Jan 2010, 10:01 AM
@vizcano / @jack sparrow:
that fixWordPaste() method could use some cleaning up...


fixWordPaste : function(str) {
return (str || '').replace(/MsoNormal/gi,"")
.replace(/<\/?link[^>]*>/gi,"")
.replace(/<\/?meta[^>]*>/gi,"")
.replace(/<\/?xml[^>]*>/gi,"")
.replace(/<\?xml[^>]*\/>/gi,"")
.replace(/<!--(.*)-->/gi, "")
.replace(/<!--(.*)>/gi, "")
.replace(/<!(.*)-->/gi, "")
.replace(/<w:[^>]*>(.*)<\/w:[^>]*>/gi,'')
.replace(/<w:[^>]*\/>/gi,'')
.replace(/<\/?w:[^>]*>/gi,"")
.replace(/<m:[^>]*\/>/gi,'')
.replace(/<m:[^>]>(.*)<\/m:[^>]*>/gi,'')
.replace(/<o:[^>]*>(.*)<\/o:[^>]*>/gi,'')
.replace(/<o:[^>]*\/>/gi,'')
.replace(/<\/?m:[^>]*>/gi,"")
.replace(/style=\"([^>]*)\"/gi,"")
.replace(/style=\'([^>]*)\'/gi,"")
.replace(/class=\"(.*)\"/gi,"")
.replace(/class=\'(.*)\'/gi,"")
.replace(/<p[^>]*>/gi,'<p>')
.replace(/<\/p[^>]*>/gi,'<\/p>')
.replace(/<span[^>]*>/gi,'')
.replace(/<\/span[^>]*>/gi,'')
.replace(/<st1:[^>]*>/gi,'')
.replace(/<\/st1:[^>]*>/gi,'')
.replace(/<font[^>]*>/gi,'')
.replace(/<\/font[^>]*>/gi,'')
.replace(/[\r\n]/g,' ')
.replace(/<wordPasteong><\/wordPasteong>/gi,'')
.replace(/<p><\/p>/gi,'')
.replace(/\/\*(.*)\*\//gi,'')
.replace(/<!--/gi, "")
.replace(/-->/gi, "")
.replace(/<style[^>]*>[^<]*<\/style[^>]*>/gi,'')
.replace(/<hr>/gi,'');
}

langtul
24 Jan 2010, 5:36 PM
It works very well, I just changed some fix patterns.. Thank u very much..


Hi Vizcano, I had your same problem, but I solved in another way...more simple I think, so I want to share with you and others guys my solution....feel free to comment TOO ;)

I used "paste" event on EditorBody, so cleaning function will be called anytime a text is pasted in the editor, whether you use CTRL+V or you use mouse right click!!

Tested succesfully with IE7+, FF3+, Chrome.

This is my FULL CODE: (I use the Word Paste functionality automatically in my projects, so I don't have "paste button" in the editor toolbar)



Ext.ux.form.HtmlEditor.Word = Ext.extend(Ext.util.Observable, {

init: function(cmp){

this.cmp = cmp;
this.cmp.on('initialize', this.onInit, this, {delay:100, single: true});
},

onInit: function(){

this.cel = Ext.get(this.cmp.getEditorBody()) ;
this.cel.on('paste', this.checkIfPaste , this, {buffer:100});
},

checkIfPaste: function(e){

var filtered = this.fixWordPaste(this.cmp.getValue());
this.cmp.setValue(filtered);
},

fixWordPaste: function (wordPaste) {

wordPaste = wordPaste.replace(/MsoNormal/gi,"");
wordPaste = wordPaste.replace(/<\/?link[^>]*>/gi,"");
wordPaste = wordPaste.replace(/<\/?meta[^>]*>/gi,"");
wordPaste = wordPaste.replace(/<\/?xml[^>]*>/gi,"");
wordPaste = wordPaste.replace(/<\?xml[^>]*\/>/gi,"");
wordPaste = wordPaste.replace(/<!--(.*)-->/gi, "");
wordPaste = wordPaste.replace(/<!--(.*)>/gi, "");
wordPaste = wordPaste.replace(/<!(.*)-->/gi, "");
wordPaste = wordPaste.replace(/<w:[^>]*>(.*)<\/w:[^>]*>/gi,'');
wordPaste = wordPaste.replace(/<w:[^>]*\/>/gi,'');
wordPaste = wordPaste.replace(/<\/?w:[^>]*>/gi,"");
wordPaste = wordPaste.replace(/<m:[^>]*\/>/gi,'');
wordPaste = wordPaste.replace(/<m:[^>]>(.*)<\/m:[^>]*>/gi,'');
wordPaste = wordPaste.replace(/<o:[^>]*>(.*)<\/o:[^>]*>/gi,'');
wordPaste = wordPaste.replace(/<o:[^>]*\/>/gi,'');
wordPaste = wordPaste.replace(/<\/?m:[^>]*>/gi,"");
wordPaste = wordPaste.replace(/style=\"([^>]*)\"/gi,"");
wordPaste = wordPaste.replace(/style=\'([^>]*)\'/gi,"");
wordPaste = wordPaste.replace(/class=\"(.*)\"/gi,"");
wordPaste = wordPaste.replace(/class=\'(.*)\'/gi,"");
wordPaste = wordPaste.replace(/<p[^>]*>/gi,'<p>');
wordPaste = wordPaste.replace(/<\/p[^>]*>/gi,'<\/p>');
wordPaste = wordPaste.replace(/<span[^>]*>/gi,'');
wordPaste = wordPaste.replace(/<\/span[^>]*>/gi,'');
wordPaste = wordPaste.replace(/<st1:[^>]*>/gi,'');
wordPaste = wordPaste.replace(/<\/st1:[^>]*>/gi,'');
wordPaste = wordPaste.replace(/<font[^>]*>/gi,'');
wordPaste = wordPaste.replace(/<\/font[^>]*>/gi,'');
wordPaste = wordPaste.replace(/[\r\n]/g,' ');
wordPaste = wordPaste.replace(/<wordPasteong><\/wordPasteong>/gi,'');
wordPaste = wordPaste.replace(/<p><\/p>/gi,'');
wordPaste = wordPaste.replace(/\/\*(.*)\*\//gi,'');
wordPaste = wordPaste.replace(/<!--/gi, "");
wordPaste = wordPaste.replace(/-->/gi, "");
wordPaste = wordPaste.replace(/<style[^>]*>[^<]*<\/style[^>]*>/gi,'');
wordPaste = wordPaste.replace(/<hr>/gi,'');

return wordPaste;
}

});

mystix
25 Jan 2010, 7:37 AM
It works very well, I just changed some fix patterns.. Thank u very much..

perhaps you could list the changes you made so everyone else can benefit from them? thanks.

langtul
25 Jan 2010, 7:40 AM
I just changed the <p> tag to <br />. that's all. I do believe that only i may use it in this way and it is not necessary to list it...hehe:)

perhaps you could list the changes you made so everyone else can benefit from them? thanks.

ivanatora
5 Feb 2010, 6:30 AM
Is there such functionality to replace the Increase/decrease font size with a fontsize combo box to directly select the size?
I see there is such midas command 'fontsize' and I'm willing to write myself a plugin, but how can I add parameter to that command trough the MidasCommand class?

meisong
7 Feb 2010, 10:09 PM
very nice!

mschwartz
9 Feb 2010, 9:26 AM
Weirdness with 3.1.1.

I replaced .Plugins-0.2-all.js with .Plugins-0.2-all-debug.js and the weirdness goes away.

Might want to test this, VF.

Weirdness == Firebug generating errors in the console like:

a is null line 4
doc is null inside ext-all-debug line 62324

&c

moegal
22 Feb 2010, 10:17 AM
I am getting an error when I try to insert a special character.

"this.charWindow.charView is undefined"

The error is on this line

Ext.each(this.charWindow.charView.getSelectedRecords(), function(rec){


Any ideas?

Using 3.x

Thanks, Marty

mschwartz
23 Feb 2010, 6:02 AM
Weirdness with 3.1.1.

I replaced .Plugins-0.2-all.js with .Plugins-0.2-all-debug.js and the weirdness goes away.

Might want to test this, VF.

Weirdness == Firebug generating errors in the console like:

a is null line 4
doc is null inside ext-all-debug line 62324

&c

My bad, the errors occur with the debug version, too.

moegal
23 Feb 2010, 6:18 AM
I am getting an error when I try to insert a special character.

"this.charWindow.charView is undefined"

The error is on this line

Ext.each(this.charWindow.charView.getSelectedRecords(), function(rec){


Any ideas?

Using 3.x

Thanks, Marty

Here is what I did to fix this. Not sure if it is right but it works.



/**
* @author Shea Frederick - http://www.vinylfox.com
* @class Ext.ux.form.HtmlEditor.SpecialCharacters
* @extends Ext.util.Observable
* <p>A plugin that creates a button on the HtmlEditor for inserting special characters.</p>
*/
Ext.ux.form.HtmlEditor.SpecialCharacters = Ext.extend(Ext.util.Observable, {
// SpecialCharacters language text
langTitle : 'Insert Special Character',
langInsert : 'Insert',
langCancel : 'Cancel',
/**
* @cfg {Array} specialChars
* An array of additional characters to display for user selection. Uses numeric portion of the ASCII HTML Character Code only. For example, to use the Copyright symbol, which is © we would just specify <tt>169</tt> (ie: <tt>specialChars:[169]</tt>).
*/
specialChars: [153],
/**
* @cfg {Array} charRange
* Two numbers specifying a range of ASCII HTML Characters to display for user selection. Defaults to <tt>[160, 256]</tt>.
*/
charRange: [160, 256],
// private
chars: [],
// private
init: function(cmp){
this.cmp = cmp;
this.cmp.on('render', this.onRender, this);
},
// private
onRender: function(){
var cmp = this.cmp;
var btn = this.cmp.getToolbar().addButton({
iconCls: 'x-edit-char',
handler: function(){
if (!this.chars.length) {
if (this.specialChars.length) {
Ext.each(this.specialChars, function(c, i){
this.chars[i] = ['&#' + c + ';'];
}, this);
}
for (i = this.charRange[0]; i < this.charRange[1]; i++) {
this.chars.push(['&#' + i + ';']);
}
}
var charStore = new Ext.data.ArrayStore({
fields: ['char'],
data: this.chars
});
this.charView = new Ext.DataView({
store: charStore,
ref: 'charView',
autoHeight: true,
multiSelect: true,
tpl: new Ext.XTemplate('<tpl for="."><div class="char-item">{char}</div></tpl><div class="x-clear"></div>'),
overClass: 'char-over',
itemSelector: 'div.char-item',
listeners: {
dblclick: function(t, i, n, e){
this.insertChar(t.getStore().getAt(i).get('char'));
this.charWindow.close();
},
scope: this
}
})
this.charWindow = new Ext.Window({
title: this.langTitle,
width: 436,
autoHeight: true,
layout: 'fit',
items: [this.charView],
buttons: [{
text: this.langInsert,
handler: function(){
Ext.each(this.charView.getSelectedRecords(), function(rec){
var c = rec.get('char');
this.insertChar(c);
}, this);
this.charWindow.close();
},
scope: this
}, {
text: this.langCancel,
handler: function(){
this.charWindow.close();
},
scope: this
}]
});
this.charWindow.show();
},
scope: this,
tooltip: {
title: this.langTitle
},
overflowText: this.langTitle
});
},
/**
* Insert a single special character into the document.
* @param c String The special character to insert (not just the numeric code, but the entire ASCII HTML entity).
*/
insertChar: function(c){
if (c) {
this.cmp.insertAtCursor(c);
}
}
});

farcek
24 Feb 2010, 2:38 AM
nice

moegal
26 Feb 2010, 12:33 PM
Here is a modified version of Ext.ux.form.HtmlEditor.SpecialCharacters.js. I wanted smileys. Very basic.



/**
* @author Marty Galabinski - http://www.moegal.com
* Most of the credit should go to @author Shea Frederick - http://www.vinylfox.com, did most of the work, I just modified "Ext.ux.form.HtmlEditor.SpecialCharacters.js" to fit my needs
* @class Ext.ux.form.HtmlEditor.Smileys
* @extends Ext.util.Observable
* <p>A plugin that creates a button on the HtmlEditor for inserting smileys.</p>
*/
Ext.ux.form.HtmlEditor.Smileys = Ext.extend(Ext.util.Observable, {
// Smileys language text
langTitle : 'Insert Smileys',
langInsert : 'Insert',
langCancel : 'Cancel',
imageUrl : 'ext/shared/icons/',
// private
init: function(cmp){
this.cmp = cmp;
this.cmp.on('render', this.onRender, this);
},
// private
onRender: function(){
var cmp = this.cmp;
var btn = this.cmp.getToolbar().addButton({
iconCls: 'x-edit-smiley',
handler: function(){
var smileyStore = new Ext.data.ArrayStore({
fields: ['url','altname'],
data: [
['emoticon_smile.png','Smile'],
['emoticon_grin.png','Grin'],
['emoticon_evilgrin.png','Evil Grin'],
['emoticon_happy.png','Happy'],
['emoticon_surprised.png','Surprised'],
['emoticon_tongue.png','Tongue'],
['emoticon_unhappy.png','Unhappy'],
['emoticon_waii.png','Waii'],
['emoticon_wink.png','Wink']
]
});
this.smileyView = new Ext.DataView({
store: smileyStore,
ref: 'smileyView',
autoHeight: true,
multiSelect: true,
tpl: new Ext.XTemplate('<tpl for="."><div class="smiley-item"><img src="'+this.imageUrl+'{url}" title="{altname}"></div></tpl><div class="x-clear"></div>'),
overClass: 'smiley-over',
itemSelector: 'div.smiley-item',
listeners: {
dblclick: function(t, i, n, e){
this.insertSmiley(t.getStore().getAt(i).get('url'), t.getStore().getAt(i).get('altname'));
this.smileyWindow.close();
},
scope: this
}
})
this.smileyWindow = new Ext.Window({
title: this.langTitle,
width: 180,
autoHeight: true,
layout: 'fit',
items: [this.smileyView],
buttons: [{
text: this.langInsert,
handler: function(){
Ext.each(this.smileyView.getSelectedRecords(), function(rec){
var url = rec.get('url');
var altname = rec.get('altname');
this.insertSmiley(url,altname);
}, this);
this.smileyWindow.close();
},
scope: this
}, {
text: this.langCancel,
handler: function(){
this.smileyWindow.close();
},
scope: this
}]
});
this.smileyWindow.show();
},
scope: this,
tooltip: {
title: this.langTitle
},
overflowText: this.langTitle
});
},
/**
* Insert a single smiley into the document.
* @param c String The smiley image and title to insert.
*/
insertSmiley: function(url,altname){
if (url) {
this.cmp.insertAtCursor('<img src="'+this.imageUrl+url+'" alt="'+altname+'">');
}
}
});



Also CSS:



/*
These styles are used for the smileys
*/
.x-edit-smiley {background: url(../ext/shared/icons/emoticon_smile.png) 0 0 no-repeat !important;}
.smiley-item {
float: left;
border: 1px solid #99BBE8;
margin: 3px;
text-align: center;
vertical-align: middle;
width: 20px;
height: 20px;
font-size: 14px;
color: #15428B;
cursor: pointer;
}

.smiley-item.x-view-selected {
background-color: #777;
}

.smiley-over {
border: 1px solid #15428B;
background-color:#d0def0;
}


Thanks, Marty

VinylFox
26 Feb 2010, 4:40 PM
Marty - Thats awesome! Smileys! Great idea.

Seems like both the SpecialCharacters and Smileys could be sub-classed from a base class for selecting 'things'. Ill see what I can do about that.

moegal
26 Feb 2010, 5:17 PM
Thanks, your code made it easy to follow.

I was thinking the same thing about selecting things, you could pass the langTitle, store of images, the iconCls of the button, imageURL, etc and then it could load a lot of different things.

Marty

moegal
1 Mar 2010, 6:04 AM
you can add smileys( or whatever you end up with) to your project at ext-ux-htmleditor-plugins

I don't care about any of the copyright stuff either.

Marty

mschwartz
1 Mar 2010, 1:55 PM
It seems that Word Paste cannot be properly done using the approach of the plugin.

If you have some HTML in the editor already and select a range of text and paste, the findValueDiffAt function cannot return the right value much of the time. Example, if the original text is <h1>blah blah</h1> and you select all and paste, findValueDiffAt() will return 1 because the < of the h1 is the same as the < of the <meta tag pasted in.

The this.fixWordPaste() call in the parts array initializer is passed the wrong diffAt and the wrong length as well. In fact, (this.curLength - this.lastLength) might even be negative if you select 100 characters and paste in 10!

Looking at the midas spec, I don't see a way to get the length of the selection. The cut and paste functions are privileged, so you can't use those to determine the length either. If you could use them, you could get the length of the selection by length of buffer - length of buffer after cut.

Stepping through the code, it seems that the value to paste is added to the iframe before the checkIfPaste() function even gets to run.

You cannot run the regex's on the resulting buffer or you'll wipe out various (e.g. span) tags and attributes that the user added to the document by typing and clicking the other buttons.

I'm afraid the only way to really do word paste is to present the user with a dialog when he clicks on "word paste button" and have him paste to a textarea in the dialog, then process the entire contents of the textarea, then copy the resulting HTML to the editor with the insertHTML command.

Another thing is the regex for newline is broken. You don't want to replace newlines with '', you want to replace them with a space! Same thing for &nbsp;

mschwartz
1 Mar 2010, 2:24 PM
Fixed:



/**
* @author Shea Frederick - http://www.vinylfox.com
* @class Ext.ux.form.HtmlEditor.Word
* @extends Ext.util.Observable
* <p>
* A plugin that creates a button on the HtmlEditor for pasting text
* from Word without all the jibberish html.
* </p>
*/
Ext.ux.form.HtmlEditor.Word = Ext.extend(Ext.util.Observable, {
// curLength: 0,
// lastLength: 0,
// lastValue: '',
// wordPasteEnabled: true,

// private
init: function(cmp) {

this.cmp = cmp;
this.cmp.on('render', this.onRender, this);
this.cmp.on('initialize', this.onInit, this, {
delay: 100,
single: true
});

},
// private
onInit: function() {

// Ext.EventManager.on(this.cmp.getDoc(), {
// 'keyup': this.checkIfPaste,
// scope: this
// });
// this.lastValue = this.cmp.getValue();
// this.curLength = this.lastValue.length;
// this.lastLength = this.lastValue.length;

},
// private
onDestroy: function() {
this.cmp.un('render', this.onRender, this);
this.cmp.un('initialize', this.onInit, this);
// Ext.EventManager.un(this.cmp.getDoc(), {
// 'keyup': this.checkIfPaste
// });
},
// private
// checkIfPaste: function(e) {
//
// var diffAt = 0;
// this.curLength = this.cmp.getValue().length;
//
// if ( e.V == e.getKey() && e.ctrlKey && this.wordPasteEnabled ) {
//
// this.cmp.suspendEvents();
//
// diffAt = this.findValueDiffAt(this.cmp.getValue());
// var parts = [
// this.cmp.getValue().substr(0, diffAt),
// this.fixWordPaste(this.cmp.getValue().substr(diffAt, (this.curLength -
// this.lastLength))),
// this.cmp.getValue().substr((this.curLength - this.lastLength) + diffAt,
// this.curLength)
// ];
// this.cmp.setValue(parts.join(''));
//
// this.cmp.resumeEvents();
// }
//
// this.lastLength = this.cmp.getValue().length;
// this.lastValue = this.cmp.getValue();
//
// },
// private
// findValueDiffAt: function(val) {
//
// for ( i = 0; i < this.curLength; i++ ) {
// if ( this.lastValue[i] != val[i] ) {
// return i;
// }
// }
//
// },
/**
* Cleans up the jubberish html from Word pasted text.
*
* @param wordPaste
* String The text that needs to be cleansed of Word jibberish
* html.
* @return {String} The passed in text with all Word jibberish html removed.
*/
fixWordPaste: function(wordPaste) {
wordPaste = wordPaste.split('\n')
wordPaste = wordPaste.join(' ');
var removals = [
/^<\/?meta.*?>/ig,
/<\/?meta.*?>/ig,
/<\/?link.*?>/ig,
/<(xml|style)[^>]*>.*?<\/\1>/ig,
/<\/?(meta|object|span)[^>]*>/ig,
/<\/?[A-Z0-9]*:[A-Z]*[^>]*>/ig,
/(lang|class|type|href|name|title|id|clear)=\"[^\"]*\"/ig,
/style=(\'\'|\"\")/ig,
/<![\[-].*?-*>/g,
/MsoNormal/g,
/<\\?\?xml[^>]*>/g,
/<\/?o:p[^>]*>/g,
/<\/?v:[^>]*>/g,
/<\/?o:[^>]*>/g,
/<\/?st1:[^>]*>/g,
/&nbsp;/g,
/<\/?SPAN[^>]*>/g,
/<\/?FONT[^>]*>/g,
/<\/?STRONG[^>]*>/g,
/<\/?H1[^>]*>/g,
/<\/?H2[^>]*>/g,
/<\/?H3[^>]*>/g,
/<\/?H4[^>]*>/g,
/<\/?H5[^>]*>/g,
/<\/?H6[^>]*>/g,
/<\/?P[^>]*><\/P>/g,
/<!--(.*)-->/g,
/<!--(.*)>/g,
/<!(.*)-->/g,
/<\\?\?xml[^>]*>/g,
/<\/?o:p.*?>/g,
/<\/?v:[^>]*>/g,
/<\/?o:.*?>/g,
/<\/?st1:[^>]*>/g,
/style=\"[^\"]*\"/g,
/style=\'[^\"]*\'/g,
/lang=\"[^\"]*\"/g,
/lang=\'[^\"]*\'/g,
/class=\"[^\"]*\"/g,
/class=\'[^\"]*\'/g,
/type=\"[^\"]*\"/g,
/type=\'[^\"]*\'/g,
/href=\'#[^\"]*\'/g,
/href=\"#[^\"]*\"/g,
/name=\"[^\"]*\"/g,
/name=\'[^\"]*\'/g,
/ clear=\"all\"/g,
/id=\"[^\"]*\"/g,
/title=\"[^\"]*\"/g,
/<span.*?>/ig,
/<\/?span.*?>/ig,
/class=/g
];

wordPaste = wordPaste.replace(/&nbsp\;/img, ' '); // white-space must be removed first
wordPaste = wordPaste.replace(/\r\n/img, '\n'); // white-space must be removed first
wordPaste = wordPaste.replace(/[\r\n]/img, ' '); // white-space must be removed first

Ext.each(removals, function(s) {
wordPaste = wordPaste.replace(s, "");
});

// keep the divs in paragraphs
wordPaste = wordPaste.replace(/<div[^>]*>/g, "<p>");
wordPaste = wordPaste.replace(/<\/?div[^>]*>/g, "</p>");
return wordPaste;

},
// private
onRender: function() {
var me = this;
this.cmp.getToolbar().add({
iconCls: 'x-edit-wordpaste',
// pressed: true,
handler: function(t) {
var dialog = new Ext.Window({
title: 'Paste from Word Document',
modal: true,
width: 600,
height: 400,
layout: 'form',
labelAlign: 'top',
items: [
{
xtype: 'textarea',
fieldLabel: 'Paste in the text area below',
anchor: '100% 0',
id: 'wordpaste-textarea'
}
],
buttonAlign: 'center',
buttons: [
{
text: 'Paste',
handler: function() {
me.cmp.relayCmd('inserthtml', me.fixWordPaste(Ext.getCmp('wordpaste-textarea').getValue()));
dialog.close();
}
},
{
text: 'Cancel',
handler: function() {
dialog.close();
}
}
]
});
dialog.show();
dialog.center();
// t.toggle(!t.pressed);
// this.wordPasteEnabled = !this.wordPasteEnabled;
},
scope: this,
tooltip: {
text: 'Cleanse text pasted from Word or other Rich Text applications'
}
});

}
});

mschwartz
2 Mar 2010, 6:37 AM
Looking at the midas spec, I don't see a way to get the length of the selection. The cut and paste functions are privileged, so you can't use those to determine the length either. If you could use them, you could get the length of the selection by length of buffer - length of buffer after cut.


There is a way to examine the range/selection:

http://www.quirksmode.org/dom/range_intro.html

If you wanted to maintain it so the editor can know what it is at paste time, you'd have to keep updating a copy on every keystroke and mouse click.

TigersWay
3 Mar 2010, 8:13 PM
Hi,

I'm following that thread with a lot of interest.... And I would love to get some input from you!

I have some "templates" to fill a HTMLEditor. For now, I am dealing with an external combo, and it's working fine. But I was wondering if it could be easily built as a plugin.

Where should I start?
Thanks
Ben

dytaylor
4 Mar 2010, 2:58 PM
I cannot download the code from google for some reasons. Keep on saying this:

Not Found

The requested URL /files/Ext.ux.HtmlEditor.Plugins-0.2-all.js?project=ext-ux-htmleditor-plugins was not found on this server.

Is there another place I can have a copy of this. I really need it.

Thanks

moegal
4 Mar 2010, 3:51 PM
Hi,

I'm following that thread with a lot of interest.... And I would love to get some input from you!

I have some "templates" to fill a HTMLEditor. For now, I am dealing with an external combo, and it's working fine. But I was wondering if it could be easily built as a plugin.

Where should I start?
Thanks
Ben

Download some of the other extensions. I created smileys after looking at the symbols plugin. I would look at one of those as well as the table plugin.

Marty

ryanpetrello
12 Mar 2010, 9:05 PM
I'm currently attempting to get the Undo/Redo plugin working, and am having no luck at all in Safari (OSX, 4.0.4):

http://code.google.com/p/ext-ux-htmleditor-plugins/source/browse/trunk/src/Ext.ux.form.HtmlEditor.UndoRedo.js

I've downloaded the code and have had good luck w/ many of the other commands (such as `removeformat` and the table functions), but I can't get the undo/redo up and running on Safari (4.0.4 on Mac OS X Snow Leopard). It works great on Firefox 3.5.7, but for some reason, document.execCommand('undo', false, null) just doesn't seem to want to work on Safari. I've traced the code up until the point that Ext.form.HtmlEditor internally calls the native execCommand function - when it gets to that point in the code, it executes, returns false, and seems to have no effect at all on the component.

My research seems to suggest that latest versions of Safari *do* have support for the `undo` Midas command (as evidenced by Mozilla's example form at http://www.mozilla.org/editor/midasdemo/ and several other demos I've found online).

Curious, I actually visited the HtmlEditor demo at http://www.extjs.com/deploy/dev/examples/form/dynamic.html (Form 3). Using Safari's debug console (similar to Firebug), if I manually run:


Ext.query('iframe')[0].contentDocument.execCommand('undo', false, null);

...it returns false, and has no effect on the iframe content I type.

Any idea if this is a Safari bug? Perhaps Ext's HtmlEditor implementation is doing something that breaks the undo/redo MIDAS commands in Safari? I've spent hours on this and am slamming my head up against the wall. Has anybody else experienced this issue and overcome it?

Any insight is *much* appreciated :-?!

Takai30
26 Mar 2010, 9:47 AM
Hi all,

I've been attempting to use the plugins but am running into a problem, the buttons are not appearing on my htmleditor toolbar and I can't for the life of me figure out why.

I've grouped all the plugins (in their include order into a single file) called HtmlEditorPlugins.js



/**
* @author Shea Frederick - http://www.vinylfox.com
* @class Ext.ux.form.HtmlEditor.MidasCommand
* @extends Ext.util.Observable
* <p>A base plugin for extending to create standard Midas command buttons.</p>
*/
Ext.ns('Ext.ux.form.HtmlEditor');

if (!Ext.isObject) {
Ext.isObject = function(v){
return v && typeof v == "object";
};
}

Ext.override(Ext.form.HtmlEditor, {
getSelectedText: function(clip){
var doc = this.getDoc(), selDocFrag;
var txt = '', hasHTML = false, selNodes = [], ret, html = '';
if (this.win.getSelection || doc.getSelection) {
// FF, Chrome, Safari
var sel = this.win.getSelection();
if (!sel) {
sel = doc.getSelection();
}
if (clip) {
selDocFrag = sel.getRangeAt(0).extractContents();
} else {
selDocFrag = this.win.getSelection().getRangeAt(0).cloneContents();
}
Ext.each(selDocFrag.childNodes, function(n){
if (n.nodeType !== 3) {
hasHTML = true;
}
});
if (hasHTML) {
var div = document.createElement('div');
div.appendChild(selDocFrag);
html = div.innerHTML;
txt = this.win.getSelection() + '';
} else {
html = txt = selDocFrag.textContent;
}
ret = {
textContent: txt,
hasHTML: hasHTML,
html: html
};
} else if (doc.selection) {
// IE
this.win.focus();
txt = doc.selection.createRange();
if (txt.text !== txt.htmlText) {
hasHTML = true;
}
ret = {
textContent: txt.text,
hasHTML: hasHTML,
html: txt.htmlText
};
} else {
return {
textContent: ''
};
}

return ret;
}
});

Ext.ux.form.HtmlEditor.MidasCommand = Ext.extend(Ext.util.Observable, {
// private
init: function(cmp){
this.cmp = cmp;
this.btns = [];
this.cmp.on('render', this.onRender, this);
this.cmp.on('initialize', this.onInit, this, {
delay: 100,
single: true
});
},
// private
onInit: function(){
Ext.EventManager.on(this.cmp.getDoc(), {
'mousedown': this.onEditorEvent,
'dblclick': this.onEditorEvent,
'click': this.onEditorEvent,
'keyup': this.onEditorEvent,
buffer: 100,
scope: this
});
},
// private
onRender: function(){
var midasCmdButton, tb = this.cmp.getToolbar(), btn;
Ext.each(this.midasBtns, function(b){
if (Ext.isObject(b)) {
midasCmdButton = {
iconCls: 'x-edit-' + b.cmd,
handler: function(){
this.cmp.relayCmd(b.cmd);
},
scope: this,
tooltip: b.tooltip ||
{
title: b.title
},
overflowText: b.overflowText || b.title
};
} else {
midasCmdButton = new Ext.Toolbar.Separator();
}
btn = tb.addButton(midasCmdButton);
if (b.enableOnSelection) {
btn.disable();
}
this.btns.push(btn);
}, this);
},
// private
onEditorEvent: function(){
var doc = this.cmp.getDoc();
Ext.each(this.btns, function(b, i){
if (this.midasBtns[i].enableOnSelection || this.midasBtns[i].disableOnSelection) {
if (doc.getSelection) {
if ((this.midasBtns[i].enableOnSelection && doc.getSelection() !== '') || (this.midasBtns[i].disableOnSelection && doc.getSelection() === '')) {
b.enable();
} else {
b.disable();
}
} else if (doc.selection) {
if ((this.midasBtns[i].enableOnSelection && doc.selection.createRange().text !== '') || (this.midasBtns[i].disableOnSelection && doc.selection.createRange().text === '')) {
b.enable();
} else {
b.disable();
}
}
}
if (this.midasBtns[i].monitorCmdState) {
b.toggle(doc.queryCommandState(this.midasBtns[i].cmd));
}
}, this);
}
});


/**
* @author Shea Frederick - http://www.vinylfox.com
* @class Ext.ux.form.HtmlEditor.Divider
* @extends Ext.util.Observable
* <p>A plugin that creates a divider on the HtmlEditor. Used for separating additional buttons.</p>
*/
Ext.ux.form.HtmlEditor.Divider = Ext.extend(Ext.util.Observable, {
// private
init: function(cmp){
this.cmp = cmp;
this.cmp.on('render', this.onRender, this);
},
// private
onRender: function(){
this.cmp.getToolbar().addButton([new Ext.Toolbar.Separator()]);
}
});


/**
* @author Shea Frederick - http://www.vinylfox.com
* @class Ext.ux.form.HtmlEditor.HR
* @extends Ext.util.Observable
* <p>A plugin that creates a button on the HtmlEditor for inserting a horizontal rule.</p>
*/
Ext.ux.form.HtmlEditor.HR = Ext.extend(Ext.util.Observable, {
// HR language text
langTitle : 'Horizontal Rule',
langHelp : 'Enter the width of the Rule in percentage<br/> followed by the % sign at the end, or to<br/> set a fixed width ommit the % symbol.',
langInsert : 'Insert',
langCancel : 'Cancel',
langWidth : 'Width',
// defaults
defaultHRWidth: '100%',
// private
cmd: 'hr',
// private
init: function(cmp){
this.cmp = cmp;
this.cmp.on('render', this.onRender, this);
},
// private
onRender: function(){
var cmp = this.cmp;
var btn = this.cmp.getToolbar().addButton({
iconCls: 'x-edit-hr',
handler: function(){
if (!this.hrWindow) {
this.hrWindow = new Ext.Window({
title: this.langTitle,
width: 240,
closeAction: 'hide',
items: [{
itemId: 'insert-hr',
xtype: 'form',
border: false,
plain: true,
bodyStyle: 'padding: 10px;',
labelWidth: 60,
labelAlign: 'right',
items: [{
xtype: 'label',
html: this.langHelp + '<br/>&nbsp;'
}, {
xtype: 'textfield',
maskRe: /[0-9]|%/,
regex: /^[1-9][0-9%]{1,3}/,
fieldLabel: this.langWidth,
name: 'hrwidth',
width: 60,
value: this.defaultHRWidth,
listeners: {
specialkey: function(f, e){
if ((e.getKey() == e.ENTER || e.getKey() == e.RETURN) && f.isValid()) {
this.doInsertHR();
}
},
scope: this
}
}]
}],
buttons: [{
text: this.langInsert,
handler: function(){
var frm = this.hrWindow.getComponent('insert-hr').getForm();
if (frm.isValid()) {
this.doInsertHR();
} else {
frm.findField('hrwidth').getEl().frame();
}
},
scope: this
}, {
text: this.langCancel,
handler: function(){
this.hrWindow.hide();
},
scope: this
}],
listeners: {
render: (Ext.isGecko) ? this.focusHRLong : this.focusHR,
show: this.focusHR,
move: this.focusHR,
scope: this
}
});
} else {
this.hrWindow.getEl().frame();
}
this.hrWindow.show();
},
scope: this,
tooltip: {
title: this.langInsert + ' ' + this.langTitle
},
overflowText: this.langTitle
});
},
// private
focusHRLong: function(w){
this.focus(w, 600);
},
// private
focusHR: function(w){
this.focus(w, 100);
},
/**
* This method is just for focusing the text field use for entering the width of the HR.
* It's extra messy because Firefox seems to take a while longer to render the window than other browsers,
* particularly when Firbug is enabled, which is all the time if your like me.
* Had to crank up the delay for focusing on render to 600ms for Firefox, and 100ms for all other focusing.
* Other browsers seem to work fine in all cases with as little as 50ms delay. Compromise bleh!
* @param {Object} win the window to focus
* @param {Integer} delay the delay in milliseconds before focusing
*/
focus: function(win, delay){
win.getComponent('insert-hr').getForm().findField('hrwidth').focus(true, delay);
},
// private
doInsertHR: function(){
var frm = this.hrWindow.getComponent('insert-hr').getForm();
if (frm.isValid()) {
var hrwidth = frm.findField('hrwidth').getValue();
if (hrwidth) {
this.insertHR(hrwidth);
} else {
this.insertHR(this.defaultHRWidth);
}
frm.reset();
this.hrWindow.hide();
}
},
/**
* Insert a horizontal rule into the document.
* @param w String The width of the horizontal rule as the <tt>width</tt> attribute of the HR tag expects. ie: '100%' or '400' (pixels).
*/
insertHR: function(w){
this.cmp.insertAtCursor('<hr width="' + w + '">');
}
});


/**
* @author Shea Frederick - http://www.vinylfox.com
* @class Ext.ux.form.HtmlEditor.Image
* @extends Ext.util.Observable
* <p>A plugin that creates an image button in the HtmlEditor toolbar for inserting an image. The method to select an image must be defined by overriding the selectImage method. Supports resizing of the image after insertion.</p>
* <p>The selectImage implementation must call insertImage after the user has selected an image, passing it a simple image object like the one below.</p>
* <pre>
* var img = {
* Width: 100,
* Height: 100,
* ID: 123,
* Title: 'My Image'
* };
* </pre>
*/
Ext.ux.form.HtmlEditor.Image = Ext.extend(Ext.util.Observable, {
urlSizeVars: ['width','height'],
basePath: 'image.php',
init: function(cmp){
this.cmp = cmp;
this.cmp.on('render', this.onRender, this);
this.cmp.on('initialize', this.onInit, this, {delay:100, single: true});
},
onEditorMouseUp : function(e){
Ext.get(e.getTarget()).select('img').each(function(el){
var w = el.getAttribute('width'), h = el.getAttribute('height'), src = el.getAttribute('src')+' ';
src = src.replace(new RegExp(this.urlSizeVars[0]+'=[0-9]{1,5}([&| ])'), this.urlSizeVars[0]+'='+w+'$1');
src = src.replace(new RegExp(this.urlSizeVars[1]+'=[0-9]{1,5}([&| ])'), this.urlSizeVars[1]+'='+h+'$1');
el.set({src:src.replace(/\s+$/,"")});
}, this);

},
onInit: function(){
Ext.EventManager.on(this.cmp.getDoc(), {
'mouseup': this.onEditorMouseUp,
buffer: 100,
scope: this
});
},
onRender: function() {
var btn = this.cmp.getToolbar().addButton({
iconCls: 'x-edit-image',
handler: this.selectImage,
scope: this,
tooltip: {
title: 'Insert Image'
},
overflowText: 'Insert Image'
});
},
selectImage: Ext.emptyFn,
insertImage: function(img) {
this.cmp.insertAtCursor('<img src="'+this.basePath+'?'+this.urlSizeVars[0]+'='+img.Width+'&'+this.urlSizeVars[1]+'='+img.Height+'&id='+img.ID+'" title="'+img.Name+'" alt="'+img.Name+'">');
}
});


/**
* @author Shea Frederick - http://www.vinylfox.com
* @class Ext.ux.form.HtmlEditor.RemoveFormat
* @extends Ext.ux.form.HtmlEditor.MidasCommand
* <p>A plugin that creates a button on the HtmlEditor that will remove all formatting on selected text.</p>
*/
Ext.ux.form.HtmlEditor.RemoveFormat = Ext.extend(Ext.ux.form.HtmlEditor.MidasCommand, {
midasBtns: ['|', {
enableOnSelection: true,
cmd: 'removeFormat',
tooltip: {
title: 'Remove Formatting'
},
overflowText: 'Remove Formatting'
}]
});


/**
* @author Shea Frederick - http://www.vinylfox.com
* @class Ext.ux.form.HtmlEditor.IndentOutdent
* @extends Ext.ux.form.HtmlEditor.MidasCommand
* <p>A plugin that creates two buttons on the HtmlEditor for indenting and outdenting of selected text.</p>
*/
Ext.ux.form.HtmlEditor.IndentOutdent = Ext.extend(Ext.ux.form.HtmlEditor.MidasCommand, {
// private
midasBtns: ['|', {
cmd: 'indent',
tooltip: {
title: 'Indent Text'
},
overflowText: 'Indent Text'
}, {
cmd: 'outdent',
tooltip: {
title: 'Outdent Text'
},
overflowText: 'Outdent Text'
}]
});


/**
* @author Shea Frederick - http://www.vinylfox.com
* @class Ext.ux.form.HtmlEditor.SubSuperScript
* @extends Ext.ux.form.HtmlEditor.MidasCommand
* <p>A plugin that creates two buttons on the HtmlEditor for superscript and subscripting of selected text.</p>
*/
Ext.ux.form.HtmlEditor.SubSuperScript = Ext.extend(Ext.ux.form.HtmlEditor.MidasCommand, {
// private
midasBtns: ['|', {
enableOnSelection: true,
cmd: 'subscript',
tooltip: {
title: 'Subscript'
},
overflowText: 'Subscript'
}, {
enableOnSelection: true,
cmd: 'superscript',
tooltip: {
title: 'Superscript'
},
overflowText: 'Superscript'
}]
});


/**
* @author Shea Frederick - http://www.vinylfox.com
* @contributor Ronald van Raaphorst - Twensoc
* @class Ext.ux.form.HtmlEditor.FindReplace
* @extends Ext.util.Observable
* <p>A plugin that provides search and replace functionality in source edit mode.</p>
*/
Ext.ux.form.HtmlEditor.FindAndReplace = Ext.extend(Ext.util.Observable, {
// private
cmd: 'findandreplace',
// private
init: function(cmp){
this.cmp = cmp;
this.cmp.on({
'render': this.onRender,
'editmodechange': this.editModeChange,
scope: this
});
this.lastSelectionStart=-1;
},
editModeChange: function(t, m){
if (this.btn && m){
this.btn.setDisabled(false);
}
},
// private
onRender: function(){
this.btn = this.cmp.getToolbar().addButton({
iconCls: 'x-edit-findandreplace',
sourceEditEnabled:true,
handler: function(){

if (!this.farWindow){

this.farWindow = new Ext.Window({
title: 'Find/Replace',
closeAction: 'hide',
items: [{
itemId: 'findandreplace',
xtype: 'form',
border: false,
plain: true,
bodyStyle: 'padding: 10px;',
labelWidth: 80,
labelAlign: 'right',
items: [{
xtype: 'textfield',
allowBlank: false,
fieldLabel: 'Find',
name: 'find',
width: 150
}, {
xtype: 'textfield',
allowBlank: true,
fieldLabel: 'Replace with',
name: 'replace',
width: 150
}]
}],
buttons: [{
text: 'Find',
handler: this.doFind,
scope: this
}, {
text: 'Replace',
handler: this.doReplace,
scope: this
}, {
text: 'Close',
handler: function(){
this.farWindow.hide();
},
scope: this
}]
});

}else{

this.farWindow.getEl().frame();

}

this.farWindow.show();

},
scope: this,
tooltip: {
title: 'Find/Replace'
},
overflowText: 'Find/Replace'
});

},
doFind: function(){

var frm = this.farWindow.getComponent('findandreplace').getForm();
if (!frm.isValid()) {
return '';
}

var findValue = frm.findField('find').getValue();
var replaceValue = frm.findField('replace').getValue();
if(this.cmp.sourceEditMode) {
// source edit mode
var textarea = this.cmp.el.dom;
var startPos = textarea.selectionStart===this.lastSelectionStart ? textarea.selectionStart+1 : textarea.selectionStart;
var txt = textarea.value.substring(startPos);

var regexp = new RegExp(findValue);
var r = txt.search(regexp);
if(r==-1) {
return;
}
this.lastSelectionStart = startPos + r;
if(Ext.isGecko) {
textarea.setSelectionRange(this.lastSelectionStart , this.lastSelectionStart + findValue.length);
this.cmp.scrollIntoView(startPos);
this.cmp.focus(false, true);
}
return;
}
// design mode
//var doc = this.cmp.getEditorBody();
//var txt = doc.innerHTML;
// Should we search/replace in the source, and push the result back to the design?

},
doReplace: function(){

var frm = this.farWindow.getComponent('findandreplace').getForm();
if (!frm.isValid()) {
return '';
}

var findValue = frm.findField('find').getValue();
var replaceValue = frm.findField('replace').getValue();
if(this.cmp.sourceEditMode) {
var textarea = this.cmp.el.dom;
var startPos = textarea.selectionStart;
var endPos = textarea.selectionEnd;
var txt = textarea.value;

//cmp.execCmd('delete', null);
//cmp.focus(false, false);
//cmp.insertAtCursor(replaceValue);

if(Ext.isGecko) {
// TODO: Scroll into view
var scrollPosition = textarea.scrollTop;
textarea.value = txt.substring(0,startPos) + replaceValue + txt.substring(endPos);
textarea.setSelectionRange(startPos,startPos + replaceValue.length);
textarea.scrollTop = scrollPosition;
this.cmp.focus(false, true);
}
return;
}
return;

}
});


/**
* @author Shea Frederick - http://www.vinylfox.com
* @class Ext.ux.form.HtmlEditor.Table
* @extends Ext.util.Observable
* <p>A plugin that creates a button on the HtmlEditor for making simple tables.</p>
*/
Ext.ux.form.HtmlEditor.Table = Ext.extend(Ext.util.Observable, {
// Table language text
langTitle : 'Insert Table',
langInsert : 'Insert',
langCancel : 'Cancel',
langRows : 'Rows',
langColumns : 'Columns',
langBorder : 'Border',
langCellLabel : 'Label Cells',
// private
cmd: 'table',
/**
* @cfg {Boolean} showCellLocationText
* Set true to display row and column informational text inside of newly created table cells.
*/
showCellLocationText: true,
/**
* @cfg {String} cellLocationText
* The string to display inside of newly created table cells.
*/
cellLocationText: '{0}&nbsp;-&nbsp;{1}',
/**
* @cfg {Array} tableBorderOptions
* A nested array of value/display options to present to the user for table border style. Defaults to a simple list of 5 varrying border types.
*/
tableBorderOptions: [['none', 'None'], ['1px solid #000', 'Sold Thin'], ['2px solid #000', 'Solid Thick'], ['1px dashed #000', 'Dashed'], ['1px dotted #000', 'Dotted']],
// private
init: function(cmp){
this.cmp = cmp;
this.cmp.on('render', this.onRender, this);
},
// private
onRender: function(){
var btn = this.cmp.getToolbar().addButton({
iconCls: 'x-edit-table',
handler: function(){
if (!this.tableWindow){
this.tableWindow = new Ext.Window({
title: this.langTitle,
closeAction: 'hide',
width: 230,
items: [{
itemId: 'insert-table',
xtype: 'form',
border: false,
plain: true,
bodyStyle: 'padding: 10px;',
labelWidth: 60,
labelAlign: 'right',
items: [{
xtype: 'numberfield',
allowBlank: false,
allowDecimals: false,
fieldLabel: this.langRows,
name: 'row',
width: 60
}, {
xtype: 'numberfield',
allowBlank: false,
allowDecimals: false,
fieldLabel: this.langColumns,
name: 'col',
width: 60
}, {
xtype: 'combo',
fieldLabel: this.langBorder,
name: 'border',
forceSelection: true,
mode: 'local',
store: new Ext.data.ArrayStore({
autoDestroy: true,
fields: ['spec', 'val'],
data: this.tableBorderOptions
}),
triggerAction: 'all',
value: 'none',
displayField: 'val',
valueField: 'spec',
anchor: '100%'
}, {
xtype: 'checkbox',
fieldLabel: this.langCellLabel,
checked: this.showCellLocationText,
listeners: {
check: function(){
this.showCellLocationText = !this.showCellLocationText;
},
scope: this
}
}]
}],
buttons: [{
text: this.langInsert,
handler: function(){
var frm = this.tableWindow.getComponent('insert-table').getForm();
if (frm.isValid()) {
var border = frm.findField('border').getValue();
var rowcol = [frm.findField('row').getValue(), frm.findField('col').getValue()];
if (rowcol.length == 2 && rowcol[0] > 0 && rowcol[1] > 0) {
var colwidth = Math.floor(100/rowcol[0]);
var html = "<table style='border-collapse: collapse'>";
var cellText = '&nbsp;';
if (this.showCellLocationText){ cellText = this.cellLocationText; }
for (var row = 0; row < rowcol[0]; row++) {
html += "<tr>";
for (var col = 0; col < rowcol[1]; col++) {
html += "<td width='" + colwidth + "%' style='border: " + border + ";'>" + String.format(cellText, (row+1), String.fromCharCode(col+65)) + "</td>";
}
html += "</tr>";
}
html += "</table>";
this.cmp.insertAtCursor(html);
}
this.tableWindow.hide();
}else{
if (!frm.findField('row').isValid()){
frm.findField('row').getEl().frame();
}else if (!frm.findField('col').isValid()){
frm.findField('col').getEl().frame();
}
}
},
scope: this
}, {
text: this.langCancel,
handler: function(){
this.tableWindow.hide();
},
scope: this
}]
});

}else{
this.tableWindow.getEl().frame();
}
this.tableWindow.show();
},
scope: this,
tooltip: {
title: this.langTitle
},
overflowText: this.langTitle
});
}
});


/**
* @author Shea Frederick - http://www.vinylfox.com
* @class Ext.ux.form.HtmlEditor.Word
* @extends Ext.util.Observable
* <p>A plugin that creates a button on the HtmlEditor for pasting text from Word without all the jibberish html.</p>
*/
Ext.ux.form.HtmlEditor.Word = Ext.extend(Ext.util.Observable, {
// Word language text
langTitle: 'Word Paste',
wordPasteEnabled: true,
// private
curLength: 0,
lastLength: 0,
lastValue: '',
// private
init: function(cmp){

this.cmp = cmp;
this.cmp.on('render', this.onRender, this);
this.cmp.on('initialize', this.onInit, this, {delay:100, single: true});

},
// private
onInit: function(){

Ext.EventManager.on(this.cmp.getDoc(), {
'keyup': this.checkIfPaste,
scope: this
});
this.lastValue = this.cmp.getValue();
this.curLength = this.lastValue.length;
this.lastLength = this.lastValue.length;

},
// private
checkIfPaste: function(e){

var diffAt = 0;
this.curLength = this.cmp.getValue().length;

if (e.V == e.getKey() && e.ctrlKey && this.wordPasteEnabled){

this.cmp.suspendEvents();

diffAt = this.findValueDiffAt(this.cmp.getValue());
var parts = [
this.cmp.getValue().substr(0, diffAt),
this.fixWordPaste(this.cmp.getValue().substr(diffAt, (this.curLength - this.lastLength))),
this.cmp.getValue().substr((this.curLength - this.lastLength)+diffAt, this.curLength)
];
this.cmp.setValue(parts.join(''));

this.cmp.resumeEvents();
}

this.lastLength = this.cmp.getValue().length;
this.lastValue = this.cmp.getValue();

},
// private
findValueDiffAt: function(val){

for (i=0;i<this.curLength;i++){
if (this.lastValue[i] != val[i]){
return i;
}
}

},
/**
* Cleans up the jubberish html from Word pasted text.
* @param wordPaste String The text that needs to be cleansed of Word jibberish html.
* @return {String} The passed in text with all Word jibberish html removed.
*/
fixWordPaste: function(wordPaste) {

var removals = [/&nbsp;/ig, /[\r\n]/g, /<(xml|style)[^>]*>.*?<\/\1>/ig, /<\/?(meta|object|span)[^>]*>/ig,
/<\/?[A-Z0-9]*:[A-Z]*[^>]*>/ig, /(lang|class|type|href|name|title|id|clear)=\"[^\"]*\"/ig, /style=(\'\'|\"\")/ig, /<![\[-].*?-*>/g,
/MsoNormal/g, /<\\?\?xml[^>]*>/g, /<\/?o:p[^>]*>/g, /<\/?v:[^>]*>/g, /<\/?o:[^>]*>/g, /<\/?st1:[^>]*>/g, /&nbsp;/g,
/<\/?SPAN[^>]*>/g, /<\/?FONT[^>]*>/g, /<\/?STRONG[^>]*>/g, /<\/?H1[^>]*>/g, /<\/?H2[^>]*>/g, /<\/?H3[^>]*>/g, /<\/?H4[^>]*>/g,
/<\/?H5[^>]*>/g, /<\/?H6[^>]*>/g, /<\/?P[^>]*><\/P>/g, /<!--(.*)-->/g, /<!--(.*)>/g, /<!(.*)-->/g, /<\\?\?xml[^>]*>/g,
/<\/?o:p[^>]*>/g, /<\/?v:[^>]*>/g, /<\/?o:[^>]*>/g, /<\/?st1:[^>]*>/g, /style=\"[^\"]*\"/g, /style=\'[^\"]*\'/g, /lang=\"[^\"]*\"/g,
/lang=\'[^\"]*\'/g, /class=\"[^\"]*\"/g, /class=\'[^\"]*\'/g, /type=\"[^\"]*\"/g, /type=\'[^\"]*\'/g, /href=\'#[^\"]*\'/g,
/href=\"#[^\"]*\"/g, /name=\"[^\"]*\"/g, /name=\'[^\"]*\'/g, / clear=\"all\"/g, /id=\"[^\"]*\"/g, /title=\"[^\"]*\"/g,
/<span[^>]*>/g, /<\/?span[^>]*>/g, /<title>(.*)<\/title>/g, /class=/g, /<meta[^>]*>/g, /<link[^>]*>/g, /<style>(.*)<\/style>/g,
/<w:[^>]*>(.*)<\/w:[^>]*>/g];

Ext.each(removals, function(s){
wordPaste = wordPaste.replace(s, "");
});

// keep the divs in paragraphs
wordPaste = wordPaste.replace(/<div[^>]*>/g, "<p>");
wordPaste = wordPaste.replace(/<\/?div[^>]*>/g, "</p>");
return wordPaste;

},
// private
onRender: function() {

this.cmp.getToolbar().add({
iconCls: 'x-edit-wordpaste',
pressed: true,
handler: function(t){
t.toggle(!t.pressed);
this.wordPasteEnabled = !this.wordPasteEnabled;
},
scope: this,
tooltip: {
text: 'Cleanse text pasted from Word or other Rich Text applications'
},
overflowText: this.langTitle
});

}
});


/**
* @author Shea Frederick - http://www.vinylfox.com
* @class Ext.ux.form.HtmlEditor.Link
* @extends Ext.util.Observable
* <p>A plugin that creates a button on the HtmlEditor for inserting a link.</p>
*/
Ext.ux.form.HtmlEditor.Link = Ext.extend(Ext.util.Observable, {
// Link language text
langTitle : 'Insert Link',
langInsert : 'Insert',
langCancel : 'Cancel',
langTarget : 'Target',
langURL : 'URL',
langText : 'Text',
// private
linkTargetOptions: [['_self', 'Default'], ['_blank', 'New Window'], ['_parent', 'Parent Window'], ['_top', 'Entire Window']],
init: function(cmp){
cmp.enableLinks = false;
this.cmp = cmp;
this.cmp.on('render', this.onRender, this);
},
onRender: function(){
var cmp = this.cmp;
var btn = this.cmp.getToolbar().addButton({
iconCls: 'x-edit-createlink',
handler: function(){
var sel = this.cmp.getSelectedText();
if (!this.tableWindow) {
this.linkWindow = new Ext.Window({
title: this.langTitle,
closeAction: 'hide',
width: 250,
height: 160,
items: [{
xtype: 'form',
itemId: 'insert-link',
border: false,
plain: true,
bodyStyle: 'padding: 10px;',
labelWidth: 40,
labelAlign: 'right',
items: [{
xtype: 'textfield',
fieldLabel: this.langText,
name: 'text',
anchor: '100%',
value: sel.textContent,
disabled: sel.hasHTML
}, {
xtype: 'textfield',
fieldLabel: this.langURL,
vtype: 'url',
name: 'url',
anchor: '100%',
value: 'http://'
}, {
xtype: 'combo',
fieldLabel: this.langTarget,
name: 'target',
forceSelection: true,
mode: 'local',
store: new Ext.data.ArrayStore({
autoDestroy: true,
fields: ['spec', 'val'],
data: this.linkTargetOptions
}),
triggerAction: 'all',
value: '_self',
displayField: 'val',
valueField: 'spec',
anchor: '100%'
}]
}],
buttons: [{
text: this.langInsert,
handler: function(){
var frm = this.linkWindow.getComponent('insert-link').getForm();
if (frm.isValid()) {
var afterSpace = '', sel = this.cmp.getSelectedText(true), text = frm.findField('text').getValue(), url = frm.findField('url').getValue(), target = frm.findField('target').getValue();
if (text.length && text[text.length - 1] == ' ') {
text = text.substr(0, text.length - 1);
afterSpace = ' ';
}
if (sel.hasHTML) {
text = sel.html;
}
var html = '<a href="' + url + '" target="' + target + '">' + text + '</a>' + afterSpace;
this.cmp.insertAtCursor(html);
this.linkWindow.close();
} else {
if (!frm.findField('url').isValid()) {
frm.findField('url').getEl().frame();
} else if (!frm.findField('target').isValid()) {
frm.findField('target').getEl().frame();
}
}

},
scope: this
}, {
text: this.langCancel,
handler: function(){
this.linkWindow.close();
},
scope: this
}],
listeners: {
show: {
fn: function(){
var frm = this.linkWindow.getComponent('insert-link').getForm();
frm.findField('url').focus(true, 50);
},
scope: this,
defer: 200
}
}
});
} else {
this.linkWindow.getEl().frame();
}
this.linkWindow.show();
},
scope: this,
tooltip: this.langTitle
});
}
});


/**
* @author Shea Frederick - http://www.vinylfox.com
* @class Ext.ux.form.HtmlEditor.SpecialCharacters
* @extends Ext.util.Observable
* <p>A plugin that creates a button on the HtmlEditor for inserting special characters.</p>
*/
Ext.ux.form.HtmlEditor.SpecialCharacters = Ext.extend(Ext.util.Observable, {
// SpecialCharacters language text
langTitle : 'Insert Special Character',
langInsert : 'Insert',
langCancel : 'Cancel',
/**
* @cfg {Array} specialChars
* An array of additional characters to display for user selection. Uses numeric portion of the ASCII HTML Character Code only. For example, to use the Copyright symbol, which is &#169; we would just specify <tt>169</tt> (ie: <tt>specialChars:[169]</tt>).
*/
specialChars: [153],
/**
* @cfg {Array} charRange
* Two numbers specifying a range of ASCII HTML Characters to display for user selection. Defaults to <tt>[160, 256]</tt>.
*/
charRange: [160, 256],
// private
chars: [],
// private
init: function(cmp){
this.cmp = cmp;
this.cmp.on('render', this.onRender, this);
},
// private
onRender: function(){
var cmp = this.cmp;
var btn = this.cmp.getToolbar().addButton({
iconCls: 'x-edit-char',
handler: function(){
if (!this.chars.length) {
if (this.specialChars.length) {
Ext.each(this.specialChars, function(c, i){
this.chars[i] = ['&#' + c + ';'];
}, this);
}
for (i = this.charRange[0]; i < this.charRange[1]; i++) {
this.chars.push(['&#' + i + ';']);
}
}
var charStore = new Ext.data.ArrayStore({
fields: ['char'],
data: this.chars
});
this.charWindow = new Ext.Window({
title: this.langTitle,
width: 436,
autoHeight: true,
layout: 'fit',
items: [{
xtype: 'dataview',
store: charStore,
ref: '../charView',
autoHeight: true,
multiSelect: true,
tpl: new Ext.XTemplate('<tpl for="."><div class="char-item">{char}</div></tpl><div class="x-clear"></div>'),
overClass: 'char-over',
itemSelector: 'div.char-item',
listeners: {
dblclick: function(t, i, n, e){
this.insertChar(t.getStore().getAt(i).get('char'));
this.charWindow.close();
},
scope: this
}
}],
buttons: [{
text: this.langInsert,
handler: function(){
Ext.each(this.charWindow.charView.getSelectedRecords(), function(rec){
var c = rec.get('char');
this.insertChar(c);
}, this);
this.charWindow.close();
},
scope: this
}, {
text: this.langCancel,
handler: function(){
this.charWindow.close();
},
scope: this
}]
});
this.charWindow.show();
},
scope: this,
tooltip: {
title: this.langTitle
},
overflowText: this.langTitle
});
},
/**
* Insert a single special character into the document.
* @param c String The special character to insert (not just the numeric code, but the entire ASCII HTML entity).
*/
insertChar: function(c){
if (c) {
this.cmp.insertAtCursor(c);
}
}
});


/**
* @author Shea Frederick - http://www.vinylfox.com
* @contributor vizcano - http://www.extjs.com/forum/member.php?u=23512
* @class Ext.ux.form.HtmlEditor.UndoRedo
* @extends Ext.ux.form.HtmlEditor.MidasCommand
* <p>A plugin that creates undo and redo buttons on the HtmlEditor. Incomplete.</p>
*/
Ext.ux.form.HtmlEditor.UndoRedo = Ext.extend(Ext.ux.form.HtmlEditor.MidasCommand, {
// private
midasBtns: ['|', {
cmd: 'undo',
tooltip: {
title: 'Undo'
},
overflowText: 'Undo'
}, {
cmd: 'redo',
tooltip: {
title: 'Redo'
},
overflowText: 'Redo'
}]
});



In my code for my formpanel I have the following:



var staticPageContentItem = {
xtype: 'form',
id: 'static_page_content-panel',
border: false,
defaults: {width: 400},
url: 'json.php',
method: 'POST',
items: [{
xtype: 'textfield',
fieldLabel: 'Content Identifier',
id: 'content_item_variable',
name: 'content_item_variable'
},{
fieldLabel: 'Content Body',
xtype: 'htmleditor',
plugins: [
new Ext.ux.form.HtmlEditor.Link(),
new Ext.ux.form.HtmlEditor.Divider(),
new Ext.ux.form.HtmlEditor.Word(),
new Ext.ux.form.HtmlEditor.FindAndReplace(),
new Ext.ux.form.HtmlEditor.UndoRedo(),
new Ext.ux.form.HtmlEditor.Divider(),
new Ext.ux.form.HtmlEditor.Image(),
new Ext.ux.form.HtmlEditor.Table(),
new Ext.ux.form.HtmlEditor.HR(),
new Ext.ux.form.HtmlEditor.SpecialCharacters(),
new Ext.ux.form.HtmlEditor.IndentOutdent(),
new Ext.ux.form.HtmlEditor.SubSuperScript(),
new Ext.ux.form.HtmlEditor.RemoveFormat()
],
id: 'content_item_body',
width: '95%',
height: 400,
name: 'content_item_body'
}],
buttons: [{
text: 'Save'
},{
text: 'Clear'
}]
}; // fn StaticPageContentItem


The editor loads without a problem, but I'm seeing no buttons appearing.

StaticPageContentItem is rendered inside a tabpanel called staticPages which is rendered inside of a card layout panel rendered inside a Viewport.

lakilevi
11 May 2010, 4:17 AM
Hello.
Thank for this amazing plugin.
For the first view I have only one single question.
Here http://www.vinylfox.com/htmleditor-plugin-updates-fixes/ writes that the plugin has the "Overflow Text" feature, so if the editor is small, than the buttons will appear as additional, as it is on the preview image.
I have to insert for this a config option? Or should work automatically?
Because for me (extjs 3.2) the rest of icons are not visible.
Thanks for your help.

sabline
19 May 2010, 7:11 AM
Those plugins are great but i have a problem i can't fix: in Firefox the insertAtCursor function cuts my absolute urls when the url are one the same domain than my htmleditor...

Example: my site is http://sub.mydomain.com and when i create a link with the Ext.ux.form.HtmlEditor.Link plugin to the picture http://sub.mydomain.com/mypicture.jpg, in Firefox, the insertAtCursorwill return "/mypicture.jpg" instead of "http://sub.mydomain.com/mypicture.jpg".... it seems to be relationated with the ExecCommand funcion

if i put another url wich is not on my domain it works fine and url is not cut...

Someone knows how i can fix this???? i really don't understant the execcommand function and why it cuts some information...

Thanks !

mschwartz
19 May 2010, 10:32 AM
Those plugins are great but i have a problem i can't fix: in Firefox the insertAtCursor function cuts my absolute urls when the url are one the same domain than my htmleditor...

Example: my site is http://sub.mydomain.com and when i create a link with the Ext.ux.form.HtmlEditor.Link plugin to the picture http://sub.mydomain.com/mypicture.jpg, in Firefox, the insertAtCursorwill return "/mypicture.jpg" instead of "http://sub.mydomain.com/mypicture.jpg".... it seems to be relationated with the ExecCommand funcion

if i put another url wich is not on my domain it works fine and url is not cut...

Someone knows how i can fix this???? i really don't understant the execcommand function and why it cuts some information...

Thanks !

This is a browser dependent bug, actually. The html cleanup code needs to know about relative and absolute URLs and do the right thing to get around this issue.

sabline
19 May 2010, 10:53 AM
i think i'm going to fix it with PHP (a replace function) because i'm not sure to understand the way the code cleans the code... Anyway thank you very much for the information because i've been searching the reasons of the problem for hours!

mschwartz
19 May 2010, 11:04 AM
It's going to be confusing for the end user, if he clicks the view source/html button in the editor and sees the wrong URL...

sabline
19 May 2010, 11:08 AM
that's right.. so i'm going to try to understand the clean code mechanism!

mschwartz
19 May 2010, 11:14 AM
You might dig through the sources to TinyMCE. It properly handles the issue.

Specifically, there's a config option for absolute or relative URLs. Unfortunately, its implementation of absolute URLs makes it put the http://host/whatever in front every time, when /whatever should work if that's what you want. With relative URLs, it turns /whatever into ../whatever

sabline
19 May 2010, 1:57 PM
thank you! i'll see tinyMCE code and i will post any solution if find

sabline
20 May 2010, 9:37 PM
i tried to understand the tinymce sources but it was a bit hard for me... I hope i'll find a cleaner solution later but for now i do that :



if(Ext.isIE) this.cmp.insertAtCursor('<img src="' + this.basePath + data.url + '" alt="" title="" >');
else this.cmp.insertAtCursor_Image(this.basePath + data.url);

and...


Ext.override(Ext.form.HtmlEditor, {
insertAtCursor_Image : function(text){
if(!this.activated){
return;
}
if(Ext.isIE){
this.win.focus();
var doc = this.getDoc(),
r = doc.selection.createRange();
if(r){
r.pasteHTML(text);
this.syncValue();
this.deferFocus();
}
}else{
this.win.focus();
this.execCmd('InsertImage', text);
this.deferFocus();
}
}
});insertAtCursor_Image is a just copy of insertAtCursor with 'InsertImage' parameter instead of 'InsertHTML'

lakilevi
3 Jun 2010, 4:45 AM
any solutions for "Owerfow text" problem?
Thanks

VinylFox
3 Jun 2010, 9:07 AM
I have not seen any issues with overflow text. Can you give some more information. Are you building the plugins from the most recent source?

lakilevi
3 Jun 2010, 9:53 PM
Hello.
Thank for this amazing plugin.
For the first view I have only one single question.
Here http://www.vinylfox.com/htmleditor-plugin-updates-fixes/ writes that the plugin has the "Overflow Text" feature, so if the editor is small, than the buttons will appear as additional, as it is on the preview image.
I have to insert for this a config option? Or should work automatically?
Because for me (extjs 3.2) the rest of icons are not visible.
Thanks for your help.

This was my original post. The problem is that I have a fixed size editor and there are not visible the buttons only if I increase the width. How can I insert the Overflow button into the toolbar?

boonkerz
7 Jul 2010, 8:16 AM
Anyone have an Flash Button implemented?

medusadelft
7 Aug 2010, 12:32 PM
Hi,

I'm having problems using the 'removeFormat' button and the Link button. But maybe it's working and it is my understanding of the buttons that is wrong ;)

When I have a link selected, I can click on the Link button (again) but the link text itself is not selected. Adding another link, it gives <a href="link"><a href="link">linktext</a> in the source. Notice the single </a> - tag.

When trying to remove this link-information, the removeFormat button doesn't remove the tags. It also doesn't remove the <br>-tag. Is that correct behaviour?

I'm only tried FF 3.6 (for now).

Thanks in advance,
Maurice.

medusadelft
7 Aug 2010, 1:22 PM
I've added a simple button/command: unlink

Select the link-text you want to remove and click the button.


Ext.ux.form.HtmlEditor.Unlink = Ext.extend(Ext.ux.form.HtmlEditor.MidasCommand, {
midasBtns: [{
enableOnSelection: true,
cmd: 'unlink',
tooltip: {
title: 'Remove Link'
},
overflowText: 'Remove Link'
}]
});

Somani
11 Aug 2010, 9:24 AM
Hi

I tried to Add some Buttons for Heading such as <h1> and so on.
i thought i can simply copy and edit the subscript/superscript section... but nothing happns when i click my new buttons...

Here is my code, any idea?:

Ext.ux.form.HtmlEditor.Heading = Ext.extend(Ext.ux.form.HtmlEditor.MidasCommand, {
// private
midasBtns: ['|', {
enableOnSelection: true,
cmd: 'h1',
tooltip: {
title: 'Headline 1'
},
overflowText: 'Headline 1'
}, {
enableOnSelection: true,
cmd: 'h2',
tooltip: {
title: 'Headline 2'
},
overflowText: 'Headline 2'
}]
});

VinylFox
11 Aug 2010, 9:51 AM
The 'cmd' config refers to a Midas command, so take a look at the specification for that - http://www.mozilla.org/editor/midas-spec.html

I see there is a 'heading' command, however it requires a 'value' argument, which the MidasCommand component cannot handle - it can only execute commands with no value.

VinylFox
11 Aug 2010, 10:07 AM
Do me a favor and try this out...Place this override directly after Ext.ux.form.HtmlEditor.MidasCommand.js is included.


Ext.override(Ext.ux.form.HtmlEditor.MidasCommand, {
onRender: function(){
var midasCmdButton, tb = this.cmp.getToolbar(), btn;
Ext.each(this.midasBtns, function(b){
if (Ext.isObject(b)) {
midasCmdButton = {
iconCls: 'x-edit-' + b.cmd,
handler: function(){
this.cmp.relayCmd(b.cmd,b.value);
},
scope: this,
tooltip: b.tooltip ||
{
title: b.title
},
overflowText: b.overflowText || b.title
};
} else {
midasCmdButton = new Ext.Toolbar.Separator();
}
btn = tb.addButton(midasCmdButton);
if (b.enableOnSelection) {
btn.disable();
}
this.btns.push(btn);
}, this);
}
});

Then change your code to be...


Ext.ux.form.HtmlEditor.Heading = Ext.extend(Ext.ux.form.HtmlEditor.MidasCommand, {
// private
midasBtns: ['|', {
enableOnSelection: true,
cmd: 'heading',
value: '<h1>',
tooltip: {
title: 'Headline 1'
},
overflowText: 'Headline 1'
}, {
enableOnSelection: true,
cmd: 'heading',
value: '<h2>',
tooltip: {
title: 'Headline 2'
},
overflowText: 'Headline 2'
}]
});

I havent tested this, so let me know if it works.

Somani
11 Aug 2010, 11:51 AM
Hey

Great work! Works as inteded!
Does not Work with IE, but this is because of MS does not support those Type of Blockformatting..

Thanks for your help!

VinylFox
11 Aug 2010, 12:08 PM
Ill see if I can find a fix for IE, and thanks for testing this.

Somani
11 Aug 2010, 12:25 PM
I changed the following to seperate the icons (u where using the b.cmp component)



onRender: function ()
{
var midasCmdButton, tb = this.cmp.getToolbar(), btn;
Ext.each(this.midasBtns, function (b)
{
if (Ext.isObject(b))
{
if (Ext.isDefined(b.icon))
{
var iconCls = 'x-edit-' + b.icon
} else
{
var iconCls = 'x-edit-' + b.cmd
}
midasCmdButton = {
iconCls: iconCls,
handler: function ()
{
this.cmp.relayCmd(b.cmd, b.value);
},
scope: this,
tooltip: b.tooltip ||
{
title: b.title
},
overflowText: b.overflowText || b.title
};
} else
{
midasCmdButton = new Ext.Toolbar.Separator();
}
btn = tb.addButton(midasCmdButton);
if (b.enableOnSelection)
{
btn.disable();
}
this.btns.push(btn);
}, this);
},
Further i'll try to create a Dropdown for this function, but step by step ;) It's a whole new World for me ;)

Greez

VinylFox
13 Aug 2010, 6:21 AM
@Somani - Try this out...

http://github.com/VinylFox/ExtJS.ux.HtmlEditor.Plugins/blob/6986ec4e3ca2ccac6358a870eaf6cf7bb4fb23cb/src/Ext.ux.form.HtmlEditor.Heading.js

Also check out the other updates I posted, like the changes to MidasCommand and Heading.

Somani
17 Aug 2010, 2:45 AM
Hi VinylFox

Works fine. All of your posted changes.
From hour to hour i like ext more and more ;)

Thank you!

Greez

lakilevi
30 Sep 2010, 1:57 AM
Hello,
I am using Ext.ux.form.HtmlEditor.Link and recently I met some problems.
In FireFox I when I select a word, and put a link on it all goes fine. But if I try to add a link to one more word (so open for second time the Link insert tool) than I got an error:

this.dom is undefined

Ext.DomHelper=function(){var w=null,k=...lclick",this.onNodeDblClick,this)}});

Also the same error I got in IE8. But in IE8 there is another thing: if I select a word and want to insert a Link on it, than the new link jumps to the beginning of the text.

Thank you very much for your help!

VinylFox
30 Sep 2010, 2:29 AM
Yeah, Link can be quirky sometimes, it has some problems to be worked out. Are you using my override for getSelectedText?

Feel free to track down the problem and offer solutions, im more than willing to accept help.

despay
5 Oct 2010, 6:27 PM
Hi there,

I'm interested in tweaking the fixWordPaste() method because it doesn't work correctly for me. It seems to strip out any span tags and style attributes but in ms word these tags are what apply the colour and size. It was easy enough to stop stripping Span and style but what I want to do is only allow style attributes which are within span tags and strip all other style attributes. Can you help?

Another issue is - if Ctrl+v is clicked quickly it is not picked up by the code at all and so the text doesn't get stripped.

Cheers,
Mel

VinylFox
6 Oct 2010, 4:40 AM
That sounds like a great idea, and I would love to add your suggested change, but my RegEx skills suck. If you want to give it a shot and let me know what to change, I will integrate it.

I have seen reports on ctrl-v not working sometimes, I think it has to do with focus on the htmleditor, but it's not been a priority for me to fix at this point.

despay
6 Oct 2010, 4:43 PM
Hi,

To get around the problem of the Ctr+v not working, i've stopped using the wordpaste wand icon and have done the wordpaste strip on clicking "ok" on the rich text editor window. This way the result is always the same.

To get around the incorrect character stripping I've just taken what another member has suggested earlier and made a few tweaks:

(This can be probably be done in a more efficient way but I'm not that profficient in regex either so suggestions are welcome)



function fixWordPaste(wordPaste) {

wordPaste = wordPaste.split('\n')
wordPaste = wordPaste.join(' ');
var removals = [
/^<\/?meta.*?>/ig,
/<\/?meta.*?>/ig,
/<\/?link.*?>/ig,
/<(xml|style)[^>]*>.*?<\/\1>/ig,
/<\/?(meta|object)[^>]*>/ig,
/<\/?[A-Z0-9]*:[A-Z]*[^>]*>/ig,
/(lang|class|type|href|name|title|id|clear)=\"[^\"]*\"/ig,
/<![\[-].*?-*>/g,
/MsoNormal/g,
/<\\?\?xml[^>]*>/g,
/<\/?o:p[^>]*>/g,
/<\/?v:[^>]*>/g,
/<\/?o:[^>]*>/g,
/<\/?st1:[^>]*>/g,
/&nbsp;/g,
/<\/?STRONG[^>]*>/g,
/<\/?H1[^>]*>/g,
/<\/?H2[^>]*>/g,
/<\/?H3[^>]*>/g,
/<\/?H4[^>]*>/g,
/<\/?H5[^>]*>/g,
/<\/?H6[^>]*>/g,
/<\/?P[^>]*><\/P>/g,
/<!--(.*)-->/g,
/<!--(.*)>/g,
/<!(.*)-->/g,
/<\\?\?xml[^>]*>/g,
/<\/?o:p.*?>/g,
/<\/?v:[^>]*>/g,
/<\/?o:.*?>/g,
/<\/?st1:[^>]*>/g,
/lang=\"[^\"]*\"/g,
/lang=\'[^\"]*\'/g,
/class=\"[^\"]*\"/g,
/class=\'[^\"]*\'/g,
/type=\"[^\"]*\"/g,
/type=\'[^\"]*\'/g,
/href=\'#[^\"]*\'/g,
/href=\"#[^\"]*\"/g,
/name=\"[^\"]*\"/g,
/name=\'[^\"]*\'/g,
/ clear=\"all\"/g,
/id=\"[^\"]*\"/g,
/title=\"[^\"]*\"/g,
/mso.*?(?=\")/g,
/class=/g
];

wordPaste = wordPaste.replace(/&nbsp\;/img, ' '); // white-space must be removed first
wordPaste = wordPaste.replace(/\r\n/img, '\n'); // white-space must be removed first
wordPaste = wordPaste.replace(/[\r\n]/img, ' '); // white-space must be removed first

Ext.each(removals, function(s) {
wordPaste = wordPaste.replace(s, "");
});

// Remove style attributes from all tags except SPAN
wordPaste = wordPaste.replace(/<B[^>]*>/g, "<B>");
wordPaste = wordPaste.replace(/<P[^>]*>/g, "<P>");
wordPaste = wordPaste.replace(/<I[^>]*>/g, "<I>");
wordPaste = wordPaste.replace(/<TR[^>]*>/g, "<TR>");

// Keep the divs in paragraphs
wordPaste = wordPaste.replace(/<div[^>]*>/g, "<p>");
wordPaste = wordPaste.replace(/<\/?div[^>]*>/g, "</p>");
return wordPaste;

}

timriedel
20 Oct 2010, 11:52 PM
Hi VinyFox,

great plugins!

I came around a lot of problems when using the regex to clean clipboard data from Word. So I tried a different approach - in the case you just need clean text without images or formatting:

1. create a hidden textarea in the document the editor is placed
2. redirect the paste to the textarea
3. grab the cleaned text from the textarea and put it back in the editor.

By the paste in the html textarea nearly every trash is cleaned up....

Seems to work fine in FF and IE6/7/8 - didn't get it to work in Chrome....



var editor = new Ext.form.HtmlEditor({
renderTo : elPlaceHolder
,id : 'objContentEditorText'
,width : Ext.getCmp('cmpEditorWindow').getInnerWidth()
,height : 340
,defaultButton : true
,enableFont : false
,enableFontSize : false
,enableLinks : false
,value : strText
,plugins: [
new Ext.ux.form.HtmlEditor.SubSuperScript()
,new Ext.ux.form.HtmlEditor.Divider()
,new Ext.ux.form.HtmlEditor.eyekitRemoveLinkage()
,new Ext.ux.form.HtmlEditor.Divider()
,new Ext.ux.form.HtmlEditor.SpecialCharacters()
,new Ext.ux.form.HtmlEditor.Divider()
,new Ext.ux.form.HtmlEditor.IndentOutdent()
,new Ext.ux.form.HtmlEditor.RemoveFormat()
]
,listeners: {

initialize: function(cmp) {

Ext.DomHelper.append(
Ext.getBody()
,{
id : 'wordCleaner'
,tag : 'textarea'
,style : 'position: absolute; left: -10000px; top: -10000px;'
}
);

Ext.EventManager.on(cmp.getDoc(), {
'keydown': function(e){
if (e.V == e.getKey() && e.ctrlKey) {
document.getElementById('wordCleaner').focus();
var t = setTimeout("Ext.getCmp('objContentEditorText').insertAtCursor(document.getElementById('wordCleaner').value)", 10);
document.getElementById('wordCleaner').value = '';
}
}
});
}
}
}); Bye, Tim

VinylFox
21 Oct 2010, 4:00 AM
Interesting concept, ill have to give it a try.

This strips ALL of the html though, correct?

timriedel
21 Oct 2010, 4:06 AM
Right - it strips all html....

rpnoble
10 Dec 2010, 6:17 PM
vinylfox;

Love the plugins, but I stll have the issue of the cursor returning to the top of the textarea after the insert HR, Table or Special Character. I have added the override described in this thread. I was wondering if upgrading to 3.3 would help?

rpnoble
10 Dec 2010, 9:01 PM
vinylfox;

Love the plugins, but I stll have the issue of the cursor returning to the top of the textarea after the insert HR, Table or Special Character. I have added the override described in this thread. I was wondering if upgrading to 3.3 would help?

I think I found a solution to the insertAtCursor issue with IE8. Can someone please confirm my test.

Using the insertAtCursor override code from a thread at EXT.net as my base this is what I came up with. See the lines in RED...



Ext.override(Ext.form.HtmlEditor, {
onEditorEvent: function() {
if (Ext.isIE) {
this.currentRange = this.getDoc().selection.createRange();
// alert(this.currentRange)
}
this.updateToolbar();
},
insertAtCursor: function(text) {

if(!this.activated){
return;
}
if (Ext.isIE) {
this.win.focus();
var r = this.currentRange || this.doc.selection.createRange();

if (r) {

r.collapse(true);
r.pasteHTML(text);
this.syncValue();
this.deferFocus();

//these three lines will return the cursor to the right
// of the insert
r.moveEnd('character', 0);
r.moveStart('character', 0);
r.select();


}
} else if (Ext.isGecko || Ext.isOpera|| Ext.isChrome) {
this.win.focus();
this.execCmd('InsertHTML', text);
this.deferFocus();
} else if (Ext.isWebKit) {
this.execCmd('InsertText', text);
this.deferFocus();
}
}
}
);


by adding these three lines, my IE8 is behaving just like FF and chrome. I have tested it using the following plugins:
Ext.ux.form.HtmlEditor.Table
Ext.ux.form.HtmlEditor.HR
Ext.ux.form.HtmlEditor.SpecialCharacters

yugikhoi
11 Apr 2011, 2:08 AM
I cannot make it works with extjs 3.3. All button and icon render to htmleditor correctly but not function, anyone try it and found solution. Thanks.

renaudham
18 Apr 2011, 2:57 AM
Hi

Is there any plan to make it work for EXTJS4?

VinylFox
18 Apr 2011, 9:45 AM
Not currently - The code is available on Github, so feel free to fork it and make the fixes yourself.

jstockdale
2 May 2011, 12:20 PM
How would you go about adding a save button to the HTML Editor which uses a Ext.data.Store? The autoSave doesn't always work right.

VinylFox
2 May 2011, 12:51 PM
How would you go about adding a save button to the HTML Editor which uses a Ext.data.Store? The autoSave doesn't always work right.

autoSave? I'm not familiar with this option - where have you seen this?

jstockdale
2 May 2011, 12:59 PM
I'm sorry, I'm referring to the autoSave function in the data.store.

I'm using an EditorGridPanel and using the HtmlEditor as a renderer for a "Notes" field. When you click on the cell your HtmlEditor replaces the cell and when you click off of the cell the datastore autoSaves. But I've been getting complaints recently about the "notes" field not saving so I wanted to add a Save button to the HtmlEditor to make sure their data is saved.

oxo
4 Jul 2011, 7:26 AM
Hi everybody.
I am new to EXTJS.
I have a problem with an HTML Editor.
When I suppress the value within the field and save de field, on IE , I get a '&nbsp<br>' value instead of a blank value.

Could someone help me ?

Thank's a lot.

lorezyra
5 Jul 2011, 12:31 AM
Hi everybody.
I am new to EXTJS.
I have a problem with an HTML Editor.
When I suppress the value within the field and save de field, on IE , I get a '&nbsp<br>' value instead of a blank value.

Could someone help me ?

Thank's a lot.


Just post process it on the back-end. This is a known bug. If you see this blank value then reset the string to ""...

joeri
24 Nov 2011, 2:39 PM
I've made a port of the gmail feature where in chrome and firefox you can paste an image directly (without having to upload it first).

The image gets pasted as a data uri. It's up to the developer to handle that correctly server-side. Works in recent chrome and firefox (not sure about other browsers).


Ext.ns('Ext.ux.form.HtmlEditor');
Ext.ux.form.HtmlEditor.ImagePaste = Ext.extend(Ext.util.Observable, {
validMimeTypes: ['image/png', 'image/jpg', 'image/jpeg', 'image/gif'],
init: function(cmp){
this.cmp = cmp;
this.cmp.on('initialize', this.onInit, this, {delay:100, single: true});
},
onInit: function(){
Ext.EventManager.on(this.cmp.getDoc(), {
paste: this.doPaste,
scope: this
});
},
doPaste: function(e){
var clipboardData = e.browserEvent.clipboardData;
if (clipboardData && clipboardData.items && (clipboardData.items.length >= 1)) {
var item = clipboardData.items[0];
if (this.validMimeTypes.indexOf(item.type) >= 0) {
var blob = item.getAsFile();
var reader = new FileReader();
var scope = this;
reader.onload = function(event){
var cmp = scope.cmp;
var datauri = event.target.result;
if (datauri) cmp.insertAtCursor('<img border="1" style="border-width: 0;" src="'+datauri+'">');
};
reader.readAsDataURL(blob);
};
};
}
});

29556

Dumbledore
17 Dec 2011, 12:51 AM
@joeri

interesting plugin. But it seems that my Firefox (8.0.1) inserts a image inline also without the plugin!?

Dumbledore
17 Dec 2011, 12:56 AM
maybe someone will find it useful... here are the plugins from vinylfox for extjs4:

30107

zillabyte
13 Jan 2012, 6:49 PM
Thanks for your work on upgrading this to Ext 4. I'll give them a try

bas_tzx
19 Jan 2012, 7:40 AM
maybe someone will find it useful... here are the plugins from vinylfox for extjs4:

30107

Thanks, can you give us some more information about the files? Are you the one who made the port for ExtJS4? And it seems that some plugins are not completely finished yet?

Nevertheless, these plugins are a great starting point for your own implementation. :)

ateodorescu
23 Apr 2012, 12:09 PM
Checkout the plugins for extjs4 here: http://www.sencha.com/forum/showthread.php?183024-Ext.ux.form.plugin.HtmlEditor