Files
OMS/app/desktop/statics/js/coms/editor.js
2026-01-04 19:08:31 +08:00

434 lines
15 KiB
JavaScript

/**
* Copyright 2012-2026 ShopeX (https://www.shopex.cn)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
function $cleanNull(){
var obj=arguments[0];
for(p in obj){
if(!obj[p])
delete(obj[p]);
}
return obj;
};
var mceInstance = new Class({
Implements:[Events,Options],
options:{
acitve:false,
maskOpacity:0.5,
autoHeight:false,
cleanup:true,
includeBase:true
},
initialize:function(seri,options){
this.seri=seri;
var name = 'mce_body_'+seri;
this.el = $(name);
this.setOptions(options);
this.input = $E('textarea',this.el).setProperty('ishtml','true');
this.frmContainer=$(name+"_frm_container");
var that=this;
var includeBase=this.options.includeBase;
var state=0;
var frm=this.frm=new Element('iframe',{'src':DESKTOPRESFULLURL+'/about.html','id':name+"_frm",'name':name+"_frm",'frameborder':0,'border':0}).addEvent('load',function(){
if(state)return;
state=1;
var editor=that;
var frmWin=frm.contentWindow;
var baseStr='<base href='+SHOPBASE+'/>';
var documentTemplate = ''
+'<html>'
+'<head>'+(includeBase?baseStr:"")
+'<script>window.onerror=function(){return false;}</script>'
+'<link rel="stylesheet" type="text/css" href="'+DESKTOPRESURL+'/wysiwyg_editor.css"/>'
+'</head><body spellcheck="false" id="'+seri+'" style="break-word:break-all;word-wrap:break-word;">'
+(editor.cleanup(editor.input.getProperty('value'))||'&nbsp;')
+'</body></html>';
frmWin.document.open();
frmWin.document.write(documentTemplate);
frmWin.document.close();
frmWin.document.designMode = 'on';
if (frmWin.document.addEventListener) frmWin.document.addEventListener('mousedown', editor.active.bind(editor), false);
else frmWin.document.attachEvent('on' + 'mousedown', editor.active.bind(editor));
editor.win=frmWin;
editor.doc=frmWin.document;
editor.input.setAttribute('filled','true');
this.removeEvent('load', arguments.callee);
});
frm.inject(this.frmContainer);
this.input.getValue=function(){
if(!this.input.getAttribute('filled')){
return 'textarea-unfilled';
}
if('textarea'==this.editType){
return this.input.value;
}
var v=this.getValue();
this.input.value=v;
return v;
}.bind(this);
if(this.options.autoHeight) this.autoHeight.call(this);
if($(name))this.el = $(name).setStyle('visibility','visible');
},
autoHeight:function(){
try{
this.frm.setStyle('height',this.doc.body.offsetHeight+50);
}catch(e){}
},
setValue:function(){
this.doc.body.innerHTML=this.input.value;
},
getValue:function(){
return this.cleanup(this.doc.body.innerHTML);
},
regexpReplace:function(in_str, reg_exp, replace_str, opts) {
if (in_str == null)
return in_str;
if (typeof(opts) == "undefined")
opts = 'g';
var re = new RegExp(reg_exp, opts);
return in_str.replace(re, replace_str);
},
cleanup: function(html) {
var br = '<br/>';
var xhtml = [
[/(<(?:img|input)[^\/>]*)>/g, '$1 />'] // Greyed out - make img tags xhtml compatable #if (this.options.xhtml)
];
var semantic = [
[/<li>\s*<div>(.+?)<\/div><\/li>/g, '<li>$1</li>'], // remove divs from <li> #if (Browser.Engine.trident)
[/<span style="font-weight: bold;">(.*)<\/span>/gi, '<strong>$1</strong>'], //
[/<span style="font-style: italic;">(.*)<\/span>/gi, '<em>$1</em>'], //
[/<b\b[^>]*>(.*?)<\/b[^>]*>/gi, '<strong>$1</strong>'], //
[/<i\b[^>]*>(.*?)<\/i[^>]*>/gi, '<em>$1</em>'], //
[/<u\b[^>]*>(.*?)<\/u[^>]*>/gi, '<span style="text-decoration: underline;">$1</span>'], //
[/<p>[\s\n]*(<(?:ul|ol)>.*?<\/(?:ul|ol)>)(.*?)<\/p>/ig, '$1<p>$2</p>'], // <p> tags around a list will get moved to after the list. not working properly in safari? #if (['gecko', 'presto', 'webkit'].contains(Browser.Engine.name))
[/<\/(ol|ul)>\s*(?!<(?:p|ol|ul|img).*?>)((?:<[^>]*>)?\w.*)$/g, '</$1><p>$2</p>'], // ''
[/<br[^>]*><\/p>/g, '</p>'], // Remove <br>'s that end a paragraph here.
[/<p>\s*(<img[^>]+>)\s*<\/p>/ig, '$1\n'], // If a <p> only contains <img>, remove the <p> tags
[/<p([^>]*)>(.*?)<\/p>(?!\n)/g, '<p$1>$2</p>\n'], // Break after paragraphs
[/<\/(ul|ol|p)>(?!\n)/g, '</$1>\n'], // Break after </p></ol></ul> tags
[/><li>/g, '>\n\t<li>'], // Break and indent <li>
[/([^\n])<\/(ol|ul)>/g, '$1\n</$2>'], // Break before </ol></ul> tags
[/([^\n])<img/ig, '$1\n<img'], // Move images to their own line
[/^\s*$/g, ''] // Delete empty lines in the source code (not working in opera)
];
var nonSemantic = [
[/\s*<br ?\/?>\s*<\/p>/gi, '</p>'] // if (!this.options.semantics) - Remove padded paragraphs
];
var appleCleanup = [
[/<br class\="webkit-block-placeholder">/gi, "<br />"], // Webkit cleanup - add an if(webkit) check
[/<span class="Apple-style-span">(.*)<\/span>/gi, '$1'], // Webkit cleanup - should be corrected not to get messed over on nested spans - SG!!!
[/ class="Apple-style-span"/gi, ''], // Webkit cleanup
[/<span style="">/gi, ''], // Webkit cleanup
[/^([\w\s]+.*?)<div>/i, '<p>$1</p><div>'], // remove stupid apple divs #if (Browser.Engine.webkit)
[/<div>(.+?)<\/div>/ig, '<p>$1</p>'] // remove stupid apple divs #if (Browser.Engine.webkit)
];
var cleanup = [
[/<br\s*\/?>/gi, br], // Fix BRs, make it easier for next BR steps.
// [/><br\/?>/g, '>'], // Remove (arguably) useless BRs
[/^<br\/?>/g, ''], // Remove leading BRs - perhaps combine with removing useless brs.
[/<br\/?>$/g, ''], // Remove trailing BRs
[/<br\/?>\s*<\/(h1|h2|h3|h4|h5|h6|li|p)/gi, '</$1'], // Remove BRs from end of blocks
[/<p>\s*<br\/?>\s*<\/p>/gi, '<p>\u00a0</p>'], // Remove padded paragraphs - replace with non breaking space
[/<p>(&nbsp;|\s)*<\/p>/gi, '<p>\u00a0</p>'], // ''
//[/<p>\W*<\/p>/g, ''], // Remove ps with other stuff, may mess up some formatting.
[/<\/p>\s*<\/p>/g, '</p>'], // Remove empty <p> tags
[/<[^> ]*/g, function(match){return match.toLowerCase();}], // Replace uppercase element names with lowercase
[/<[^>]*>/g, function(match){ // Replace uppercase attribute names with lowercase
match = match.replace(/ [^=]+=/g, function(match2){return match2.toLowerCase();});
return match;
}],
[/<[^>]*>/g, function(match){ // Put quotes around unquoted attributes
match = match.replace(/( [^=]+=)([^"][^ >]*)/g, "$1\"$2\"");
return match;
}]
];
var depracated = [
// The same except for BRs have had optional space removed
[/<p>\s*<br ?\/?>\s*<\/p>/gi, '<p>\u00a0</p>'], // modified as <br> is handled previously
[/<br>/gi, "<br />"], // Replace improper BRs if (this.options.xhtml) Handled at very beginning
[/<br ?\/?>$/gi, ''], // Remove leading and trailing BRs
[/^<br ?\/?>/gi, ''], // Remove trailing BRs
[/><br ?\/?>/gi,'>'], // Remove useless BRs
[/<br ?\/?>\s*<\/(h1|h2|h3|h4|h5|h6|li|p)/gi, '</$1'], // Remove BRs right before the end of blocks
//Handled with DOM:
[/<p>(?:\s*)<p>/g, '<p>'] // Remove empty <p> tags
];
cleanup.extend(xhtml);
cleanup.extend(semantic);
if(Browser.Engine.webkit)cleanup.extend(appleCleanup);
cleanup.each(function(reg){ html = html.replace(reg[0], reg[1]); });
html = html.trim();
return html;
},
active:function(){
if(!this.actived){
this.actived = true;
var doc = this.doc;
var _fn = function(e){
this.fireEvent('docClick',new Event(e));
}.bind(this);
if (doc.addEventListener) doc.addEventListener('click', _fn, false);
else doc.attachEvent('on' + 'click', _fn);
}
this.fireEvent('active',this);
},
sleep:function(){}
});
var mceHandler = new Class({
Implements:[Events,Options],
initialize:function(name,instance,options){
try{
this.el = $(name);
$ES('img',this.el).each(function(e){new DropMenu(e);});
this.setOptions(options);
if(instance){
if(instance.length){
instance.each(this.addInstance.bind(this));
}else{
this.addInstance.call(this,instance);
}
}
}catch(e){alert(e.message)}
if('style_init' in this){this.style_init();}
},
addInstance:function(inc){
inc.addEvent('active',this.active.bind(this));
inc.addEvent('docClick',this.docClick.bind(this));
},
active:function(e){
this.inc =e;
if(this.inc){
this.inc.sleep.call(this.inc);
}
},
docClick:function(e){
var curEl =this.currentEl =e.target||e.srcElement;
this.fireEvent('click',e);
},
getRange:function(){
if(!this.inc)return false;
if(this.range)return this.range;
return window.ie?this.inc.doc.selection.createRange():this.inc.win.getSelection();
},
getSelection:function(){
return window.ie?this.inc.doc.selection:this.inc.win.getSelection();
},
getRangeText:function(){
return window.ie?this.inc.doc.selection.createRange().text:this.inc.win.getSelection().toString();
},
exec:function(cmd,arg){
if (!this.busy) {
this.busy=true;
if(!cmd || !this.inc){return}
if(this.dlg){
if(window.ie){
if(this.range)
this.range.select();
}
this.dlg.hide();
this.dlg=null;
}
switch(cmd){
case "formatblock":
this.inc.doc.execCommand("FormatBlock", false,'<'+arg+'>');
break;
case "wrap":
this.exec('insertHTML',arg[0]+this.getRangetext()+arg[1]);
break;
case "insertHTML":
if(window.ie){
this.getSelection().clear();
}
if(this.replaceEl&&this.replaceEl.tagName&&!this.replaceEl.tagName.match(/body/g)){
try{
var _tempDiv=this.inc.doc.createElement('div');
_tempDiv.innerHTML=arg;
_tempDiv= _tempDiv.firstChild;
this.replaceEl.parentNode.replaceChild(_tempDiv, this.replaceEl);
}catch(e){
MessageBox.error(e);
}finally{
this.replaceEl = null;
}
}else{
if(window.ie){
this.inc.win.focus();
if(!this.getRange().pasteHTML)return;
this.getRange().pasteHTML(arg);
}else{
this.inc.doc.execCommand('inserthtml', false,arg);
}
}
break;
default:
try{
this.inc.doc.execCommand(cmd, false,arg);
}catch(e){MessageBox.error(e)}
}
this.busy = false;
}
},
mklink:function(){
if(!this.inc)return;
this.replaceEl = null;
var curEl=this.currentEl;
var data;
if('body'==curEl.tagName.toLowerCase()&&!this.getRangeText()){
return;
}
if(curEl&&curEl.tagName&&curEl.tagName.toLowerCase()=='img'){
return MessageBox.error(LANG_Editor['error']);
}
if(curEl&&curEl.tagName&&curEl.tagName.toLowerCase()=='a'){
data={
text:this.currentEl.innerHTML,
href:this.currentEl.href,
alt:this.currentEl.alt,
title:this.currentEl.title,
target:this.currentEl.target
};
this.replaceEl =curEl;
}else{
data={'text':this.getRangeText()};
}
this.dialog('link',{height:null,width:450,ajaxoptions:{method:'post','data':data}});
},
editHTML:function(){
var _this=this;
if(!this.inc)return;
var mhh=$('mce_handle_htmledit_'+this.inc.seri);
var mh=$('mce_handle_'+this.inc.seri);
this.inc.input.getValue();
mhh.show();
mh.hide();
var frmcis=this.inc.frm.getSize();
this.inc.input.show();
this.inc.frmContainer.hide();
mhh.getElement('.returnwyswyg').addEvent('click',function(){
mh.show();
mhh.hide();
_this.inc.doc.body.innerHTML=_this.inc.input.value.clean().trim();
_this.inc.input.hide();
_this.inc.frmContainer.show();
_this.inc.editType='wysiwyg';
this.removeEvent('click',arguments.callee);
})
this.inc.editType='textarea';
/*var data={'htmls':this.inc.getValue(),'seri':this.inc.seri};
this.dialog('editHTML',{title:'HTML编辑模式',height:null,width:450,ajaxoptions:{method:'post','data':data}});*/
},
dialog:function(action,options){
if(!this.inc)return;
this.inc.win.focus();
this.range=null;
this.range = this.getRange();
var url=('image'==action)?'index.php?app=desktop&act=alertpages&goto='+encodeURIComponent("index.php?app=image&ctl=admin_manage&act=image_broswer&type=big"):'index.php?ctl=editor&act='+action;
options=$cleanNull(options);
var _this=this;
if ('image'==action)
return Ex_Loader('modedialog',function(){
new imgDialog(url,{onCallback:function(image_id,image_src){
_this.insertImg(image_id,image_src);
}});
});
this.dlg =new Dialog(url,$merge(options,{modal:true}));
window.curEditor = this;
},
insertImg:function(image_id,image_src,center){
if(SHOPBASE&&image_src.contains(SHOPBASE)){
//image_src=image_src.replace(SHOPBASE,'');
}
var img=new Element('img',{src:image_src}),imgID='img'+$uid(img);
img.set('id',imgID).set('turl',image_src);
var d=new Element('div').adopt(img);
var html=center?'<center>'+d.get('html')+'</center>':d.get('html');
this.exec.bind(this)('insertHTML',html);
var win_img=this.inc.doc.getElementById(imgID);
if(!win_img)return;
win_img.src=img.get('turl');
win_img.removeAttribute('turl','id');
},
queryValue:function(s,state){
if(s=='align'){
s = 'justifyRight';
}
try{
if(state){
return this.inc.doc.queryCommandState(s);
}else{
return this.inc.doc.queryCommandValue(s);
}
}catch(e){}
},
queryValues:function(){
var ret={};
var stat = ['Bold','Italic','Underline','strikeThrough','subscript','superscript','insertOrderedList','insertUnorderedList'];
for(var i=0;i<arguments.length;i++){
ret[arguments[i]] = this.queryValue(arguments[i],stat.contains(arguments[i]));
}
return ret;
}
});