Add multilingual text editor

git-svn-id: http://xe-core.googlecode.com/svn/branches/1.5.0@9114 201d5d3c-b55e-5fd7-737f-ddc643e51545
This commit is contained in:
taggon 2011-09-08 06:24:49 +00:00
parent 5787f10485
commit 1dc7a85a4c
5 changed files with 340 additions and 86 deletions

View file

@ -3,3 +3,6 @@
<p>Powered by <strong><a href="http://xpressengine.org/">XE</a></strong>.</p>
</div>
</div>
<script type="text/javascript">
xe.current_lang = '{$lang_type}';
</script>

View file

@ -223,6 +223,8 @@ header,footer,section,article,aside,nav,hgroup,details,menu,figure,figcaption{di
.x .prgrs.prgrsLarge .pBar,
.x .prgrs.prgrsLarge .pAction,
.x .prgrs.prgrsLarge .pNum{height:34px;line-height:34px;font-size:14px}
/* Labels */
.x label.overlap{position:absolute;color:#aaa}
/* Modal Window */
.modal{position:absolute;top:0;left:0;width:100%;_height:100%;min-height:100%;z-index:100}
.modal .bg{position:absolute;background:#000;_background:none;width:100%;height:100%;opacity:.5;z-index:2;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=50);zoom:1}
@ -388,11 +390,13 @@ body.modalContainer{_height:100%;_width:100%} /* IE6 only */
.x .btnArea:after{content:"";display:block;clear:both}
/* Multilingual */
.x .langEdit{background:#fff;position:absolute;*left:0;*margin-top:28px;z-index:10;box-shadow:3px 3px 6px #999;-moz-box-shadow:3px 3px 6px #999;-webkit-box-shadow:3px 3px 6px #999;filter:progid:DXImageTransform.Microsoft.Shadow(color=#999999,direction=135, strength=5)}
.x .langEdit .langList{float:left}
.x .langEdit .langEditControls{float:right}
.x .langEdit .action{clear:both;border:1px solid #eee;width:268px;padding:0 10px}
.x .langEdit ul{border-top:1px solid #ccc;border-left:1px solid #eee;border-right:1px solid #eee;margin:0}
.x .langEdit li{padding:.5em 10px}
.x .langEdit input[type=text]{width:220px;padding-right:40px}
.x .langEdit label{left:15px !important}
.x .langEdit .action{border:1px solid #eee;width:268px;padding:0 10px}
.x .langEdit p,
.x .langEdit .btnArea{white-space:normal}
.x .langEdit li.en input,
@ -451,7 +455,8 @@ body.modalContainer{_height:100%;_width:100%} /* IE6 only */
.x .suggestion li button{border:0;background:#fff;text-align:left;width:288px;padding:2px 4px;display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.x .suggestion li button:hover,
.x .suggestion li button:active,
.x .suggestion li button:focus{background:#eee}
.x .suggestion li button:focus,
.x .suggestion li button.active{background:#eee}
/* Image Mark */
.x #imageMark{right:0}
/* Easy Installer */

View file

@ -7,7 +7,8 @@ jQuery(function($){
$('.form li').find('>input:text,>input:password,>textarea')
.filter('input[value!=""],textarea:not(:empty)').prev('label').css('visibility','hidden').end().end()
.prev('label')
.css({position:'absolute',top:'15px',left:'5px'})
.addClass('overlap')
.css({top:'15px',left:'5px'})
.next()
.focus(function(){
var $label = $(this).prev().stop().animate({opacity:0, left:'25px'},'fast',function(){ $label.css('visibility','hidden') });
@ -292,12 +293,26 @@ $('a.modalAnchor').xeModalWindow();
// Content Toggler
jQuery(function($){
var dont_close_this_time = false;
var ESC = 27;
$.fn.xeContentToggler = function(){
this
.not('.xe-content-toggler')
.addClass('xe-content-toggler')
.each(function(){
$($(this).attr('href')).hide().focusout(focusoutContent);
var $anchor = $(this); $layer = $($anchor.attr('href'));
$layer.hide()
.not('.xe-toggling-content')
.addClass('xe-toggling-content')
.mousedown(function(event){ dont_close_this_time = true })
.focusout(function(event){
setTimeout(function(){
if(!dont_close_this_time && !$layer.find(':focus').length && $layer.data('state') == 'showing') $anchor.trigger('close.tc');
dont_close_this_time = false;
}, 1);
});
})
.click(function(){
var $this = $(this), $layer;
@ -334,13 +349,15 @@ $.fn.xeContentToggler = function(){
// before event trigger
$this.trigger('before-open.tc');
dont_close_this_time = false;
// When mouse button is down or when ESC key is pressed close this layer
$(document)
.unbind('mousedown.tc keydown.tc')
.bind('mousedown.tc keydown.tc',
function(event){
if(event && (
(event.type == 'keydown' && event.which != ESC_KEY) ||
(event.type == 'keydown' && event.which != ESC) ||
(event.type == 'mousedown' && ($(event.target).is('.tgAnchor,.tgContent') || $layer.has(event.target)[0]))
)) return true;
@ -418,13 +435,6 @@ $.fn.xeContentToggler = function(){
$('a.tgAnchor').xeContentToggler();
function focusoutContent(event) {
var $this = $(this), $anchor = $this.data('anchor');
setTimeout(function(){
if(!$this.find(':focus').length && $this.data('state') == 'showing') $anchor.trigger('close.tc');
}, 1);
};
});
// Module finder
@ -668,26 +678,38 @@ function getOffset(elem, offsetParent) {
// Language selector
jQuery(function($){
var w_timer = null, r_timer = null, r_idx = 0, f_timer = null;
var KEY_UP = 38, KEY_DOWN = 40;
var w_timer = null, r_timer = null, r_idx = 0, f_timer = null, skip_textchange=false;
var ESC=27, UP=38, DOWN=40, ENTER=13;
$('.multiLangEdit')
.find('input.vLang,textarea.vLang')
.each(function(){
var $this = $(this), $container;
$this
.data('mle-container', $container=$this.closest('.multiLangEdit'))
.data('mle-langkey', $container.find('.vLang').eq(0))
.data('mle-suggestion', $container.find('.suggestion'));
})
.bind('textchange', function(){
var $this = $(this), val = $.trim($this.val()), $ul, $mle;
var $this = $(this), val = $.trim($this.val()), $ul, $container;
if(r_timer) {
clearTimeout(r_timer);
r_timer = null;
}
if(!val) return;
$container = $this.data('mle-container');
$ul = $this.data('mle-suggestion').find('>ul');
$mle = $this.closest('.multiLangEdit');
$ul = $mle.find('.suggestion > ul');
if(!val || skip_textchange) {
skip_textchange = false;
$ul.parent().hide();
return;
}
// remove lagnauge key
$mle.find('.vLang').eq(0).val('');
$this.data('mle-langkey').val('');
function request() {
$this.addClass('loading');
@ -713,11 +735,51 @@ $('.multiLangEdit')
$btn = $('<button type="button" class="_btnLang" />').data('langkey', results[i].name).text(results[i].value);
$('<li />').append($btn).appendTo($ul);
}
$ul.parent().slideDown(300);
$ul.parent().show();
};
r_timer = setTimeout(request, 100);
})
.keydown(function(event){
var $this, $suggest, $ul, $active, $after, key = event.which;
$this = $(this);
$suggest = $this.data('mle-suggestion');
$ul = $suggest.find('>ul');
if(!$suggest.is(':visible') || $.inArray(key, [UP,DOWN,ENTER,ESC]) < 0) return true;
if(key == ESC) {
$suggest.hide();
return false;
}
$active = $ul.find('button.active');
if(key == ENTER) {
$active.click();
return false;
}
if(!$active.length) {
$ul.find('li>button:first').addClass('active');
return false;
}
// Move the cursor
if(key == UP) {
$after = $active.parent().prev('li').find('>button');
if(!$after.length) $after = $ul.find('>li:last>button');
} else if(key == DOWN) {
$after = $active.parent().next('li').find('>button');
if(!$after.length) $after = $ul.find('>li:first>button');
}
$active.removeClass('active');
$after.addClass('active');
return false;
})
.focus(function(){
var $this = $(this), oldValue = $.trim($this.val());
@ -740,34 +802,94 @@ $('.multiLangEdit')
.end()
.find('a.tgAnchor.editUserLang')
.bind('before-open.tc', function(){
var $this, $layer, $vlangs;
var $this, $layer, $mle, $vlang, key, text, api, params;
$this = $(this);
$layer = $($this.attr('href'));
$vlangs = $this.closest('.multiLangEdit').find('input.vLang,textarea.vLang');
$this = $(this);
$layer = $($this.attr('href'));
$mle = $this.closest('.multiLangEdit');
$vlang = $mle.find('input.vLang,textarea.vLang');
key = $vlang.eq(0).val();
text = $vlang.eq(1).val();
// initialize the layer
initLayer($layer);
// reset
$layer
.trigger('multilang-reset')
.find('.langEditControls li.'+xe.current_lang+' > input').val(text).prev('label').css('visibility','hidden');
// hide suggestion layer
$mle.find('.suggestion').hide();
if(/^\$user_lang->(.+)$/.test(key)) {
api = 'module.getModuleAdminLangListByName';
params = {lang_name:RegExp.$1};
} else {
api = 'module.getModuleAdminLangListByValue';
params = {value:text};
}
function on_complete(data) {
var list = data.lang_list, i, c;
var list = data.lang_list, obj, i, c, name, $langlist;
if(data.error || !list || !list.length) return;
if(data.error || !list) return;
$layer.find('li > input').val('').prev('label').css('visibility','visible');
for(i=0,c=list.length; i < c; i++) {
$layer.find('li.'+list[i].lang_code).find('> input').val(list[i].value).prev('label').css('visibility','hidden');
list = extractList(list);
// set data
$layer.data('multilang-list', list);
// make language list
$langlist = $layer.find('.langList').empty();
$.each(list, function(key){
var $li = $('<li />').appendTo($langlist);
$('<button type="button" />')
.text(this[xe.current_lang])
.data('multilang-name', key)
.appendTo($li);
});
if(count(list) > 1) {
$langlist.show();
} else {
$langlist.hide();
}
$layer.find('.langList>li>button').click();
};
$.exec_json('module.getModuleAdminLangListByName', {lang_name:$vlangs.eq(0).val()}, on_complete);
show_waiting_message = false;
$.exec_json(api, params, on_complete);
show_waiting_message = true;
})
.end()
.delegate('.suggestion button._btnLang', 'click', function(){
var $this = $(this), key = $this.data('langkey'), text = $this.text();
.find('.suggestion')
.delegate('button._btnLang', {
click : function(){
var $this = $(this);
$this.closest('.suggestion')
.hide()
.prev('.vLang').val(text)
.prev('.vLang').val(key);
})
skip_textchange = true;
$this.closest('.suggestion').hide()
.closest('.multiLangEdit')
.find('input.vLang,textarea.vLang')
.eq(0).val($this.data('langkey')).end()
.eq(1).val($this.text()).end();
return false;
},
focus : function(){
$(this).mouseover();
},
mouseover : function(){
$(this).closest('ul').find('button.active').removeClass('active');
}
})
.end()
.focusout(function(){
var self = this;
@ -775,12 +897,148 @@ $('.multiLangEdit')
var $this = $(self);
if($this.find(':focus').is('.vLang,._btnLang')) return;
$this.find('>.suggestion').slideUp(300);
$this.find('>.suggestion').hide();
}
clearTimeout(f_timer);
f_timer = setTimeout(check, 10);
})
});
function initLayer($layer) {
var $submit, $input, value='', current_status = 0, mode, cmd_add, cmd_edit, status_texts=[];
var USE = 0, UPDATE_AND_USE = 1, MODE_SAVE = 0, MODE_UPDATE = 1;
if($layer.data('init-multilang-editor')) return;
$layer
.data('init-multilang-editor', true)
.bind('multilang-reset', function(){
mode = MODE_SAVE;
setTitleText();
$layer
.data('multilang-current-name', '')
.find('.langEditControls li > input').val('').prev('label').css('visibility','visible');
})
.find('h3 a')
.click(function(){
mode = !mode;
setTitleText();
return false;
})
.end()
.delegate('button[type="button"]', 'click', function(){
var $this = $(this), $controls, list, name, i, c;
list = $layer.data('multilang-list');
name = $this.data('multilang-name');
if(!list || !list[name]) return;
list = list[name];
$controls = $layer.find('.langEditControls');
// reset
$layer.trigger('multilann-reset');
for(var code in list) {
if(!list.hasOwnProperty(code)) continue;
$controls.find('li.'+code).find('> input').data('multilang-value',list[code]).val(list[code]).prev('label').css('visibility','hidden');
}
value = get_value();
current_status = 0;
$submit.val(status_texts[current_status]);
$layer.data('multilang-current-name', name);
mode = MODE_UPDATE;
setTitleText();
});
cmd_edit = $layer.find('h3 em').text();
cmd_add = $layer.find('h3 a').text();
$input = $layer.find('input:text,textarea')
.change(function(){
var status = (get_value() == value)?USE:UPDATE_AND_USE;
if(status != current_status) {
$submit.val(status_texts[current_status=status]);
}
});
function get_value() {
var text = [];
$input.each(function(){ text.push(this.value); });
return text.join('\n');
};
function setTitleText() {
$layer.find('h3')
.find('em').text(mode==MODE_SAVE?cmd_add:cmd_edit).end()
.find('a').text(mode==MODE_SAVE?cmd_edit:cmd_add).end()
};
// process the submit button
$submit = $layer.find('input[type=submit]')
.click(function(){
var name = $layer.data('multilang-current-name');
function use_lang() {
$layer.hide().closest('.multiLangEdit').find('.vLang')
.eq(0).val('$user_lang->'+name).end()
.eq(1).val($layer.find('.langEditControls li.'+xe.current_lang+' >input').val()).end();
};
function save_lang() {
var params = {};
if(name && !$layer.data('multilang-command-add')) params.lang_name = name;
$input.each(function(k,v){ var $this = $(this); params[$this.parent('li').attr('class')] = $this.val() });
$.exec_json('module.procModuleAdminInsertLang', params, on_complete);
};
function on_complete(data) {
if(!data || data.error || !data.name) return;
name = data.name;
use_lang();
};
(get_value() == value) ? use_lang() : save_lang();
return false;
});
status_texts = $submit.val().split('|');
$submit.val(status_texts[USE]);
};
function extractList(list) {
var i, c, obj={}, item;
for(i=0,c=list.length; i < c; i++) {
item = list[i];
if(!obj[item.name]) obj[item.name] = {};
obj[item.name][item.lang_code] = item.value;
}
return obj;
};
function count(obj) {
var size = 0;
for(var x in obj) {
if(obj.hasOwnProperty(x)) size++;
}
return size;
};
});
// filebox

View file

@ -8,6 +8,8 @@
<style type="text/css">
._imageMarkButton img {max-height:16px}
.filebox_item { border: 1px solid #ccc!important; padding: 2px; max-height: 16px; }
.x .multiLangEdit input.vLang {width:120px}
.x .multiLangEdit input.vLang.loading {padding-right:24px;width:100px;background:transparent url(../../admin/tpl/img/preLoader16.gif) no-repeat 96px center}
</style>
<div cond="$XE_VALIDATOR_MESSAGE" class="message {$XE_VALIDATOR_MESSAGE_TYPE}">
<p>{$XE_VALIDATOR_MESSAGE}</p>
@ -52,13 +54,8 @@
<td class="multiLangEdit"><div class="wrap" style="height:22px"><button type="button" class="dragBtn">Move to</button>
<input type="hidden" name="group_srls[]" value="{$group_info->group_srl}" />
<input type="hidden" name="group_titles[]" value="{htmlspecialchars($group_info->title)}" class="vLang"/>
<input type="text" value="{$group_info->title}" class="vLang" style="width:120px;display:inline" />
<!-- Suggestion -->
<div class="suggestion">
<ul>
</ul>
</div>
<!-- /Suggestion -->
<input type="text" value="{$group_info->title}" class="vLang" />
<div class="suggestion"><ul></ul></div>
<span class="desc"><a href="#langEdit" class="tgAnchor editUserLang">{$lang->cmd_set_multilingual}</a></span>
</div>
</td>
@ -73,13 +70,8 @@
<td class="multiLangEdit"><div class="wrap" style="height:22px"><button type="button" class="dragBtn">Move to</button>
<input type="hidden" name="group_srls[]" value="new" disabled="disabled"/>
<input type="hidden" name="group_titles[]" value="" disabled="disabled" class="vLang" />
<input type="text" value="" class="vLang" style="width:120px;display:inline" />
<!-- Suggestion -->
<div class="suggestion">
<ul>
</ul>
</div>
<!-- /Suggestion -->
<input type="text" value="" class="vLang" />
<div class="suggestion"><ul></ul></div>
<span class="desc"><a href="#langEdit" class="tgAnchor editUserLang">{$lang->cmd_set_multilingual}</a></span>
</div>
</td>
@ -98,30 +90,25 @@
</div>
</form>
<!-- Multilingual -->
<div id="langEdit" class="langEdit tgContent">
<ul>
<li class="ko"><label for="ko_var1">Korean</label> <input type="text" value="안녕 세상아!" id="ko_var1" /></li>
<li class="en"><label for="en_var1">English</label> <input type="text" value="Hello World!" id="en_var1" /></li>
<li class="ja"><label for="ja_var1">Japanese</label> <input type="text" value="" id="ja_var1" /></li>
<li class="zh"><label for="zhcn_var1">Chinese(Simplified)</label> <input type="text" value="" id="zhcn_var1" /></li>
<li class="zh"><label for="zhtw_var1">Chinese(Traditional)</label> <input type="text" value="" id="zhtw_var1" /></li>
<li class="fr"><label for="fr_var1">French</label> <input type="text" value="" id="fr_var1" /></li>
<li class="de"><label for="de_var1">Deutsch</label> <input type="text" value="" id="de_var1" /></li>
<li class="ru"><label for="ru_var1">Russian</label> <input type="text" value="" id="ru_var1" /></li>
<li class="es"><label for="es_var1">Spanish</label> <input type="text" value="" id="es_var1" /></li>
<li class="tr"><label for="tr_var1">Turkish</label> <input type="text" value="" id="tr_var1" /></li>
<li class="vi"><label for="vi_var1">Vietnamese</label> <input type="text" value="" id="vi_var1" /></li>
<li class="mn"><label for="mn_var1">Mongolian</label> <input type="text" value="" id="mn_var1" /></li>
</ul>
<div id="langEdit" class="form langEdit tgContent">
<ul class="langList"></ul>
<div class="langEditControls">
<h3>다국어 <em>수정</em> | <a href="#langEdit">추가</a></h3>
<ul>
{@
/* move current language to the top */
$a = array($lang_type=>$lang_supported[$lang_type]);
unset($lang_supported[$lang_type]);
$lang_supported = array_merge($a, $lang_supported);
}
<li loop="$lang_supported=>$code,$name" class="{$code}"><label for="{$code}_var1">{$name}</label> <input type="text" value="" id="{$code}_var1" /></li>
</ul>
</div>
<div class="action">
<!-- 여기부터 기존 랭귀지 변수를 수정한 경우에만 보인다 -->
<p>다국어 텍스트가 변경되었습니다. <em>업데이트</em> 버튼을 클릭하면 같은 텍스트를 사용하는 다른 페이지에도 반영됩니다. <em>새로 저장</em> 버튼을 클릭하면 이 페이지에만 적용됩니다.</p>
<div class="btnArea">
<span class="btn small"><input type="submit" value="업데이트" /></span>
<span class="btn small"><input type="submit" value="새로저장" /></span>
<span class="btn small"><input type="submit" value="사용|저장 후 사용" /></span>
</div>
<!-- 여기까지 기존 랭귀지 변수를 수정한 경우에만 보인다 -->
<p><a href="#">다국어 텍스트 관리</a></p>
<p><a href="{getUrl('act','dispModuleAdminLangcode')}">다국어 텍스트 관리</a></p>
</div>
</div>

View file

@ -186,18 +186,18 @@
}
/**
* @brief Return lang list
* @brief Returns lang list by lang name
**/
function getModuleAdminLangListByName()
{
$args = Context::getRequestVars();
if(!$args->site_srl) $args->site_srl = 0;
$columnList = array('lang_code', 'value');
$columnList = array('lang_code', 'name', 'value');
$langList = array();
$args->langName = preg_replace('/\$user_lang->/', '', $args->lang_name);
$args->langName = preg_replace('/^\$user_lang->/', '', $args->lang_name);
$output = executeQueryArray('module.getLangListByName', $args, $columnList);
if($output->toBool()) $langList = $output->data;
@ -213,26 +213,27 @@
$args = Context::getRequestVars();
if(!$args->site_srl) $args->site_srl = 0;
$langList = array();
$args->value = $args->lang_name;
// search value
$output = executeQueryArray('module.getLangNameByValue', $args);
if ($output->toBool()){
if ($output->toBool() && is_array($output->data)){
unset($args->value);
$args->langName = $output->data[0]->name;
$columnList = array('lang_code', 'value');
$output = executeQueryArray('module.getLangListByName', $args, $columnList);
foreach($output->data as $data) {
$args->langName = $data->name;
$columnList = array('lang_code', 'name', 'value');
$outputByName = executeQueryArray('module.getLangListByName', $args, $columnList);
if($output->toBool()) $langList = $output->data;
if($outputByName->toBool()) {
$langList = array_merge($langList, $outputByName->data);
}
}
}
$this->add('lang_list', $langList);
$this->add('lang_name', $args->langName);
}
/**
* @brief Return current lang list
**/