diff --git l10n_community/ajax.inc l10n_community/ajax.inc deleted file mode 100644 index 779ea10..0000000 --- l10n_community/ajax.inc +++ /dev/null @@ -1,166 +0,0 @@ -project_title != $previous_project) { - if (!empty($list)) { - $output[] = join(', ', $list); - } - $list = array(''. $instance->project_title .': '. $instance->release_title .' ('. $instance->occurance_count .')'); - } - else { - $list[] = $instance->release_title .' ('. $instance->occurance_count .')'; - } - $previous_project = $instance->project_title; - } - $output[] = join(', ', $list); - $usage_info = ''. t('Used in:') .''. theme('item_list', $output); - - // Information about translator and translation timestamp. - $translation_info = ''; - $translation = db_fetch_object(db_query("SELECT translation, uid_entered, time_entered FROM {l10n_community_translation} WHERE language = '%s' AND sid = %d AND is_active = 1 AND is_suggestion = 0", $langcode, $sid)); - if (!empty($translation->translation)) { - $account = user_load(array('uid' => $translation->uid_entered)); - $translation_info = t('Translated by:
%username at %date', array('%username' => $account->name, '%date' => format_date($translation->time_entered))); - } - - print $usage_info . $translation_info; // . $suggestion_info; - exit; -} - -function l10n_community_string_suggestions($langcode = NULL, $sid = 0) { - global $user; - - // Existing, "unresolved" suggestions. - $suggestions = array(); - $result = db_query("SELECT t.tid, t.sid, t.translation, t.uid_entered, t.time_entered, u.name FROM {l10n_community_translation} t LEFT JOIN {users} u ON u.uid = t.uid_entered WHERE t.language = '%s' AND t.sid = %d AND t.is_active = 1 AND t.is_suggestion = 1 ORDER BY t.time_entered", $langcode, $sid); - - $perm = l10n_community_get_permission($langcode); - while ($suggestion = db_fetch_object($result)) { - // This detail pane is only retrieved from JS, so we are free to output obtrusive JS here. - $token = "'". drupal_get_token('l10n_server_'. $suggestion->tid .'_'. $suggestion->sid) ."'"; - $approve_button = $decline_button = ''; - if ($perm & ($suggestion->uid_entered == $user->uid ? L10N_PERM_MODERATE_OWN : L10N_PERM_MODERATE_OTHERS)) { - $approve_button = theme('l10n_community_button', 'approve', 'l10n-approve', 'onclick="l10nCommunity.approveSuggestion('. $suggestion->tid .','. $suggestion->sid .', this, '. $token .');" title="'. t('Approve suggestion.') .'"'); - $decline_button = theme('l10n_community_button', 'decline', 'l10n-decline', 'onclick="l10nCommunity.declineSuggestion('. $suggestion->tid .','. $suggestion->sid .', this, '. $token .');" title="'. t('Decline suggestion.') .'"'); - } - - // Plural versions are shown in a short form. - // Since we no longer store an additional copy of strings in JS - // and the null byte is stripped out by browsers (?), we need to - // use a DOM-safe delimiter: "; " - $translation = strpos($suggestion->translation, "\0") ? str_replace(chr(0), "; ", $suggestion->translation) : $suggestion->translation; - $translation = l10n_community_format_text($translation, $suggestion->sid); - $suggestions[] = $translation ."
". $approve_button . $decline_button .'
'. t('by %user at %date', array('%user' => (empty($suggestion->name) ? variable_get('anonymous', t('Anonymous')) : $suggestion->name), '%date' => format_date($suggestion->time_entered))) .'
'; - } - if (count($suggestions)) { - // List of suggestions. - $suggestion_info = ''. t('Outstanding suggestions:') .''. theme('l10n_community_strings', $suggestions); - } - else { - $suggestion_info .= ''. t('No suggestions found.') .''; - } - print $suggestion_info; - exit; -} - -/** - * Checks permission of current user to approve or decline $tid. - * - * @param $tid - * Suggestion ID. - * @return - * The suggestion object if validated $tid, user permissions and token. - */ -function l10n_community_string_ajax_suggestion($tid = 0) { - global $user; - - if (($suggestion = db_fetch_object(db_query("SELECT * FROM {l10n_community_translation} WHERE tid = %d AND is_suggestion = 1 AND is_active = 1", $tid))) && - ($perm = l10n_community_get_permission($suggestion->language)) && - ($perm & ($suggestion->uid_entered == $user->uid ? L10N_PERM_MODERATE_OWN : L10N_PERM_MODERATE_OTHERS)) && - !empty($_GET['form_token']) && - drupal_valid_token($_GET['form_token'], 'l10n_server_'. $suggestion->tid .'_'. $suggestion->sid)) { - return $suggestion; - } - return FALSE; -} - -/** - * Records approval of a previous string suggestion. - * - * This callback is invoked from JavaScript. - * - * @param $tid - * Suggestion ID. - */ -function l10n_community_string_approve($tid = 0) { - global $user; - - if ($suggestion = l10n_community_string_ajax_suggestion($tid)) { - // Mark existing translations and suggestions as inactive in this language. - db_query("UPDATE {l10n_community_translation} SET is_active = 0 WHERE sid = %d AND language = '%s'", $suggestion->sid, $suggestion->language); - // Remove placeholder translation record (which was there if - // first came suggestions, before an actual translation). - db_query("DELETE FROM {l10n_community_translation} WHERE sid = %d AND translation = '' AND language = '%s'", $suggestion->sid, $suggestion->language); - // Mark this exact suggestion as active, and set approval time. - db_query("UPDATE {l10n_community_translation} SET time_approved = %d, uid_approved = %d, has_suggestion = 0, is_suggestion = 0, is_active = 1 WHERE tid = %d;", time(), $user->uid, $suggestion->tid); - // Return something so the client sees we are OK. - print 'done'; - exit; - } - print 'error'; - exit; -} - -/** - * Records decline action of a previous string suggestion. - * - * This callback is invoked from JavaScript. - * - * @param $tid - * Suggestion ID. - */ -function l10n_community_string_decline($tid = 0) { - if ($suggestion = l10n_community_string_ajax_suggestion($tid)) { - // Mark this suggestion as inactive. - db_query("UPDATE {l10n_community_translation} SET is_active = 0 WHERE tid = %d", $tid); - // Let's see if we have any other suggestion remaining in this language. - $count = db_result(db_query("SELECT COUNT(*) FROM {l10n_community_translation} WHERE sid = %d AND is_suggestion = 1 AND is_active = 1 AND language = '%s'", $suggestion->sid, $suggestion->language)); - if (!$count) { - // No remaining suggestions in this language. - db_query("UPDATE {l10n_community_translation} SET has_suggestion = 0 WHERE sid = %d AND is_suggestion = 0 AND is_active = 1 AND language = '%s'", $suggestion->sid, $suggestion->language); - } - // Return something so the client sees we are OK. - print 'done'; - exit; - } - print 'error'; - exit; -} diff --git l10n_community/editor.css l10n_community/editor.css new file mode 100644 index 0000000..3407e7c --- /dev/null +++ l10n_community/editor.css @@ -0,0 +1,298 @@ +body { + font-family:Verdana; + font-size:12px; +} + + +#l10n-community-list-form .sticky-header { + z-index:2; +} + +.l10n-table .form-item { + margin:0; + padding:0; +} + +.l10n-table td { + vertical-align:top; +} + +.l10n-table ul { margin:0; padding:0; } + +.l10n-table li { + list-style: none; + background: none; + padding:0; + margin:0; +} + +td.source { + width:45%; +} + td.source .l10n-string { + padding:4px 6px; + } + td.source .l10n-usage { + padding:0 6px 6px 16px; + font-size:10px; + line-height:12px; + } + td.source .l10n-usage .l10n-more { + font-size:10px; + text-decoration:none; + color:#000; + opacity:0.4; + } + td.source .l10n-usage .l10n-more:before { + content:"▾ "; + } + td.source .l10n-usage .l10n-more:hover, + td.source .l10n-usage .l10n-more:active { + color:#666; + } + td.source .l10n-usage li { + list-style-type:disc; + background:none; + margin:0 0 0 22px; + padding:0; + } + +li.translation { + padding:4px 0; + padding-left:20px; + clear:left; + position:relative; + -webkit-transition: opacity 0.2s ease-out; +} + +li.translation > .selector { + position:absolute; + left:2px; + top:5px; + margin:0; +} + + +.l10n-table ul.actions { + float:right; +} + li.translation:not(.is-active) > .actions li.stable, + li.translation.is-active > .actions li.declined { display:none; } + .l10n-table ul.actions { + overflow:hidden; + float: right; + margin-right:4px; + -webkit-user-select:none; + } + .l10n-table ul.actions li { + float:right; + font-size:80%; + line-height:20px; + padding:0 0 4px 4px; + } + .l10n-table ul.actions li label { + width:16px; + height:16px; + overflow:hidden; + display:block; + text-indent:-1000px; + opacity:0.6; + cursor:pointer; + } + .l10n-table ul.actions li label:active { + position:relative; top:1px; + } + .l10n-table ul.actions li.declined label { + background:url(images/decline.png) no-repeat right top; + } + .l10n-table ul.actions li.edit label { + background:url(images/edit.png) no-repeat right top; + } + li.translation.is-declined > .actions li.declined label { + background-image:url(images/undecline.png); + } + .l10n-table ul.actions li.stable label { + background:url(images/stable-inactive.png) no-repeat right top; + } + li.translation.is-stable > .actions li.stable label, + li.translation:hover > .actions li label { + opacity:1; + } + li.translation.is-stable > .actions li.stable label { + background-image:url(images/stable-active.png); + } + + .l10n-string > br { + display:block; + content:""; + height:1px; + background:#CCC; + margin:2px 0; + } + +li.translation .l10n-string, +td.source .l10n-string { + display:block; + line-height:16px; +} + li.translation .l10n-string > span, + td.source .l10n-string > span { + white-space:pre-wrap; + -webkit-transition: opacity 0.2s ease-out; + } + li.translation.is-declined .l10n-string { + color:#999; + height:16px; + overflow:hidden; + text-decoration:line-through; + } + li.translation.no-translation .l10n-string span { + font-style:italic; + color:#666; + } + +li.translation > .author { + font-size:10px; + padding-left:10px; + line-height:12px; + color:#999; +} + li.translation.is-declined > .author { + display:none; + } + li.translation > .author a { + color:#666; + } + li.translation > .author span[title] { + cursor:pointer; + } + + + li.new-translation:not(.focussed):not(.has-content):not(.is-active) > .l10n-string { + display:none; + } + li.new-translation.has-content > .actions { + display:block; + } + li.new-translation .l10n-string { + display:block; + margin-bottom:4px; + margin-right:6px; + } + li.new-translation .l10n-string > span:empty:before { + content:attr(data-empty); + font-style:italic; + color:#666; + } + li.new-translation > .form-item ~ .form-item { + margin-top:5px; + } + li.new-translation:not(.has-content):not(.focussed) > .form-item ~ .form-item { + display:none; + } + li.new-translation textarea { + height:60px; + clear:left; + width:100%; + padding:0; + margin:0; + font-size:100%; + font-family:inherit; + -webkit-transition: height 0.2s ease-out; + } + li.new-translation:not(.focussed) textarea:not(:focus):hover { + opacity:0.75; + } + li.new-translation:not(.focussed) .grippie { + visibility:hidden; + margin-bottom:-9px; + } + li.new-translation textarea:not(:focus) { + height:19px !important; + overflow:hidden; + resize:none; + } + li.new-translation:not(.focussed) textarea { + opacity:0.5 !important; + } + li.new-translation.focussed textarea { + opacity:1; + position:relative; + z-index:1; + } + + + + + +span.l10n-escape, +span.l10n-nl::after, +em.l10n-placeholder, +code + { + font-family:Menlo, Monaco, Consolas, "Lucida Console", monospace; + font-size:12px; + line-height:14px; +} + + + + +.l10n-string span.l10n-nl::after { + content:"¬"; + opacity:0.4; + line-height:19px; + vertical-align:top; +} + + +.l10n-string code, +.l10n-string em.l10n-placeholder { + background:transparent; + padding:0 1px; + margin:0 -1px; + -webkit-border-radius:4px; + -moz-border-radius:4px; + border:1px solid transparent; + font-style:normal; +} + +tr:hover .l10n-string code, +tr:hover .l10n-string em.l10n-placeholder { + background:rgba(0,0,0,0.05); +} + +tr:hover .l10n-string em.l10n-placeholder { +} + +tr:hover .l10n-string em.l10n-placeholder.highlight { +border-color:rgba(0,0,0,0.2); + background:rgba(0,0,0,0.1); +} + +.l10n-string code em.l10n-placeholder { + top:0; +} + +.l10n-string span.worddiff-del del, +.l10n-string span.worddiff-ins ins { + text-decoration: none; + border:1px solid transparent; + -webkit-border-radius:4px; + -moz-border-radius:4px; + padding:0; + margin:0 -1px; +} + +.l10n-string > span.worddiff-del { background: #FFD8D8; } +.l10n-string > span.worddiff-del del { background: #FF8888; } +.l10n-string > span.worddiff-ins { background: #DDF8CC; } +.l10n-string > span.worddiff-ins ins { background: #90F678; } + +.clearfix:after { + content: "."; + display: block; + height: 0; + clear: both; + visibility: hidden; + } \ No newline at end of file diff --git l10n_community/editor.js l10n_community/editor.js new file mode 100644 index 0000000..10af71a --- /dev/null +++ l10n_community/editor.js @@ -0,0 +1,202 @@ + +(function ($) { + encode = function (str) { + str = String(str); + var replace = { '&': '&', '<': '<', '>': '>' }; + for (var character in replace) { + var regex = new RegExp(character, 'g'); + str = str.replace(regex, replace[character]); + } + return str; + }; + + $('em.l10n-placeholder') + .live('mouseover', function () { + $(this).closest('tr').find('.l10n-placeholder:contains("' + $(this).text() + '")').addClass('highlight'); + }) + .live('mouseout', function () { + $('.l10n-placeholder.highlight').removeClass('highlight'); + }); + + $(function () { + $('.l10n-more').click(function () { + $(this) + .addClass('loading') + .parent().load(this.href); + return false; + }); + + var markup = function (string) { + // Highlight placeholders. + string = string.replace(/([!@%]|<(ins|del)>[!@%]<\/(ins|del)>)(\w+|<(ins|del)>\w+<\/(ins|del)>)/g, '$&'); + + // Wrap HTML tags in tags. + string = string.replace(/(<.+?(>|$))/g, function (str) { + return '' + str.replace(/<[^>]+>/g, '$&') + ''; + }); + + string = string.replace(/\\[^<]/g, '$&'); + string = string.replace(/\n/g, '$&'); + return string; + }; + + $('td.translation').parent().each(function () { + var all = $('li.translation', this); + var strings = all.find('.l10n-string > span'); + var source = $('td.source', this); + + source.find('.l10n-string span').each(function () { + $(this).html(markup($(this).html())); + }); + + strings.each(function () { + var orig = $(this).html(), markedUp = markup(orig); + $(this) + .html(markedUp) + .data('worddiff:original', orig) + .data('worddiff:markup', markedUp); + }); + + var setStatus = function (elem, status, value) { + newValue = elem.find('.' + status + ' :checkbox').attr('checked', value).attr('checked'); + elem[(newValue === undefined ? value : newValue) ? 'addClass' : 'removeClass']('is-' + status); + }; + + var textareas = all.filter('.new-translation').find('textarea'); + + $(this).find('ul.actions .edit').click(function () { + var translation = $(this).closest('td.source, li.translation'); + var confirmed = undefined; + textareas.each(function (i) { + var textarea = $(this); + var val = textarea.val(); + if (confirmed || val === textarea.attr('defaultValue') || !val || (confirmed === undefined && (confirmed = confirm("Do you want to overwrite the current suggestion?")))) { + textarea.val(translation.find('.l10n-string > span:eq('+ i +')').text()).keyup(); + if (i == 0) { + // Since we can't have multiple focuses, we jut focus the first textarea. + textarea.focus(); + } + } + }); + }); + + all.each(function () { + var translation = $(this); + var isTranslation = !translation.is('.no-translation'); + var siblings = all.not(this).not('.no-translation'); + + var removeDiff = function () { + strings.worddiffRevert(); + }; + + var updateDiff = function () { + removeDiff(); + if (isTranslation) { + var orig = siblings.filter('.is-active'); + if (!orig.length) + orig = siblings.filter('.default'); + if (!orig.length) + orig = all.not('.no-translation').eq(0).not(translation); + if (orig.length) { + orig = orig.find('.l10n-string > span'); + translation.find('.l10n-string > span').each(function (i) { + $(this).worddiff(orig.get(i), markup); + }); + } + } + }; + + translation.find('> .selector').click(function () { + setStatus(translation, 'declined', false); + // Mark this as the active translation. + setStatus(translation.siblings('.is-active:not(.new-translation)'), 'declined', true); + setStatus(translation.siblings('.is-active'), 'active', false); + translation.addClass('is-active'); + }); + + translation.find('> .actions .declined :checkbox').change(function () { + setStatus(translation, 'declined', this.checked); + }); + + translation.find('> .actions .stable :checkbox').change(function () { + setStatus(translation, 'stable', this.checked); + }); + + translation.find('> .author span[title]').click(function () { + var $this = $(this), html = $this.html(); + $this.html($this.attr('title')).attr('title', html); + }); + + if (isTranslation) { + translation.find('.l10n-string').dblclick(function () { + translation.siblings().not('.new-translation').each(function () { + setStatus($(this), 'declined', true); + }); + }); + + translation + .mouseenter(updateDiff) + .mouseleave(removeDiff); + } + + if (translation.is('.new-translation')) { + translation.find('> .selector').click(function () { + textareas.each(function () { + var textarea = $(this); + if (textarea.val() === '' || textarea.val() === textarea.attr('defaultValue')) { + textarea.focus(); + // Stop checking the other ones. + return false; + } + }); + }); + + var hasContent = function () { + for (var i = 0; i < textareas.length; i++) { + if (textareas[i].value && textareas[i].value !== textareas[i].defaultValue) { + return true; + } + } + return false; + }; + + var blurTimeout; + textareas.each(function (n) { + var wrapper = $(this); + var textarea = $(this); + var text = translation.find('.l10n-string > span').eq(n); + + textarea + .focus(function () { + translation.addClass('focussed'); + clearTimeout(blurTimeout); + if (textarea.val() === textarea.attr('defaultValue')) { + textarea.val(''); + } + }) + .blur(function () { + blurTimeout = setTimeout(function () { + translation.removeClass('focussed'); + if (textarea.val() === '') { + textarea.val(textarea.attr('defaultValue')); + } + translation[hasContent() ? 'addClass' : 'removeClass']('has-content'); + }, 1000); + }) + .keyup(function () { + var val = encode(textarea.val()); + text + .data('worddiff:original', val) + .data('worddiff:markup', markup(val)); + var oldPos = textarea.offset().top; + updateDiff(); + var diff = textarea.offset().top - oldPos; + if (diff) + window.scrollBy(0, diff); + }); + }); + } + }); + }); + }); +})(jQ13); diff --git l10n_community/images/decline.png l10n_community/images/decline.png new file mode 100755 index 0000000..c8a5058 Binary files /dev/null and l10n_community/images/decline.png differ diff --git l10n_community/images/edit.png l10n_community/images/edit.png new file mode 100755 index 0000000..37aced3 Binary files /dev/null and l10n_community/images/edit.png differ diff --git l10n_community/images/icon_approval.gif l10n_community/images/icon_approval.gif deleted file mode 100644 index b4290ba..0000000 Binary files l10n_community/images/icon_approval.gif and /dev/null differ diff --git l10n_community/images/icon_copy.gif l10n_community/images/icon_copy.gif deleted file mode 100644 index 4be8507..0000000 Binary files l10n_community/images/icon_copy.gif and /dev/null differ diff --git l10n_community/images/icon_toolbox.gif l10n_community/images/icon_toolbox.gif deleted file mode 100644 index 3c3f40e..0000000 Binary files l10n_community/images/icon_toolbox.gif and /dev/null differ diff --git l10n_community/images/newline.png l10n_community/images/newline.png deleted file mode 100644 index 5b7c3dc..0000000 Binary files l10n_community/images/newline.png and /dev/null differ diff --git l10n_community/images/stable-active.png l10n_community/images/stable-active.png new file mode 100755 index 0000000..53542e7 Binary files /dev/null and l10n_community/images/stable-active.png differ diff --git l10n_community/images/stable-inactive.png l10n_community/images/stable-inactive.png new file mode 100755 index 0000000..262915a Binary files /dev/null and l10n_community/images/stable-inactive.png differ diff --git l10n_community/images/undecline.png l10n_community/images/undecline.png new file mode 100755 index 0000000..35ad7c2 Binary files /dev/null and l10n_community/images/undecline.png differ diff --git l10n_community/import.inc l10n_community/import.inc index cd86e98..041cfcf 100644 --- l10n_community/import.inc +++ l10n_community/import.inc @@ -129,10 +129,8 @@ function l10n_community_import_form_submit($form, &$form_state) { // Do the actual parsing on the local file. if (l10n_community_import($file, $form_state['values']['langcode'], $form_state['values']['is_suggestion'], $form_state['values']['import_uid'])) { - // Get status report on what was done in the process. - list($inserted, $updated, $unchanged, $suggested, $duplicates, $ignored) = _l10n_community_import_one_string(); drupal_set_message(t('The translation was successfully imported.')); - l10n_community_update_message($inserted, $updated, $unchanged, $suggested, $duplicates, $ignored); + l10n_community_update_message(); cache_clear_all('l10n:stats:'. $form_state['values']['langcode'], 'cache'); } } @@ -331,20 +329,10 @@ function l10n_community_import($file, $langcode, $is_suggestion, $uid) { * @param $uid * User id used to save attribution information. */ -function _l10n_community_import_one_string($value = NULL, $langcode = NULL, $is_suggestion = FALSE, $uid = NULL) { +function _l10n_community_import_one_string($value, $langcode = NULL, $is_suggestion = FALSE, $uid = NULL) { global $user; - static $inserted = 0; - static $updated = 0; - static $unchanged = 0; - static $suggested = 0; - static $duplicates = 0; - static $ignored = 0; - - if ($value == NULL) { - // Result stats queried. - return array($inserted, $updated, $unchanged, $suggested, $duplicates, $ignored); - } + static $counters = array(); // Trim translation (we will apply source string based whitespace later). if (is_string($value['msgstr'])) { @@ -389,20 +377,20 @@ function _l10n_community_import_one_string($value = NULL, $langcode = NULL, $is_ if ($is_suggestion || !$translation) { if (!l10n_community_is_duplicate($value['msgstr'], $sid, $langcode)) { - l10n_community_target_save($sid, $value['msgstr'], $langcode, $uid, $is_suggestion, $inserted, $updated, $unchanged, $suggested); + l10n_community_target_save($sid, $value['msgstr'], $langcode, $uid, $is_suggestion); } else { - $duplicates++; + l10n_community_counter('duplicates'); } } else { // We certainly did not update this one. - $unchanged++; + l10n_community_counter('unchanged'); } } else { // Source string not found, string ignored. - $ignored++; + l10n_community_counter('ignored'); } } } diff --git l10n_community/jquery.worddiff.js l10n_community/jquery.worddiff.js new file mode 100644 index 0000000..30d7464 --- /dev/null +++ l10n_community/jquery.worddiff.js @@ -0,0 +1,183 @@ +(function($) { + var Fragment = function (string) { + this.content = string; + this.equiv = false; + }; + + Fragment.prototype.toString = function (tag) { + if (this.equiv || !tag) + return this.content; + else + return '<' + tag + '>' + this.content + ''; + }; + + + var moveToEnd = function (a, i, k) { + if (!a.equiv && (!k[i-1] || k[i-1].equiv)) { + // Find next item equiv item. + for (var j = i+1; k[j] && !k[j].equiv; j++); + if (k[j] && k[j].content === a.content) { + k[i] = k[j]; + k[j] = a; + } + } + }; + + var aggregate = function (a, i, k) { + if (!a.equiv && k[i+1] && !k[i+1].equiv) { + k[i+1].content = a.content + k[i+1].content; + delete k[i]; + } + }; + + var join = function (what, t) { + return $.map(what, function (a) { + if (a) return a.toString(t); + }).join(''); + }; + + + $.wordDiff = { + nonWord: /(&.+?;|[\u0000-\u0040\u005B-\u0060\u007B-\u00A9\u00AB-\u00B4\u00B6-\u00B9\u00BB-\u00BF\u00D7\u00F7\u02C2-\u02C5\u02D2-\u02DF\u02E5-\u02EB\u02ED\u02EF-\u036F\u0375\u037E\u0384\u0385\u0387\u03F6\u0482-\u0489\u055A-\u055F\u0589\u058A\u0591-\u05C7\u05F3\u05F4\u0600-\u0603\u0606-\u061B\u061E\u061F\u064B-\u065E\u0660-\u066D\u0670\u06D4\u06D6-\u06E4\u06EA-\u06ED\u06F0-\u06F9\u06FD\u06FE\u0700-\u070D\u070F\u0711\u0730-\u074A\u07A6-\u07B0\u07C0-\u07C9\u07EB-\u07F3\u07F6-\u07F9\u0901-\u0903\u093C\u093E-\u094D\u0951-\u0954\u09E2\u0962-\u0970\u06E7-\u06E9\u0981-\u0983\u09BC\u09BE-\u09C4\u09C7\u09C8\u09CB-\u09CD\u09D7\u09E3\u09E6-\u09EF\u09F2-\u09FA\u0A01-\u0A03\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A66-\u0A71\u0A75\u0A81-\u0A83\u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AE2\u0AE3\u0AE6-\u0AEF\u0AF1\u0B01-\u0B03\u0B3C\u0B3E-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B62\u0B63\u0B66-\u0B70\u0B82\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0BE6-\u0BFA\u0C01-\u0C03\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C66-\u0C6F\u0C78-\u0C7F\u0C82\u0C83\u0CBC\u0CBE-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CE2\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D02\u0D03\u0D3E-\u0D44\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D62\u0D63\u0D66-\u0D75\u0D79\u0D82\u0D83\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2-\u0DF4\u0E31\u0E34-\u0E3A\u0E3F\u0E47-\u0E5B\u0EB1\u0EB4-\u0EB9\u0EBB\u0EBC\u0EC8-\u0ECD\u0ED0-\u0ED9\u0F01-\u0F3F\u0F71-\u0F87\u0F90-\u0F97\u0F99-\u0FBC\u0FBE-\u0FCC\u0FCE-\u0FD4\u102B-\u103E\u1040-\u104F\u1056-\u1059\u105E-\u1060\u1062-\u1064\u1067-\u106D\u1071-\u1074\u1082-\u108D\u108F-\u1099\u109E\u109F\u10FB\u135F-\u137C\u1390-\u1399\u166D\u166E\u1680\u169B\u169C\u16EB-\u16F0\u1712-\u1714\u1732-\u1736\u1752\u1753\u1772\u1773\u17B4-\u17D6\u17D8-\u17DB\u17DD\u17E0-\u17E9\u17F0-\u17F9\u1800-\u180E\u1810-\u1819\u18A9\u1920-\u192B\u1930-\u193B\u1940\u1944-\u194F\u19B0-\u19C0\u19C8\u19C9\u19D0-\u19D9\u19DE-\u19FF\u1A17-\u1A1B\u1A1E\u1A1F\u1B00-\u1B04\u1B34-\u1B44\u1B50-\u1B7C\u1B80-\u1B82\u1BA1-\u1BAA\u1BB0-\u1BB9\u1C24-\u1C37\u1C3B-\u1C49\u1C50-\u1C59\u1C7E\u1C7F\u1DC0-\u1DE6\u1DFE\u1DFF\u1FBD\u1FBF-\u1FC1\u1FCD-\u1FCF\u1FDD-\u1FDF\u1FED-\u1FEF\u1FFD\u1FFE\u2000-\u2064\u206A-\u2070\u2074-\u207E\u2080-\u208E\u20A0-\u20B5\u20D0-\u20F0\u2100\u2101\u2103-\u2106\u2108\u2109\u2114\u2116-\u2118\u211E-\u2123\u2125\u2127\u2129\u212E\u213A\u213B\u2140-\u2144\u214A-\u214D\u214F\u2153-\u2182\u2185-\u2188\u2190-\u23E7\u2400-\u2426\u2440-\u244A\u2460-\u269D\u26A0-\u26BC\u26C0-\u26C3\u2701-\u2704\u2706-\u2709\u270C-\u2727\u2729-\u274B\u274D\u274F-\u2752\u2756\u2758-\u275E\u2761-\u2794\u2798-\u27AF\u27B1-\u27BE\u27C0-\u27CA\u27CC\u27D0-\u2B4C\u2B50-\u2B54\u2CE5-\u2CEA\u2CF9-\u2CFF\u2DE0-\u2E2E\u2E30\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFB\u3000-\u3004\u3007-\u3030\u3036-\u303A\u303D-\u303F\u3099-\u309C\u30A0\u30FB\u3190-\u319F\u31C0-\u31E3\u3200-\u321E\u3220-\u3243\u3250-\u32FE\u3300-\u33FF\u4DC0-\u4DFF\uA490-\uA4C6\uA60D-\uA60F\uA620-\uA629\uA66F-\uA673\uA67C-\uA67E\uA700-\uA716\uA720\uA721\uA789\uA78A\uA802\uA806\uA80B\uA823-\uA82B\uA874-\uA877\uA880\uA881\uA8B4-\uA8C4\uA8CE-\uA8D9\uA900-\uA909\uA926-\uA92F\uA947-\uA953\uA95F\uAA29-\uAA36\uAA43\uAA4C\uAA4D\uAA50-\uAA59\uAA5C-\uAA5F\uD800\uDB7F\uDB80\uDBFF\uDC00\uDFFF\uE000\uF8FF\uFB1E\uFB29\uFD3E\uFD3F\uFDFC\uFDFD\uFE00-\uFE19\uFE20-\uFE26\uFE30-\uFE52\uFE54-\uFE66\uFE68-\uFE6B\uFEFF\uFF01-\uFF20\uFF3B-\uFF40\uFF5B-\uFF65\uFFE0-\uFFE6\uFFE8-\uFFEE\uFFF9-\uFFFD])/, + + tokenize: function (args) { + // Split on non-word characters. + for (var type in args) { + args[type] = $.grep( + args[type].split($.wordDiff.nonWord), + function (s) { return s.length; } + ); + } + + // Calculate the indexes and offsets for common suffixes and prefixes. + var i = -1, j = args.del.length, k = args.ins.length; + while (args.del[++i] === args.ins[i] && i <= j); + while (j >= i && k >= i && args.del[--j] === args.ins[--k]); + + args.prefix = args.del.slice(0, i).join(''); + args.suffix = args.del.slice(j + 1).join(''); + args.del = args.del.slice(i, ++j); + args.ins = args.ins.slice(i, ++k); + }, + + lcs: function (args) { + var matrix = []; + + for (var i = 0; i < args.del.length; i++) { + matrix[i] = []; + for (var j = 0; j < args.ins.length; j++) { + if (args.del[i] === args.ins[j]) { + matrix[i][j] = (matrix[i - 1] && matrix[i - 1][j - 1] || 0) + args.del[i].length; + } + else { + matrix[i][j] = Math.max(matrix[i][j - 1] || 0, matrix[i - 1] && matrix[i - 1][j] || 0); + } + } + } + + return matrix; + }, + + changeset: function (args, matrix) { + var result = {}; + + $.each(['del', 'ins'], function () { + result[this] = $.map(args[this], function (a) { return new Fragment(a); }); + }); + + // Backtrack through the matrix. + for (var i = result.del.length - 1, j = result.ins.length - 1; i >= 0; i--, j--) { + if (j < 0 || result.del[i].content !== result.ins[j].content) { + if (j < 0 || (j > 0 && matrix[i - 1] && (matrix[i][j - 1] < matrix[i - 1][j]))) { + j++; + } + else { + i++; + } + } + else { + result.del[i] = result.ins[j]; + result.del[i].equiv = true; + } + } + + // Fill up gaps. + for (var i = 0; i < result.del.length; i++) { + if (result.del[i].equiv && result.del[i].content.length < 3) { + var j = result.ins.indexOf(result.del[i]); + if (result.del[i-1] && result.del[i+1] && result.ins[j-1] && result.ins[j+1] && !result.del[i-1].equiv && !result.del[i+1].equiv && !result.ins[j-1].equiv && !result.ins[j+1].equiv){ + result.del[i].equiv = false; + result.ins[j] = $.extend(true, {}, result.del[i]); + } + } + } + + $.each(['del', 'ins'], function () { + // Try to move changes to the end. + for (var i = 0; i < result[this].length; i++) + moveToEnd(result[this][i], i, result[this]); + + // Aggregate subsequent changes to minimize ins/del tags. + for (var i = 0; i < result[this].length; i++) + aggregate(result[this][i], i, result[this]); + }); + + return result; + }, + + render: function (args, result) { + var diff = { + del: args.prefix + join(result.del, 'del') + args.suffix, + ins: args.prefix + join(result.ins, 'ins') + args.suffix + }; + + return diff; + }, + + diff: function (del, ins) { + var args = { 'del': del, 'ins': ins }; + + $.wordDiff.tokenize(args); + var matrix = $.wordDiff.lcs(args); + var result = $.wordDiff.changeset(args, matrix); + return $.wordDiff.render(args, result); + } + }; + + $.fn.worddiff = function (selector, processor) { + var src = $(selector), del = src.data('worddiff:original'); + if (del === undefined) + src.data('worddiff:original', del = src.html()); + + return this.each(function () { + var dst = $(this), ins = dst.data('worddiff:original'); + if (ins === undefined) + dst.data('worddiff:original', ins = dst.html()); + + var diff = $.wordDiff.diff(del, ins); + if (processor) { + diff.del = processor(diff.del); + diff.ins = processor(diff.ins); + } + + if (diff.del && diff.ins) { + src.html(diff.del).addClass('worddiff-del'); + dst.html(diff.ins).addClass('worddiff-ins'); + } + }); + }; + + $.fn.worddiffRevert = function () { + return this.each(function () { + var dest = $(this), orig = dest.data('worddiff:markup'); + if (orig === undefined) { + orig = dest.data('worddiff:original'); + } + if (orig !== undefined) { + dest + .html(orig) + .removeClass('worddiff-del').removeClass('worddiff-ins'); + } + }); + }; +})(jQ13); \ No newline at end of file diff --git l10n_community/jquery13.js l10n_community/jquery13.js new file mode 100644 index 0000000..e8a7732 --- /dev/null +++ l10n_community/jquery13.js @@ -0,0 +1,23 @@ +// $Id: jquery.js,v 1.17 2009-06-30 03:35:11 webchick Exp $ +/*jsl:ignore*/ +/* + * jQuery JavaScript Library v1.3.2 + * http://jquery.com/ + * + * Copyright (c) 2009 John Resig + * Dual licensed under the MIT and GPL licenses. + * http://docs.jquery.com/License + * + * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) + * Revision: 6246 + */ +(function(){var l=this,g,y=l.jQuery,p=l.$,o=l.jQuery=l.$=function(E,F){return new o.fn.init(E,F)},D=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;o.fn=o.prototype={init:function(E,H){E=E||document;if(E.nodeType){this[0]=E;this.length=1;this.context=E;return this}if(typeof E==="string"){var G=D.exec(E);if(G&&(G[1]||!H)){if(G[1]){E=o.clean([G[1]],H)}else{var I=document.getElementById(G[3]);if(I&&I.id!=G[3]){return o().find(E)}var F=o(I||[]);F.context=document;F.selector=E;return F}}else{return o(H).find(E)}}else{if(o.isFunction(E)){return o(document).ready(E)}}if(E.selector&&E.context){this.selector=E.selector;this.context=E.context}return this.setArray(o.isArray(E)?E:o.makeArray(E))},selector:"",jquery:"1.3.2",size:function(){return this.length},get:function(E){return E===g?Array.prototype.slice.call(this):this[E]},pushStack:function(F,H,E){var G=o(F);G.prevObject=this;G.context=this.context;if(H==="find"){G.selector=this.selector+(this.selector?" ":"")+E}else{if(H){G.selector=this.selector+"."+H+"("+E+")"}}return G},setArray:function(E){this.length=0;Array.prototype.push.apply(this,E);return this},each:function(F,E){return o.each(this,F,E)},index:function(E){return o.inArray(E&&E.jquery?E[0]:E,this)},attr:function(F,H,G){var E=F;if(typeof F==="string"){if(H===g){return this[0]&&o[G||"attr"](this[0],F)}else{E={};E[F]=H}}return this.each(function(I){for(F in E){o.attr(G?this.style:this,F,o.prop(this,E[F],G,I,F))}})},css:function(E,F){if((E=="width"||E=="height")&&parseFloat(F)<0){F=g}return this.attr(E,F,"curCSS")},text:function(F){if(typeof F!=="object"&&F!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(F))}var E="";o.each(F||this,function(){o.each(this.childNodes,function(){if(this.nodeType!=8){E+=this.nodeType!=1?this.nodeValue:o.fn.text([this])}})});return E},wrapAll:function(E){if(this[0]){var F=o(E,this[0].ownerDocument).clone();if(this[0].parentNode){F.insertBefore(this[0])}F.map(function(){var G=this;while(G.firstChild){G=G.firstChild}return G}).append(this)}return this},wrapInner:function(E){return this.each(function(){o(this).contents().wrapAll(E)})},wrap:function(E){return this.each(function(){o(this).wrapAll(E)})},append:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.appendChild(E)}})},prepend:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.insertBefore(E,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this)})},after:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this.nextSibling)})},end:function(){return this.prevObject||o([])},push:[].push,sort:[].sort,splice:[].splice,find:function(E){if(this.length===1){var F=this.pushStack([],"find",E);F.length=0;o.find(E,this[0],F);return F}else{return this.pushStack(o.unique(o.map(this,function(G){return o.find(E,G)})),"find",E)}},clone:function(G){var E=this.map(function(){if(!o.support.noCloneEvent&&!o.isXMLDoc(this)){var I=this.outerHTML;if(!I){var J=this.ownerDocument.createElement("div");J.appendChild(this.cloneNode(true));I=J.innerHTML}return o.clean([I.replace(/ jQuery\d+="(?:\d+|null)"/g,"").replace(/^\s*/,"")])[0]}else{return this.cloneNode(true)}});if(G===true){var H=this.find("*").andSelf(),F=0;E.find("*").andSelf().each(function(){if(this.nodeName!==H[F].nodeName){return}var I=o.data(H[F],"events");for(var K in I){for(var J in I[K]){o.event.add(this,K,I[K][J],I[K][J].data)}}F++})}return E},filter:function(E){return this.pushStack(o.isFunction(E)&&o.grep(this,function(G,F){return E.call(G,F)})||o.multiFilter(E,o.grep(this,function(F){return F.nodeType===1})),"filter",E)},closest:function(E){var G=o.expr.match.POS.test(E)?o(E):null,F=0;return this.map(function(){var H=this;while(H&&H.ownerDocument){if(G?G.index(H)>-1:o(H).is(E)){o.data(H,"closest",F);return H}H=H.parentNode;F++}})},not:function(E){if(typeof E==="string"){if(f.test(E)){return this.pushStack(o.multiFilter(E,this,true),"not",E)}else{E=o.multiFilter(E,this)}}var F=E.length&&E[E.length-1]!==g&&!E.nodeType;return this.filter(function(){return F?o.inArray(this,E)<0:this!=E})},add:function(E){return this.pushStack(o.unique(o.merge(this.get(),typeof E==="string"?o(E):o.makeArray(E))))},is:function(E){return !!E&&o.multiFilter(E,this).length>0},hasClass:function(E){return !!E&&this.is("."+E)},val:function(K){if(K===g){var E=this[0];if(E){if(o.nodeName(E,"option")){return(E.attributes.value||{}).specified?E.value:E.text}if(o.nodeName(E,"select")){var I=E.selectedIndex,L=[],M=E.options,H=E.type=="select-one";if(I<0){return null}for(var F=H?I:0,J=H?I+1:M.length;F=0||o.inArray(this.name,K)>=0)}else{if(o.nodeName(this,"select")){var N=o.makeArray(K);o("option",this).each(function(){this.selected=(o.inArray(this.value,N)>=0||o.inArray(this.text,N)>=0)});if(!N.length){this.selectedIndex=-1}}else{this.value=K}}})},html:function(E){return E===g?(this[0]?this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g,""):null):this.empty().append(E)},replaceWith:function(E){return this.after(E).remove()},eq:function(E){return this.slice(E,+E+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(E){return this.pushStack(o.map(this,function(G,F){return E.call(G,F,G)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(J,M,L){if(this[0]){var I=(this[0].ownerDocument||this[0]).createDocumentFragment(),F=o.clean(J,(this[0].ownerDocument||this[0]),I),H=I.firstChild;if(H){for(var G=0,E=this.length;G1||G>0?I.cloneNode(true):I)}}if(F){o.each(F,z)}}return this;function K(N,O){return M&&o.nodeName(N,"table")&&o.nodeName(O,"tr")?(N.getElementsByTagName("tbody")[0]||N.appendChild(N.ownerDocument.createElement("tbody"))):N}}};o.fn.init.prototype=o.fn;function z(E,F){if(F.src){o.ajax({url:F.src,async:false,dataType:"script"})}else{o.globalEval(F.text||F.textContent||F.innerHTML||"")}if(F.parentNode){F.parentNode.removeChild(F)}}function e(){return +new Date}o.extend=o.fn.extend=function(){var J=arguments[0]||{},H=1,I=arguments.length,E=false,G;if(typeof J==="boolean"){E=J;J=arguments[1]||{};H=2}if(typeof J!=="object"&&!o.isFunction(J)){J={}}if(I==H){J=this;--H}for(;H-1}},swap:function(H,G,I){var E={};for(var F in G){E[F]=H.style[F];H.style[F]=G[F]}I.call(H);for(var F in G){H.style[F]=E[F]}},css:function(H,F,J,E){if(F=="width"||F=="height"){var L,G={position:"absolute",visibility:"hidden",display:"block"},K=F=="width"?["Left","Right"]:["Top","Bottom"];function I(){L=F=="width"?H.offsetWidth:H.offsetHeight;if(E==="border"){return}o.each(K,function(){if(!E){L-=parseFloat(o.curCSS(H,"padding"+this,true))||0}if(E==="margin"){L+=parseFloat(o.curCSS(H,"margin"+this,true))||0}else{L-=parseFloat(o.curCSS(H,"border"+this+"Width",true))||0}})}if(H.offsetWidth!==0){I()}else{o.swap(H,G,I)}return Math.max(0,Math.round(L))}return o.curCSS(H,F,J)},curCSS:function(I,F,G){var L,E=I.style;if(F=="opacity"&&!o.support.opacity){L=o.attr(E,"opacity");return L==""?"1":L}if(F.match(/float/i)){F=w}if(!G&&E&&E[F]){L=E[F]}else{if(q.getComputedStyle){if(F.match(/float/i)){F="float"}F=F.replace(/([A-Z])/g,"-$1").toLowerCase();var M=q.getComputedStyle(I,null);if(M){L=M.getPropertyValue(F)}if(F=="opacity"&&L==""){L="1"}}else{if(I.currentStyle){var J=F.replace(/\-(\w)/g,function(N,O){return O.toUpperCase()});L=I.currentStyle[F]||I.currentStyle[J];if(!/^\d+(px)?$/i.test(L)&&/^\d/.test(L)){var H=E.left,K=I.runtimeStyle.left;I.runtimeStyle.left=I.currentStyle.left;E.left=L||0;L=E.pixelLeft+"px";E.left=H;I.runtimeStyle.left=K}}}}return L},clean:function(F,K,I){K=K||document;if(typeof K.createElement==="undefined"){K=K.ownerDocument||K[0]&&K[0].ownerDocument||document}if(!I&&F.length===1&&typeof F[0]==="string"){var H=/^<(\w+)\s*\/?>$/.exec(F[0]);if(H){return[K.createElement(H[1])]}}var G=[],E=[],L=K.createElement("div");o.each(F,function(P,S){if(typeof S==="number"){S+=""}if(!S){return}if(typeof S==="string"){S=S.replace(/(<(\w+)[^>]*?)\/>/g,function(U,V,T){return T.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?U:V+">"});var O=S.replace(/^\s+/,"").substring(0,10).toLowerCase();var Q=!O.indexOf("",""]||!O.indexOf("",""]||O.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"","
"]||!O.indexOf("",""]||(!O.indexOf("",""]||!O.indexOf("",""]||!o.support.htmlSerialize&&[1,"div
","
"]||[0,"",""];L.innerHTML=Q[1]+S+Q[2];while(Q[0]--){L=L.lastChild}if(!o.support.tbody){var R=/"&&!R?L.childNodes:[];for(var M=N.length-1;M>=0;--M){if(o.nodeName(N[M],"tbody")&&!N[M].childNodes.length){N[M].parentNode.removeChild(N[M])}}}if(!o.support.leadingWhitespace&&/^\s/.test(S)){L.insertBefore(K.createTextNode(S.match(/^\s*/)[0]),L.firstChild)}S=o.makeArray(L.childNodes)}if(S.nodeType){G.push(S)}else{G=o.merge(G,S)}});if(I){for(var J=0;G[J];J++){if(o.nodeName(G[J],"script")&&(!G[J].type||G[J].type.toLowerCase()==="text/javascript")){E.push(G[J].parentNode?G[J].parentNode.removeChild(G[J]):G[J])}else{if(G[J].nodeType===1){G.splice.apply(G,[J+1,0].concat(o.makeArray(G[J].getElementsByTagName("script"))))}I.appendChild(G[J])}}return E}return G},attr:function(J,G,K){if(!J||J.nodeType==3||J.nodeType==8){return g}var H=!o.isXMLDoc(J),L=K!==g;G=H&&o.props[G]||G;if(J.tagName){var F=/href|src|style/.test(G);if(G=="selected"&&J.parentNode){J.parentNode.selectedIndex}if(G in J&&H&&!F){if(L){if(G=="type"&&o.nodeName(J,"input")&&J.parentNode){throw"type property can't be changed"}J[G]=K}if(o.nodeName(J,"form")&&J.getAttributeNode(G)){return J.getAttributeNode(G).nodeValue}if(G=="tabIndex"){var I=J.getAttributeNode("tabIndex");return I&&I.specified?I.value:J.nodeName.match(/(button|input|object|select|textarea)/i)?0:J.nodeName.match(/^(a|area)$/i)&&J.href?0:g}return J[G]}if(!o.support.style&&H&&G=="style"){return o.attr(J.style,"cssText",K)}if(L){J.setAttribute(G,""+K)}var E=!o.support.hrefNormalized&&H&&F?J.getAttribute(G,2):J.getAttribute(G);return E===null?g:E}if(!o.support.opacity&&G=="opacity"){if(L){J.zoom=1;J.filter=(J.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(K)+""=="NaN"?"":"alpha(opacity="+K*100+")")}return J.filter&&J.filter.indexOf("opacity=")>=0?(parseFloat(J.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}G=G.replace(/-([a-z])/ig,function(M,N){return N.toUpperCase()});if(L){J[G]=K}return J[G]},trim:function(E){return(E||"").replace(/^\s+|\s+$/g,"")},makeArray:function(G){var E=[];if(G!=null){var F=G.length;if(F==null||typeof G==="string"||o.isFunction(G)||G.setInterval){E[0]=G}else{while(F){E[--F]=G[F]}}}return E},inArray:function(G,H){for(var E=0,F=H.length;E0?this.clone(true):this).get();o.fn[F].apply(o(L[K]),I);J=J.concat(I)}return this.pushStack(J,E,G)}});o.each({removeAttr:function(E){o.attr(this,E,"");if(this.nodeType==1){this.removeAttribute(E)}},addClass:function(E){o.className.add(this,E)},removeClass:function(E){o.className.remove(this,E)},toggleClass:function(F,E){if(typeof E!=="boolean"){E=!o.className.has(this,F)}o.className[E?"add":"remove"](this,F)},remove:function(E){if(!E||o.filter(E,[this]).length){o("*",this).add([this]).each(function(){o.event.remove(this);o.removeData(this)});if(this.parentNode){this.parentNode.removeChild(this)}}},empty:function(){o(this).children().remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(E,F){o.fn[E]=function(){return this.each(F,arguments)}});function j(E,F){return E[0]&&parseInt(o.curCSS(E[0],F,true),10)||0}var h="jQuery"+e(),v=0,A={};o.extend({cache:{},data:function(F,E,G){F=F==l?A:F;var H=F[h];if(!H){H=F[h]=++v}if(E&&!o.cache[H]){o.cache[H]={}}if(G!==g){o.cache[H][E]=G}return E?o.cache[H][E]:H},removeData:function(F,E){F=F==l?A:F;var H=F[h];if(E){if(o.cache[H]){delete o.cache[H][E];E="";for(E in o.cache[H]){break}if(!E){o.removeData(F)}}}else{try{delete F[h]}catch(G){if(F.removeAttribute){F.removeAttribute(h)}}delete o.cache[H]}},queue:function(F,E,H){if(F){E=(E||"fx")+"queue";var G=o.data(F,E);if(!G||o.isArray(H)){G=o.data(F,E,o.makeArray(H))}else{if(H){G.push(H)}}}return G},dequeue:function(H,G){var E=o.queue(H,G),F=E.shift();if(!G||G==="fx"){F=E[0]}if(F!==g){F.call(H)}}});o.fn.extend({data:function(E,G){var H=E.split(".");H[1]=H[1]?"."+H[1]:"";if(G===g){var F=this.triggerHandler("getData"+H[1]+"!",[H[0]]);if(F===g&&this.length){F=o.data(this[0],E)}return F===g&&H[1]?this.data(H[0]):F}else{return this.trigger("setData"+H[1]+"!",[H[0],G]).each(function(){o.data(this,E,G)})}},removeData:function(E){return this.each(function(){o.removeData(this,E)})},queue:function(E,F){if(typeof E!=="string"){F=E;E="fx"}if(F===g){return o.queue(this[0],E)}return this.each(function(){var G=o.queue(this,E,F);if(E=="fx"&&G.length==1){G[0].call(this)}})},dequeue:function(E){return this.each(function(){o.dequeue(this,E)})}}); +/* + * Sizzle CSS Selector Engine - v0.9.3 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){var R=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,L=0,H=Object.prototype.toString;var F=function(Y,U,ab,ac){ab=ab||[];U=U||document;if(U.nodeType!==1&&U.nodeType!==9){return[]}if(!Y||typeof Y!=="string"){return ab}var Z=[],W,af,ai,T,ad,V,X=true;R.lastIndex=0;while((W=R.exec(Y))!==null){Z.push(W[1]);if(W[2]){V=RegExp.rightContext;break}}if(Z.length>1&&M.exec(Y)){if(Z.length===2&&I.relative[Z[0]]){af=J(Z[0]+Z[1],U)}else{af=I.relative[Z[0]]?[U]:F(Z.shift(),U);while(Z.length){Y=Z.shift();if(I.relative[Y]){Y+=Z.shift()}af=J(Y,af)}}}else{var ae=ac?{expr:Z.pop(),set:E(ac)}:F.find(Z.pop(),Z.length===1&&U.parentNode?U.parentNode:U,Q(U));af=F.filter(ae.expr,ae.set);if(Z.length>0){ai=E(af)}else{X=false}while(Z.length){var ah=Z.pop(),ag=ah;if(!I.relative[ah]){ah=""}else{ag=Z.pop()}if(ag==null){ag=U}I.relative[ah](ai,ag,Q(U))}}if(!ai){ai=af}if(!ai){throw"Syntax error, unrecognized expression: "+(ah||Y)}if(H.call(ai)==="[object Array]"){if(!X){ab.push.apply(ab,ai)}else{if(U.nodeType===1){for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&(ai[aa]===true||ai[aa].nodeType===1&&K(U,ai[aa]))){ab.push(af[aa])}}}else{for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&ai[aa].nodeType===1){ab.push(af[aa])}}}}}else{E(ai,ab)}if(V){F(V,U,ab,ac);if(G){hasDuplicate=false;ab.sort(G);if(hasDuplicate){for(var aa=1;aa":function(Z,U,aa){var X=typeof U==="string";if(X&&!/\W/.test(U)){U=aa?U:U.toUpperCase();for(var V=0,T=Z.length;V=0)){if(!V){T.push(Y)}}else{if(V){U[X]=false}}}}return false},ID:function(T){return T[1].replace(/\\/g,"")},TAG:function(U,T){for(var V=0;T[V]===false;V++){}return T[V]&&Q(T[V])?U[1]:U[1].toUpperCase()},CHILD:function(T){if(T[1]=="nth"){var U=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(T[2]=="even"&&"2n"||T[2]=="odd"&&"2n+1"||!/\D/.test(T[2])&&"0n+"+T[2]||T[2]);T[2]=(U[1]+(U[2]||1))-0;T[3]=U[3]-0}T[0]=L++;return T},ATTR:function(X,U,V,T,Y,Z){var W=X[1].replace(/\\/g,"");if(!Z&&I.attrMap[W]){X[1]=I.attrMap[W]}if(X[2]==="~="){X[4]=" "+X[4]+" "}return X},PSEUDO:function(X,U,V,T,Y){if(X[1]==="not"){if(X[3].match(R).length>1||/^\w/.test(X[3])){X[3]=F(X[3],null,null,U)}else{var W=F.filter(X[3],U,V,true^Y);if(!V){T.push.apply(T,W)}return false}}else{if(I.match.POS.test(X[0])||I.match.CHILD.test(X[0])){return true}}return X},POS:function(T){T.unshift(true);return T}},filters:{enabled:function(T){return T.disabled===false&&T.type!=="hidden"},disabled:function(T){return T.disabled===true},checked:function(T){return T.checked===true},selected:function(T){T.parentNode.selectedIndex;return T.selected===true},parent:function(T){return !!T.firstChild},empty:function(T){return !T.firstChild},has:function(V,U,T){return !!F(T[3],V).length},header:function(T){return/h\d/i.test(T.nodeName)},text:function(T){return"text"===T.type},radio:function(T){return"radio"===T.type},checkbox:function(T){return"checkbox"===T.type},file:function(T){return"file"===T.type},password:function(T){return"password"===T.type},submit:function(T){return"submit"===T.type},image:function(T){return"image"===T.type},reset:function(T){return"reset"===T.type},button:function(T){return"button"===T.type||T.nodeName.toUpperCase()==="BUTTON"},input:function(T){return/input|select|textarea|button/i.test(T.nodeName)}},setFilters:{first:function(U,T){return T===0},last:function(V,U,T,W){return U===W.length-1},even:function(U,T){return T%2===0},odd:function(U,T){return T%2===1},lt:function(V,U,T){return UT[3]-0},nth:function(V,U,T){return T[3]-0==U},eq:function(V,U,T){return T[3]-0==U}},filter:{PSEUDO:function(Z,V,W,aa){var U=V[1],X=I.filters[U];if(X){return X(Z,W,V,aa)}else{if(U==="contains"){return(Z.textContent||Z.innerText||"").indexOf(V[3])>=0}else{if(U==="not"){var Y=V[3];for(var W=0,T=Y.length;W=0)}}},ID:function(U,T){return U.nodeType===1&&U.getAttribute("id")===T},TAG:function(U,T){return(T==="*"&&U.nodeType===1)||U.nodeName===T},CLASS:function(U,T){return(" "+(U.className||U.getAttribute("class"))+" ").indexOf(T)>-1},ATTR:function(Y,W){var V=W[1],T=I.attrHandle[V]?I.attrHandle[V](Y):Y[V]!=null?Y[V]:Y.getAttribute(V),Z=T+"",X=W[2],U=W[4];return T==null?X==="!=":X==="="?Z===U:X==="*="?Z.indexOf(U)>=0:X==="~="?(" "+Z+" ").indexOf(U)>=0:!U?Z&&T!==false:X==="!="?Z!=U:X==="^="?Z.indexOf(U)===0:X==="$="?Z.substr(Z.length-U.length)===U:X==="|="?Z===U||Z.substr(0,U.length+1)===U+"-":false},POS:function(X,U,V,Y){var T=U[2],W=I.setFilters[T];if(W){return W(X,V,U,Y)}}}};var M=I.match.POS;for(var O in I.match){I.match[O]=RegExp(I.match[O].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(U,T){U=Array.prototype.slice.call(U);if(T){T.push.apply(T,U);return T}return U};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(N){E=function(X,W){var U=W||[];if(H.call(X)==="[object Array]"){Array.prototype.push.apply(U,X)}else{if(typeof X.length==="number"){for(var V=0,T=X.length;V";var T=document.documentElement;T.insertBefore(U,T.firstChild);if(!!document.getElementById(V)){I.find.ID=function(X,Y,Z){if(typeof Y.getElementById!=="undefined"&&!Z){var W=Y.getElementById(X[1]);return W?W.id===X[1]||typeof W.getAttributeNode!=="undefined"&&W.getAttributeNode("id").nodeValue===X[1]?[W]:g:[]}};I.filter.ID=function(Y,W){var X=typeof Y.getAttributeNode!=="undefined"&&Y.getAttributeNode("id");return Y.nodeType===1&&X&&X.nodeValue===W}}T.removeChild(U)})();(function(){var T=document.createElement("div");T.appendChild(document.createComment(""));if(T.getElementsByTagName("*").length>0){I.find.TAG=function(U,Y){var X=Y.getElementsByTagName(U[1]);if(U[1]==="*"){var W=[];for(var V=0;X[V];V++){if(X[V].nodeType===1){W.push(X[V])}}X=W}return X}}T.innerHTML="";if(T.firstChild&&typeof T.firstChild.getAttribute!=="undefined"&&T.firstChild.getAttribute("href")!=="#"){I.attrHandle.href=function(U){return U.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var T=F,U=document.createElement("div");U.innerHTML="

";if(U.querySelectorAll&&U.querySelectorAll(".TEST").length===0){return}F=function(Y,X,V,W){X=X||document;if(!W&&X.nodeType===9&&!Q(X)){try{return E(X.querySelectorAll(Y),V)}catch(Z){}}return T(Y,X,V,W)};F.find=T.find;F.filter=T.filter;F.selectors=T.selectors;F.matches=T.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){(function(){var T=document.createElement("div");T.innerHTML="
";if(T.getElementsByClassName("e").length===0){return}T.lastChild.className="e";if(T.getElementsByClassName("e").length===1){return}I.order.splice(1,0,"CLASS");I.find.CLASS=function(U,V,W){if(typeof V.getElementsByClassName!=="undefined"&&!W){return V.getElementsByClassName(U[1])}}})()}function P(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W0){X=T;break}}}T=T[U]}ad[W]=X}}}var K=document.compareDocumentPosition?function(U,T){return U.compareDocumentPosition(T)&16}:function(U,T){return U!==T&&(U.contains?U.contains(T):true)};var Q=function(T){return T.nodeType===9&&T.documentElement.nodeName!=="HTML"||!!T.ownerDocument&&Q(T.ownerDocument)};var J=function(T,aa){var W=[],X="",Y,V=aa.nodeType?[aa]:aa;while((Y=I.match.PSEUDO.exec(T))){X+=Y[0];T=T.replace(I.match.PSEUDO,"")}T=I.relative[T]?T+"*":T;for(var Z=0,U=V.length;Z0||T.offsetHeight>0};F.selectors.filters.animated=function(T){return o.grep(o.timers,function(U){return T===U.elem}).length};o.multiFilter=function(V,T,U){if(U){V=":not("+V+")"}return F.matches(V,T)};o.dir=function(V,U){var T=[],W=V[U];while(W&&W!=document){if(W.nodeType==1){T.push(W)}W=W[U]}return T};o.nth=function(X,T,V,W){T=T||1;var U=0;for(;X;X=X[V]){if(X.nodeType==1&&++U==T){break}}return X};o.sibling=function(V,U){var T=[];for(;V;V=V.nextSibling){if(V.nodeType==1&&V!=U){T.push(V)}}return T};return;l.Sizzle=F})();o.event={add:function(I,F,H,K){if(I.nodeType==3||I.nodeType==8){return}if(I.setInterval&&I!=l){I=l}if(!H.guid){H.guid=this.guid++}if(K!==g){var G=H;H=this.proxy(G);H.data=K}var E=o.data(I,"events")||o.data(I,"events",{}),J=o.data(I,"handle")||o.data(I,"handle",function(){return typeof o!=="undefined"&&!o.event.triggered?o.event.handle.apply(arguments.callee.elem,arguments):g});J.elem=I;o.each(F.split(/\s+/),function(M,N){var O=N.split(".");N=O.shift();H.type=O.slice().sort().join(".");var L=E[N];if(o.event.specialAll[N]){o.event.specialAll[N].setup.call(I,K,O)}if(!L){L=E[N]={};if(!o.event.special[N]||o.event.special[N].setup.call(I,K,O)===false){if(I.addEventListener){I.addEventListener(N,J,false)}else{if(I.attachEvent){I.attachEvent("on"+N,J)}}}}L[H.guid]=H;o.event.global[N]=true});I=null},guid:1,global:{},remove:function(K,H,J){if(K.nodeType==3||K.nodeType==8){return}var G=o.data(K,"events"),F,E;if(G){if(H===g||(typeof H==="string"&&H.charAt(0)==".")){for(var I in G){this.remove(K,I+(H||""))}}else{if(H.type){J=H.handler;H=H.type}o.each(H.split(/\s+/),function(M,O){var Q=O.split(".");O=Q.shift();var N=RegExp("(^|\\.)"+Q.slice().sort().join(".*\\.")+"(\\.|$)");if(G[O]){if(J){delete G[O][J.guid]}else{for(var P in G[O]){if(N.test(G[O][P].type)){delete G[O][P]}}}if(o.event.specialAll[O]){o.event.specialAll[O].teardown.call(K,Q)}for(F in G[O]){break}if(!F){if(!o.event.special[O]||o.event.special[O].teardown.call(K,Q)===false){if(K.removeEventListener){K.removeEventListener(O,o.data(K,"handle"),false)}else{if(K.detachEvent){K.detachEvent("on"+O,o.data(K,"handle"))}}}F=null;delete G[O]}}})}for(F in G){break}if(!F){var L=o.data(K,"handle");if(L){L.elem=null}o.removeData(K,"events");o.removeData(K,"handle")}}},trigger:function(I,K,H,E){var G=I.type||I;if(!E){I=typeof I==="object"?I[h]?I:o.extend(o.Event(G),I):o.Event(G);if(G.indexOf("!")>=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);K.currentTarget=this;var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(F=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("
").append(M.responseText.replace(//g,"")).find(E):M.responseText)}if(K){F.each(K,[M.responseText,L,M])}}});return this},serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?o.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password|search/i.test(this.type))}).map(function(E,F){var G=o(this).val();return G==null?null:o.isArray(G)?o.map(G,function(I,H){return{name:F.name,value:I}}):{name:F.name,value:G}}).get()}});o.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(E,F){o.fn[F]=function(G){return this.bind(F,G)}});var r=e();o.extend({get:function(E,G,H,F){if(o.isFunction(G)){H=G;G=null}return o.ajax({type:"GET",url:E,data:G,success:H,dataType:F})},getScript:function(E,F){return o.get(E,null,F,"script")},getJSON:function(E,F,G){return o.get(E,F,G,"json")},post:function(E,G,H,F){if(o.isFunction(G)){H=G;G={}}return o.ajax({type:"POST",url:E,data:G,success:H,dataType:F})},ajaxSetup:function(E){o.extend(o.ajaxSettings,E)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(M){M=o.extend(true,M,o.extend(true,{},o.ajaxSettings,M));var W,F=/=\?(&|$)/g,R,V,G=M.type.toUpperCase();if(M.data&&M.processData&&typeof M.data!=="string"){M.data=o.param(M.data)}if(M.dataType=="jsonp"){if(G=="GET"){if(!M.url.match(F)){M.url+=(M.url.match(/\?/)?"&":"?")+(M.jsonp||"callback")+"=?"}}else{if(!M.data||!M.data.match(F)){M.data=(M.data?M.data+"&":"")+(M.jsonp||"callback")+"=?"}}M.dataType="json"}if(M.dataType=="json"&&(M.data&&M.data.match(F)||M.url.match(F))){W="jsonp"+r++;if(M.data){M.data=(M.data+"").replace(F,"="+W+"$1")}M.url=M.url.replace(F,"="+W+"$1");M.dataType="script";l[W]=function(X){V=X;I();L();l[W]=g;try{delete l[W]}catch(Y){}if(H){H.removeChild(T)}}}if(M.dataType=="script"&&M.cache==null){M.cache=false}if(M.cache===false&&G=="GET"){var E=e();var U=M.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+E+"$2");M.url=U+((U==M.url)?(M.url.match(/\?/)?"&":"?")+"_="+E:"")}if(M.data&&G=="GET"){M.url+=(M.url.match(/\?/)?"&":"?")+M.data;M.data=null}if(M.global&&!o.active++){o.event.trigger("ajaxStart")}var Q=/^(\w+:)?\/\/([^\/?#]+)/.exec(M.url);if(M.dataType=="script"&&G=="GET"&&Q&&(Q[1]&&Q[1]!=location.protocol||Q[2]!=location.host)){var H=document.getElementsByTagName("head")[0];var T=document.createElement("script");T.src=M.url;if(M.scriptCharset){T.charset=M.scriptCharset}if(!W){var O=false;T.onload=T.onreadystatechange=function(){if(!O&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){O=true;I();L();T.onload=T.onreadystatechange=null;H.removeChild(T)}}}H.appendChild(T);return g}var K=false;var J=M.xhr();if(M.username){J.open(G,M.url,M.async,M.username,M.password)}else{J.open(G,M.url,M.async)}try{if(M.data){J.setRequestHeader("Content-Type",M.contentType)}if(M.ifModified){J.setRequestHeader("If-Modified-Since",o.lastModified[M.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}J.setRequestHeader("X-Requested-With","XMLHttpRequest");J.setRequestHeader("Accept",M.dataType&&M.accepts[M.dataType]?M.accepts[M.dataType]+", */*":M.accepts._default)}catch(S){}if(M.beforeSend&&M.beforeSend(J,M)===false){if(M.global&&!--o.active){o.event.trigger("ajaxStop")}J.abort();return false}if(M.global){o.event.trigger("ajaxSend",[J,M])}var N=function(X){if(J.readyState==0){if(P){clearInterval(P);P=null;if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}}else{if(!K&&J&&(J.readyState==4||X=="timeout")){K=true;if(P){clearInterval(P);P=null}R=X=="timeout"?"timeout":!o.httpSuccess(J)?"error":M.ifModified&&o.httpNotModified(J,M.url)?"notmodified":"success";if(R=="success"){try{V=o.httpData(J,M.dataType,M)}catch(Z){R="parsererror"}}if(R=="success"){var Y;try{Y=J.getResponseHeader("Last-Modified")}catch(Z){}if(M.ifModified&&Y){o.lastModified[M.url]=Y}if(!W){I()}}else{o.handleError(M,J,R)}L();if(X){J.abort()}if(M.async){J=null}}}};if(M.async){var P=setInterval(N,13);if(M.timeout>0){setTimeout(function(){if(J&&!K){N("timeout")}},M.timeout)}}try{J.send(M.data)}catch(S){o.handleError(M,J,null,S)}if(!M.async){N()}function I(){if(M.success){M.success(V,R)}if(M.global){o.event.trigger("ajaxSuccess",[J,M])}}function L(){if(M.complete){M.complete(J,R)}if(M.global){o.event.trigger("ajaxComplete",[J,M])}if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}return J},handleError:function(F,H,E,G){if(F.error){F.error(H,E,G)}if(F.global){o.event.trigger("ajaxError",[H,F,G])}},active:0,httpSuccess:function(F){try{return !F.status&&location.protocol=="file:"||(F.status>=200&&F.status<300)||F.status==304||F.status==1223}catch(E){}return false},httpNotModified:function(G,E){try{var H=G.getResponseHeader("Last-Modified");return G.status==304||H==o.lastModified[E]}catch(F){}return false},httpData:function(J,H,G){var F=J.getResponseHeader("content-type"),E=H=="xml"||!H&&F&&F.indexOf("xml")>=0,I=E?J.responseXML:J.responseText;if(E&&I.documentElement.tagName=="parsererror"){throw"parsererror"}if(G&&G.dataFilter){I=G.dataFilter(I,H)}if(typeof I==="string"){if(H=="script"){o.globalEval(I)}if(H=="json"){I=l["eval"]("("+I+")")}}return I},param:function(E){var G=[];function H(I,J){G[G.length]=encodeURIComponent(I)+"="+encodeURIComponent(J)}if(o.isArray(E)||E.jquery){o.each(E,function(){H(this.name,this.value)})}else{for(var F in E){if(o.isArray(E[F])){o.each(E[F],function(){H(F,this)})}else{H(F,o.isFunction(E[F])?E[F]():E[F])}}}return G.join("&").replace(/%20/g,"+")}});var m={},n,d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function t(F,E){var G={};o.each(d.concat.apply([],d.slice(0,E)),function(){G[this]=F});return G}o.fn.extend({show:function(J,L){if(J){return this.animate(t("show",3),J,L)}else{for(var H=0,F=this.length;H").appendTo("body");K=I.css("display");if(K==="none"){K="block"}I.remove();m[G]=K}o.data(this[H],"olddisplay",K)}}for(var H=0,F=this.length;H=0;H--){if(G[H].elem==this){if(E){G[H](true)}G.splice(H,1)}}});if(!E){this.dequeue()}return this}});o.each({slideDown:t("show",1),slideUp:t("hide",1),slideToggle:t("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(E,F){o.fn[E]=function(G,H){return this.animate(F,G,H)}});o.extend({speed:function(G,H,F){var E=typeof G==="object"?G:{complete:F||!F&&H||o.isFunction(G)&&G,duration:G,easing:F&&H||H&&!o.isFunction(H)&&H};E.duration=o.fx.off?0:typeof E.duration==="number"?E.duration:o.fx.speeds[E.duration]||o.fx.speeds._default;E.old=E.complete;E.complete=function(){if(E.queue!==false){o(this).dequeue()}if(o.isFunction(E.old)){E.old.call(this)}};return E},easing:{linear:function(G,H,E,F){return E+F*G},swing:function(G,H,E,F){return((-Math.cos(G*Math.PI)/2)+0.5)*F+E}},timers:[],fx:function(F,E,G){this.options=E;this.elem=F;this.prop=G;if(!E.orig){E.orig={}}}});o.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(o.fx.step[this.prop]||o.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(F){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var E=parseFloat(o.css(this.elem,this.prop,F));return E&&E>-10000?E:parseFloat(o.curCSS(this.elem,this.prop))||0},custom:function(I,H,G){this.startTime=e();this.start=I;this.end=H;this.unit=G||this.unit||"px";this.now=this.start;this.pos=this.state=0;var E=this;function F(J){return E.step(J)}F.elem=this.elem;if(F()&&o.timers.push(F)&&!n){n=setInterval(function(){var K=o.timers;for(var J=0;J=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var E=true;for(var F in this.options.curAnim){if(this.options.curAnim[F]!==true){E=false}}if(E){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(o.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){o(this.elem).hide()}if(this.options.hide||this.options.show){for(var I in this.options.curAnim){o.attr(this.elem.style,I,this.options.orig[I])}}this.options.complete.call(this.elem)}return false}else{var J=G-this.startTime;this.state=J/this.options.duration;this.pos=o.easing[this.options.easing||(o.easing.swing?"swing":"linear")](this.state,J,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};o.extend(o.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(E){o.attr(E.elem.style,"opacity",E.now)},_default:function(E){if(E.elem.style&&E.elem.style[E.prop]!=null){E.elem.style[E.prop]=E.now+E.unit}else{E.elem[E.prop]=E.now}}}});if(document.documentElement.getBoundingClientRect){o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}var G=this[0].getBoundingClientRect(),J=this[0].ownerDocument,F=J.body,E=J.documentElement,L=E.clientTop||F.clientTop||0,K=E.clientLeft||F.clientLeft||0,I=G.top+(self.pageYOffset||o.boxModel&&E.scrollTop||F.scrollTop)-L,H=G.left+(self.pageXOffset||o.boxModel&&E.scrollLeft||F.scrollLeft)-K;return{top:I,left:H}}}else{o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}o.offset.initialized||o.offset.initialize();var J=this[0],G=J.offsetParent,F=J,O=J.ownerDocument,M,H=O.documentElement,K=O.body,L=O.defaultView,E=L.getComputedStyle(J,null),N=J.offsetTop,I=J.offsetLeft;while((J=J.parentNode)&&J!==K&&J!==H){M=L.getComputedStyle(J,null);N-=J.scrollTop,I-=J.scrollLeft;if(J===G){N+=J.offsetTop,I+=J.offsetLeft;if(o.offset.doesNotAddBorder&&!(o.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(J.tagName))){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}F=G,G=J.offsetParent}if(o.offset.subtractsBorderForOverflowNotVisible&&M.overflow!=="visible"){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}E=M}if(E.position==="relative"||E.position==="static"){N+=K.offsetTop,I+=K.offsetLeft}if(E.position==="fixed"){N+=Math.max(H.scrollTop,K.scrollTop),I+=Math.max(H.scrollLeft,K.scrollLeft)}return{top:N,left:I}}}o.offset={initialize:function(){if(this.initialized){return}var L=document.body,F=document.createElement("div"),H,G,N,I,M,E,J=L.style.marginTop,K='
';M={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(E in M){F.style[E]=M[E]}F.innerHTML=K;L.insertBefore(F,L.firstChild);H=F.firstChild,G=H.firstChild,I=H.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(G.offsetTop!==5);this.doesAddBorderForTableAndCells=(I.offsetTop===5);H.style.overflow="hidden",H.style.position="relative";this.subtractsBorderForOverflowNotVisible=(G.offsetTop===-5);L.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(L.offsetTop===0);L.style.marginTop=J;L.removeChild(F);this.initialized=true},bodyOffset:function(E){o.offset.initialized||o.offset.initialize();var G=E.offsetTop,F=E.offsetLeft;if(o.offset.doesNotIncludeMarginInBodyOffset){G+=parseInt(o.curCSS(E,"marginTop",true),10)||0,F+=parseInt(o.curCSS(E,"marginLeft",true),10)||0}return{top:G,left:F}}};o.fn.extend({position:function(){var I=0,H=0,F;if(this[0]){var G=this.offsetParent(),J=this.offset(),E=/^body|html$/i.test(G[0].tagName)?{top:0,left:0}:G.offset();J.top-=j(this,"marginTop");J.left-=j(this,"marginLeft");E.top+=j(G,"borderTopWidth");E.left+=j(G,"borderLeftWidth");F={top:J.top-E.top,left:J.left-E.left}}return F},offsetParent:function(){var E=this[0].offsetParent||document.body;while(E&&(!/^body|html$/i.test(E.tagName)&&o.css(E,"position")=="static")){E=E.offsetParent}return o(E)}});o.each(["Left","Top"],function(F,E){var G="scroll"+E;o.fn[G]=function(H){if(!this[0]){return null}return H!==g?this.each(function(){this==l||this==document?l.scrollTo(!F?H:o(l).scrollLeft(),F?H:o(l).scrollTop()):this[G]=H}):this[0]==l||this[0]==document?self[F?"pageYOffset":"pageXOffset"]||o.boxModel&&document.documentElement[G]||document.body[G]:this[0][G]}});o.each(["Height","Width"],function(I,G){var E=I?"Left":"Top",H=I?"Right":"Bottom",F=G.toLowerCase();o.fn["inner"+G]=function(){return this[0]?o.css(this[0],F,false,"padding"):null};o.fn["outer"+G]=function(K){return this[0]?o.css(this[0],F,false,K?"margin":"border"):null};var J=G.toLowerCase();o.fn[J]=function(K){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+G]||document.body["client"+G]:this[0]==document?Math.max(document.documentElement["client"+G],document.body["scroll"+G],document.documentElement["scroll"+G],document.body["offset"+G],document.documentElement["offset"+G]):K===g?(this.length?o.css(this[0],J):null):this.css(J,typeof K==="string"?K:K+"px")}})})(); +jQ13 = jQuery.noConflict(true); +/*jsl:end*/ \ No newline at end of file diff --git l10n_community/l10n_community.css l10n_community/l10n_community.css index 1db630a..98fd2eb 100644 --- l10n_community/l10n_community.css +++ l10n_community/l10n_community.css @@ -62,147 +62,3 @@ table.l10n-server-filter td.last { em.l10n-community-marker { font-weight: bold; } - -/* Translation editor styles */ -#l10n-community-translate-view .pager, -#l10n-community-translate-form .pager { - margin:1em 0em; -} - -table.l10n-server-translate .hidden { - display:none; -} - -table.l10n-server-translate input.form-text, -table.l10n-server-translate textarea { - width:95%; -} - -table.l10n-server-translate td { - vertical-align:top; - width:50%; -} - - table.l10n-server-translate td.source { - padding:10px 5px; - } - - table .string-context { - color: #777; - font-size: 0.8em; - } - - table.l10n-server-translate td.translation { - padding:0px; - } - - table.l10n-server-translate td .l10n-panes { - min-height:60px; - position:relative; - padding:0px 20px 0px 0px; - } - - table.l10n-server-translate td .pane { - padding:10px 5px; - } - - table.l10n-server-translate td .suggestions, - table.l10n-server-translate td .lookup { - display:none; - } - -table.l10n-server-translate .toolbox { - position:absolute; top:0px; right:0px; -} - -table.l10n-server-translate tr .form-item { - white-space: normal; -} - -table.l10n-server-translate label.option { - font-size:90%; -} - -.l10n-button { - cursor:pointer; - display: block; - width: 20px; - height: 20px; - overflow: hidden; - text-indent: -9999px; -} - - .l10n-approval-buttons { - padding-right:10px; - float:right; - } - - .l10n-approval-buttons .l10n-button { - float:left; - } - - .l10n-approval-buttons .l10n-approve, - .l10n-save { - background: url(images/icon_approval.gif) 0px 0px no-repeat; - } - - .l10n-approval-buttons .l10n-approve:active, - .l10n-save:active { - background-position: 0px bottom; - } - - .l10n-approval-buttons .l10n-decline, - .l10n-clear { - width:21px; - background: url(images/icon_approval.gif) -20px 0px no-repeat; - } - - .l10n-approval-buttons .l10n-decline:active, - .l10n-clear:active { - background-position: -20px bottom; - } - - .l10n-community-copy { - background: url(images/icon_copy.gif) no-repeat; - } - - .l10n-translate { - background: url(images/icon_toolbox.gif) 0px 0px no-repeat; - } - - .l10n-translate.active { - background: url(images/icon_toolbox.gif) right 0px no-repeat; - } - - .l10n-suggestions { - background: url(images/icon_toolbox.gif) 0px -40px no-repeat; - } - - .l10n-suggestions.active { - background: url(images/icon_toolbox.gif) right -40px no-repeat; - } - - .l10n-lookup { - background: url(images/icon_toolbox.gif) 0px -20px no-repeat; - } - - .l10n-lookup.active { - background: url(images/icon_toolbox.gif) right -20px no-repeat; - } - -ul.l10n-community-strings { - margin: 0; -} - -ul.l10n-community-strings li { - padding-left: 20px; -} - -ul.l10n-community-strings li .buttons { - margin-left:-20px; - float:left; -} - -.l10n-community-string .original { - display:none; -} diff --git l10n_community/l10n_community.js l10n_community/l10n_community.js deleted file mode 100644 index e96bf79..0000000 --- l10n_community/l10n_community.js +++ /dev/null @@ -1,220 +0,0 @@ -// $Id: l10n_community.js,v 1.1.2.8.2.5 2009-10-15 08:46:40 goba Exp $ - -l10nCommunity = {}; - -l10nCommunity.switchPanes = function(elem, id) { - if ($(elem).parents('.translation').find('.'+id).css('display') == 'none') { - // execute animation once per translation - var once = 0; - $(elem).parents('.translation').find('.pane:not(.'+id+')').each(function() { - if ($(this).css('display') == 'block') { - $(this).slideUp('fast', function() { - if (once == 0) { - once = 1; - $(this).parents('.translation').find('.'+id).slideDown('fast'); - } - }); - } - }); - } - $(elem).parents('.toolbox').find('span.l10n-button').removeClass('active'); - $(elem).addClass('active'); -} - -/** - * Initialize action images: toolboxes and string copy buttons. - */ -l10nCommunity.init = function() { - // Only attempt to register events if form exists - if ($('#l10n-community-translate-form').size() > 0) { - // When the copy button is clicked, copy the original string value to the - // translation field for the given strings. Relations are maintained with - // the string ideitifiers. - $('span.l10n-community-copy').click(function() { - l10nCommunity.copyString(this); - ;}) - - // Screw AJAX submit -- we'll just hard submit the form for now - $('span.l10n-save').click(function() { - $('#l10n-community-translate-form').submit(); - }); - - // Clear sibling text fields - $('span.l10n-clear').click(function() { - $(this).parents('.translation').find('input.form-text, textarea').val(''); - }); - - - $('#l10n-community-translate-form .l10n-translate').click(function() { - // switch display panes - l10nCommunity.switchPanes(this, 'translate'); - }); - - $('#l10n-community-translate-form .l10n-lookup').click(function() { - // switch display panes - var elem = this; - if ($(this).is(".active")) { - // Switch back to editing form if already clicked. Convenience feature, - // so that you don't need to move your mouse to switch back. - var parent = $(elem).parents('.translation'); - var tool = $('.l10n-translate', parent); - l10nCommunity.switchPanes(tool, 'translate'); - return; - } - var sid = $(this).parents('.translation').attr('id').substring(6); - $.get(Drupal.settings.l10n_details_callback + sid, null, function(data) { - $('#tpane-' + sid + ' .lookup').empty().append(data); - l10nCommunity.switchPanes(elem, 'lookup'); - }); - }); - - $('#l10n-community-translate-form .l10n-suggestions').click(function() { - // switch display panes - var elem = this; - if ($(this).is(".active")) { - // Switch back to editing form if already clicked. Convenience feature, - // so that you don't need to move your mouse to switch back. - var parent = $(elem).parents('.translation'); - var tool = $('.l10n-translate', parent); - l10nCommunity.switchPanes(tool, 'translate'); - return; - } - var sid = $(this).parents('.translation').attr('id').substring(6); - $.get(Drupal.settings.l10n_suggestions_callback + sid, null, function(data) { - $('#tpane-' + sid + ' .suggestions').empty().append(data); - l10nCommunity.switchPanes(elem, 'suggestions'); - var suggestions = $('#tpane-' + sid + ' .suggestions'); - $('span.l10n-community-copy', suggestions).click(function() { - l10nCommunity.copyString(this); - }); - // Hide the has-suggestion marker if we don't have suggestions anymore. - // Could happen if we declined the last suggestion and reloading. - $('#l10n-community-editor-' + sid + ' .l10n-no-suggestions').parent().parent().find('.l10n-has-suggestion').hide(); - }); - }); - } -} - -l10nCommunity.copyString = function(elem) { - var item = $(elem).parents('li').find('div.string > div'); - var parentlist = $(item).parents('ul.l10n-community-strings'); - var original = $('.original', item).text(); - var sid = item.attr('class').substring(7); - - if (sid.indexOf('-') > 0) { - // Copying orignal plural string or active plural translation. We should - // get the original sid prefix and then copy the strings to the textareas. - sid = sid.split('-'); - sid = sid[0]; - for (i = 0; i < Drupal.settings.l10n_num_plurals; i++) { - source_index = (i > 0 ? 1 : 0); - $('#l10n-community-translation-'+ sid +'-'+ i).val($('.string-'+ sid +'-'+ source_index +' .original', parentlist).text()); - } - } - else if (original.indexOf("; ") > 0) { - // Copying plural suggestions from the suggestion list. This has the special - // "; " delimiter (which we suppose does not exist in source strings). - var strings = original.split("; "); - for (string in strings) { - $('#l10n-community-translation-'+ sid +'-'+ string).val(strings[string]); - } - } - else { - // Otherwise simple string. Just copy over the original string. - $('#l10n-community-translation-' + sid).val(original); - } - - // Show the editing controls. - $('#l10n-community-wrapper-' + sid).show(); - - // Switch to translate pane. The pane is in the translation column, so - // if we are in the source column, we need to switch columns. - var parent = $(elem).parents('td.source, td.translation'); - if (parent.get(0).className == 'source') { - parent = $(parent.get(0)).siblings(); - } - var tool = $('.l10n-translate', parent); - l10nCommunity.switchPanes(tool, 'translate'); -} - -/** - * Suggestion approval callback. - */ -l10nCommunity.approveSuggestion = function(tid, sid, elem, token) { - // Invoke server side callback to save the approval. - $.ajax({ - type: "GET", - url: Drupal.settings.l10n_approve_callback + tid + Drupal.settings.l10n_form_token_path + token, - success: function (data) { - if (data == 'done') { - // Empty translate pane and inform user that the suggestion was saved. - $('#tpane-'+ sid +' .translate').empty().append(Drupal.settings.l10n_approve_confirm); - // Switch back to translate pane and hide suggestion icon - var parent = $(elem).parents('.translation'); - l10nCommunity.switchPanes($('.l10n-translate', parent), 'translate'); - $('.l10n-suggestions', parent).hide(); - } - else { - alert(Drupal.settings.l10n_approve_error); - }; - }, - error: function (xmlhttp) { - // Being an internal/system error, this is not translatable. - alert('An HTTP error '+ xmlhttp.status +' occured.\n'+ uri); - } - }); - // Return false for onclick handling. - return false; -} - -/** - * Suggestion decline callback. - */ -l10nCommunity.declineSuggestion = function(tid, sid, elem, token) { - // Invoke server side callback to save the decline action. - $.ajax({ - type: "GET", - url: Drupal.settings.l10n_decline_callback + tid + Drupal.settings.l10n_form_token_path + token, - success: function (data) { - if (data == 'done') { - // Reload info pane. - $('#tpane-' + sid + ' .l10n-suggestions').click(); - } - else { - alert(Drupal.settings.l10n_decline_error); - }; - }, - error: function (xmlhttp) { - // Being an internal/system error, this is not translatable. - alert('An HTTP error '+ xmlhttp.status +' occured.\n'+ uri); - } - }); - // Return false for onclick handling. - return false; -} - -/** - * Suggestion editing copy callback. - */ -l10nCommunity.copySuggestion = function(sid, translation) { - if (translation.indexOf("\O") > 0) { - // If we have the null byte, suggestion has plurals, so we need to - // copy over the distinct strings to the distinct textareas. - var strings = translation.split("\O"); - for (string in strings) { - $('#l10n-community-translation-'+ sid +'-'+ string).val(strings[string]); - } - } - else { - // Otherwise standard string. - $('#l10n-community-translation-'+ sid).val(translation); - } - // Show the editing controls. - $('#l10n-community-wrapper-'+ sid).css('display', 'block'); - return false; -} - -// Global killswitch -if (Drupal.jsEnabled) { - $(document).ready(l10nCommunity.init); -} diff --git l10n_community/l10n_community.module l10n_community/l10n_community.module index bbd3de7..cd86a40 100644 --- l10n_community/l10n_community.module +++ l10n_community/l10n_community.module @@ -207,33 +207,10 @@ function l10n_community_menu() { $items['translate/details'] = array( 'title' => 'String details', 'page callback' => 'l10n_community_string_details', - 'file' => 'ajax.inc', - 'access arguments' => array('browse translations'), - 'type' => MENU_CALLBACK, - ); - $items['translate/suggestions'] = array( - 'title' => 'String suggestions', - 'page callback' => 'l10n_community_string_suggestions', - 'file' => 'ajax.inc', + 'file' => 'translate.inc', 'access arguments' => array('browse translations'), 'type' => MENU_CALLBACK, ); - $items['translate/approve'] = array( - 'title' => 'Approve suggestion', - 'page callback' => 'l10n_community_string_approve', - 'file' => 'ajax.inc', - // Permission is enforced in l10n_community_string_ajax_suggestion(). - 'access arguments' => array('access localization community'), - 'type' => MENU_CALLBACK, - ); - $items['translate/decline'] = array( - 'title' => 'Decline suggestion', - 'page callback' => 'l10n_community_string_decline', - 'file' => 'ajax.inc', - // Permission is enforced in l10n_community_string_ajax_suggestion(). - 'access arguments' => array('access localization community'), - 'type' => MENU_CALLBACK, - ); // As soon as we have a language code, we can translate. $items['translate/languages/%l10n_community_language'] = array( @@ -251,8 +228,8 @@ function l10n_community_menu() { 'weight' => -20, ); // Tabs to translate, import and export projects. - $items['translate/languages/%l10n_community_language/view'] = array( - 'title' => 'Browse', + $items['translate/languages/%l10n_community_language/translate'] = array( + 'title' => 'Translate', 'page callback' => 'l10n_community_translate_page', 'page arguments' => array(2), 'file' => 'translate.inc', @@ -261,16 +238,6 @@ function l10n_community_menu() { 'type' => MENU_LOCAL_TASK, 'weight' => -10, ); - $items['translate/languages/%l10n_community_language/edit'] = array( - 'title' => 'Translate', - 'page callback' => 'l10n_community_translate_page', - 'page arguments' => array(2, 'edit'), - 'file' => 'translate.inc', - 'access callback' => 'l10n_community_has_permission', - 'access arguments' => array(2, (string)L10N_PERM_SUGGEST), - 'type' => MENU_LOCAL_TASK, - 'weight' => -8, - ); $items['translate/languages/%l10n_community_language/moderate'] = array( 'title' => 'Moderate', 'page callback' => 'l10n_community_moderate_page', @@ -496,6 +463,7 @@ function l10n_community_block_help() { // We are dealing with a groups based permission model. $permission_help[] = l10n_groups_block_help($perm, isset($args['langcode']) ? $args['langcode'] : NULL); } + if ($perm & L10N_PERM_SUGGEST) { $permission_notes[] = t('You can suggest translations to be reviewed by moderators of the team.'); } @@ -949,6 +917,19 @@ function l10n_community_get_languages($key = NULL) { } /** + * Returns a language object for a specific language. + * + * @param $langcode + * Language code, for example 'hu', 'pt-br', 'de' or 'it'. + * @return + * A populated language object. + */ +function l10n_community_get_language($langcode) { + $languages = l10n_community_get_languages(); + return $languages[$langcode]; +} + +/** * Get translation permission level for a specific user. * * @param $langcode @@ -958,7 +939,7 @@ function l10n_community_get_languages($key = NULL) { * @return * A bitmask of all permission flags the user has. */ -function l10n_community_get_permission($langcode, $account = NULL) { +function l10n_community_get_permission($langcode, $account = NULL, $check = NULL) { static $permissions = array(); global $user; @@ -1000,12 +981,47 @@ function l10n_community_get_permission($langcode, $account = NULL) { * @param $langcode * Language code, for example 'hu', 'pt-br', 'de' or 'it'. * @param $permission - * The permission to check for. + * The permission to check for. Note that if you pass multiple permission + * constants, this function will return TRUE if the user has at least one + * of them. * @return * TRUE if the user has at least one of the specified permissions. */ function l10n_community_has_permission($langcode, $permission) { - return (bool)(l10n_community_get_permission($langcode, NULL) & $permission); + return (bool)(l10n_community_get_permission($langcode) & $permission); +} + +/** + * Checks whether the current user may approve a translation. + * + * @param $translation + * The translation in question. + * @return + * TRUE if the user may approve the translation. + */ +function l10n_community_may_approve_translation($translation) { + global $user; + $required = $translation->uid_entered == $user->uid ? L10N_PERM_MODERATE_OWN : L10N_PERM_MODERATE_OTHERS; + return l10n_community_has_permission($translation->language, $required); +} + +/** + * Checks whether the current user may decline a translation. + * + * @param $translation + * The translation in question. + * @return + * TRUE if the user may decline the translation. + */ +function l10n_community_may_decline_translation($translation) { + global $user; + if ($translation->uid_entered == $user->uid) { + // Everyone may decline their own translations. + return TRUE; + } + else { + return l10n_community_has_permission($translation->language, L10N_PERM_MODERATE_OTHERS); + } } /** @@ -1157,31 +1173,30 @@ function l10n_community_get_contexts() { * * @see l10n_community_is_duplicate() */ -function l10n_community_target_save($sid, $translation, $langcode, $uid, $suggestion, &$inserted, &$updated, &$unchanged, &$suggested) { +function l10n_community_target_save($sid, $translation, $langcode, $uid, $suggestion) { // Look for an existing active translation, if any. $existing_string = db_fetch_object(db_query("SELECT sid, tid, translation FROM {l10n_community_translation} WHERE sid = %d AND language = '%s' AND is_suggestion = 0 AND is_active = 1", $sid, $langcode)); if (!empty($existing_string->sid)) { - // We have an active translation. if ($existing_string->translation != $translation) { // And what we should save now is different. if ($suggestion) { // Saving a suggestion, so set flag on translation. db_query("UPDATE {l10n_community_translation} SET has_suggestion = 1 WHERE tid = %d", $existing_string->tid); - $suggested++; + l10n_community_counter('suggested'); } else { // Saving a different translation -> deactivate previous translations and suggestions. db_query("UPDATE {l10n_community_translation} SET is_active = 0 WHERE sid = %d AND language = '%s';", $sid, $langcode); - $updated++; + l10n_community_counter('updated'); } db_query("INSERT INTO {l10n_community_translation} (sid, translation, language, uid_entered, time_entered, uid_approved, time_approved, is_suggestion, is_active) VALUES (%d, '%s', '%s', %d, %d, %d, %d, %d, 1)", $sid, $translation, $langcode, $uid, time(), ($suggestion ? 0 : $uid), ($suggestion ? 0 : time()), $suggestion); } else { // Same string as existing translation. - $unchanged++; + l10n_community_counter('unchanged'); } } @@ -1192,12 +1207,12 @@ function l10n_community_target_save($sid, $translation, $langcode, $uid, $sugges // suggestions. We track and exclude these by translation = '' later. db_query("INSERT INTO {l10n_community_translation} (sid, translation, language, uid_entered, time_entered, has_suggestion, is_active) VALUES (%d, '', '%s', 0, %d, 1, 1)", $sid, $langcode, time()); db_query("INSERT INTO {l10n_community_translation} (sid, language, translation, uid_entered, time_entered, is_suggestion, is_active) VALUES (%d, '%s', '%s', %d, %d, 1, 1)", $sid, $langcode, $translation, $uid, time()); - $suggested++; + l10n_community_counter('suggested'); } else { // No active translation yet -> INSERT. db_query("INSERT INTO {l10n_community_translation} (sid, translation, language, uid_entered, uid_approved, time_entered, time_approved, is_active) VALUES (%d, '%s', '%s', %d, %d, %d, %d, 1)", $sid, $translation, $langcode, $uid, $uid, time(), time()); - $inserted++; + l10n_community_counter('added'); } } } @@ -1222,34 +1237,68 @@ function l10n_community_trim($translation, $source) { } /** + * Stores counters for status messages when modifying translations. + * + * @param $field + * The field to increment. Can be one of declined, approved, inserted, + * suggested, duplicates', ignored or unchanged. + * If not specified, the counters are returned and reset afterwards. + * @param $increment + * (Optional) The increment for the counter. Defaults to 1. + */ +function l10n_community_counter($field = NULL, $increment = 1) { + static $counters = array(); + if (isset($field)) { + if (!isset($counters[$field])) { + $counters[$field] = 0; + } + $counters[$field] += $increment; + } + else { + $return = $counters; + $counters = array(); + return $return; + } +} + +/** * Set a message based on the number of translations changed. * * Used by both the save and import process. */ -function l10n_community_update_message($inserted, $updated, $unchanged, $suggested, $duplicates, $ignored) { - // Inform user about changes made. - $message = array(); - if ($inserted) { - $message[] = format_plural($inserted, '1 new translation added', '@count new translations added'); - } - if ($suggested) { - $message[] = format_plural($suggested, '1 new suggestion added', '@count new suggestions added'); - } - if ($updated) { - $message[] = format_plural($updated, '1 translation updated', '@count translations updated'); - } - if ($unchanged) { - $message[] = format_plural($unchanged, '1 translation unchanged', '@count translations unchanged'); - } - if ($duplicates) { - $message[] = format_plural($duplicates, '1 duplicate translation not saved', '@count duplicate translations not saved'); - } - if ($ignored) { - $message[] = format_plural($ignored, '1 source string not found, its translation ignored', '@count source strings not found, their translations were ignored'); - } - if (count($message)) { - drupal_set_message(join('; ', $message) .'.'); - } +function l10n_community_update_message() { + $counters = l10n_community_counter(); + $messages = array(); + + if (!empty($counters['declined'])) + $messages[] = format_plural($counters['declined'], '1 translation declined', '@count translations declined'); + + if (!empty($counters['suggestion_declined'])) + $messages[] = format_plural($counters['suggestion_declined'], '1 suggestion declined', '@count suggestion declined'); + + if (!empty($counters['approved'])) + $messages[] = format_plural($counters['approved'], '1 translation approved', '@count translations approved'); + + if (!empty($counters['added'])) + $messages[] = format_plural($counters['added'], '1 translation added', '@count translations added'); + + if (!empty($counters['suggested'])) + $message[] = format_plural($counters['suggested'], '1 new suggestion added', '@count new suggestions added'); + + if (!empty($counters['updated'])) + $message[] = format_plural($counters['updated'], '1 translation updated', '@count translations updated'); + + if (!empty($counters['duplicates'])) + $message[] = format_plural($counters['duplicates'], '1 duplicate translation not saved', '@count duplicate translations not saved'); + + if (!empty($counters['ignored'])) + $message[] = format_plural($counters['ignored'], '1 source string not found; its translation was ignored', '@count source strings not found; their translations were ignored'); + + if (!empty($counters['unchanged'])) + $message[] = format_plural($counters['unchanged'], '1 translation unchanged', '@count translations unchanged'); + + if ($messages) + drupal_set_message(implode(', ', $messages)); } /** @@ -1310,15 +1359,6 @@ function l10n_community_project_uri_by_title($title) { return db_result(db_query("SELECT uri FROM {l10n_community_project} WHERE title = '%s'", $title)); } -/** - * Check whether $suggestion is duplicate for $sid in $langcode. - */ -function l10n_community_is_duplicate($suggestion, $sid, $langcode) { - // Use BINARY matching to avoid marking case-corrections as duplicate. - // Matches everything active, regardless of being translations or suggestions. - return (bool) db_result(db_query("SELECT s.sid FROM {l10n_community_string} s LEFT JOIN {l10n_community_translation} t ON s.sid = t.sid WHERE t.translation = BINARY '%s' AND t.is_active = 1 AND t.language = '%s' AND s.sid = %d", $suggestion, $langcode, $sid)); -} - // = Theme functions =========================================================== /** @@ -1326,16 +1366,6 @@ function l10n_community_is_duplicate($suggestion, $sid, $langcode) { */ function l10n_community_theme($existing, $type, $theme, $path) { return array( - // l10n_community.module - 'l10n_community_button' => array( - 'arguments' => array('type' => NULL, 'class' => NULL, 'extras' => ''), - ), - 'l10n_community_strings' => array( - 'arguments' => array('items' => NULL, 'form' => TRUE), - ), - 'l10n_community_copy_button' => array( - 'arguments' => array(), - ), // pages.inc 'l10n_community_progress_columns' => array( 'arguments' => array('sum' => NULL, 'translated' => NULL, 'has_suggestion' => NULL), @@ -1350,11 +1380,23 @@ function l10n_community_theme($existing, $type, $theme, $path) { 'l10n_community_filter_form' => array( 'arguments' => array('form' => NULL), ), - 'l10n_community_translate_form' => array( - 'arguments' => array('form' => NULL), + 'l10n_community_translate_translation' => array( + 'arguments' => array('element' => NULL), + ), + 'l10n_community_translate_actions' => array( + 'arguments' => array('element' => NULL), ), - 'l10n_community_in_context' => array( - 'arguments' => array('source' => NULL), + 'l10n_community_translate_radio' => array( + 'arguments' => array('element' => NULL), + ), + 'l10n_community_translate_source' => array( + 'arguments' => array('element' => NULL), + ), + 'l10n_community_translate_translation_list' => array( + 'arguments' => array('element' => NULL), + ), + 'l10n_community_translate_table' => array( + 'arguments' => array('element' => NULL), ), // l10n_community.admin.inc 'l10n_community_admin_projects_form' => array( @@ -1371,77 +1413,6 @@ function l10n_community_theme($existing, $type, $theme, $path) { } /** - * Theme a textual button. - * - * Text values are centralized here so it is easy to change. - */ -function theme_l10n_community_button($type, $class, $extras = '') { - switch ($type) { - case 'translate': - $text = t('Translate'); - break; - case 'lookup': - $text = t('Information'); - break; - case 'edit': - // Source string and translation edit field. - $text = t('Edit'); - break; - case 'has-suggestion': - case 'has-no-suggestion': - case 'untranslated': - // Star in a filled circle. - $text = t('Suggestions'); - break; - case 'approve': - // Checkmark. - $text = t('Approve'); - break; - case 'decline': - // Checkmark. - $text = t('Decline'); - break; - case 'save': - // Save button. - $text = t('Save'); - break; - case 'clear': - // Clear form button. - $text = t('Clear'); - break; - } - return ' '. $text .''; -} - -/** - * Theme a list of translatable strings. Adds a copy button to each string - * for quickly copying its source text into a translation form. - */ -function theme_l10n_community_strings($items, $form = TRUE) { - $output = "
    "; - foreach ($items as $i => $item) { - $output .= "
  • "; - if ($form && ($i < 1)) { - // Only print copy button if we are displaying a form and on the first item. - // For plurals, only the first item will have the copy button, but will copy - // all the values into the form. - $output .= "
    ". theme('l10n_community_copy_button') ."
    "; - } - $output .= "
    ". $item ."
    "; - $output .= "
  • "; - } - $output .= "
"; - return $output; -} - -/** - * Copy button for string values. - */ -function theme_l10n_community_copy_button() { - return theme('l10n_community_button', 'edit', 'l10n-community-copy'); -} - -/** * Format string for display. Takes plurals into account. */ function l10n_community_format_string($value, $rich_markup = TRUE) { diff --git l10n_community/translate.inc l10n_community/translate.inc index 8d07552..afbe381 100644 --- l10n_community/translate.inc +++ l10n_community/translate.inc @@ -6,51 +6,6 @@ * Translation view and editing pages for localization community. */ -// = Translation interface hub ================================================= - -/** - * Menu callback for the translation pages. - * - * Displays a translation view or translation edit page depending - * on permissions. If no strings are found, an error is printed. - * - * @param $langcode - * Language code, for example 'hu', 'pt-br', 'de', 'it'. - */ -function l10n_community_translate_page($langcode = NULL, $mode = 'view') { - - // Add missing breadcrumb. - drupal_set_breadcrumb( - array( - l(t('Home'), NULL), - l(t('Translate'), 'translate') - ) - ); - - $languages = l10n_community_get_languages(); - $perm = l10n_community_get_permission($langcode); - - $filters = l10n_community_build_filter_values($_GET); - $output = drupal_get_form('l10n_community_filter_form', $filters); - - $strings = l10n_community_get_strings($languages[$langcode]->language, $filters, $filters['limit']); - if (!count($strings)) { - drupal_set_message(t('No strings found with this filter. Try adjusting the filter options.')); - } - elseif (!($perm & L10N_PERM_SUGGEST) || $mode == 'view') { - // For users without permission to translate or suggest, display the view. - drupal_set_title(t('@language translations', array('@language' => $languages[$langcode]->name))); - $output .= l10n_community_translate_view($strings, $languages[$langcode], $filters); - } - else { - // For users with some permission, display the form. - drupal_add_js(drupal_get_path('module', 'l10n_community') .'/l10n_community.js'); - drupal_set_title(t('Translate to @language', array('@language' => $languages[$langcode]->name))); - $output .= drupal_get_form('l10n_community_translate_form', $strings, $languages[$langcode], $filters, $perm); - } - return $output; -} - // = Filter form handling ====================================================== /** @@ -190,386 +145,557 @@ function l10n_community_filter_form_submit($form, &$form_state) { } } -// = Translation viewer ======================================================== - /** - * Form for translations display. - * - * @param $strings - * Array of string objects to display on the page. - * @param $language - * Language object corresponding to the page displayed. - * @param $filters - * Filters used to present this listing view. + * Theme function for l10n_community_filter_form. */ -function l10n_community_translate_view($strings = array(), $language = NULL, $filters = array()) { - $output = ''; - $rows = array(); - foreach ($strings as $string) { - $row = array(); - // Source display - $source = l10n_community_format_string($string->value); - $source .= theme('l10n_community_in_context', $string); - $row[] = array('data' => $source, 'class' => 'source'); - - // Translation display. - if (!empty($string->translation)) { - if (strpos($string->value, chr(0)) !== FALSE) { - $translations = explode(chr(0), l10n_community_format_text($string->translation)); - // Fill in any missing items, so it is shown that not all items are done. - if (count($translations) < $language->plurals) { - $translations = array_merge($translations, array_fill(0, count($translations) - $language->plurals, '')); - } - $translation = theme('item_list', $translations); - } - else { - $translation = l10n_community_format_text($string->translation); - } - $row[] = $translation; - } - else { - $row[] = ''; +function theme_l10n_community_filter_form($form) { + $row = array(); + $labels = array(); + // Only display these elements in distinct table cells + $elements = array('project', 'release', 'context', 'status', 'author', 'search', 'limit'); + foreach ($form as $id => &$element) { + if (in_array($id, $elements)) { + $labels[] = $element['#title']; + unset($element['#title']); + $row[] = drupal_render($element); } - $rows[] = $row; } - $output .= ($pager = theme('pager', NULL, $filters['limit'], 0)); - $output .= theme('table', array(t('Source Text'), t('Translations')), $rows, array('class' => 'l10n-server-translate')); - $output .= $pager; - $output = "
". $output ."
"; - return $output; + // Fill in the rest of the header above the buttons. + $labels[] = ''; + // Display the rest of the form in the last cell + $row[] = array('data' => drupal_render($form), 'class' => 'last'); + return theme('table', $labels, array($row), array('class' => 'l10n-server-filter')); } -// = Translation editor ======================================================== +// = Translation view ========================================================== + +/** + * Menu callback: List translations and suggestions + */ +function l10n_community_translate_page($langcode) { + drupal_add_css(drupal_get_path('module', 'l10n_community') .'/editor.css'); + drupal_add_js(drupal_get_path('module', 'l10n_community') .'/jquery13.js'); + drupal_add_js(drupal_get_path('module', 'l10n_community') .'/jquery.worddiff.js'); + drupal_add_js(drupal_get_path('module', 'l10n_community') .'/editor.js'); + + $language = l10n_community_get_language($langcode); + $filters = l10n_community_build_filter_values($_GET); + $strings = l10n_community_get_strings($language->language, $filters, $filters['limit']); + + $output = drupal_get_form('l10n_community_filter_form', $filters); + + if (!count($strings)) { + drupal_set_message(t('No strings found with this filter. Try adjusting the filter options.')); + } + else { + $output .= drupal_get_form('l10n_community_translate_form', $language, $filters, $strings); + } + + return $output; +} /** - * Translation web interface. + * Form callback: List translations and suggestions. * - * @param $strings - * Array of string objects to display. + * @param $form_state + * The form state array. * @param $language - * Language object. + * A language object. * @param $filters - * Filters used to present this editing view. - * @param $perm - * Community permission level of user watching the page. + * An array of filters applied to the strings. + * @param $strings + * The strings to render. */ -function l10n_community_translate_form(&$form_state, $strings = array(), $language = NULL, $filters = array(), $perm = L10N_PERM_SUGGEST) { - - if (isset($_GET['page'])) { - // Ensure that we keep all filter values, even the page number, so - // after submission, the same page can be shown. - $filters['page'] = (int) $_GET['page']; - } +function l10n_community_translate_form(&$form_state, $language, $filters, $strings) { + $permission = l10n_community_get_permission($language->language); + $pager = theme('pager', NULL, $filters['limit'], 0); + $redirect_url = $_GET; + unset($redirect_url['q']); $form = array( - '#tree' => TRUE, - '#redirect' => array($_GET['q'], l10n_community_flat_filters($filters)) - ); - $form['pager_top'] = array( - '#weight' => -10, - '#value' => ($pager = theme('pager', NULL, $filters['limit'], 0)), - ); - $form['pager_bottom'] = array( - '#weight' => 10, - '#value' => $pager, - ); - // Keep language code and URI in form for further reference. - $form['langcode'] = array( - '#type' => 'value', - '#value' => $language->language - ); - $form['project'] = array( - '#type' => 'value', - '#value' => isset($project) ? $project->uri : NULL + '#submit' => array('l10n_community_translate_submit'), + '#redirect' => array($_GET['q'], $redirect_url), + 'langcode' => array('#type' => 'value', '#value' => $language->language), + 'pager_top' => array('#weight' => -10, '#value' => $pager), + 'submit_top' => array('#type' => 'submit', '#value' => t('Save changes'), '#access' => $permission & L10N_PERM_SUGGEST), + 'strings' => array('#tree' => TRUE, '#theme' => 'l10n_community_translate_table'), + 'submit' => array('#type' => 'submit', '#value' => t('Save changes'), '#access' => $permission & L10N_PERM_SUGGEST), + 'pager_bottom' => array('#weight' => 10, '#value' => $pager), ); foreach ($strings as $string) { - $form[$string->sid] = array( - '#tree' => TRUE, - ); + $form['strings'][$string->sid] = _l10n_community_translate_string($form_state, $string, $language, $permission); + } - // A toolbox which displays action icons on each string editor fieldset. - $toolbox = theme('l10n_community_button', 'translate', 'l10n-translate active'); - $toolbox .= theme('l10n_community_button', 'lookup', 'l10n-lookup'); - $toolbox .= $string->has_suggestion ? theme('l10n_community_button', 'has-suggestion', 'l10n-suggestions') : ""; - $toolbox = "
$toolbox
"; - $form[$string->sid]['toolbox'] = array( - '#type' => 'markup', - '#value' => $toolbox, - ); - $form[$string->sid]['messagebox'] = array( - '#type' => 'markup', - '#value' => "
", - ); + return $form; +} - $is_plural = strpos($string->value, "\0"); - // Multiple source strings if we deal with plurals. The form item and - // consequently the JavaScript strings identifiers are the sid and then - // the index of the plural being displayed. - $string_parts = explode(chr(0), $string->value); - foreach ($string_parts as $delta => &$part) { - $part = l10n_community_format_text($part, $string->sid, (count($string_parts) > 1) ? $delta : NULL); - } - $source = theme('l10n_community_strings', $string_parts); - $source .= theme('l10n_community_in_context', $string); +/** + * Return a marked-up string. + */ +function _l10n_community_translate_render_string($strings, $empty = '') { + if ($empty) { + $empty = ' data-empty="'. check_plain($empty) .'"'; + } + return "". implode("
", array_map('check_plain', $strings)) .''; +} - $form[$string->sid]['source'] = array( - '#type' => 'item', - '#value' => $source, - ); +function _l10n_community_translate_string(&$form_state, $source, $language, $permission) { + // Normalize empty default translation. + if (!$source->translation) { + $source->tid = '0'; + $source->translation = array(t('(not translated)')); + $source->is_active = '1'; + $source->is_suggestion = '0'; + } + else { + $source->translation = l10n_community_unpack_string($source->translation); + } + + $source->value = l10n_community_unpack_string($source->value); - $translated = !empty($string->translation); - $form[$string->sid]['translation'] = array( - '#type' => 'item', - // Hide editing controls of translated stuff to save some space and guide user eyes. - '#prefix' => '', + $form = array( + '#string' => $source, + '#langcode' => $language->language, + 'source' => array( + 'string' => array('#value' => _l10n_community_translate_render_string($source->value)), + ), + ); + + if ($permission & L10N_PERM_SUGGEST) { + $form['source']['edit'] = array( + '#value' => t('Edit Copy'), + '#prefix' => '', ); + } - if ($is_plural) { + // Add the current string (either a approved translation or a mock object + // for the "untranslated" string). + $form[$source->tid] = _l10n_community_translate_translation($form_state, $source, $permission, $source); + + // When there are suggestions, load them from the database. + if ($source->has_suggestion) { + $result = db_query("SELECT t.tid, t.sid, t.translation, t.uid_entered, t.time_entered, t.is_active, t.is_suggestion, u.name as username FROM {l10n_community_translation} t LEFT JOIN {users} u ON u.uid = t.uid_entered WHERE t.language = '%s' AND t.sid = %d AND t.is_active = 1 AND t.is_suggestion = 1 ORDER BY t.time_entered", $language->language, $source->sid); + while ($suggestion = db_fetch_object($result)) { + $suggestion->translation = l10n_community_unpack_string($suggestion->translation); + // Add the suggestion to the list. + $form[$suggestion->tid] = _l10n_community_translate_translation($form_state, $suggestion, $permission, $source); + } + } - // Dealing with a string with plural versions. - if ($translated) { - // Add translation form element with all plural versions. - $translations = explode("\0", $string->translation); - $string_parts = array(); - for ($i = 0; $i < $language->plurals; $i++) { - $target = $string->sid .'-'. $i; - $string_parts[] = l10n_community_format_text($translations[$i], $string->sid, $i); - } - $form[$string->sid]['translation_existing'] = array( - '#type' => 'item', - '#value' => theme('l10n_community_strings', $string_parts), - ); - } + // If the user may add new suggestions, display a textarea. + if ($permission & L10N_PERM_SUGGEST) { + $textarea = _l10n_community_translate_translation_textarea($source, $language); + $form[$textarea->tid] = _l10n_community_translate_translation($form_state, $textarea, $permission, $source); + } - $string_parts = explode(chr(0), $string->value); + return $form; +} - for ($i = 0; $i < $language->plurals; $i++) { - $target = $string->sid .'-'. $i; - if ($translated) { - // Already translated so we ask for new translation or suggestion. - $description = !($perm & L10N_PERM_MODERATE_OWN) ? t('New suggestion for variant #%d', array('%d' => $i)) : t('New translation for variant #%d', array('%d' => $i)); - } - else { - // Not translated yet, so we ask for initial translation or suggestion. - $description = !($perm & L10N_PERM_MODERATE_OWN) ? t('Suggestion for variant #%d', array('%d' => $i)) : t('Translation for variant #%d', array('%d' => $i)); - } +// Build mock object for new textarea +function _l10n_community_translate_translation_textarea($source, $language) { + global $user; - // Include editing area for each plural variant. - $source_index = ($i > 0 ? 1 : 0); - $form[$string->sid]['translation']['value'][$i] = array( - // Use textarea for long and multiline strings. - '#type' => ((strlen($string_parts[$source_index]) > 45) || (count(explode("\n", $string_parts[$source_index])) > 1)) ? 'textarea' : 'textfield', - '#description' => $description, - '#rows' => 1, - '#id' => 'l10n-community-translation-'. $target, - ); - } + return (object)array( + 'sid' => $source->sid, + 'tid' => 'new', + 'translation' => array_fill(0, count($source->value), ''), + 'is_active' => '1', + 'is_suggestion' => '1', + 'uid_entered' => $user->uid, + ); +} + +function l10n_community_translate_byline($string) { + $params = array( + '!author' => theme('username', (object)array('name' => $string->username, 'uid' => $string->uid_entered)), + '@date' => format_date($string->time_entered), + '@ago' => t('@time ago', array('@time' => format_interval(time() - $string->time_entered))), + ); + + if (!empty($string->uid_approved) && $string->uid_entered === $string->uid_approved && $string->time_entered === $string->time_approved) { + return t('translated and approved by !author on @date', $params); + } + else { + $title = t('suggested by !author on @date', $params); + if (!empty($string->uid_approved)) { + $title .= '
'. t('approved by !author on @date', array( + '!author' => theme('username', (object)array('name' => $string->username_approved, 'uid' => $string->uid_approved)), + '@date' => format_date($string->time_approved), + '@ago' => t('@time ago', array('@time' => format_interval(time() - $string->time_approved))), + )); } + return $title; + } +} + +function _l10n_community_translate_translation(&$form_state, $string, $permission, $source) { + global $user; + $is_own = $user->uid == $string->uid_entered; + $is_active = $string->is_active && !$string->is_suggestion; + $is_new = $string->tid == 'new'; + $may_moderate = $permission & ($is_own ? L10N_PERM_MODERATE_OWN : L10N_PERM_MODERATE_OTHERS); + //$may_stabilize = $permission & ($is_own ? L10N_PERM_STABILIZE_OWN : L10N_PERM_STABILIZE_OTHERS); - // Dealing with a simple string (no plurals). + $form = array( + '#theme' => 'l10n_community_translate_translation', + 'original' => array('#type' => 'value', '#value' => $string), + ); + $form['active'] = array( + '#type' => 'radio', + '#theme' => 'l10n_community_translate_radio', + '#title' => _l10n_community_translate_render_string($string->translation, $is_new ? t('(empty)') : FALSE), + '#return_value' => $string->tid, + '#default_value' => $is_active ? $string->tid : NULL, + '#parents' => array('strings', $string->sid, 'active'), + '#disabled' => !$may_moderate && !$is_active, + '#attributes' => array('class' => 'selector'), + ); + + if ($string->tid) { + if ($may_moderate && $string->tid != 'new') { + $form['declined'] = array( + '#type' => 'checkbox', + '#title' => t('Declined'), + '#default_value' => !($string->is_active || $string->is_suggestion), + ); + } + // if ($may_stabilize) { + // $form['stable'] = array( + // '#type' => 'checkbox', + // '#title' => t('Stable'), + // '#default_value' => FALSE, // $string->is_stable, + // ); + // } + if ($string->tid == 'new') { + $form['value'] = array_fill(0, count($source->value), array( + '#type' => 'textarea', + '#cols' => 60, + '#rows' => 3, + '#default_value' => t(''), + )); + } else { - if ($translated) { - $form[$string->sid]['translation_existing'] = array( - '#type' => 'item', - '#value' => theme('l10n_community_strings', array(l10n_community_format_text($string->translation, $string->sid))), + if ($permission & L10N_PERM_SUGGEST) { + $form['edit'] = array( + '#value' => t('Edit Copy'), + '#prefix' => '', ); } - $form[$string->sid]['translation']['value'] = array( - // Use textarea for long and multiline strings. - '#type' => ((strlen($string->value) > 45) || (count(explode("\n", $string->value)) > 1)) ? 'textarea' : 'textfield', - // Provide accurate title based on previous data and permission. - '#description' => $translated ? (!($perm & L10N_PERM_MODERATE_OWN) ? t('Add a new suggestion') : t('Add a new translation')) : (!($perm & L10N_PERM_MODERATE_OWN) ? t('Suggestion') : ''), - '#rows' => 4, - '#resizable' => FALSE, - '#cols' => NULL, - '#size' => NULL, - '#id' => 'l10n-community-translation-'. $string->sid, - ); - if (strlen($string->value) > 200) { - $form[$string->sid]['translation']['value']['#rows'] = floor(strlen($string->value) * .03); - $form[$string->sid]['translation']['value']['#resizable'] = TRUE; + if (isset($string->username)) { + $title = l10n_community_translate_byline($string); + + $form['author'] = array( + '#value' => $title, + ); } } + } - // Add AJAX saving buttons - $form[$string->sid]['translation']['save'] = array( - '#prefix' => "", - '#value' => theme('l10n_community_button', 'save', 'l10n-save'), - '#type' => 'markup', - ); - $form[$string->sid]['translation']['clear'] = array( - '#suffix' => "", - '#value' => theme('l10n_community_button', 'clear', 'l10n-clear'), - '#type' => 'markup', - ); + return $form; +} - if (!($perm & L10N_PERM_MODERATE_OWN)) { - // User with suggestion capability only, record this. - $form[$string->sid]['translation']['is_suggestion'] = array( - '#type' => 'value', - '#value' => TRUE - ); +function theme_l10n_community_translate_actions($element) { + $actions = ''; + foreach (array('declined', /*'stable', */'edit') as $type) { + if (isset($element[$type])) { + $actions .= '
  • '. drupal_render($element[$type]) .'
  • '; } - else { - // User with full privileges, offer option to submit suggestion. - $form[$string->sid]['translation']['is_suggestion'] = array( - '#title' => t('Suggestion for discussion'), - '#type' => 'checkbox', - ); + } + if (!empty($actions)) { + return '
      '. $actions .'
    '; + } + else { + return ''; + } +} + +function theme_l10n_community_translate_translation($element) { + if (!isset($element['#attributes']['class'])) { + $element['#attributes']['class'] = ''; + } + $element['#attributes']['class'] .= ' clearfix translation'; + + switch ($element['active']['#return_value']) { + case 'new': + $element['#attributes']['class'] .= ' new-translation'; + break; + case '0': + $element['#attributes']['class'] .= ' no-translation'; + // Fallthrough. + default: + if ($element['active']['#value'] !== '') { + $element['#attributes']['class'] .= ' is-active default'; + } + } + + $output = ''; + $output .= theme('l10n_community_translate_actions', $element); + $output .= drupal_render($element['active']); + + if (isset($element['author'])) { + $output .= '
    '. drupal_render($element['author']) .'
    '; + } + + if (isset($element['value'])) { + $output .= drupal_render($element['value']); + } + + return $output . ''; +} + +function theme_l10n_community_translate_radio($element) { + _form_set_class($element, array('form-radio')); + $output = ''; + + if (isset($element['#title'])) { + $output .= ''; + } + + return $output; +} + +function theme_l10n_community_translate_translation_list($element) { + $output = '
      '; + foreach (element_children($element) as $child) { + if (is_numeric($child) || $child == 'new') { + $output .= drupal_render($element[$child]); } } + $output .= '
    '; - // Add all strings for copy-pasting and some helpers. - drupal_add_js( - array( - 'l10n_lookup_help' => t('Show detailed information.'), - 'l10n_approve_error' => t('There was an error approving this suggestion. You might not have permission or the suggestion id was invalid.'), - 'l10n_approve_confirm' => t('!icon Suggestion approved.', array('!icon' => '✔')), + return $output; +} - 'l10n_decline_error' => t('There was an error declining this suggestion. You might not have permission or the suggestion id was invalid.'), - 'l10n_decline_confirm' => t('Suggestion declined.'), +function theme_l10n_community_translate_source($element) { + $output = theme('l10n_community_translate_actions', $element['source']); + $output .= ''; + $output .= ''; + return $output; +} - 'l10n_details_callback' => url('translate/details/'. $language->language .'/'), - 'l10n_suggestions_callback' => url('translate/suggestions/'. $language->language .'/'), - 'l10n_approve_callback' => url('translate/approve/'), - 'l10n_decline_callback' => url('translate/decline/'), - 'l10n_form_token_path' => variable_get('clean_url', '0') ? '?form_token=' : '&form_token=', - 'l10n_num_plurals' => $language->plurals - ), - 'setting' +function theme_l10n_community_translate_table($element) { + $header = array( + t('Source Text'), + t('Translations'), ); - // Let the user submit the form. - $form['submit'] = array( - '#type' => 'submit', - '#value' => !($perm & L10N_PERM_MODERATE_OWN) ? t('Save suggestions') : t('Save translations') - ); + $rows = array(); + foreach (element_children($element) as $key) { + $rows[] = array( + array('class' => 'source', 'data' => theme('l10n_community_translate_source', $element[$key])), + array('class' => 'translation', 'data' => theme('l10n_community_translate_translation_list', $element[$key])), + ); + } - $form['#theme'] = 'l10n_community_translate_form'; + return theme('table', $header, $rows, array('class' => 'l10n-table')); +} - return $form; + +function l10n_community_translate_submit($form, &$form_state) { + $langcode = $form_state['values']['langcode']; + + foreach ($form_state['values']['strings'] as $sid => $string) { + foreach ($string as $tid => $options) { + // Store new suggestion. + $empty_values = 0; + if (isset($options['value']) && is_array($options['value'])) { + foreach ($options['value'] as $key => $value) { + if ($value === t('')) { + $options['value'] = ''; + $empty_values++; + } + } + if ($tid === 'new' && count($options['value']) > $empty_values) { + $tid = l10n_community_add_suggestion($langcode, $sid, $options['value']); + if ($tid) { + l10n_community_counter('added'); + if ($string['active'] === 'new') { + $string['active'] = $tid; + } + } + } + } + + if (is_numeric($tid) && $tid > 0) { + if ($tid == $string['active']) { + if ($options['original']->is_suggestion) { + l10n_community_approve_string($langcode, $sid, $tid); + l10n_community_counter('approved'); + } + // if (/*!$options['original']->is_stable && */!empty($options['stable'])) { + // dsm('mark as stable'); + // l10n_community_counter('stabilized'); + // } + } + elseif (!empty($options['declined'])) { + // also remove stable flag! + l10n_community_counter($options['original']->is_suggestion ? 'suggestion_declined' : 'declined'); + l10n_community_decline_string($langcode, $sid, $tid); + } + } + } + } + + l10n_community_update_message(); +} + +/** + * Ensures that there is a mock translation for a given language/string. + * + * @param $langcode + * The language to be checked for a mock translation. + * @param $sid + * The string ID that needs to have a mock translation. + */ +function l10n_community_mock_translation($langcode, $sid) { + if (!db_result(db_query("SELECT COUNT(*) FROM {l10n_community_translation} WHERE sid = %d AND language = '%s' AND is_suggestion = 0 AND is_active = 1", $sid, $langcode))) { + // Insert mock tuple that acts as placeholder. + db_query("INSERT INTO {l10n_community_translation} (sid, translation, language, uid_entered, time_entered, is_suggestion, is_active) VALUES (%d, '', '%s', 0, 0, 0, 1)", $sid, $langcode); + } } /** - * Save translations entered in the web form. + * Adds a suggestion to a language/string. + * + * @param $langcode + * The language of the new translation. + * @param $sid + * The string ID for which a new translation should be added. + * @param $translation + * An array of strings which constitute the new translation. */ -function l10n_community_translate_form_submit($form, &$form_state) { +function l10n_community_add_suggestion($langcode, $sid, $translation) { global $user; - $inserted = $updated = $unchanged = $suggested = $duplicates = $ignored = 0; + // Load source string and adjust translation whitespace based on source. + $source_string = db_result(db_query('SELECT value FROM {l10n_community_string} WHERE sid = %d', $sid)); + $translation = l10n_community_pack_string($translation); + $translation = l10n_community_trim($translation, $source_string); - foreach ($form_state['values'] as $sid => $item) { - if (!is_array($item) || !isset($item['translation'])) { - // Skip, if we don't have translations in this form item, - // which means this is some other form value. - continue; - } + // Don't store empty translations. + if ($translation === '') { + return NULL; + } - $source_string = db_result(db_query('SELECT value FROM {l10n_community_string} WHERE sid = %d', $sid)); - $text = ''; - if (is_string($item['translation']['value']) && strlen(trim($item['translation']['value']))) { - // Single string representation: simple translation. - $text = l10n_community_trim($item['translation']['value'], $source_string); - } - if (is_array($item['translation']['value'])) { - // Array -> plural variants are provided. Join them with a NULL separator. - $text = join("\0", $item['translation']['value']); - if (trim($text) == '') { - // If the whole string only contains NULL bytes, empty the string, so - // we don't save an empty translation. Otherwise the NULL bytes need - // to be there, so we know plural variant indices. - $text = ''; - } - } + // Look for an existing active translation, if any. + $existing = db_fetch_object(db_query("SELECT tid FROM {l10n_community_translation} WHERE sid = %d AND language = '%s' AND translation = '%s'", $sid, $langcode, $translation)); + if (!empty($existing)) { + // The translation is already in the db. Make it an active suggestion again. + db_query("UPDATE {l10n_community_translation} SET is_suggestion = 1, is_active = 1 WHERE tid = %d", $existing->tid); + $tid = $existing->tid; + } + else { + // This is a new translation. + l10n_community_mock_translation($langcode, $sid); + // Insert the new suggestion. + db_query("INSERT INTO {l10n_community_translation} (sid, translation, language, uid_entered, time_entered, is_suggestion, is_active) VALUES (%d, '%s', '%s', %d, %d, 1, 1)", $sid, $translation, $langcode, $user->uid, time()); + $tid = db_last_insert_id('l10n_community_translation', 'tid'); + } - if (!empty($text)) { - // Check for duplicate translation or suggestion. - if (l10n_community_is_duplicate($text, $sid, $form_state['values']['langcode'])) { - $duplicates++; - continue; - } + // Mark the existing or mock translation has having suggestions. + l10n_community_update_suggestion_status($langcode, $sid); - // We have some string to save. - l10n_community_target_save( - $sid, $text, $form_state['values']['langcode'], $user->uid, - ($item['translation']['is_suggestion'] == TRUE), - $inserted, $updated, $unchanged, $suggested - ); - } - } + return $tid; +} - // Inform user about changes made to the database. - l10n_community_update_message($inserted, $updated, $unchanged, $suggested, $duplicates, $ignored); +/** + * Marks a translation as declined. + * + * @param $langcode + * The language of the declined translation. + * @param $sid + * The string ID the translation belongs to. + * @param $tid + * The translation ID of the translation. + */ +function l10n_community_decline_string($langcode, $sid, $tid) { + // Mark this translation as inactive. + db_query("UPDATE {l10n_community_translation} SET is_suggestion = 0, is_active = 0 WHERE tid = %d", $tid); + + // Make sure the mock translation is there in case we declined the active translation. + l10n_community_mock_translation($langcode, $sid); + l10n_community_update_suggestion_status($langcode, $sid); } -// = Theme functions =========================================================== +/** + * Updates the has_suggestion flag for the active translation. + * + * @param $langcode + * The language of the string. + * @param $sid + * The string ID that should be updated. + */ +function l10n_community_update_suggestion_status($langcode, $sid) { + // Let's see if we have any suggestions remaining in this language. + $count = db_result(db_query("SELECT COUNT(*) FROM {l10n_community_translation} + WHERE sid = %d AND is_suggestion = 1 AND is_active = 1 AND language = '%s'", $sid, $langcode)); + + // Update the status according to the number of suggestions. + db_query("UPDATE {l10n_community_translation} SET has_suggestion = %d + WHERE sid = %d AND is_suggestion = 0 AND is_active = 1 AND language = '%s'", $count ? 1 : 0, $sid, $langcode); +} /** - * Theme function for l10n_community_filter_form. + * Marks a translation as approve. + * + * @param $langcode + * The language of the approved translation. + * @param $sid + * The string ID the translation belongs to. + * @param $tid + * The translation ID of the translation. */ -function theme_l10n_community_filter_form($form) { - $row = array(); - $labels = array(); - // Only display these elements in distinct table cells - $elements = array('project', 'release', 'context', 'status', 'author', 'search', 'limit'); - foreach ($form as $id => &$element) { - if (in_array($id, $elements)) { - $labels[] = $element['#title']; - unset($element['#title']); - $row[] = drupal_render($element); - } - } - // Fill in the rest of the header above the buttons. - $labels[] = ''; - // Display the rest of the form in the last cell - $row[] = array('data' => drupal_render($form), 'class' => 'last'); - return theme('table', $labels, array($row), array('class' => 'l10n-server-filter')); +function l10n_community_approve_string($langcode, $sid, $tid) { + global $user; + + // Remove placeholder translation record (which was there if + // first came suggestions, before an actual translation). + db_query("DELETE FROM {l10n_community_translation} WHERE sid = %d AND translation = '' AND language = '%s'", $sid, $langcode); + + // Make the existing approved string a suggestion. + db_query("UPDATE {l10n_community_translation} SET is_suggestion = 1 WHERE sid = %d AND language = '%s' AND is_suggestion = 0 AND is_active = 1", $sid, $langcode); + + // Mark this exact suggestion as active, and set approval time. + db_query("UPDATE {l10n_community_translation} SET time_approved = %d, uid_approved = %d, is_suggestion = 0, is_active = 1 WHERE tid = %d;", time(), $user->uid, $tid); + l10n_community_update_suggestion_status($langcode, $sid); } /** - * Theme function for l10n_community_translate_form. + * Unpacks a string as retrieved from the database. + * + * @param $string + * The string with separation markers (NULL byte) + * @return + * An array of strings with one element for each plural form in case of + * a plural string, or one element in case of a regular string. */ -function theme_l10n_community_translate_form($form) { - $rows = array(); - $output = ''; +function l10n_community_unpack_string($string) { + return explode("\0", $string); +} - foreach ($form as $id => &$element) { - // if the form id is numeric, this form element is for editing a string - if (is_numeric($id)) { - $source_pane = drupal_render($element['source']); - $translation_pane = "
    "; - $translation_pane .= drupal_render($element['toolbox']); - $translation_pane .= "
    "; - $translation_pane .= !empty($element['translation_existing']) ? drupal_render($element['translation_existing']) : ''; - $translation_pane .= drupal_render($element['messagebox']); - $translation_pane .= drupal_render($element['translation']) .'
    '; - $translation_pane .= "
    "; - $translation_pane .= "
    "; - $row = array( - array( - 'data' => $source_pane, - 'class' => 'source', - 'id' => 'spane-'. $id, - ), - array( - 'data' => $translation_pane, - 'class' => 'translation', - 'id' => 'tpane-'. $id, - ), - ); - $rows[] = $row; - unset($form[$id]); - } - } - $output .= drupal_render($form['pager_top']); - $output .= theme('table', array(t('Source Text'), t('Translations')), $rows, array('class' => 'l10n-server-translate')); - $output .= drupal_render($form); - return $output; +/** + * Packs a string for storage in the database. + * + * @param $string + * An array of strings. + * @return + * A packed string with NULL bytes separating each string. + */ +function l10n_community_pack_string($strings) { + return implode("\0", $strings); } +// = Miscellaneous ============================================================= + /** * Theme context information for source strings. * @@ -615,10 +741,12 @@ function l10n_community_get_strings($langcode, $filters, $pager = NULL) { $join = $join_args = $where = $where_args = array(); $sql = $sql_count = ''; - $select = "SELECT DISTINCT s.sid, s.value, s.context, t.tid, t.language, t.translation, t.uid_entered, t.uid_approved, t.time_entered, t.time_approved, t.has_suggestion, t.is_suggestion, t.is_active FROM {l10n_community_string} s"; + $select = "SELECT DISTINCT s.sid, s.value, s.context, t.tid, t.language, t.translation, t.uid_entered, t.uid_approved, t.time_entered, t.time_approved, t.has_suggestion, t.is_suggestion, t.is_active, u.name as username, u2.name as username_approved FROM {l10n_community_string} s"; $select_count = "SELECT COUNT(DISTINCT(s.sid)) FROM {l10n_community_string} s"; $join[] = "LEFT JOIN {l10n_community_translation} t ON s.sid = t.sid AND t.language = '%s' AND t.is_active = 1 AND t.is_suggestion = 0"; $join_args[] = $langcode; + $join[] = "LEFT JOIN {users} u ON u.uid = t.uid_entered"; + $join[] = "LEFT JOIN {users} u2 ON u2.uid = t.uid_approved"; // Add submitted by condition if (!empty($filters['author'])) { @@ -659,7 +787,7 @@ function l10n_community_get_strings($langcode, $filters, $pager = NULL) { // Restriction based on string status by translation / suggestions. $status_sql = ''; - if ($filters['status'] & L10N_STATUS_UNTRANSLATED) { + if (isset($filters['status']) && $filters['status'] & L10N_STATUS_UNTRANSLATED) { // We are doing a LEFT JOIN especially to look into the case, when we have nothing // to match in the translation table, but we still have the string. (We get our // records in the result set in this case). The translation field is empty or @@ -670,7 +798,7 @@ function l10n_community_get_strings($langcode, $filters, $pager = NULL) { elseif ($filters['status'] & L10N_STATUS_TRANSLATED) { $where[] = "t.translation != ''"; } - if ($filters['status'] & L10N_STATUS_HAS_SUGGESTION) { + if (isset($filters['status']) && $filters['status'] & L10N_STATUS_HAS_SUGGESTION) { // Note that we are not searching in the suggestions themselfs, only // the source and active translation values. The user interface underlines // that we are looking for strings which have suggestions, not the @@ -755,6 +883,51 @@ function l10n_community_build_filter_values($params, $suggestions = FALSE) { return $filter; } +// = AJAX callbacks ============================================================ + +/** + * Return a HTML list of projects, releases and counts of where strings + * appear in the managed projects. + * + * We could have been provided much more information, but usability should + * also be kept in mind. It is possible to investigate hidden information + * sources though, like tooltips on the release titles presented. + * + * This callback is invoked from JavaScript and is used as an AHAH provider. + * + * @param $langcode + * Language code. + * @param $sid + * Source string id. + */ +function l10n_community_string_details($langcode = NULL, $sid = 0) { + // Prevent devel module information. + $GLOBALS['devel_shutdown'] = FALSE; + + // List of project releases, where this string is used. + $result = db_query('SELECT l.pid, p.title project_title, l.rid, r.title release_title, COUNT(l.lineno) as occurance_count FROM {l10n_community_line} l INNER JOIN {l10n_community_project} p ON l.pid = p.pid INNER JOIN {l10n_community_release} r ON l.rid = r.rid WHERE l.sid = %d AND p.status = 1 GROUP BY l.rid ORDER BY l.pid, l.rid', $sid); + + $list = array(); + $output = array(); + $previous_project = ''; + while ($instance = db_fetch_object($result)) { + if ($instance->project_title != $previous_project) { + if (!empty($list)) { + $output[] = join(', ', $list); + } + $list = array(''. $instance->project_title .': '. $instance->release_title .' ('. $instance->occurance_count .')'); + } + else { + $list[] = $instance->release_title .' ('. $instance->occurance_count .')'; + } + $previous_project = $instance->project_title; + } + $output[] = join(', ', $list); + print ''. t('Used in:') .''. theme('item_list', $output); + + exit; +} + /** * Replace complex data filters (objects or arrays) with string representations. * diff --git l10n_remote/l10n_remote.module l10n_remote/l10n_remote.module index 4c1c384..0e42aa2 100644 --- l10n_remote/l10n_remote.module +++ l10n_remote/l10n_remote.module @@ -141,7 +141,7 @@ function l10n_remote_xmlrpc_string_submit($langcode, $source, $translation, $uid //watchdog('l10n_community', 'Language not allowed for remote submission.', NULL, WATCHDOG_WARNING); return array('status' => FALSE, 'reason' => 'Language not accepted.'); } - + // Check if the user has permission to submit strings in this language. if (!(l10n_community_get_permission($langcode, $account) & L10N_PERM_SUGGEST)) { //watchdog('l10n_community', 'Not allowed to submit translations in this language remotely.', NULL, WATCHDOG_WARNING);