|
1 /** |
|
2 * WordPress View plugin. |
|
3 */ |
|
4 |
|
5 (function() { |
|
6 var VK = tinymce.VK, |
|
7 TreeWalker = tinymce.dom.TreeWalker, |
|
8 selected; |
|
9 |
|
10 tinymce.create('tinymce.plugins.wpView', { |
|
11 init : function( editor, url ) { |
|
12 var wpView = this; |
|
13 |
|
14 // Check if the `wp.mce` API exists. |
|
15 if ( typeof wp === 'undefined' || ! wp.mce ) |
|
16 return; |
|
17 |
|
18 editor.onPreInit.add( function( editor ) { |
|
19 // Add elements so we can set `contenteditable` to false. |
|
20 editor.schema.addValidElements('div[*],span[*]'); |
|
21 }); |
|
22 |
|
23 // When the editor's content changes, scan the new content for |
|
24 // matching view patterns, and transform the matches into |
|
25 // view wrappers. Since the editor's DOM is outdated at this point, |
|
26 // we'll wait to render the views. |
|
27 editor.onBeforeSetContent.add( function( editor, o ) { |
|
28 if ( ! o.content ) |
|
29 return; |
|
30 |
|
31 o.content = wp.mce.view.toViews( o.content ); |
|
32 }); |
|
33 |
|
34 // When the editor's content has been updated and the DOM has been |
|
35 // processed, render the views in the document. |
|
36 editor.onSetContent.add( function( editor, o ) { |
|
37 wp.mce.view.render( editor.getDoc() ); |
|
38 }); |
|
39 |
|
40 editor.onInit.add( function( editor ) { |
|
41 |
|
42 // When a view is selected, ensure content that is being pasted |
|
43 // or inserted is added to a text node (instead of the view). |
|
44 editor.selection.onBeforeSetContent.add( function( selection, o ) { |
|
45 var view = wpView.getParentView( selection.getNode() ), |
|
46 walker, target; |
|
47 |
|
48 // If the selection is not within a view, bail. |
|
49 if ( ! view ) |
|
50 return; |
|
51 |
|
52 // If there are no additional nodes or the next node is a |
|
53 // view, create a text node after the current view. |
|
54 if ( ! view.nextSibling || wpView.isView( view.nextSibling ) ) { |
|
55 target = editor.getDoc().createTextNode(''); |
|
56 editor.dom.insertAfter( target, view ); |
|
57 |
|
58 // Otherwise, find the next text node. |
|
59 } else { |
|
60 walker = new TreeWalker( view.nextSibling, view.nextSibling ); |
|
61 target = walker.next(); |
|
62 } |
|
63 |
|
64 // Select the `target` text node. |
|
65 selection.select( target ); |
|
66 selection.collapse( true ); |
|
67 }); |
|
68 |
|
69 // When the selection's content changes, scan any new content |
|
70 // for matching views and immediately render them. |
|
71 // |
|
72 // Runs on paste and on inserting nodes/html. |
|
73 editor.selection.onSetContent.add( function( selection, o ) { |
|
74 if ( ! o.context ) |
|
75 return; |
|
76 |
|
77 var node = selection.getNode(); |
|
78 |
|
79 if ( ! node.innerHTML ) |
|
80 return; |
|
81 |
|
82 node.innerHTML = wp.mce.view.toViews( node.innerHTML ); |
|
83 wp.mce.view.render( node ); |
|
84 }); |
|
85 }); |
|
86 |
|
87 // When the editor's contents are being accessed as a string, |
|
88 // transform any views back to their text representations. |
|
89 editor.onPostProcess.add( function( editor, o ) { |
|
90 if ( ( ! o.get && ! o.save ) || ! o.content ) |
|
91 return; |
|
92 |
|
93 o.content = wp.mce.view.toText( o.content ); |
|
94 }); |
|
95 |
|
96 // Triggers when the selection is changed. |
|
97 // Add the event handler to the top of the stack. |
|
98 editor.onNodeChange.addToTop( function( editor, controlManager, node, collapsed, o ) { |
|
99 var view = wpView.getParentView( node ); |
|
100 |
|
101 // Update the selected view. |
|
102 if ( view ) { |
|
103 wpView.select( view ); |
|
104 |
|
105 // Prevent the selection from propagating to other plugins. |
|
106 return false; |
|
107 |
|
108 // If we've clicked off of the selected view, deselect it. |
|
109 } else { |
|
110 wpView.deselect(); |
|
111 } |
|
112 }); |
|
113 |
|
114 editor.onKeyDown.addToTop( function( editor, event ) { |
|
115 var keyCode = event.keyCode, |
|
116 view, instance; |
|
117 |
|
118 // If a view isn't selected, let the event go on its merry way. |
|
119 if ( ! selected ) |
|
120 return; |
|
121 |
|
122 // If the caret is not within the selected view, deselect the |
|
123 // view and bail. |
|
124 view = wpView.getParentView( editor.selection.getNode() ); |
|
125 if ( view !== selected ) { |
|
126 wpView.deselect(); |
|
127 return; |
|
128 } |
|
129 |
|
130 // If delete or backspace is pressed, delete the view. |
|
131 if ( keyCode === VK.DELETE || keyCode === VK.BACKSPACE ) { |
|
132 if ( (instance = wp.mce.view.instance( selected )) ) { |
|
133 instance.remove(); |
|
134 wpView.deselect(); |
|
135 } |
|
136 } |
|
137 |
|
138 // Let keypresses that involve the command or control keys through. |
|
139 // Also, let any of the F# keys through. |
|
140 if ( event.metaKey || event.ctrlKey || ( keyCode >= 112 && keyCode <= 123 ) ) |
|
141 return; |
|
142 |
|
143 event.preventDefault(); |
|
144 }); |
|
145 }, |
|
146 |
|
147 getParentView : function( node ) { |
|
148 while ( node ) { |
|
149 if ( this.isView( node ) ) |
|
150 return node; |
|
151 |
|
152 node = node.parentNode; |
|
153 } |
|
154 }, |
|
155 |
|
156 isView : function( node ) { |
|
157 return (/(?:^|\s)wp-view-wrap(?:\s|$)/).test( node.className ); |
|
158 }, |
|
159 |
|
160 select : function( view ) { |
|
161 if ( view === selected ) |
|
162 return; |
|
163 |
|
164 this.deselect(); |
|
165 selected = view; |
|
166 wp.mce.view.select( selected ); |
|
167 }, |
|
168 |
|
169 deselect : function() { |
|
170 if ( selected ) |
|
171 wp.mce.view.deselect( selected ); |
|
172 selected = null; |
|
173 }, |
|
174 |
|
175 getInfo : function() { |
|
176 return { |
|
177 longname : 'WordPress Views', |
|
178 author : 'WordPress', |
|
179 authorurl : 'http://wordpress.org', |
|
180 infourl : 'http://wordpress.org', |
|
181 version : '1.0' |
|
182 }; |
|
183 } |
|
184 }); |
|
185 |
|
186 // Register plugin |
|
187 tinymce.PluginManager.add( 'wpview', tinymce.plugins.wpView ); |
|
188 })(); |