OpenWiki

Wiki Editor



Edit this page (last edited May 22, 2010)
Open Wiki | Sand Box | Create Page | User Preferences | Recent Changes | Title Index | Random Page | Find Page | Help
» Help On Formatting » Scripting News » Sand Box » All The News/Aggregation » Wiki Editor

See now the Open Wiki NG tracker item for this where all the real Open Wiki developement is taking place WikiEditor

Paul Robertson 27 March 2004

  • WikiEditor
  • Why?
  • Use this in your Wiki
  • How implemented
  • Something borrowed
  • Work required
  • What has been done so far
  • The code
  • Integrating the Code with OpenWiki
  • Integration with OpenWiki
  • Put editor at top and bottom of textarea
  • Setting width of menu to match textarea width set through preferences.
  • Make the WikiEditor an option through preferences
  • Add save, preview and cancel options to the menu.
  • Additional Features
  • Bug fix
  • Spell check
  • Find in page and Find / Replace
  • Initializing at the end of the textarea
  • Populating drop down menu?s from OpenWiki
  • Adding useful menu items to a 'right click' menu
  • Perhaps adding keyboard accelerators for some of the functions.
  • Adding an 'emotions' pop up with emotions available in OpenWiki
  • Adding support for the rest of the OpenWiki mark up codes, i.e. numbered lists etc.
  • Making it easier to link to other Wiki pages.
  • Put in context help
  • Fit & Finish
  • Tidy up presentation, see RichText editor for an example of how the editor could look
  • Rationalise the use of buttons
  • Mozilla Compatibility
  • Comments
  • WikiEditor

    Why?

    Open Wiki is designed to be the ultimate yellow sticky note (Post it) (see Open Wiki intro) and it is one of the better Wiki implementations around. Adding content needs to be easy and the use of plain text is certainly a good start. However, Open Wiki markup is slightly cryptic. A simple editor that lets you paste in the necessary markup code would help. I have started work on one; see here for the prototype. This editor is not designed for HTML style markup as used in Edit WYSIWYG Wiki's.

    Unfortunately I am a lame programmer and xsl confuses the heck out of me. My attempt to get the code I have written integrated with Open Wiki has failed so far but I don’t believe that there is too much wrong. I invite other users of Open Wiki to pick up the cudgels and get this code working (or suggest a suitable alternative)

    The code is attached to this page.

    Adopted the "eating my own dogfood" approach, this entry was written using Wiki Editor. 1

    I am disappointed that no one has jumped in to show me 
    what needs to be done to get this code running properly!
    
    
    The ANSWERS have arrived!!!   ;)  ( Thanks for the great start )
    
    

    Paul Robertson 29 January 2004

    Use this in your Wiki

    Download Source from http://www.kiddo.co.nz/wiki/wikieditor.html
      Source Zip contains: editor.css and htmledit_test.html (and toolber GIFs)
    

    1. Put the editor.css file with the other css files at <yourwikidir>\ow\css
    2. Add a reference to the editor.css file in mystyle.xsl

    3. <link rel="stylesheet" type="text/css" href="ow/css/editor.css" />
      
    4. Start a subdirectory for the Wiki Editor icons in <yourwikidir>\ow\images\editicons
    5. Put the icons in the directory.
    6. Modify the following template in ow.xsl:

    7. <xsl:template match="/ow:wiki" mode="edit">
      1. Cut and paste functions from htmledit_test.html into the script
      2. Cut and paste the <div id="toolbar"> contents from htmledit_test.html above the <textarea id="text" name="text"... tag
      3. Update img src links in the toolbar div to point to your toolbar graphics

      Thanks Paul and all, works great! -- Oddible 04Jun05
    

    How implemented

    Something borrowed

    I drew on code from all over, but mainly the O?Reilly Network article by Meg Houriham at http://www.oreillynet.com/pub/a/javascript/2001/12/21/js_toolbar.html

    I followed her lead and used JavaScript? strings to put together the Wiki markup tags required.

    The more sophisticated Rich Text Editor at http://richtext.sourceforge.net/ has much useful code not used yet.

    I Googled for any code I couldn?t work out myself but found the Webmonkey tutorial ? Thau?s Javascript Tutorial on forms very helpful.

    This article was also very useful: The Select Element

    The idea to use the SELECTED attribute of an OPTION element to show what is the default choice is taken from http://www.faqts.com/knowledge_base/view.phtml/aid/6025

    Work required

    What has been done so far

    See here for a page with the code as it is running. http://www.kiddo.co.nz/wiki/wikieditor.html

    The code

    The following extracts are based on the current version of the code, kudos to Michael Meyers and Ola  and others
    
    

    The buttons are a series of images with the mouseover() etc events controlling the CSS changes and the onclick() event the formatting actions.

    <form name="f">
    <div id="toolbar">
    
    <img class="button" 
     onmouseover="mouseover(this);" 
     onmouseout="mouseout(this);" 
     onmousedown="mousedown(this);" 
     onmouseup="mouseup(this);" 
     onclick="insert_code('<BR>');" 
     src="code.gif"
     width="20" height="21" 
     align="middle" 
     alt="Line break">
    </img>
    

    The insert_code() function is one of a handful that insert the Open Wiki markup.

    // function insert_code(v)
    // Inserts wiki code at selected point
    
    function insert_code(v) {
              var str = document.selection.createRange().text;
              document.f.text.focus();
             var sel = document.selection.createRange();
             //sel.text =  v + " ";
            sel.text =  unescape(v);
    
              return;
    }
    

    Since first writing the above I have taken note of the helpful comments of others and updated the code. Two examples follow;

    // function format_sel(s,e)
    // Inserts Wiki markup around selected text
    
    function format_sel(s,e) {
              var str = document.selection.createRange().text;
              //added to remove white space at start or end
               reg=/^ *(\S.*\S) *$/;
            str=str.replace(reg,"$1");
              document.f.text.focus();
              var sel = document.selection.createRange();
              //sel.text = s + str + e ;
            sel.text = unescape(s) + str + unescape(e) ;
              return;
    }
    

    I have added some regex stuff to take out leading and trailing spaces. This was necessary as when you 'double click' some text to select it, IE has a habit of selecting a white space or two outside the selection.

    //
    // from various places, funtion to turn selected text into leading capitals.
    //
    function capitalise() {
    
              var str = document.selection.createRange().text;
              document.f.text.focus();
              var sel = document.selection.createRange();
            str=str.toLowerCase();
            str=str.replace(/\b\w+\b/g, function(word) {
            return word.substring(0,1).toUpperCase()+ word.substring(1);
    });
    sel.text=str
    return;
    }
    

    The above function is to help with a particular problem we have, turning all caps text into leading caps.

    The function would be even more useful if it 'cycled' through leading caps, all caps, all lower case like Word does...

    Other changes made since the first iteration of the code include:

    onclick="insert_code('\n----\n');"
     

    Integrating the Code with OpenWiki

    I tried have now successfully integrated it with Open Wiki by:

    1. Putting the editor.css file with the other css files at C:\OpenWiki\owbase\ow\css
    2. Adding a reference to the editor.css file in mystyle.xsl
    3. Starting a subdirectory for the Wiki Editor icons in C:\OpenWiki\owbase\ow\images\editicons
    4. Putting the icons in the directory.
    5. Putting the resulting html including the JavaScript?(in a <script> element)
    through the HTML Tidy page with xml as the desired output.
    1. Pasting the html code in ow.xsl just before the textarea in the
    editing section of the code, and the JavaScript? at the end of the code section (changed path names to point to image files as necessary).
    1. Modified code to make it xsl friendly.

    This isnnt working and I need thanks for the help.

    Once integrated I have some other features I want to implement as below.

    Anyway, suggestions please?

    This is a great start. I was able to get it all working fairly easily. (Using the code from http://www.kiddo.co.nz/wiki/wikieditor.html and not the files attached) The .xsl cannot have any nested < or >. In order to get the code you listed above to work chage it as follows:
    <form name="f">
    <div id="toolbar">
    
    <img class="button" 
     onmouseover="mouseover(this);" 
     onmouseout="mouseout(this);" 
     onmousedown="mousedown(this);" 
     onmouseup="mouseup(this);" 
     onclick="insert_code(' %3CBR%3E ');" 
     src="code.gif"
     width="20" height="21" 
     align="middle" 
     alt="Line break">


    Use this tecnique throughout your code.
    %3C = < when unescape'ed
    %3E = > when unescape'ed

    function insert_code(v) {
              var str = document.selection.createRange().text;
              document.f.text.focus();
             var sel = document.selection.createRange();
             sel.text =  unescape(v) + " ";
              return;
    }
    
    


    Also:



    I think that is all of it. The placement of the code in the .xsl files is as you suggested. I removed the spell checking code. It will not work if your security settings are set too tight. (They are for our Wiki, and I've allready tryed to get spell checking working ) Also, I have played with the ActiveXObject?("Word.Application") and spell checking before and discovered that it tends to leave multiple copies of word running in the background ( check your running programs after a few spell checks).

    - Michael Meyers



    I added some new functionality for Indent, Unindent, bullet, and numbered list... Just add in the appropriate places.

    Additional functionality for script.js
    
    function indent_line( s){
      var new_s="";
      if ( s.match(/^\s+$/) ) return s;
      if ( s.match(/^\s*:|^\s*\*\s|^\s*[0-9]+\.|^\s*[a-z]+\.|^\s*;/) ){
         new_s = "  " + s;
      }else{
         new_s = "  : " + s;
      }
    
      return new_s;
    }
    
    
    
    function dd_indent()
    {        
     
             var str = document.selection.createRange().text;
             var sel = document.selection.createRange();
             if ( !str.length ) return;
             if ( str.indexOf( "\n" ) == -1 ){
               sel.text = indent_line( str ) + "\n";  
               return;
             }
    
             var new_str = "";
             str += "\n";
             pos = str.indexOf( "\n" );
              
             do {
    
               line = str.substring( 0, pos );
               new_str += indent_line( line );
               str = str.substring( pos+1 , str.length );
               pos = str.indexOf( "\n" );
    
             }while( pos != -1 );
                 
             // process remainder
             if ( str.length != 0 ) {
               new_str += indent_line( str )  ;
             }else{
               new_str += "\n";
             } 
    
    
             sel.text =  new_str;
    
    }
    
    
    
    function unindent_line( s){
      var new_s="";
      if ( s.match(/^(\s+)/)  ){
         len = RegExp.$1.length ;
        
         if ( len == 1 ) {
           if( s.match(/^ |^\t/) ) {
             new_s = new_s = s.substring( 1, s.length );
           }else{
             new_s = s;
           }
    
         }else if ( len == 2 ){
           new_s = s.substring( 1, s.length );     
         }else{
           new_s = s.substring( 2, s.length );
         }
      }else{
         new_s = s;
      }
    
      return new_s;
    }
    
    
    
    
    function dd_unindent()
    {        
     
             var str = document.selection.createRange().text;
             var sel = document.selection.createRange();
             if ( !str.length ) return;
             if ( str.indexOf( "\n" ) == -1 ){
               sel.text = unindent_line( str ) + "\n";  
               return;
             }
    
             var new_str = "";
             str += "\n";
             pos = str.indexOf( "\n" );
              
             do {
    
               line = str.substring( 0, pos );
               new_str += unindent_line( line );
               str = str.substring( pos+1 , str.length );
               pos = str.indexOf( "\n" );
    
             }while( pos != -1 );
                 
             // process remainder
             if ( str.length != 0 ) {
               new_str += unindent_line( str )  ;
             }else{
               new_str += "\n";
             } 
    
    
             sel.text =  new_str;
    
    }
    
    
    
    // re-clicking button cycles through styles 1. --> a. -->  1.  ect...
    function number_list( s){
      var new_s="";
      if ( s.match(/^(\s+):/) ){
         len = RegExp.$1.length ;
         new_s = RegExp.$1 + "1." + s.substring( len + 1 , s.length );
      }else if( s.match(/^(\s+)\*\s/) ){
         len = RegExp.$1.length ;
         new_s = RegExp.$1 + "1." + s.substring( len + 1 , s.length );
      }else if( s.match(/^(\s+)([0-9]+)\./) ){
         len = RegExp.$1.length ;
         new_s = RegExp.$1 + "a" + s.substring( len + RegExp.$2.length , s.length );
      }else if( s.match(/^(\s+)([a-z]+)\./) ){
         len = RegExp.$1.length ;
         new_s = RegExp.$1 + "1" + s.substring( len + RegExp.$2.length , s.length );
      }else if( s.match(/^(\s+);/) ){
         len = RegExp.$1.length ;
         new_s = RegExp.$1 + "1." + s.substring( len + 1 , s.length );
      }else{
         new_s = "  1. " + s;
      }
    
      return new_s;
    }
    
    
    
    
    
    function dd_numberlist()
    {        
     
             var str = document.selection.createRange().text;
             var sel = document.selection.createRange();
             if ( !str.length ) return;
             if ( str.indexOf( "\n" ) == -1 ){
               sel.text = number_list( str ) + "\n";  
               return;
             }
    
             var new_str = "";
             str += "\n";
             pos = str.indexOf( "\n" );
              
             do {
    
               line = str.substring( 0, pos );
               new_str += number_list( line );
               str = str.substring( pos+1 , str.length );
               pos = str.indexOf( "\n" );
    
    
    
    
             }while( pos != -1 );
                 
             // process remainder
             if ( str.length != 0 ) {
               new_str += number_list( str )  ;
             }else{
               new_str += "\n";
             } 
    
    
             sel.text =  new_str;
    
    }
    
    
    
    
    
    
    function bullet_list( s){
      var new_s="";
      if ( s.match(/^(\s+):/) ){
         len = RegExp.$1.length ;
         new_s = RegExp.$1 + "*" + s.substring( len + 1 , s.length );
      }else if( s.match(/^(\s+)\*\s/) ){
         len = RegExp.$1.length ;
         new_s = RegExp.$1 + "*" + s.substring( len + 1 , s.length );
      }else if( s.match(/^(\s+)([0-9]+\.#?[0-9]*)/) ){
         len = RegExp.$1.length ;
         new_s = RegExp.$1 + "*" + s.substring( len +  RegExp.$2.length  , s.length );
      }else if( s.match(/^(\s+)([a-z]+\.#?[0-9]*)/) ){
         len = RegExp.$1.length ;
         new_s = RegExp.$1 + "*" + s.substring( len + RegExp.$2.length, s.length );
      }else if( s.match(/^(\s+);/) ){
         len = RegExp.$1.length ;
         new_s = RegExp.$1 + "*" + s.substring( len + 1 , s.length );
      }else{
         new_s = "  * " + s;
      }
    
      return new_s;
    }
    
    
    
    
    
    function dd_bulletlist()
    {        
     
             var str = document.selection.createRange().text;
             var sel = document.selection.createRange();
             if ( !str.length ) return;
             if ( str.indexOf( "\n" ) == -1 ){
               sel.text = bullet_list( str ) + "\n";  
               return;
             }
    
             var new_str = "";
             str += "\n";
    
             pos = str.indexOf( "\n" );
              
             do {
    
               line = str.substring( 0, pos );
               new_str += bullet_list( line );
               str = str.substring( pos+1 , str.length );
               pos = str.indexOf( "\n" );
    
             }while( pos != -1 );
                 
             // process remainder
             if ( str.length != 0 ) {
               new_str += bullet_list( str )  ;
             }else{
               new_str += "\n";
             } 
    
    
             sel.text =  new_str;
    
    }
    
    
    
    

    Buttons for new functionality
    
    <img class="button" 
     onmouseover="mouseover(this);" 
     onmouseout="mouseout(this);" 
     onmousedown="mousedown(this);" 
     onmouseup="mouseup(this);" 
     onclick="dd_indent();" 
     src="UI_indent.gif"
     width="24" height="25" 
     align="middle" 
     alt="Indent"></img>
    
    
    <img class="button" 
     onmouseover="mouseover(this);" 
     onmouseout="mouseout(this);" 
     onmousedown="mousedown(this);" 
     onmouseup="mouseup(this);" 
     onclick="dd_unindent();" 
     src="UI_outdent.gif"
     width="24" height="25" 
     align="middle" 
     alt="Un-indent"></img>
    
    <img class="button" 
     onmouseover="mouseover(this);" 
     onmouseout="mouseout(this);" 
     onmousedown="mousedown(this);" 
     onmouseup="mouseup(this);" 
     onclick="dd_numberlist();" 
     src="UI_numberlist.gif"
     width="24" height="25" 
     align="middle" 
     alt="Number"></img>
    
    
    <img class="button" 
    
     onmouseover="mouseover(this);" 
     onmouseout="mouseout(this);" 
     onmousedown="mousedown(this);" 
     onmouseup="mouseup(this);" 
     onclick="dd_bulletlist();" 
     src="UI_bulletlist.gif"
     width="24" height="25" 
     align="middle" 
     alt="Bullet"></img>
    
    



    - Michael Meyers


    Integration with OpenWiki

    I would like to add the following features

    Put editor at top and bottom of textarea

    Or else it scrolls out of view.

    Setting width of menu to match textarea width set through preferences.

    Make the WikiEditor an option through preferences

    Add save, preview and cancel options to the menu.

    Additional Features

    Bug fix

    IE 5.5 onwards sometimes displays a stale copy of the textarea, so that text from a previous editing session is displayed. I have taken to refreshing the page before editing.

    Anyone have a fix for this? It might be necessary to 'refresh' the textarea as part of inialising the editing view.

    I fixed this on the client, see Internet Explorer6 Wont Let Me Edit Wiki Pages

    This may be a better fix which involves changing the Wiki code http://www.openwiki.com/ow.asp?OpenWiki%2FBugs#h85

    Spell check

    See the following starting point (spellold()) that relies on MS Word being installed. The cut and paste functions are not working properly, I suspect it is a scripting security issue in my browser (IE6). I have tried using WSH as per the example , and MS execCommand() as used in other MS based editors (see spell()). Both approaches not working.

    function spellold()
    {
    var tempval=eval(document.f.text);
    tempval.focus();
    tempval.select();
    if (document.all&&copytoclip==1){
    therange=tempval.createTextRange();
    therange.execCommand("Copy");
    window.status="Contents highlighted and copied to clipboard!";
    setTimeout("window.status=''",1800);
    }
    oWord = new ActiveXObject ("Word.Application");
    oWord.Visible = true;
    oWord.Documents.Add();
    oWord.Selection.Paste();
    oWord.ActiveDocument.CheckSpelling();
    oWord.Selection.WholeStory();
    oWord.Selection.Copy();
    oWord.ActiveDocument.Close(0);
    oWord.Quit();
    var tempval=eval(document.f.text);
    tempval.focus();
    tempval.select();
    therange=tempval.createTextRange();
    therange.execCommand("Paste");
    

    function spell_old()
    {
    oShell=new ActiveXObject ("WScript.Shell");
    oShell.SendKeys ("^c"); //copy
    oWord = new ActiveXObject ("Word.Application");
    oWord.Visible = true;
    oWord.Documents.Add();
    oWord.Selection.Paste();
    oWord.ActiveDocument.CheckSpelling();
    oWord.Selection.WholeStory();
    oWord.Selection.Copy();
    oWord.ActiveDocument.Close(0);
    oWord.Quit();
    var nRet = oShell.Popup ("Apply changes?\nClick OK to replace all selected text.", 0, "Spell Check Complete", 33);
    if (nRet==1) {oShell.SendKeys ("^v");}
    

    See Select and Copy form element script for a fuction that at least copies all the text in the textarea....

    It would be preferable to use a server based spell check instead of this Word kludge. See http://spellerpages.sourceforge.net/ I haven't looked at the solution used by Rich Text Editor .

    Find in page and Find / Replace

    It is awkward jumping to a particular part of the textrange, especially if it is near the end. The brower find feature is OK, but it would be preferable to have this built in. Similarly, a search / replace function would be great. See here for a code eg http://www.dynamicdrive.com/dynamicindex11/findpage.htm

    I envisage a popup with the options like in Word.

    Initializing at the end of the textarea

    It is annoying that the edit page always opens with the flashing cursor at the top, when you usually want to add something to the bottom of the page. It should be reasonably straightforward to make a selection out of the textarea as part of initialising the page, and then position the caret at the end ready for editing.

    A more sophisticated option would be to save the position of the caret / cursor when the text is saved as a cookie ( or with one of MS?s other options) and then put the cursor back there when you next try and edit.

    Populating drop down menu?s from OpenWiki

    I.e. getting the Inter Wiki names from the table of these names in the Open Wiki database.

    Adding useful menu items to a 'right click' menu

    Having the most commonly used editing features available through a 'right click' context menu would be helpful. See here for an e.g. of the code. http://markl.f2o.org/Right_Click_Menu.html

    Perhaps adding keyboard accelerators for some of the functions.

    Adding an 'emotions' pop up with emotions available in OpenWiki

    This is part of many html editors, i.e. see this feature in the Rich Text Editor

    Adding support for the rest of the OpenWiki mark up codes, i.e. numbered lists etc.

    Again, see the features in the Rich Text Editor

    Making it easier to link to other Wiki pages.

    Its all very well that you can enter any Camel Case word and generate a link to a new Wiki page. But when you want to link to a specific page and you can?t remember its name you find yourself in a bind. At present I open another browser page (Control + N in IE) and search for the other page, switch back to the page I was working on and add the appropriate link. This is too awkward.

    One idea is to generate a list of Wiki pages on the fly from that you can choose from, much like the pop up page of emotions some editors provide.

    Put in context help

    Perhaps DHTML type tool tips. Doesn't seem to be a way to add tool tips to <select> <option> elements though in IE as they don't fire a mouseover() event. [1] [2]

    Anyway, smart DHTML tooltips like this would be grand [3]

    A thought would be to display the relevant page from Open Wiki which has the help information in a popup page...

    Fit & Finish

    Tidy up presentation, see RichText editor for an example of how the editor could look

    Rationalise the use of buttons

    Ie put the subscript and superscript functions into the heading drop down.

    Help!!! (and thanks so far)


    Mozilla Compatibility

    I added some javascript to make script.js work in mozilla. Actually, that's not entirely true. Things like bold and italic work. The copy functionality does not work (yet). Mozilla doesn't support document.selection. This means that for every instance of the line:

    var sel = document.selection.createRange();
    

    it needs to be replaced with an if statement and mozilla code in addition to the IE code. What I did instead was write a getSelection() function that figures out what should be done. Each time the selected text is necessary, just call that function and it will work. The drop downs work in mozilla now too.

    This code:

    var sel = document.selection.createRange();
    sel.text =  v + " ";
    

    Is IE specific and can be replaced with:

    insertTag(document.f.text,v + " ")
    

    or if the tag needs to be wrapped instead of inserted:

    //var sel = document.selection.createRange();
    //sel.text = v + " " + str + " " + v ;
    wrapTag(document.f.text, v + " ", " " + v )
    

    This is all under the assumption that mozilla support is important (which to me, it is). - Ola (2/4/3)

    Comments

    Umm.. this looks very cool. But none of the links to your demo are working. When you fix, would you email me at matt@lig.net. Thanks

    Fixed now

    Paul Robertson

    This looks GREAT! But from programmers' point of view, since they are used to write codes, a few Wiki markup won't kill them anyway, so an editor just for inserting code seems to be not that necessary...

    - Ng Ming Hong (13 Jan 2004)

    We use Open Wiki at our office as a KnowledgeManagement? tool so I want to make it very easy to add content... Anyway, even us programmers can benefit from being reminded of the syntax etc for OpenWikiMarkup? and OpenWikiMacros? ...

    Paul Robertson (13 january 2004)

    Same here. No one used the wiki I set up until I plugged this toolbar into it. Now they all code with the wikiscript anyway but needed a handholding to get them over the threshold.

    -- Oddible 2004Jun10


    What about something like FCKeditor, something completly transparent for the user.

    - RA (26 Jan 2004)


    No FCKeditor. It only works for IE. I think it would be better to use the rich text editing capability of IE and Mozilla. That is a wrapper class that hide the difference between IE and Mozilla's rich text editing funtions.

    - Ng Ming Hong (30 Jan 2004)


    My concept is a really basic editor to add OpenWikiMarkup?. A RichText? editor is only any good if the text is being entered into a WYSIWYGWiki

    Paul Robertson 30 1 2004


    After doing all that, I realized we'd probably be better off customizing this. It sounds like that's what's really wanted anyway. - Ola


    Please, take a look on Wiki Edit.

    -- RomanIvanov? (28 Mar 2004)


    This wysiwyg editor looks great, but the Zip seems to be unavailable. Is there an alternative download site?

    Thanks a lot guys. This info. is very usefull for all of us.


    Or at least, this article was authored in Word, then pasted into the WikiEditor to be Wikified before being posted here…


    Open Wiki | Sand Box | Create Page | User Preferences | Recent Changes | Title Index | Random Page | Find Page | Help
    Edit this page | View other revisions
    Print this page | View XML
    Find page by browsing, searching or an index
    Edited May 22, 2010 (diff)




    Valid XHTML 1.0!Valid CSS!

    Powered by OpenWiki.com