source: t29-www/en/dev/translation/editor.js @ 209

Last change on this file since 209 was 209, checked in by sven, 13 years ago

Minor translation system improvements:

  • New top notices to be shown when user comes on page, not while topbox is positioned fixed
  • Contributors/Credits in Sidebar, included pagewise via SSI, univac9200.shtm as testing page (imaginary contributors! :-) )
  • introduced jQuery event namespace "t29tr" for easy global signal unbinding.
  • Property svn:keywords set to Id
File size: 17.3 KB
Line 
1/**
2 * technikum29.de | Translation contribution system
3 *
4 * This is a one-of-a-kind "almost wiki" interactive feedback system for user driven
5 * improvements directly on the page. There is almost no way to make it more easier
6 * for users to improve the translation.
7 *
8 * $Id: editor.js 209 2010-10-24 20:10:33Z sven $
9 * (c) GPL, Sven Koeppel, 01.09.2010
10 **/
11 
12if(!t29) t29 = {};       // defined in tools.js
13if(!t29.tr) t29.tr = {}; // this namespace, also defined in tools.js
14
15// Basic Settings
16t29.tr.settings = {
17    disable_img_license_system: true, // when tr system enabled, disable img licenses for more cleareness
18        editable_elements: function(){ return $("#content").find("p, ul, ol, blockquote, dl, table, h2, h3"); },
19        messages_url: "/en/dev/translation/messages.xml"
20};
21
22// Message system
23t29.tr.msg = {};
24t29.tr.load_messages = function() {
25        $.ajax({
26                dataType: "xml",
27                url: t29.tr.settings.messages_url,
28                async: false, timeout: 2000, // ms
29                success: function(xml) {
30                        $("body > span, body > section", xml).each(function(){
31                                t29.tr.msg[this.id] = (this.tagName == "span") ? $(this).text() : $(this).contents()
32                        });
33                },
34                error: function(xhr, status) {
35                        alert("Error while fetching translation messages: "+status);
36                }
37        });
38}
39
40/* SYSTEM STATE:       PAGE STATE:     USER STATE:
41   enabled = true      inspecting      already_edited = true => cookie
42   disabled = false    editing         never_edited = false
43*/
44
45
46/**
47 * Onload handler: Some basic preparations for translation system.
48 * tasks:
49 *  - Prepare sidebar box link handlers
50 **/
51t29.tr.onload = function(){
52        if(!t29.tr.is_enabled()) $("body").addClass("tr-disabled"); // prepare
53        t29.tr.sidebar = $("#sidebar-tr");
54        t29.tr.sidebar.find(".tr-enabled .button").attr("href","#back_to_normal_mode").click(t29.tr.call("set_enabled",false)); 
55        t29.tr.load_messages();
56        t29.tr.defaultvalue(); // as general system
57        // initial value
58        //t29.tr.set_enabled(true); // won't work any more (call t29.tr.preloader.start)
59};
60
61// helper: foobar.click(t29.tr.call(anything, value)); is shorthand for
62//         foobar.call(function(){ t29.tr.anything(value); });
63t29.tr.call = function(f, v){ return function(){ t29.tr[f](v);} };
64
65// helper: default value input elements. highly compressed selfwritten :)
66// usage: <input value="my default value bla" class="defaultvalue"/>
67t29.tr.defaultvalue = function(){
68        $("input.defaultvalue").live('focus.t29tr', function(){
69                if(!(dv=(t=$(this)).data('dv'))) t.data('dv', t.val());
70                if(t.val()==dv) t.val('');
71        }).live('blur.t29tr', function(){
72                if((dv=((t=$(this)).data('dv'))) && /^\s*$/.test(t.val())) t.val(dv);
73        });
74};
75
76/**
77 * Tells if the translation system is started up (e.g. the user started it) or not
78 * (which means just an ordinary page)
79 * @returns boolean
80 **/
81t29.tr.is_enabled = function() {
82        return t29.tr.enabled ? true : false;
83};
84
85/**
86 * The main switch to turn the translation system on or off. Turning on will always
87 * success (the system switches into inspection mode immediately), turning off will
88 * check if the user just modified a text - if yes and if the user want to continue,
89 * it will fail. In every case this is a clean operation.
90 * @param value boolean true: Turn system on, false: Turn it off
91 * @returns boolean if operation succeeded.
92 **/
93t29.tr.set_enabled = function(value) {
94        if(!value && t29.tr.is_really_editing() && t29.tr.stop_editing()) {
95                // currently enabled + user is editing
96                // + user interrupted process => fail, still enabled
97                return false;
98        }
99       
100        t29.tr.enabled = value;
101        // toggle image license system
102        if(t29.tr.settings.disable_img_license_system)
103                t29.img_license_settings.enabled = ! t29.tr.enabled;
104        // set infos on body
105        $("body").toggleClass("tr-enabled", t29.tr.enabled)
106                .toggleClass("tr-inspecting", t29.tr.enabled && !t29.tr.is_editing())
107                .toggleClass("tr-disabled", !t29.tr.enabled);
108
109        if(t29.tr.enabled) {
110                // system powered on.
111                t29.tr.create_ui();
112                t29.tr.editables
113                        .hover(t29.tr.mouseover_editables, t29.tr.mouseout_editables)
114                        .click(t29.tr.click_editables);
115                       
116                // doesn't work yet (nachschauen)
117                /*$(document).keyup(function(e) {
118                        var code = e.keyCode || e.which;
119                        if (code == 27)  // escape key -> quit system
120                                t29.tr.set_enabled(false);
121                }); */
122        } else {
123                // system powered off
124                t29.tr.set_editing(false); // for safety, just another time
125                t29.tr.editables.unbind(); // safely remove *all* hovering handlers
126                // remove any old trash:
127                $(".tr-inspecting").removeClass("tr-inspecting");
128                t29.tr.mouseout_editables(); // falls noch was uebrig ist
129               
130                // remove *ALL* handlers anywhere (new namespace .t29tr)
131                $("*").unbind(".t29tr");
132        }
133        return true; // success
134} // set_enabled
135
136/**
137 * Helper function for set_enabled(): Set up the general User Interface for
138 * the translation system, that is the inspection widgets. The function
139 * remembers if it has created them already, so it's safe to call this multiple
140 * times on each set_enabeld(true) call.
141 **/
142t29.tr.create_ui = function() {
143        // Create #tr-info, #tr-editor, all ".tr-editable"s, etc.
144        if(!t29.tr.ui_created) {
145                t29.tr.ui_created = true; // Now create all those nice elements
146
147                // Sidebar arrow
148                t29.tr.sidebar.append(t29.tr.msg.sidebar_arrow);
149               
150                // create Infobox, editorbox, topnoticebox, design elements
151                $("body").append(t29.tr.msg.create_ui_body_append);
152                $("#content").before(t29.tr.msg.create_ui_topbox);
153                $.each(["infobox", "editorbox", "topbox"], function(){ t29.tr[this] = $("#tr-"+this); });
154                t29.tr.infobox.hide();
155                t29.tr.editorbox.hide(); // default state
156               
157                // make topbox being fixed at top scrolling
158                var top = t29.tr.topbox.offset().top; // - parseFloat($('#comment').css('marginTop').replace(/auto/, 0))
159                $(window).bind("scroll.t29tr", function(e) {
160                        var now_fixed = $(this).scrollTop() >= top;
161                        // since topbox was embedded in content (not sidebar), place is
162                        // removed there when switching from static to fixed. Setting the
163                        // height is a workaround.
164                        (p=t29.tr.topbox.parent()).css('height', now_fixed ? p.height() : '');
165                        t29.tr.topbox.toggleClass('fixed', now_fixed);
166                });
167               
168                // create all event handlers in the topbox
169               
170                // Fields where clicking yields extender bar (poor man tab bar)
171                var hideall_tabs = function() {
172                        t29.tr.topbox.find(".field.extends").removeClass('active');
173                        t29.tr.topbox.find(".extender > div").slideUp();
174                }
175                t29.tr.topbox.find(".field.extends").toggle(function(){
176                        hideall_tabs(); // make sure no other is shown
177                        var extender = $(this).hasClass('name') ? 'name' : 'feedback';
178                        t29.tr.topbox.find('.extender .'+extender).slideDown()
179                        $(this).addClass('active');
180                }, hideall_tabs);
181
182                // User name box form
183                t29.tr.topbox.find(".name :text").keyup(function(){
184                        t29.tr.settings[$(this).attr('name')] = $(this).val();
185                        t29.tr.topbox.find('.name .feedback').html("<b>"+t29.tr.settings.name+"</b> from <b>"+t29.tr.settings.location+"</b>");
186                        t29.tr.topbox.find('.name .stored').text(' - Thank you');
187                });
188               
189                // Edit whole page button
190                t29.tr.topbox.find('.field.editwhole').click(t29.tr.edit_whole_page);
191                t29.tr.topbox.find('.field.help').click(t29.tr.help);
192                t29.tr.topbox.find('.field.exit').click(t29.tr.call('set_enabled', false));
193               
194
195                // Create Editor UI (only once)
196                t29.tr.editorbox.html(t29.tr.msg.create_editorui_editorbox); // remove all possible old classes
197                t29.tr.editorbox.find(".cancel").click(t29.tr.stop_editing);
198                t29.tr.editorbox.find(".submit").click(t29.tr.submit_editing);
199                t29.tr.editorbox.find(".help").click(t29.tr.help);
200
201                       
202                // create all wrapper elements (most important step)
203                t29.tr.settings.editable_elements().wrapInner("<span class='tr-editable'/>");
204                t29.tr.editables = $(".tr-editable");
205        } // INITIAL ui created
206       
207        // regular ui creations:
208        // update the nice arrow in the sidebar =) (some timeout for slow startups)
209        setTimeout(function(){
210                t29.tr.sidebar.find(".arrow").css({ "border-bottom-width":
211                (a=Math.floor(t29.tr.sidebar.outerHeight()/2-1)),"border-top-width": a });
212        }, 1000);
213}
214
215/**
216 * Mouseover (mouseenter) handler for all editables. This will handle infobox
217 * styling and content and setup other handlers.
218 **/
219t29.tr.mouseover_editables = function() {
220        if(!t29.tr.is_enabled() || t29.tr.is_editing()) return; // disable while editing
221        if(t29.tr.current_editable) {
222                // this is weird and should not be - cleanup missed!
223                t29.tr.current_editable.removeClass("tr-inspecting");
224        }
225        t29.tr.current_editable = $(this).addClass('tr-inspecting');
226
227        // show infobox for current editable
228        t29.tr.infobox.empty().append( (h=t29.tr.current_editable.hasClass("tr-corrected"))
229                ? t29.tr.msg.infobox_corrected : t29.tr.msg.infobox_default);
230        if(h) t29.tr.infobox.addClass("tr-corrected"); // tell infobox that current editable is corrected (for css)
231       
232        // position infobox for current editable
233        t29.tr.infobox.css({
234                top: t29.tr.current_editable.offset().top - t29.tr.infobox.outerHeight(),
235                left: t29.tr.current_editable.offset().left
236        }).show();
237       
238        $(document).bind("mousemove.t29tr", t29.tr.mouseover_editables_positioner);
239};
240
241/**
242 * Mouseover helper: Another handler for mouse movements to nicely arrange the
243 * infobox according to the mouse cursor x position.
244 **/
245t29.tr.mouseover_editables_positioner = function(e) {
246        if(e.pageX) { // document.mousemove event
247                var left = e.pageX - t29.tr.infobox.outerWidth()/2;
248                // check boundaries
249                left = Math.max(left, t29.tr.current_editable.offset().left);
250                left = Math.min(left, t29.tr.current_editable.offset().left + t29.tr.current_editable.outerWidth() - t29.tr.infobox.outerWidth());
251                t29.tr.infobox.css("left", left);
252        }
253}
254
255/**
256 * Mouseout (mouseleave) handler: Makes a nice cleanup. Can be called multiple times,
257 * will detect if already cleaned up. This should be done, since it unbinds a
258 * nervous mousemove handler.
259 **/
260t29.tr.mouseout_editables = function() {
261        if(t29.tr.current_editable) {
262                t29.tr.current_editable.removeClass('tr-inspecting');
263                t29.tr.current_editable = null;
264                t29.tr.infobox.hide().removeClass(); // remove all classes
265                $(document).unbind("mousemove.t29tr", t29.tr.mouseover_editables_positioner);
266        }
267};
268
269/**
270 * Click handler for clicking editables. This will immediately start the editing
271 * engine if the user is not already editing another text.
272 **/
273t29.tr.click_editables = function() {
274        if(t29.tr.is_editing()) return;
275        t29.tr.set_editing($(this));
276};
277
278/**
279 * Testing function to find out if user is currently editing, that is: There is an editor
280 * window currently open. ("The editor engine is running").
281 * @returns boolean value if an editor window is open
282 **/
283t29.tr.is_editing = function() {
284        // user is editing.
285        return (t29.tr.editor && !jQuery.isEmptyObject(t29.tr.editor));
286};
287
288/**
289 * Testing function to find out if user modified something in the currently opened editing,
290 * if avaliable. If the user doesn't currently edit at all, this returns false.
291 * @returns boolean User works with local modified text and will be angry if you delete it
292 */
293t29.tr.is_really_editing = function() {
294        // user is editing and has made some changes
295        return t29.tr.is_editing() && (t29.tr.editor.html() != t29.tr.initial_editor.html())
296};
297
298/**
299 * This is the main switch for the editor engine. Calling this function you can safely
300 * start or stop editing. Starting is simple: Just tell it what to edit:
301 *   t29.tr.set_editing($("your element"));
302 * Stopping is a brutal action, since this low level function does NOT check if there
303 * are modifications. Use stop_editing() for an interactive version. Especially,
304 * set_editing(false) won't fail :-)
305 * @param editing_target_or_false false for turning off, any jQuery object for turning on
306 * @returns nothing
307 */
308t29.tr.set_editing = function(editing_target_or_false) {
309        if(editing_target_or_false && t29.tr.is_editing()) {
310                alert("Bad state: Starting editing while editing?");
311                return; // bad
312        } else if(!editing_target_or_false && !t29.tr.is_editing()) {
313                // hab gar nicht bearbeitet.
314                return;
315        }
316       
317        var old_editor = t29.tr.editor; // backup old editor for cleanup
318        t29.tr.editor = editing_target_or_false;
319        $("body").toggleClass("tr-editing", t29.tr.is_editing());
320        $("body").toggleClass("tr-inspecting", t29.tr.is_enabled() && !t29.tr.is_editing());
321
322        if(t29.tr.is_editing()) {
323                // start up editing
324
325                // setup editor
326                t29.tr.initial_editor = t29.tr.editor.clone(); // make a backup of the original, for the form
327                t29.tr.editor.attr("contenteditable", "true").addClass("tr-editing").focus();
328               
329                // switch the boxes
330                t29.tr.mouseout_editables(); // rauemt vom editing auf (infobox.hide(), etc.)
331                t29.tr.create_editorui();
332               
333                // ask at window unloading
334                window.onbeforeunload = function() { 
335                        if(t29.tr.is_really_editing()) {
336                                return ("You have made changes on this page that you have not yet submitted."
337                                 +"If you navigate away from this page you will loose your work.");
338                        }
339                };
340        } else {
341                // stop editing
342                t29.tr.initial_editor = null;
343                if(old_editor) { // falls es noch irgendeinen anderen Muell geben sollte
344                        old_editor.add(".tr-editing").attr("contenteditable", "false").removeClass("tr-editing");
345                }
346                t29.tr.editorbox.hide();
347        }
348}; // set_editing
349
350/**
351 * This is the interactive version of set_editing(false). If the user modified the
352 * document, it will ask him if he wants to quit. If no, it returns false.
353 * @returns boolean true if editing was stopped, false if not.
354 **/
355t29.tr.stop_editing = function() {
356        // Frontend Function to *ask* user if he wants to stop editing
357        if(!t29.tr.is_really_editing(true) || 
358           confirm("Do you want to quit the currently edited paragraph and loose all changes?")) {
359                t29.tr.set_editing(false);
360                return true;
361        } else {
362                return false;
363        }
364};
365
366/**
367 * Shorthand method to start editing the whole #content area. May be called with
368 * callbacks, directly, etc. Will make sure that there is no other editor.
369 **/
370t29.tr.edit_whole_page = function() {
371        t29.tr.set_editing(false); // make sure there is no other editor
372        $("#content").wrapInner("<div class='tr-editable'/>");
373        t29.tr.set_editing( $("#content > .tr-editable") );
374};
375
376/**
377 * Button callback for Submitting in the Editor engine. This will compose the
378 * HTTP POST call and send it via AJAX. Especially it immediately returns.
379 **/
380t29.tr.submit_editing = function() {
381        // button submitting
382        if(!t29.tr.is_really_editing()) {
383                // funktioniert nicht zuverlaessig.
384                //alert("You didn't make any changes to the text. Where's the improvement? :-)");
385                //return false;
386        }
387       
388        $.ajax({
389                type: 'POST',
390                url: '/en/dev/translation/submit.php',
391                success: t29.tr.submit_successful,
392                error: function(req, textStatus) {
393                        t29.tr.editorbox.removeClass('loading').addClass('error');
394                        t29.tr.editorbox.find('h3').html("Sending your improved text failed!");
395                        t29.tr.editorbox.find('p').html("Please consult the <span class='button small red'>Help</span>")
396                                .find('.button').click(t29.tr.help);
397                        if(req.responseText)
398                                t29.tr.editorbox.find('p').prepend("<b>"+req.responseText+"</b> ");
399                        alert("Submitting your text failed!");
400                },
401                data: {
402                        source: 'ajax',
403                        page: location.href,
404                        node: "foobar", // t29.tr.editor XQUERY PATH
405                        initial_text: t29.tr.initial_editor.text().replace(/\s+/g,' '),
406                        initial_html: t29.tr.initial_editor.html(),
407                        new_text: t29.tr.editor.text().replace(/\s+/g,' '),
408                        new_html: t29.tr.editor.html()
409                }
410        });
411       
412        // wg. langen Ladezeiten: Buttons ausblenden!
413        t29.tr.editorbox.addClass('loading');
414};
415
416/**
417 * Callback function for success AJAX submission call. This will display a short
418 * nice message before shutting down nicely the editor engine, leaving the user back
419 * to inspect just another element.
420 **/
421t29.tr.submit_successful = function(server_data) {
422        // Editorbox
423        t29.tr.editorbox.removeClass('loading').addClass('success').prepend("<div class='response'>Thank you! :-)</div>");
424        t29.tr.editorbox.delay(1100).fadeOut(1000, function(){
425                t29.tr.set_editing(false); // sic - hard finishing.
426                // + TODO REMARK "USER already edited"!
427        });
428       
429        // Editor
430        t29.tr.editor.addClass("tr-corrected");
431       
432        // System, user credits
433        $("body").addClass("tr-successful");
434        t29.tr.sidebar.find(".dynamic").text(
435                "Thank you very much for your contribution! Blablabla"
436        );
437};
438
439/**
440 * Helper function for set_editing(): Set up the User Interface elements for
441 * the editor engine. This function can and should be called on each
442 * set_editing(true) call, since it also cleans up :)
443 **/
444t29.tr.create_editorui = function() {
445        // Basic initial setup of the editorbox (HTML, callbacks) is already done in general UI)
446
447       
448        t29.tr.editorbox.removeClass()
449                .css('width', t29.tr.editor.width()) // IMPORTANT - to be done before outerHeight() call
450                .css({
451                        top: t29.tr.editor.offset().top - t29.tr.editorbox.outerHeight(),
452                        left: t29.tr.editor.offset().left
453        }).show();
454};
455
456/**
457 * Open a nice help window
458 **/
459 t29.tr.help = function() {
460        window.open("http://dev.technikum29.de/wiki/%dcbersetzung/Help");
461
462};
463
464
465
466t29.tr.display_startup_notice = function(msg) {
467        // display a short text in the startup-thingy
468        t29.tr.topbox.find('.startup').html( t29.tr.msg["startup_topbox_"+msg] ).show();
469};
470
471
472// Master entry point: Load onload handler at startup.
473$(t29.tr.onload);
Note: See TracBrowser for help on using the repository browser.
© 2008 - 2013 technikum29 • Sven Köppel • Some rights reserved
Powered by Trac
Expect where otherwise noted, content on this site is licensed under a Creative Commons 3.0 License