20110601-02

Support for TinyMCE WYSIWYG editor is getting ready

Puh! Zwei Tage lang habe ich für folgende beiden Punkte gebraucht:

  • Der Benutzer sieht jetzt eine Bestätigung, wenn er gespeichert hat: erstens ist der Speichern-Button dann disabled, und zweitens wird im Hintergrund das HtmlBoxPanel bei jedem Speichern aktualisiert.
  • Wenn man was verändert hat und dann das Fenster schließt ohne gespeichert zu haben, dann fragt er nun, ob man nicht doch lieber speichern will. Die übliche Frage mit Ja / Nein / Abbrechen.

Hier einige Seiten, die ich nun relativ gut intus habe:

Dieses letztere Beispiel habe ich gleich mal ausprobiert. Hier der Code (den ich ein bisschen angepasst habe):

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
       "http://www.w3.org/TR/html4/strict.dtd">
<html>
<script type="text/javascript" src="http://dsbe-demo.saffre-rumma.net/media/tinymce/tiny_mce.js"></script>
<script type="text/javascript">
tinyMCE.init({
    mode : "textareas",
    theme : "advanced"
});

function ajaxLoad() {
    var ed = tinyMCE.get('content');

    // Do you ajax call here, window.setTimeout fakes ajax call
    ed.setProgressState(1); // Show progress
    window.setTimeout(function() {
        ed.setProgressState(0); // Hide progress
        ed.setContent('HTML content that got passed from server.');
    }, 3000);
}

function ajaxSave() {
    var ed = tinyMCE.get('content');

    // Do you ajax call here, window.setTimeout fakes ajax call
    ed.setProgressState(1); // Show progress
    window.setTimeout(function() {
        ed.setProgressState(0); // Hide progress
        alert(ed.getContent());
    }, 3000);
}
</script>

<form method="post" action="somepage">
    <textarea name="content" style="width:100%">
    </textarea>
    <input type="button" onclick="ajaxLoad()" value="Load"/>
    <input type="button" onclick="ajaxSave()" value="Save"/>
</form>
</html>

Und hier das eigentliche Beispiel.

Um das Beispiel öffentlich nachvollziehbar zu machen, habe ich die HTTP authentication auf dsbe-demo mal abgeschaltet. Man ist dort jetzt immer als root eingeloggt.

Das Beispiel erwies sich aber nur als der Anfang. Der Code in meiner Lino.edit_tinymce_text (sh. /lino/ui/extjs3/linolib.js) funktioniert jetzt zwar zufriedenstellend, aber schön ist er nicht gerade. Aber ich sehe momentan keine elegantere Lösung. Und solange es funktioniert, lass ich das als Herausforderung für eventuelle Gurus, die hoffentlich irgendwann mal bei Lino einsteigen.

Lieber will ich vielleicht irgendwann noch die andere Nutzungsmöglichkeit ausprobieren: einfach inline als textarea. Zumindest für den Inhalt einer Notiz wäre das noch denkbar. Im Detail eines Vertrags, wo auf einer Seite 4 Textfelder sind, würde es allerdings eng. Allein die Toolbars nähmen ja dann die Hälfte der Bildschirmplatzes ein… naja, die könnte ich ja abschalten. Mal sehen. Irgendwann kommt das bestimmt…

Dringender ist mir jetzt eigenlich die Tatsache, dass doch einige Fälle gibt, bei denen das Ausdrucken nicht funktioniert.

Zum Beispiel wenn ich folgenden Text eintippe:

Und <das> hier? geht das?

Das kriegt er nicht gedruckt. Er sagt dann:

Error while evaluating the expression "html(self.body)" defined in the "from" part of a statement.
File "<string>", line 1, in <module>
File "t:\hgwork\lino\lino\utils\appy_pod.py", line 63, in html_func
return renderer.renderXhtml(html,**kw)
File "l:\snapshots\appy\appy\pod\renderer.py", line 238, in renderXhtml
stylesMapping, ns).run()
File "l:\snapshots\appy\appy\pod\xhtml2odt.py", line 505, in run
self.xhtmlParser.parse(self.xhtmlString)
File "l:\snapshots\appy\appy\shared\xml_parser.py", line 193, in parse
self.parser.parse(inputSource)
File "c:\Python27\lib\xml\sax\expatreader.py", line 107, in parse
xmlreader.IncrementalParser.parse(self, source)
File "c:\Python27\lib\xml\sax\xmlreader.py", line 123, in parse
self.feed(buffer)
File "c:\Python27\lib\xml\sax\expatreader.py", line 211, in feed
self._err_handler.fatalError(exc)
File "c:\Python27\lib\xml\sax\handler.py", line 38, in fatalError
raise exception
<class 'xml.sax._exceptions.SAXParseException'>: <unknown>:35:88: not well-formed (invalid token)

TinyMCE ist es offenbar nicht schuld. Der schickt nämlich beim Speichern:

<p>und &lt;das&gt; hier? geht das?</p>

Aber meine lino.utils.html2xhtml.html2xhtml() machte daraus:

<p>und <das> hier? geht das?</p>

Tilt! Das war ein Buch in meinen HTMLParser. Der machte einfach:

def handle_entityref(self,name):
    """process a general entity reference of the form "&name;"."""
    self.handle_data(unichr(name2codepoint[name]))

Aber das war zu einfach, er darf die HTML-eigenen entities nicht einfach dekodieren, sondern muss sie in eine CDATA setzen:

def handle_entityref(self,name):
    """process a general entity reference of the form "&name;"."""
    if name in ('lt','gt','amp','quot'):
        self.handle_data('<![CDATA['+unichr(name2codepoint[name])+']]>')
        return
    self.handle_data(unichr(name2codepoint[name]))

N.B.: es ist noch sehr mühsam, solche Fälle zu analysieren wenn sie auftreten. Ich muss dann immer in appy.pod.xhtml2odt.Xhtml2OdtConverter.run() ein paar Zeilen einfügen, damit ich den beanstandeten XML-code sehen kann:

def run(self):
    if True:
        import codecs
        fn = r'c:\temp\appy_20110602.log'
        fd = codecs.open(fn,'w',encoding="utf8")
        fd.write(self.xhtmlString)
        fd.close()
        print "Wrote debug log file", fn
    self.xhtmlParser.parse(self.xhtmlString)
    return self.xhtmlParser.env.res

Aber okay, bis auf Schönheitsfehler und eventuelle noch nicht entdeckte Probleme ist das Ding jetzt womöglich benutzbar! Also Check-in um den Zwischenerfolg zu feiern.

Renamed .dpy fixtures to .py fixtures

Before going to bed I thought to quickly write another paragraph in /admin/dopytutorial to answer a question that might soon come:

Why are .dpy fixtures called .dpy and not simply .py?

While trying to explain that, I discovered that there is no reason! Except that I was afraid it would cause problems if they are called like normal Python modules. But now it turned out that my statement “We choose the file extension .dpy because simply naming them .py would conflict with the existing PythonSerializer.” was wrong.

So I changed the whole thing.