1 /**
  2  * monitor user's paste key ,clear user input,modified from ckeditor
  3  * @author yiminghe@gmail.com
  4  */
  5 KISSY.add("editor/core/clipboard", function (S, Editor, KERange, KES) {
  6     var $ = S.all,
  7         UA = S.UA,
  8         KER = Editor.RANGE,
  9         Event = S.Event;
 10 
 11     function Paste(editor) {
 12         var self = this;
 13         self.editor = editor;
 14         self._init();
 15     }
 16 
 17     S.augment(Paste, {
 18         _init:function () {
 19             var self = this,
 20                 editor = self.editor,
 21                 editorBody = editor.get("document")[0].body;
 22             // Event.on(editor.document.body, UA['ie'] ? "beforepaste" : "keydown", self._paste, self);
 23             // beforepaste not fire on webkit and firefox
 24             // paste fire too later in ie ,cause error
 25             // 奇怪哦
 26             // http://help.dottoro.com/ljxqbxkf.php
 27             // refer : http://stackoverflow.com/questions/2176861/javascript-get-clipboard-data-on-paste-event-cross-browser
 28             Event.on(editorBody,
 29                 (UA.ie ? 'beforepaste' : 'paste'),
 30                 self._paste, self);
 31 
 32             // Dismiss the (wrong) 'beforepaste' event fired on context menu open. (#7953)
 33             // Note: IE Bug: queryCommandEnabled('paste') fires also 'beforepaste(copy/cut)'
 34             Event.on(editorBody, 'contextmenu', function () {
 35                 depressBeforeEvent = 1;
 36                 setTimeout(function () {
 37                     depressBeforeEvent = 0;
 38                 }, 10);
 39             });
 40             editor.addCommand("copy", new cutCopyCmd("copy"));
 41             editor.addCommand("cut", new cutCopyCmd("cut"));
 42             editor.addCommand("paste", new cutCopyCmd("paste"));
 43 
 44         },
 45         _paste:function (ev) {
 46 
 47             if (depressBeforeEvent) {
 48                 return;
 49             }
 50 
 51             // ie beforepaste 会触发两次,第一次 pastebin 为锚点内容,奇怪
 52             // chrome keydown 也会两次
 53             S.log(ev.type + " : " + " paste event happen");
 54 
 55             var self = this,
 56                 editor = self.editor,
 57                 doc = editor.get("document")[0];
 58 
 59             // Avoid recursions on 'paste' event or consequent paste too fast. (#5730)
 60             if (doc.getElementById('ke_pastebin')) {
 61                 // ie beforepaste 会重复触发
 62                 // chrome keydown 也会重复触发
 63                 // 第一次 bms 是对的,但是 pasterbin 内容是错的
 64                 // 第二次 bms 是错的,但是内容是对的
 65                 // 这样返回刚好,用同一个 pastebin 得到最后的正确内容
 66                 // bms 第一次时创建成功
 67                 S.log(ev.type + " : trigger more than once ...");
 68                 return;
 69             }
 70 
 71             var sel = editor.getSelection(),
 72                 range = new KERange(doc);
 73 
 74             // Create container to paste into
 75             var pastebin = $(UA['webkit'] ? '<body></body>' : '<div></div>', null, doc);
 76             pastebin.attr('id', 'ke_pastebin');
 77             // Safari requires a filler node inside the div to have the content pasted into it. (#4882)
 78             UA['webkit'] && pastebin[0].appendChild(doc.createTextNode('\xa0'));
 79             doc.body.appendChild(pastebin[0]);
 80 
 81             pastebin.css({
 82                 position:'absolute',
 83                 // Position the bin exactly at the position of the selected element
 84                 // to avoid any subsequent document scroll.
 85                 top:sel.getStartElement().offset().top + 'px',
 86                 width:'1px',
 87                 height:'1px',
 88                 overflow:'hidden'
 89             });
 90 
 91             // It's definitely a better user experience if we make the paste-bin pretty unnoticed
 92             // by pulling it off the screen.
 93             pastebin.css('left', '-1000px');
 94 
 95             var bms = sel.createBookmarks();
 96 
 97             // Turn off design mode temporarily before give focus to the paste bin.
 98             range.setStartAt(pastebin, KER.POSITION_AFTER_START);
 99             range.setEndAt(pastebin, KER.POSITION_BEFORE_END);
100             range.select(true);
101             // Wait a while and grab the pasted contents
102             setTimeout(function () {
103 
104                 // Grab the HTML contents.
105                 // We need to look for a apple style wrapper on webkit it also adds
106                 // a div wrapper if you copy/paste the body of the editor.
107                 // Remove hidden div and restore selection.
108                 var bogusSpan;
109 
110                 pastebin = ( UA['webkit']
111                     && ( bogusSpan = pastebin.first() )
112                     && (bogusSpan.hasClass('Apple-style-span') ) ?
113                     bogusSpan : pastebin );
114 
115                 sel.selectBookmarks(bms);
116 
117                 pastebin.remove();
118 
119                 var html = pastebin.html();
120 
121                 //S.log("paster " + html);
122 
123                 //莫名其妙会有这个东西!,不知道
124                 //去掉
125                 if (!( html = S.trim(html.replace(/<span[^>]+_ke_bookmark[^<]*?<\/span>( )*/ig, '')) )) {
126                     // ie 第2次触发 beforepaste 会报错!
127                     // 第一次 bms 是对的,但是 pasterbin 内容是错的
128                     // 第二次 bms 是错的,但是内容是对的
129                     return;
130                 }
131 
132                 S.log("paste " + html);
133 
134                 var re = editor.fire("paste", {
135                     html:html,
136                     holder:pastebin
137                 });
138 
139                 if (re !== undefined) {
140                     html = re;
141                 }
142 
143 
144                 // MS-WORD format sniffing.
145                 if (/(class="?Mso|style="[^"]*\bmso\-|w:WordDocument)/.test(html)) {
146                     // 动态载入 word 过滤规则
147                     S.use("editor/core/dynamic/wordFilter", function (S, wordFilter) {
148                         editor.insertHtml(wordFilter.toDataFormat(html, editor));
149                     });
150                 } else {
151                     editor.insertHtml(html);
152                 }
153 
154             }, 0);
155         }
156     });
157 
158     // Tries to execute any of the paste, cut or copy commands in IE. Returns a
159     // boolean indicating that the operation succeeded.
160     var execIECommand = function (editor, command) {
161         var doc = editor.get("document")[0],
162             body = $(doc.body);
163 
164         var enabled = false;
165         var onExec = function () {
166             enabled = true;
167         };
168 
169         // The following seems to be the only reliable way to detect that
170         // clipboard commands are enabled in IE. It will fire the
171         // onpaste/oncut/oncopy events only if the security settings allowed
172         // the command to execute.
173         body.on(command, onExec);
174 
175         // IE6/7: document.execCommand has problem to paste into positioned element.
176         ( UA['ie'] > 7 ? doc : doc.selection.createRange() ) [ 'execCommand' ](command);
177 
178         body.detach(command, onExec);
179 
180         return enabled;
181     };
182 
183     // Attempts to execute the Cut and Copy operations.
184     var tryToCutCopy = UA['ie'] ?
185         function (editor, type) {
186             return execIECommand(editor, type);
187         }
188         : // !IE.
189         function (editor, type) {
190             try {
191                 // Other browsers throw an error if the command is disabled.
192                 return editor.get("document")[0].execCommand(type);
193             }
194             catch (e) {
195                 return false;
196             }
197         };
198 
199     var error_types = {
200         cut:"您的浏览器安全设置不允许编辑器自动执行剪切操作,请使用键盘快捷键(Ctrl/Cmd+X)来完成",
201         copy:"您的浏览器安全设置不允许编辑器自动执行复制操作,请使用键盘快捷键(Ctrl/Cmd+C)来完成",
202         paste:"您的浏览器安全设置不允许编辑器自动执行粘贴操作,请使用键盘快捷键(Ctrl/Cmd+V)来完成"
203     };
204 
205     // A class that represents one of the cut or copy commands.
206     var cutCopyCmd = function (type) {
207         this.type = type;
208     };
209 
210     cutCopyCmd.prototype = {
211         exec:function (editor) {
212             this.type == 'cut' && fixCut(editor);
213 
214             var success = tryToCutCopy(editor, this.type);
215 
216             if (!success)
217                 alert(error_types[this.type]);		// Show cutError or copyError.
218 
219             return success;
220         }
221     };
222 
223     // Cutting off control type element in IE standards breaks the selection entirely. (#4881)
224     function fixCut(editor) {
225         if (!UA['ie'] || editor.get("document")[0].compatMode == 'BackCompat')
226             return;
227 
228         var sel = editor.getSelection();
229         var control;
230         if (( sel.getType() == KES.SELECTION_ELEMENT ) && ( control = sel.getSelectedElement() )) {
231             var range = sel.getRanges()[ 0 ];
232             var dummy = $(editor.get("document")[0].createTextNode(''));
233             dummy.insertBefore(control);
234             range.setStartBefore(dummy);
235             range.setEndAfter(control);
236             sel.selectRanges([ range ]);
237 
238             // Clear up the fix if the paste wasn't succeeded.
239             setTimeout(function () {
240                 // Element still online?
241                 if (control.parent()) {
242                     dummy.remove();
243                     sel.selectElement(control);
244                 }
245             }, 0);
246         }
247     }
248 
249     var lang = {
250         copy:"复制",
251         paste:"粘贴",
252         cut:"剪切"
253     };
254 
255     var depressBeforeEvent;
256 
257     return {
258         init:function (editor) {
259             editor.docReady(function () {
260                 new Paste(editor);
261             });
262 
263             var pastes = {copy:1, cut:1, paste:1};
264 
265             /**
266              * 给所有右键都加入复制粘贴
267              */
268             editor.on("contextmenu", function (ev) {
269                 var contextmenu = ev.contextmenu;
270 
271                 if (contextmenu.__copy_fix) {
272                     return;
273                 }
274 
275                 contextmenu.__copy_fix = 1;
276 
277                 for (var i in pastes) {
278                     if (pastes.hasOwnProperty(i)) {
279                         contextmenu.addChild({
280                             xclass:'menuitem',
281                             content:lang[i],
282                             value:i
283                         });
284                     }
285                 }
286 
287                 contextmenu.on('click', function (e) {
288                     var value = e.target.get("value");
289                     if (pastes[value]) {
290                         this.hide();
291                         // 给 ie 一点 hide() 中的事件触发 handler 运行机会,
292                         // 原编辑器获得焦点后再进行下步操作
293                         setTimeout(function () {
294                             editor.execCommand(value);
295                         }, 30);
296                     }
297                 });
298             });
299         }
300     };
301 }, {
302     requires:['./base', './range', './selection']
303 });
304