From 065250bf3f81b8438f993553bc3a9f8f8447539f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Casta=C3=B1eda?= Date: Thu, 20 Aug 2020 12:43:50 +0200 Subject: [PATCH 01/10] Extract some common js Move ping server code and LANG_CODE replacement from several sites to a common file. --- userguide/edit.php | 1 + userguide/inc/blocks.php | 1 + userguide/shared/block_edit_tool.js | 17 +---------------- userguide/shared/common.js | 16 ++++++++++++++++ userguide/shared/edit_tool.js | 17 +++-------------- userguide/shared/translate_tool.js | 4 ---- 6 files changed, 22 insertions(+), 34 deletions(-) create mode 100644 userguide/shared/common.js diff --git a/userguide/edit.php b/userguide/edit.php index f92eec5..4219ef7 100644 --- a/userguide/edit.php +++ b/userguide/edit.php @@ -26,6 +26,7 @@ const base_dir = '$base_dir/'; //]]> + EOD; diff --git a/userguide/inc/blocks.php b/userguide/inc/blocks.php index ea318ce..c11c05f 100644 --- a/userguide/inc/blocks.php +++ b/userguide/inc/blocks.php @@ -132,6 +132,7 @@ function load_doc_with_blocks($doc_id, $lang = null) { // Pass the script path to JavaScript append_js_code($head, $js); + append_js_file($head, $base_url . '/shared/common.js'); append_css_file($head, $base_url . '/shared/blocks.css'); if ($lang) { diff --git a/userguide/shared/block_edit_tool.js b/userguide/shared/block_edit_tool.js index aabcc8b..ebd456e 100644 --- a/userguide/shared/block_edit_tool.js +++ b/userguide/shared/block_edit_tool.js @@ -1,5 +1,3 @@ -const ping_delay = 60; // in seconds - const attr_trans_id = '_translation_id'; const attr_state = '_edit_state'; @@ -89,15 +87,6 @@ function editSaveFinished() { xml_http.send(null); } -function pingServer() { - var xml_http = new XMLHttpRequest(); - - xml_http.open('GET', base_url + '/lock.php?doc_id=' + doc_id); - xml_http.send(null); - - window.setTimeout(pingServer, ping_delay * 1000); -} - function mouseClickEvent(e) { if (window.edited_node != null) { edit_window.focus(); @@ -155,10 +144,6 @@ function setProperties(node) { } } -function formatText(s) { - return s.replace(/\{LANG_CODE\}/g, 'en'); -} - window.onload = function() { var functions_ok = 0; @@ -174,7 +159,7 @@ window.onload = function() { "\nBrowser known to work : Safari 4, Firefox/BeZillaBrowser 2.x, " + "3.x."); return; } else { - window.setTimeout(pingServer, ping_delay * 1000); + lockDocument(doc_id); } setProperties(document.getElementsByTagName('body')[0]); diff --git a/userguide/shared/common.js b/userguide/shared/common.js new file mode 100644 index 0000000..3d8b79d --- /dev/null +++ b/userguide/shared/common.js @@ -0,0 +1,16 @@ +const ping_delay = 60; // in seconds + +function lockDocument(doc_id) { + function pingServer() { + var xml_http = new XMLHttpRequest(); + + xml_http.open('GET', base_url + '/lock.php?doc_id=' + doc_id); + xml_http.send(null); + } + + setInterval(pingServer, ping_delay * 1000); +} + +function formatText(s, lang = null) { + return s.replace(/\{LANG_CODE\}/g, lang || window.lang || 'en'); +} diff --git a/userguide/shared/edit_tool.js b/userguide/shared/edit_tool.js index 0c72a2e..cb899f3 100644 --- a/userguide/shared/edit_tool.js +++ b/userguide/shared/edit_tool.js @@ -1,4 +1,3 @@ -const ping_delay = 60; // in seconds const update_delay = 500; // in ms var preview_window = null; @@ -9,15 +8,6 @@ var translated_text; var count = 0; -function pingServer() { - var xml_http = new XMLHttpRequest(); - - xml_http.open('GET', base_url + '/lock.php?doc_id=' + doc_id); - xml_http.send(null); - - window.setTimeout(pingServer, ping_delay * 1000); -} - function keyDownAction() { var e = event; @@ -44,9 +34,8 @@ function updateStatus() { if (!preview_window) return; - preview_window.document.write(editor.value - .replace('', '') - .replace(/\{LANG_CODE\}/g, 'en')); + preview_window.document.write(formatText(editor.value + .replace('', ''))); preview_window.document.close(); } @@ -140,7 +129,7 @@ window.onload = function() { window.alert('Your browser does not support XML HTTP requests. ' + 'You will not be protected against concurrent edits!'); } else { - window.setTimeout(pingServer, ping_delay * 1000); + lockDocument(doc_id); } preview_button = document.getElementById('preview'); diff --git a/userguide/shared/translate_tool.js b/userguide/shared/translate_tool.js index d041e28..345af11 100644 --- a/userguide/shared/translate_tool.js +++ b/userguide/shared/translate_tool.js @@ -210,10 +210,6 @@ function setProperties(node) { } } -function formatText(s) { - return s.replace(/\{LANG_CODE\}/g, lang); -} - window.onload = function() { var functions_ok = 0; From 19c07883fc870ef305d61284c7b11dbd0e6198b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Casta=C3=B1eda?= Date: Thu, 20 Aug 2020 15:15:58 +0200 Subject: [PATCH 02/10] fix: check correct variable in img click handler --- userguide/shared/translate_tool.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/userguide/shared/translate_tool.js b/userguide/shared/translate_tool.js index 345af11..d379808 100644 --- a/userguide/shared/translate_tool.js +++ b/userguide/shared/translate_tool.js @@ -146,7 +146,7 @@ function mouseClickEvent(e) { function imgMouseClickEvent(e) { var src = this.getAttribute("src"); - if (src != '.') { + if (base_local != '.') { src = base_local + '/' + src; } window.open(base_url + '/res_upload.php?path=' + encodeURIComponent(src) + '&lang=' + lang, From 3e91d96dad8b18b920ec58e27c3987d22b9a5bb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Casta=C3=B1eda?= Date: Thu, 20 Aug 2020 18:40:06 +0200 Subject: [PATCH 03/10] Block edit tools: delegate click handlers Use a listener in the document root instead of adding a handler to each node. As a bonus, images inside translation blocks can be clicked now, solving https://dev.haiku-os.org/ticket/15871 --- userguide/inc/blocks.php | 1 + userguide/shared/block_edit_tool.js | 36 +++----------------------- userguide/shared/blocks.js | 39 +++++++++++++++++++++++++++++ userguide/shared/translate_tool.js | 38 ++++------------------------ 4 files changed, 49 insertions(+), 65 deletions(-) create mode 100644 userguide/shared/blocks.js diff --git a/userguide/inc/blocks.php b/userguide/inc/blocks.php index c11c05f..9317162 100644 --- a/userguide/inc/blocks.php +++ b/userguide/inc/blocks.php @@ -133,6 +133,7 @@ function load_doc_with_blocks($doc_id, $lang = null) { append_js_code($head, $js); append_js_file($head, $base_url . '/shared/common.js'); + append_js_file($head, $base_url . '/shared/blocks.js'); append_css_file($head, $base_url . '/shared/blocks.css'); if ($lang) { diff --git a/userguide/shared/block_edit_tool.js b/userguide/shared/block_edit_tool.js index ebd456e..4746ed0 100644 --- a/userguide/shared/block_edit_tool.js +++ b/userguide/shared/block_edit_tool.js @@ -1,6 +1,9 @@ const attr_trans_id = '_translation_id'; const attr_state = '_edit_state'; +const edit_tool = '/shared/edit_tool.html'; +const edit_tool_ctx = 'EditBlock'; + var edited_node = null; var linked_nodes = new Array(); var original_text; @@ -87,34 +90,6 @@ function editSaveFinished() { xml_http.send(null); } -function mouseClickEvent(e) { - if (window.edited_node != null) { - edit_window.focus(); - return false; - } - - window.edited_node = this; - - var id = this.getAttribute(attr_trans_id); - - edit_window = window.open(base_url + '/shared/edit_tool.html', - 'Edit Block', 'width=600,height=300,toolbar=0,status=0'); - window.original_text = source_strings[id]; - - return true; -} - -function imgMouseClickEvent(e) { - var src = this.getAttribute("src"); - if (base_local != '.') { - src = base_local + '/' + src; - } - window.open(base_url + '/res_upload.php?path=' + encodeURIComponent(src), - src, 'width=800,height=600,status=0,toolbar=0,location=0,menubar=0,directories=0,resizable=1,scrollbars=1'); - - return true; -} - function setProperties(node) { if (node == null) return; @@ -124,8 +99,6 @@ function setProperties(node) { var id = node.getAttribute(attr_trans_id); if (source_strings[id]) { - node.onclick = mouseClickEvent; - if (linked_nodes[id] == null) { linked_nodes[id] = [ node ]; } else { @@ -134,8 +107,6 @@ function setProperties(node) { } return; - } else if (node.tagName.toLowerCase() == "img") { - node.onclick = imgMouseClickEvent; } } @@ -163,4 +134,5 @@ window.onload = function() { } setProperties(document.getElementsByTagName('body')[0]); + document.addEventListener('click', clickHandler); } diff --git a/userguide/shared/blocks.js b/userguide/shared/blocks.js new file mode 100644 index 0000000..220b5ed --- /dev/null +++ b/userguide/shared/blocks.js @@ -0,0 +1,39 @@ +function clickHandler(e) { + var node = e.target; + if (node.nodeName.toLowerCase() == 'img') { + imgClickHandler(node); + } else { + node = node.closest('[' + attr_trans_id + ']'); + if (node != null) { + blockClickHandler(node); + } + } +} + +function imgClickHandler(img) { + var src = img.getAttribute("src"); + if (base_local != '.') { + src = base_local + '/' + src; + } + var lang_param = (typeof lang === 'undefined') ? '' : ('&lang=' + lang); + window.open(base_url + '/res_upload.php?path=' + encodeURIComponent(src) + lang_param, + src, 'width=800,height=600,status=0,toolbar=0,location=0,menubar=0,personalbar=0,resizable=1,scrollbars=1'); +} + +function blockClickHandler(node) { + if (window.edited_node != null) { + edit_window.focus(); + return; + } + + window.edited_node = node; + + var id = node.getAttribute(attr_trans_id); + + edit_window = window.open(base_url + edit_tool, + edit_tool_ctx, 'width=650,height=400,status=0,toolbar=0,location=0,menubar=0,personalbar=0,resizable=1,scrollbars=0'); + window.original_text = source_strings[id]; + if (typeof translated_strings !== 'undefined') { + window.translated_text = translated_strings[id]; + } +} diff --git a/userguide/shared/translate_tool.js b/userguide/shared/translate_tool.js index d379808..cee151e 100644 --- a/userguide/shared/translate_tool.js +++ b/userguide/shared/translate_tool.js @@ -1,6 +1,9 @@ const attr_trans_id = '_translation_id'; const attr_state = '_edit_state'; +const edit_tool = '/shared/translate_tool.html'; +const edit_tool_ctx = 'TranslateBlock'; + var edit_window = null; var edited_node = null; var original_text; @@ -126,35 +129,6 @@ function translateBlockDone(next_node) { } } -function mouseClickEvent(e) { - if (window.edited_node != null) { - edit_window.focus(); - return false; - } - - window.edited_node = this; - - var id = this.getAttribute(attr_trans_id); - - edit_window = window.open(base_url + '/shared/translate_tool.html', - 'Edit Translation', 'width=650,height=400,status=0,toolbar=0,location=0,menubar=0,directories=0,resizable=1,scrollbars=0'); - window.original_text = source_strings[id]; - window.translated_text = translated_strings[id]; - - return true; -} - -function imgMouseClickEvent(e) { - var src = this.getAttribute("src"); - if (base_local != '.') { - src = base_local + '/' + src; - } - window.open(base_url + '/res_upload.php?path=' + encodeURIComponent(src) + '&lang=' + lang, - src, 'width=800,height=600,status=0,toolbar=0,location=0,menubar=0,directories=0,resizable=1,scrollbars=1'); - - return true; -} - function setProperties(node) { if (node == null) return; @@ -176,8 +150,6 @@ function setProperties(node) { node.innerHTML = formatText(translated_strings[id]); } - node.onclick = mouseClickEvent; - node.setAttribute('_internal_id', all_nodes.length); all_nodes.push(node); @@ -200,8 +172,6 @@ function setProperties(node) { } } return; - } else if (node.tagName.toLowerCase() == "img") { - node.onclick = imgMouseClickEvent; } } @@ -244,4 +214,6 @@ window.onload = function() { setProperties(new_title); } + + document.addEventListener('click', clickHandler); } From 7005225c51f493e765cdc7fddd22086e214b4fdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Casta=C3=B1eda?= Date: Fri, 21 Aug 2020 11:37:39 +0200 Subject: [PATCH 04/10] blocks.js: share block edition events code --- userguide/shared/block_edit_tool.js | 73 +++++++---------------- userguide/shared/blocks.js | 52 ++++++++++++++++ userguide/shared/edit_tool.html | 2 +- userguide/shared/tool.css | 2 +- userguide/shared/translate_tool.html | 14 ++--- userguide/shared/translate_tool.js | 88 ++++++++++------------------ 6 files changed, 112 insertions(+), 119 deletions(-) diff --git a/userguide/shared/block_edit_tool.js b/userguide/shared/block_edit_tool.js index 4746ed0..c1386b0 100644 --- a/userguide/shared/block_edit_tool.js +++ b/userguide/shared/block_edit_tool.js @@ -8,63 +8,34 @@ var edited_node = null; var linked_nodes = new Array(); var original_text; -function endEditionEvent(clickOK) { - if (window.edited_node == null) - return; - - var id = window.edited_node.getAttribute(attr_trans_id); - var next_node = null; - - var new_text = edit_window.document.getElementById('modified').value; +function sendEdition(node, id, new_text, not_mark) { + source_strings[id] = new_text; - if (clickOK && new_text) { - var not_mark = edit_window.document.getElementById('not_mark').checked; + var xml_http = new XMLHttpRequest(); - source_strings[id] = new_text; + var encoded_text = encodeURI(new_text).replace(/&/g, '%26'); - var xml_http = new XMLHttpRequest(); + xml_http.open('POST', base_url + '/block_edit.php', true); + xml_http.setRequestHeader('Content-Type', + 'application/x-www-form-urlencoded'); + xml_http.addEventListener("load", serverRequestListener); + xml_http.send('edit_doc=' + doc_id + '&edit_string=' + id + + '&edit_text=' + encoded_text + '&dont_mark_fuzzy=' + (not_mark ? '1' : '0')); - var encoded_text = encodeURI(new_text).replace(/&/g, '%26'); - - xml_http.open('POST', base_url + '/block_edit.php', true); - xml_http.setRequestHeader('Content-Type', - 'application/x-www-form-urlencoded'); - xml_http.addEventListener("load", editSaveFinished); - xml_http.send('edit_doc=' + doc_id + '&edit_string=' + id + - '&edit_text=' + encoded_text + '&dont_mark_fuzzy=' + (not_mark ? '1' : '0')); - - xml_http.userguide_string_id = id; - xml_http.userguide_new_text = new_text; - return; - } else { - window.edited_node.innerHTML = formatText(source_strings[id]); - } + xml_http.userguide_string_id = id; + xml_http.userguide_new_text = new_text; + xml_http.userguide_fuzzy = !not_mark; +} - edit_window.close(); - edit_window = null; - window.edited_node = null; +function cancelEdition(node, id) { + node.innerHTML = formatText(source_strings[id]); + closeEditWindow(); } -function editSaveFinished() { - edit_window.focus(); +var removeBlock = cancelEdition; - var resp = this.responseText, - id = this.userguide_string_id, - new_text = this.userguide_new_text; - - var send_ok; - if (resp.substring(0, 7) == 'badxml ') - edit_window.alert('The server rejected the translation because of XML ' + - "parsing errors :\n" + this.responseText.substring(3) + - "\n" + 'Check the XML tags used in your translation.'); - else if (resp.substring(0, 6) == 'interr') - edit_window.alert('The original XML code seems corrupt. Please contact ' + - 'an administrator.' + "\n"); - else if (resp.substring(0, 2) != 'ok') - edit_window.alert('There was an error sending the translation. Please ' + - 'retry.' + "\n" + this.responseText); - else - send_ok = true; +function editSaveFinished(id, new_text, fuzzy, send_ok) { + edit_window.focus(); for (var i = 0 ; i < linked_nodes[id].length ; i++) { linked_nodes[id][i].innerHTML = formatText(new_text); @@ -80,9 +51,7 @@ function editSaveFinished() { return; } - edit_window.close(); - edit_window = null; - window.edited_node = null; + closeEditWindow(); // Refresh the statistics xml_http = new XMLHttpRequest(); diff --git a/userguide/shared/blocks.js b/userguide/shared/blocks.js index 220b5ed..8fa9d87 100644 --- a/userguide/shared/blocks.js +++ b/userguide/shared/blocks.js @@ -37,3 +37,55 @@ function blockClickHandler(node) { window.translated_text = translated_strings[id]; } } + +function endEditionEvent(clickOK) { + if (window.edited_node == null) + return; + + const node = window.edited_node; + const id = node.getAttribute(attr_trans_id); + + if (clickOK) { + const new_text = edit_window.document.getElementById('modified').value; + if (new_text.trim() == '') { + removeBlock(node, id); + } else { + const fuzzy = + edit_window.document.getElementById('fuzzy_check').checked; + sendEdition(node, id, new_text, fuzzy); + } + } else { + cancelEdition(node, id); + } +} + +function closeEditWindow() { + edit_window.close(); + edit_window = null; + window.edited_node = null; +} + +function serverRequestListener() { + const resp = this.responseText; + var send_ok = false; + + if (resp.substring(0, 7) == 'badxml ') + edit_window.alert('The server rejected the change because of XML ' + + "parsing errors :\n" + resp.substring(3) + + "\n" + 'Check the XML tags.'); + else if (resp.substring(0, 7) == 'diffxml') + edit_window.alert('The server rejected the change because the ' + + 'XML code used in it differs from the original string.' + "\n" + + 'Check the XML tags.'); + else if (resp.substring(0, 6) == 'interr') + edit_window.alert('The original XML code seems corrupt. Please contact ' + + 'an administrator.' + "\n"); + else if (resp.substring(0, 2) != 'ok') + edit_window.alert('There was an error sending the change. Please ' + + 'retry.' + "\n" + resp); + else + send_ok = true; + + editSaveFinished(this.userguide_string_id, this.userguide_new_text, + this.userguide_fuzzy, send_ok); +} diff --git a/userguide/shared/edit_tool.html b/userguide/shared/edit_tool.html index 5e7b8bb..2aa9092 100644 --- a/userguide/shared/edit_tool.html +++ b/userguide/shared/edit_tool.html @@ -40,7 +40,7 @@
diff --git a/userguide/shared/tool.css b/userguide/shared/tool.css index ffa9bbf..2fd0082 100644 --- a/userguide/shared/tool.css +++ b/userguide/shared/tool.css @@ -73,7 +73,7 @@ textarea, input, button { margin-left: 5px; } -#original, #translated, #modified { +#original, #modified { width: 99%; height: 99%; } diff --git a/userguide/shared/translate_tool.html b/userguide/shared/translate_tool.html index 46164bd..f33efe3 100644 --- a/userguide/shared/translate_tool.html +++ b/userguide/shared/translate_tool.html @@ -36,7 +36,7 @@ var last_update_text = ''; function updateEvent() { - var text = document.getElementById('translated').value + var text = document.getElementById('modified').value if (text == last_update_text) { window.setTimeout(5000); @@ -58,7 +58,7 @@ window.opener.original_text); document.getElementById('original').value = window.opener.original_text; - document.getElementById('translated').value = text; + document.getElementById('modified').value = text; document.getElementById('valid_button').value = 'Send ' + window.opener.lang_name + ' Translation'; @@ -69,10 +69,10 @@ pos = selectText(text); if (pos) - document.getElementById('translated').setSelectionRange(pos[0], pos[1]); + document.getElementById('modified').setSelectionRange(pos[0], pos[1]); else { - document.getElementById('translated').focus(); - document.getElementById('translated').select(); + document.getElementById('modified').focus(); + document.getElementById('modified').select(); } window.setTimeout(updateEvent, 500); @@ -106,13 +106,13 @@
Translated text
- +
diff --git a/userguide/shared/translate_tool.js b/userguide/shared/translate_tool.js index cee151e..c34c42d 100644 --- a/userguide/shared/translate_tool.js +++ b/userguide/shared/translate_tool.js @@ -15,77 +15,51 @@ var title_id = 0; var title_insert = false; var first_title = null; -function endEditionEvent(clickOK) { - if (window.edited_node == null) - return; - - var id = window.edited_node.getAttribute(attr_trans_id); - var next_node = null; +function sendEdition(node, id, trans, mark_fuzzy) { + translated_strings[id] = trans; - if (clickOK) { - var trans = edit_window.document.getElementById('translated').value; - var mark_fuzzy = edit_window.document.getElementById('mark_fuzzy').checked; + var xml_http = new XMLHttpRequest(); - translated_strings[id] = trans; + var encoded_source = encodeURI(source_strings[id]).replace(/&/g, '%26').replace(/\+/g, '%2B'); + var encoded_translation = encodeURI(trans).replace(/&/g, '%26').replace(/\+/g, '%2B'); - var xml_http = new XMLHttpRequest(); + xml_http.open('POST', base_url + '/translate.php', true); + xml_http.addEventListener("load", serverRequestListener); + xml_http.setRequestHeader('Content-Type', + 'application/x-www-form-urlencoded'); + xml_http.send('translate_lang=' + lang + '&translate_doc=' + doc_id + + '&translate_string=' + id + '&translate_text=' + encoded_translation + + '&translate_source=' + encoded_source + '&is_fuzzy=' + (mark_fuzzy ? '1' : '0')); - var encoded_source = encodeURI(source_strings[id]).replace(/&/g, '%26').replace(/\+/g, '%2B'); - var encoded_translation = encodeURI(trans).replace(/&/g, '%26').replace(/\+/g, '%2B'); + xml_http.userguide_string_id = id; + xml_http.userguide_new_text = trans; + xml_http.userguide_fuzzy = mark_fuzzy; +} - xml_http.open('POST', base_url + '/translate.php', true); - xml_http.addEventListener("load", translateSaveFinished); - xml_http.setRequestHeader('Content-Type', - 'application/x-www-form-urlencoded'); - xml_http.send('translate_lang=' + lang + '&translate_doc=' + doc_id + - '&translate_string=' + id + '&translate_text=' + encoded_translation + - '&translate_source=' + encoded_source + '&is_fuzzy=' + (mark_fuzzy ? '1' : '0')); +function cancelEdition(node, id) { + var text = translated_strings[id] == '' ? source_strings[id] : translated_strings[id]; + node.innerHTML = formatText(text); + closeEditWindow(); +} - xml_http.userguide_string_id = id; - xml_http.userguide_trans = trans; - xml_http.userguide_mark_fuzzy = mark_fuzzy; - return; - } else { - window.edited_node.innerHTML = formatText(translated_strings[id]); - if (window.edited_node.innerHTML == '' - || window.edited_node.innerText == '') - window.edited_node.innerHTML = formatText(source_strings[id]); - translateBlockDone(next_node); - } +function removeBlock(node, id) { + // TODO: currently gives an error due to empty text, but could be used + // to remove a translation + sendEdition(node, id, '', false); } -function translateSaveFinished() { +function editSaveFinished(id, trans, fuzzy, send_ok) { edit_window.focus(); - var resp = this.responseText; - var id = this.userguide_string_id; var next_node; - var send_ok; - if (resp.substring(0, 7) == 'badxml ') - edit_window.alert('The server rejected the translation because of XML ' + - "parsing errors :\n" + this.responseText.substring(3) + - "\n" + 'Check the XML tags used in your translation.'); - else if (resp.substring(0, 7) == 'diffxml') - edit_window.alert('The server rejected the translation because the ' + - 'XML code used in it differs from the original string.' + "\n" + - 'Check the XML tags used in your translation.'); - else if (resp.substring(0, 6) == 'interr') - edit_window.alert('The original XML code seems corrupt. Please contact ' + - 'an administrator.' + "\n"); - else if (resp.substring(0, 2) != 'ok') - edit_window.alert('There was an error sending the translation. Please ' + - 'retry.' + "\n" + this.responseText); - else - send_ok = true; - - is_fuzzy[id] = this.userguide_mark_fuzzy; + is_fuzzy[id] = fuzzy; for (var i = 0 ; i < linked_nodes[id].length ; i++) { - linked_nodes[id][i].innerHTML = formatText(this.userguide_trans); + linked_nodes[id][i].innerHTML = formatText(trans); if (send_ok) { - if (this.userguide_mark_fuzzy) { + if (fuzzy) { linked_nodes[id][i].setAttribute(attr_state, 'fuzzy'); } else { linked_nodes[id][i].removeAttribute(attr_state); @@ -123,9 +97,7 @@ function translateBlockDone(next_node) { window.translated_text = translated_strings[id]; window.setTimeout(edit_window.refreshAll, 0); } else { - edit_window.close(); - edit_window = null; - window.edited_node = null; + closeEditWindow(); } } From 31424074a3884802f7aab2d7da4302ede254f2da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Casta=C3=B1eda?= Date: Fri, 21 Aug 2020 19:39:28 +0200 Subject: [PATCH 05/10] fix: blocks js: update internal state only after accepting a change The text and fuzzy variables are updated even when the server does not accept a change. The block is marked and the edit window is kept open, but the user may just cancel and the block will keep the incorrect text, instead of going back to the original. Update the variables only for accepted changes, so that on cancel the page reflects the real values. --- userguide/shared/block_edit_tool.js | 27 ++++++++-------- userguide/shared/translate_tool.js | 48 ++++++++++++++--------------- 2 files changed, 38 insertions(+), 37 deletions(-) diff --git a/userguide/shared/block_edit_tool.js b/userguide/shared/block_edit_tool.js index c1386b0..202f868 100644 --- a/userguide/shared/block_edit_tool.js +++ b/userguide/shared/block_edit_tool.js @@ -9,8 +9,6 @@ var linked_nodes = new Array(); var original_text; function sendEdition(node, id, new_text, not_mark) { - source_strings[id] = new_text; - var xml_http = new XMLHttpRequest(); var encoded_text = encodeURI(new_text).replace(/&/g, '%26'); @@ -29,6 +27,7 @@ function sendEdition(node, id, new_text, not_mark) { function cancelEdition(node, id) { node.innerHTML = formatText(source_strings[id]); + node.setAttribute(attr_state, getBlockState(id)); closeEditWindow(); } @@ -37,20 +36,20 @@ var removeBlock = cancelEdition; function editSaveFinished(id, new_text, fuzzy, send_ok) { edit_window.focus(); - for (var i = 0 ; i < linked_nodes[id].length ; i++) { - linked_nodes[id][i].innerHTML = formatText(new_text); - if (send_ok) { - linked_nodes[id][i].removeAttribute(attr_state); - } else { - linked_nodes[id][i].setAttribute(attr_state, 'error'); - } - } - if (!send_ok) { - edit_window.focus(); + window.edited_node.setAttribute(attr_state, 'error'); return; } + source_strings[id] = new_text; + new_text = formatText(new_text); + const state = getBlockState(id); + + for (var i = 0 ; i < linked_nodes[id].length ; i++) { + linked_nodes[id][i].innerHTML = new_text; + linked_nodes[id][i].setAttribute(attr_state, state); + } + closeEditWindow(); // Refresh the statistics @@ -59,6 +58,10 @@ function editSaveFinished(id, new_text, fuzzy, send_ok) { xml_http.send(null); } +function getBlockState(id) { + return ''; +} + function setProperties(node) { if (node == null) return; diff --git a/userguide/shared/translate_tool.js b/userguide/shared/translate_tool.js index c34c42d..6607c06 100644 --- a/userguide/shared/translate_tool.js +++ b/userguide/shared/translate_tool.js @@ -16,8 +16,6 @@ var title_insert = false; var first_title = null; function sendEdition(node, id, trans, mark_fuzzy) { - translated_strings[id] = trans; - var xml_http = new XMLHttpRequest(); var encoded_source = encodeURI(source_strings[id]).replace(/&/g, '%26').replace(/\+/g, '%2B'); @@ -39,6 +37,7 @@ function sendEdition(node, id, trans, mark_fuzzy) { function cancelEdition(node, id) { var text = translated_strings[id] == '' ? source_strings[id] : translated_strings[id]; node.innerHTML = formatText(text); + node.setAttribute(attr_state, getBlockState(id)); closeEditWindow(); } @@ -51,27 +50,21 @@ function removeBlock(node, id) { function editSaveFinished(id, trans, fuzzy, send_ok) { edit_window.focus(); + if (!send_ok) { + window.edited_node.setAttribute(attr_state, 'error'); + return; + } + var next_node; is_fuzzy[id] = fuzzy; + translated_strings[id] = trans; + trans = formatText(trans); + const state = getBlockState(id); for (var i = 0 ; i < linked_nodes[id].length ; i++) { - linked_nodes[id][i].innerHTML = formatText(trans); - - if (send_ok) { - if (fuzzy) { - linked_nodes[id][i].setAttribute(attr_state, 'fuzzy'); - } else { - linked_nodes[id][i].removeAttribute(attr_state); - } - } else { - linked_nodes[id][i].setAttribute(attr_state, 'error'); - } - } - - if (!send_ok) { - edit_window.focus(); - return; + linked_nodes[id][i].innerHTML = trans; + linked_nodes[id][i].setAttribute(attr_state, state); } if (edit_window.document.getElementById('auto_cont').checked) { @@ -101,6 +94,15 @@ function translateBlockDone(next_node) { } } +function getBlockState(id) { + if (translated_strings[id] == '') { + return 'untranslated'; + } else if (is_fuzzy[id]) { + return 'fuzzy'; + } + return ''; +} + function setProperties(node) { if (node == null) return; @@ -113,15 +115,11 @@ function setProperties(node) { var node_name = node.tagName.toLowerCase(); if (node_name != "title") { // We can't touch it - if (translated_strings[id] == '') { - node.setAttribute(attr_state, 'untranslated'); - } else if (is_fuzzy[id]) { - node.setAttribute(attr_state, 'fuzzy'); - node.innerHTML = formatText(translated_strings[id]); - } else { + const state = getBlockState(id); + if (translated_strings[id] != '') { node.innerHTML = formatText(translated_strings[id]); } - + node.setAttribute(attr_state, state); node.setAttribute('_internal_id', all_nodes.length); all_nodes.push(node); From 5888110828e36d9b36bf8f105969c8de6fe77503 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Casta=C3=B1eda?= Date: Fri, 21 Aug 2020 19:59:59 +0200 Subject: [PATCH 06/10] blocks js: remove linked_nodes array --- userguide/shared/block_edit_tool.js | 36 +++-------------------------- userguide/shared/blocks.js | 7 ++++++ userguide/shared/translate_tool.js | 16 +++---------- 3 files changed, 13 insertions(+), 46 deletions(-) diff --git a/userguide/shared/block_edit_tool.js b/userguide/shared/block_edit_tool.js index 202f868..b1fb034 100644 --- a/userguide/shared/block_edit_tool.js +++ b/userguide/shared/block_edit_tool.js @@ -1,11 +1,7 @@ -const attr_trans_id = '_translation_id'; -const attr_state = '_edit_state'; - const edit_tool = '/shared/edit_tool.html'; const edit_tool_ctx = 'EditBlock'; var edited_node = null; -var linked_nodes = new Array(); var original_text; function sendEdition(node, id, new_text, not_mark) { @@ -45,9 +41,9 @@ function editSaveFinished(id, new_text, fuzzy, send_ok) { new_text = formatText(new_text); const state = getBlockState(id); - for (var i = 0 ; i < linked_nodes[id].length ; i++) { - linked_nodes[id][i].innerHTML = new_text; - linked_nodes[id][i].setAttribute(attr_state, state); + for (const node of getBlockNodes(id)) { + node.innerHTML = new_text; + node.setAttribute(attr_state, state); } closeEditWindow(); @@ -62,31 +58,6 @@ function getBlockState(id) { return ''; } -function setProperties(node) { - if (node == null) - return; - - if (node.getAttribute) { // Avoid special nodes - if (node.getAttribute(attr_trans_id) != null) { - var id = node.getAttribute(attr_trans_id); - - if (source_strings[id]) { - if (linked_nodes[id] == null) { - linked_nodes[id] = [ node ]; - } else { - linked_nodes[id].push(node); - } - } - - return; - } - } - - for (var i = 0 ; i < node.childNodes.length ; i++) { - setProperties(node.childNodes[i]); - } -} - window.onload = function() { var functions_ok = 0; @@ -105,6 +76,5 @@ window.onload = function() { lockDocument(doc_id); } - setProperties(document.getElementsByTagName('body')[0]); document.addEventListener('click', clickHandler); } diff --git a/userguide/shared/blocks.js b/userguide/shared/blocks.js index 8fa9d87..7f2e696 100644 --- a/userguide/shared/blocks.js +++ b/userguide/shared/blocks.js @@ -1,3 +1,6 @@ +const attr_trans_id = '_translation_id'; +const attr_state = '_edit_state'; + function clickHandler(e) { var node = e.target; if (node.nodeName.toLowerCase() == 'img') { @@ -89,3 +92,7 @@ function serverRequestListener() { editSaveFinished(this.userguide_string_id, this.userguide_new_text, this.userguide_fuzzy, send_ok); } + +function getBlockNodes(id) { + return document.querySelectorAll('[' + attr_trans_id + '="' + id + '"]'); +} diff --git a/userguide/shared/translate_tool.js b/userguide/shared/translate_tool.js index 6607c06..dcc0c88 100644 --- a/userguide/shared/translate_tool.js +++ b/userguide/shared/translate_tool.js @@ -1,6 +1,3 @@ -const attr_trans_id = '_translation_id'; -const attr_state = '_edit_state'; - const edit_tool = '/shared/translate_tool.html'; const edit_tool_ctx = 'TranslateBlock'; @@ -8,7 +5,6 @@ var edit_window = null; var edited_node = null; var original_text; var translated_text; -var linked_nodes = new Array(); var all_nodes = new Array(); var title_id = 0; @@ -62,9 +58,9 @@ function editSaveFinished(id, trans, fuzzy, send_ok) { trans = formatText(trans); const state = getBlockState(id); - for (var i = 0 ; i < linked_nodes[id].length ; i++) { - linked_nodes[id][i].innerHTML = trans; - linked_nodes[id][i].setAttribute(attr_state, state); + for (const node of getBlockNodes(id)) { + node.innerHTML = trans; + node.setAttribute(attr_state, state); } if (edit_window.document.getElementById('auto_cont').checked) { @@ -122,12 +118,6 @@ function setProperties(node) { node.setAttribute(attr_state, state); node.setAttribute('_internal_id', all_nodes.length); all_nodes.push(node); - - if (linked_nodes[id] == null) { - linked_nodes[id] = [ node ]; - } else { - linked_nodes[id].push(node); - } } if (title_id == 0 && node_name == "title") { From bf5ebba45b5e3ffdf6f3a28988624e288fc8e338 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Casta=C3=B1eda?= Date: Sat, 22 Aug 2020 11:56:27 +0200 Subject: [PATCH 07/10] translate tool: take the title hack out of the recursive DOM walk Also fixes the title not being translated on load. --- userguide/shared/blocks.js | 30 ++++++++++++++++-- userguide/shared/translate_tool.js | 49 +++++------------------------- 2 files changed, 35 insertions(+), 44 deletions(-) diff --git a/userguide/shared/blocks.js b/userguide/shared/blocks.js index 7f2e696..9ce81c7 100644 --- a/userguide/shared/blocks.js +++ b/userguide/shared/blocks.js @@ -93,6 +93,32 @@ function serverRequestListener() { this.userguide_fuzzy, send_ok); } -function getBlockNodes(id) { - return document.querySelectorAll('[' + attr_trans_id + '="' + id + '"]'); +function getBlockNodes(id, root = document) { + return root.querySelectorAll('[' + attr_trans_id + '="' + id + '"]'); +} + +function insertUnreachableBlocks() { + // We can't click some translatable blocks (namely title), so we insert + // them in the document if there is no other copy. + var insertPoint = null; + + const blocks = document.head.querySelectorAll('[' + attr_trans_id + ']'); + for (const block of blocks) { + const id = block.getAttribute(attr_trans_id); + + if (getBlockNodes(id, document.body).length == 0) { + if (insertPoint === null) { + insertPoint = document.querySelector('h1, h2'); + if (insertPoint === null) { + insertPoint = document.body.firstChild; + } + } + + var new_node = document.createElement('h1'); + new_node.innerHTML = block.innerHTML; + new_node.setAttribute(attr_trans_id, id); + new_node.setAttribute('title', 'This is a fake header for content that the tool cannot reach. It won\'t be here in the final document.'); + insertPoint.parentNode.insertBefore(new_node, insertPoint); + } + } } diff --git a/userguide/shared/translate_tool.js b/userguide/shared/translate_tool.js index dcc0c88..5942a75 100644 --- a/userguide/shared/translate_tool.js +++ b/userguide/shared/translate_tool.js @@ -7,10 +7,6 @@ var original_text; var translated_text; var all_nodes = new Array(); -var title_id = 0; -var title_insert = false; -var first_title = null; - function sendEdition(node, id, trans, mark_fuzzy) { var xml_http = new XMLHttpRequest(); @@ -108,28 +104,13 @@ function setProperties(node) { var id = node.getAttribute(attr_trans_id); if (source_strings[id]) { - var node_name = node.tagName.toLowerCase(); - - if (node_name != "title") { // We can't touch it - const state = getBlockState(id); - if (translated_strings[id] != '') { - node.innerHTML = formatText(translated_strings[id]); - } - node.setAttribute(attr_state, state); - node.setAttribute('_internal_id', all_nodes.length); - all_nodes.push(node); - } - - if (title_id == 0 && node_name == "title") { - title_id = id; - title_insert = true; - } else if (id == title_id) { - title_insert = false; - } - - if (first_title == null && (node_name == "h1" || node_name == "h2")) { - first_title = node; + const state = getBlockState(id); + if (translated_strings[id] != '') { + node.innerHTML = formatText(translated_strings[id]); } + node.setAttribute(attr_state, state); + node.setAttribute('_internal_id', all_nodes.length); + all_nodes.push(node); } return; } @@ -156,24 +137,8 @@ window.onload = function() { return; } + insertUnreachableBlocks(); setProperties(document.getElementsByTagName('html')[0]); - if (title_insert) { - // The translatable text used in the tag is not used anywhere else. - // Since the translate tool does not allow translating <title>, we must insert - // this text somewhere. - var new_title = document.createElement("h1"); - new_title.setAttribute(attr_trans_id, title_id); - new_title.appendChild(document.createTextNode(source_strings[title_id])); - - if (first_title != null && first_title.parentNode != null) { - first_title.parentNode.insertBefore(new_title, first_title); - } else { - document.body.insertBefore(new_title, document.body.firstChild); - } - - setProperties(new_title); - } - document.addEventListener('click', clickHandler); } From 2168626e47548797cfbd25b64e1079092af3348a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Casta=C3=B1eda?= <antiswen@yahoo.es> Date: Sat, 22 Aug 2020 12:04:35 +0200 Subject: [PATCH 08/10] block edit: add the title to the document flow Add unreachable blocks like in the translate tool, so that they can be edited like body blocks. --- userguide/shared/block_edit_tool.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/userguide/shared/block_edit_tool.js b/userguide/shared/block_edit_tool.js index b1fb034..20fa404 100644 --- a/userguide/shared/block_edit_tool.js +++ b/userguide/shared/block_edit_tool.js @@ -76,5 +76,7 @@ window.onload = function() { lockDocument(doc_id); } + insertUnreachableBlocks(); + document.addEventListener('click', clickHandler); } From b8de06ed71f43bc8e4979eac9a6dc7e796913f11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Casta=C3=B1eda?= <antiswen@yahoo.es> Date: Sat, 22 Aug 2020 12:59:49 +0200 Subject: [PATCH 09/10] translate tool: rewrite node initialization Directly loop through the nodes marked as translatable blocks, instead of recursively visiting each node and its children to find them. --- userguide/shared/blocks.js | 4 ++++ userguide/shared/translate_tool.js | 34 ++++++++++-------------------- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/userguide/shared/blocks.js b/userguide/shared/blocks.js index 9ce81c7..f8e361f 100644 --- a/userguide/shared/blocks.js +++ b/userguide/shared/blocks.js @@ -97,6 +97,10 @@ function getBlockNodes(id, root = document) { return root.querySelectorAll('[' + attr_trans_id + '="' + id + '"]'); } +function getAllBlockNodes(root = document) { + return root.querySelectorAll('[' + attr_trans_id + ']'); +} + function insertUnreachableBlocks() { // We can't click some translatable blocks (namely title), so we insert // them in the document if there is no other copy. diff --git a/userguide/shared/translate_tool.js b/userguide/shared/translate_tool.js index 5942a75..e5441a7 100644 --- a/userguide/shared/translate_tool.js +++ b/userguide/shared/translate_tool.js @@ -95,30 +95,16 @@ function getBlockState(id) { return ''; } -function setProperties(node) { - if (node == null) - return; - - if (node.getAttribute) { // Avoid special nodes - if (node.getAttribute(attr_trans_id) != null) { - var id = node.getAttribute(attr_trans_id); - - if (source_strings[id]) { - const state = getBlockState(id); - if (translated_strings[id] != '') { - node.innerHTML = formatText(translated_strings[id]); - } - node.setAttribute(attr_state, state); - node.setAttribute('_internal_id', all_nodes.length); - all_nodes.push(node); - } - return; - } - } +function initializeNode(node) { + const id = node.getAttribute(attr_trans_id); + const state = getBlockState(id); - for (var i = 0 ; i < node.childNodes.length ; i++) { - setProperties(node.childNodes[i]); + if (translated_strings[id] != '') { + node.innerHTML = formatText(translated_strings[id]); } + node.setAttribute(attr_state, state); + node.setAttribute('_internal_id', all_nodes.length); + all_nodes.push(node); } window.onload = function() { @@ -138,7 +124,9 @@ window.onload = function() { } insertUnreachableBlocks(); - setProperties(document.getElementsByTagName('html')[0]); + for (const node of getAllBlockNodes()) { + initializeNode(node); + } document.addEventListener('click', clickHandler); } From 7b6791d0cef3c54c6ac73c03e0a986624a7c1b8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1ximo=20Casta=C3=B1eda?= <antiswen@yahoo.es> Date: Sat, 22 Aug 2020 14:54:58 +0200 Subject: [PATCH 10/10] Edit tool: Collect common XMLHttpRequest use into a function --- userguide/shared/block_edit_tool.js | 27 +++++++++---------- userguide/shared/common.js | 41 ++++++++++++++++++++++++++--- userguide/shared/translate_tool.js | 27 +++++++++---------- 3 files changed, 62 insertions(+), 33 deletions(-) diff --git a/userguide/shared/block_edit_tool.js b/userguide/shared/block_edit_tool.js index 20fa404..ae7c6a8 100644 --- a/userguide/shared/block_edit_tool.js +++ b/userguide/shared/block_edit_tool.js @@ -5,16 +5,15 @@ var edited_node = null; var original_text; function sendEdition(node, id, new_text, not_mark) { - var xml_http = new XMLHttpRequest(); - - var encoded_text = encodeURI(new_text).replace(/&/g, '%26'); - - xml_http.open('POST', base_url + '/block_edit.php', true); - xml_http.setRequestHeader('Content-Type', - 'application/x-www-form-urlencoded'); - xml_http.addEventListener("load", serverRequestListener); - xml_http.send('edit_doc=' + doc_id + '&edit_string=' + id + - '&edit_text=' + encoded_text + '&dont_mark_fuzzy=' + (not_mark ? '1' : '0')); + var xml_http = HTTPRequest('POST', 'block_edit.php', + { + edit_doc: doc_id, + edit_string: id, + edit_text: new_text, + dont_mark_fuzzy: (not_mark ? '1' : '0'), + } , { + load: serverRequestListener, + }); xml_http.userguide_string_id = id; xml_http.userguide_new_text = new_text; @@ -49,9 +48,7 @@ function editSaveFinished(id, new_text, fuzzy, send_ok) { closeEditWindow(); // Refresh the statistics - xml_http = new XMLHttpRequest(); - xml_http.open('GET', base_url + '/update_stats.php?doc_id=' + doc_id, true); - xml_http.send(null); + HTTPRequest('GET', 'update_stats.php', {doc_id: doc_id}); } function getBlockState(id) { @@ -64,13 +61,13 @@ window.onload = function() { if (window.XMLHttpRequest) functions_ok++; - if (encodeURI) + if (Object.entries) functions_ok++; if (functions_ok != 2) { window.alert('Your browser does not support some JavaScript ' + 'functions which are needed for this page to work correctly. ' + - "\nBrowser known to work : Safari 4, Firefox/BeZillaBrowser 2.x, " + "3.x."); + "\nTry again with an updated modern browser."); return; } else { lockDocument(doc_id); diff --git a/userguide/shared/common.js b/userguide/shared/common.js index 3d8b79d..9c63c77 100644 --- a/userguide/shared/common.js +++ b/userguide/shared/common.js @@ -1,11 +1,44 @@ const ping_delay = 60; // in seconds +function HTTPRequest(method, endpoint, params = null, listeners = null) { + var xml_http = new XMLHttpRequest(); + var url = base_url + '/' + endpoint; + var body = null; + var paramString = null; + + if (params != null) { + var paramArray = []; + for (const [k,v] of Object.entries(params)) { + paramArray.push(encodeURIComponent(k) + '=' + encodeURIComponent(v)); + } + paramString = paramArray.join('&'); + + if (method == 'GET') { + url = url + '?' + paramString; + } else if (method == 'POST') { + body = paramString; + } + } + + if (listeners != null) { + for (const [k,v] of Object.entries(listeners)) { + xml_http.addEventListener(k, v); + } + } + + xml_http.open(method, url, true); + if (method == 'POST') { + xml_http.setRequestHeader('Content-Type', + 'application/x-www-form-urlencoded'); + } + xml_http.send(body); + + return xml_http; +} + function lockDocument(doc_id) { function pingServer() { - var xml_http = new XMLHttpRequest(); - - xml_http.open('GET', base_url + '/lock.php?doc_id=' + doc_id); - xml_http.send(null); + HTTPRequest('GET', 'lock.php', {doc_id: doc_id}); } setInterval(pingServer, ping_delay * 1000); diff --git a/userguide/shared/translate_tool.js b/userguide/shared/translate_tool.js index e5441a7..3f36b61 100644 --- a/userguide/shared/translate_tool.js +++ b/userguide/shared/translate_tool.js @@ -8,18 +8,17 @@ var translated_text; var all_nodes = new Array(); function sendEdition(node, id, trans, mark_fuzzy) { - var xml_http = new XMLHttpRequest(); - - var encoded_source = encodeURI(source_strings[id]).replace(/&/g, '%26').replace(/\+/g, '%2B'); - var encoded_translation = encodeURI(trans).replace(/&/g, '%26').replace(/\+/g, '%2B'); - - xml_http.open('POST', base_url + '/translate.php', true); - xml_http.addEventListener("load", serverRequestListener); - xml_http.setRequestHeader('Content-Type', - 'application/x-www-form-urlencoded'); - xml_http.send('translate_lang=' + lang + '&translate_doc=' + doc_id + - '&translate_string=' + id + '&translate_text=' + encoded_translation + - '&translate_source=' + encoded_source + '&is_fuzzy=' + (mark_fuzzy ? '1' : '0')); + var xml_http = HTTPRequest('POST', 'translate.php', + { + translate_lang: lang, + translate_doc: doc_id, + translate_string: id, + translate_text: trans, + translate_source: source_strings[id], + is_fuzzy: (mark_fuzzy ? '1' : '0'), + } , { + load: serverRequestListener, + }); xml_http.userguide_string_id = id; xml_http.userguide_new_text = trans; @@ -113,13 +112,13 @@ window.onload = function() { if (window.XMLHttpRequest) functions_ok++; - if (encodeURI) + if (Object.entries) functions_ok++; if (functions_ok != 2) { window.alert('Your browser does not support some JavaScript ' + 'functions which are needed for this page to work correctly. ' + - "\nBrowser known to work : Safari 4, Firefox/BeZillaBrowser 2.x, " + "3.x."); + "\nTry again with an updated modern browser."); return; }