1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.ui.vaadin.gwt.client.connector;
35
36 import info.magnolia.ui.vaadin.editor.PageEditor;
37 import info.magnolia.ui.vaadin.gwt.client.css.PageEditorCssProvider;
38 import info.magnolia.ui.vaadin.gwt.client.editor.dom.CmsNode;
39 import info.magnolia.ui.vaadin.gwt.client.editor.dom.Comment;
40 import info.magnolia.ui.vaadin.gwt.client.editor.dom.MgnlComponent;
41 import info.magnolia.ui.vaadin.gwt.client.editor.dom.MgnlElement;
42 import info.magnolia.ui.vaadin.gwt.client.editor.dom.processor.AbstractMgnlElementProcessor;
43 import info.magnolia.ui.vaadin.gwt.client.editor.dom.processor.CommentProcessor;
44 import info.magnolia.ui.vaadin.gwt.client.editor.dom.processor.ElementProcessor;
45 import info.magnolia.ui.vaadin.gwt.client.editor.dom.processor.MgnlElementProcessorFactory;
46 import info.magnolia.ui.vaadin.gwt.client.editor.dom.processor.ProcessException;
47 import info.magnolia.ui.vaadin.gwt.client.editor.event.ComponentActionEvent;
48 import info.magnolia.ui.vaadin.gwt.client.editor.event.ComponentStartMoveEvent;
49 import info.magnolia.ui.vaadin.gwt.client.editor.event.ComponentStopMoveEvent;
50 import info.magnolia.ui.vaadin.gwt.client.editor.event.EditAreaEvent;
51 import info.magnolia.ui.vaadin.gwt.client.editor.event.EditComponentEvent;
52 import info.magnolia.ui.vaadin.gwt.client.editor.event.FrameNavigationEvent;
53 import info.magnolia.ui.vaadin.gwt.client.editor.event.NewAreaEvent;
54 import info.magnolia.ui.vaadin.gwt.client.editor.event.NewComponentEvent;
55 import info.magnolia.ui.vaadin.gwt.client.editor.event.SelectElementEvent;
56 import info.magnolia.ui.vaadin.gwt.client.editor.event.SortComponentEvent;
57 import info.magnolia.ui.vaadin.gwt.client.editor.jsni.event.FrameLoadedEvent;
58 import info.magnolia.ui.vaadin.gwt.client.editor.jsni.event.RefreshEvent;
59 import info.magnolia.ui.vaadin.gwt.client.editor.model.Model;
60 import info.magnolia.ui.vaadin.gwt.client.editor.model.ModelImpl;
61 import info.magnolia.ui.vaadin.gwt.client.editor.model.focus.FocusModel;
62 import info.magnolia.ui.vaadin.gwt.client.editor.model.focus.FocusModelImpl;
63 import info.magnolia.ui.vaadin.gwt.client.rpc.PageEditorClientRpc;
64 import info.magnolia.ui.vaadin.gwt.client.rpc.PageEditorServerRpc;
65 import info.magnolia.ui.vaadin.gwt.client.shared.AbstractElement;
66 import info.magnolia.ui.vaadin.gwt.client.shared.AreaElement;
67 import info.magnolia.ui.vaadin.gwt.client.shared.ComponentElement;
68 import info.magnolia.ui.vaadin.gwt.client.shared.PageElement;
69 import info.magnolia.ui.vaadin.gwt.client.widget.PageEditorView;
70 import info.magnolia.ui.vaadin.gwt.client.widget.PageEditorViewImpl;
71 import info.magnolia.ui.vaadin.gwt.client.widget.dnd.MoveWidget;
72
73 import java.util.ArrayList;
74 import java.util.List;
75 import java.util.logging.Logger;
76
77 import com.google.gwt.core.client.GWT;
78 import com.google.gwt.core.client.JavaScriptException;
79 import com.google.gwt.dom.client.Document;
80 import com.google.gwt.dom.client.Element;
81 import com.google.gwt.dom.client.HeadElement;
82 import com.google.gwt.dom.client.LinkElement;
83 import com.google.gwt.dom.client.Node;
84 import com.google.gwt.dom.client.NodeList;
85 import com.google.gwt.event.shared.EventBus;
86 import com.google.gwt.event.shared.SimpleEventBus;
87 import com.google.gwt.regexp.shared.MatchResult;
88 import com.google.gwt.regexp.shared.RegExp;
89 import com.google.gwt.user.client.ui.Widget;
90 import com.vaadin.client.BrowserInfo;
91 import com.vaadin.client.communication.RpcProxy;
92 import com.vaadin.client.communication.StateChangeEvent;
93 import com.vaadin.client.ui.AbstractComponentConnector;
94 import com.vaadin.shared.ui.Connect;
95
96
97
98
99 @Connect(PageEditor.class)
100 public class PageEditorConnector extends AbstractComponentConnector implements PageEditorView.Listener {
101
102 private Logger log = Logger.getLogger(getClass().getName());
103
104 private static final String IFRAME_PRELOADER_STYLE = "iframe-preloader";
105
106 private final PageEditorServerRpc rpc = RpcProxy.create(PageEditorServerRpc.class, this);
107
108 private final EventBus eventBus = new SimpleEventBus();
109
110 private PageEditorView view;
111 private MoveWidget moveWidget;
112
113 private Model model;
114
115 private FocusModel focusModel;
116 private ElementProcessor elementProcessor;
117 private CommentProcessor commentProcessor;
118
119
120
121
122
123
124 private boolean pageChange = false;
125
126 @Override
127 protected void init() {
128 super.init();
129 this.model = new ModelImpl();
130 this.focusModel = new FocusModelImpl(eventBus, model);
131 this.elementProcessor = new ElementProcessor(eventBus, model);
132 this.commentProcessor = new CommentProcessor();
133
134 addStateChangeHandler("url", (StateChangeEvent.StateChangeHandler) stateChangeEvent -> {
135 view.setUrl(getState().getUrl());
136 if (getState().isPreview()) {
137 view.getFrame().addStyleName(IFRAME_PRELOADER_STYLE);
138 }
139 });
140 registerRpc(PageEditorClientRpc.class, new PageEditorClientRpc() {
141
142 @Override
143 public void refresh() {
144 view.reload();
145 }
146
147 @Override
148 public void startMoveComponent() {
149 MgnlElement selectedElement = model.getMgnlElement(getState().getSelectedElement());
150 if (selectedElement != null && selectedElement.isComponent()) {
151 MgnlComponent./../../../info/magnolia/ui/vaadin/gwt/client/editor/dom/MgnlComponent.html#MgnlComponent">MgnlComponent component = (MgnlComponent) selectedElement;
152 component.doStartMove(false);
153 model.setMoving(true);
154 if (!BrowserInfo.get().isTouchDevice()) {
155 PageEditorConnector.this.moveWidget = component.getMoveWidget();
156 moveWidget.attach(view.getFrame());
157 }
158 }
159 }
160
161 @Override
162 public void cancelMoveComponent() {
163 eventBus.fireEvent(new ComponentStopMoveEvent(null, true));
164 }
165 });
166
167
168
169
170
171 eventBus.addHandler(FrameLoadedEvent.TYPE, event -> {
172 model.reset();
173 focusModel.resetFocus();
174 Document document = null;
175 try {
176 document = event.getFrame().getContentDocument();
177 } catch (JavaScriptException e) {
178 GWT.log("Error getting content document from iframe: " + e.getMessage());
179 }
180 if (document == null) {
181 rpc.selectExternalPage();
182 } else {
183 process(document);
184
185 view.initKeyEventListeners();
186
187 if (!getState().isPreview()) {
188 view.initDomEventListeners();
189 focusModel.init();
190 }
191 AbstractElement elementToSelect = pageChange ? null : getState().getSelectedElement();
192 focusModel.selectElement(elementToSelect);
193 }
194 PageEditorConnector.this.pageChange = false;
195
196 if (event.getFrame() != null) {
197 rpc.onNavigation(removePageEditorParameters(event.getFrame().getUrl()));
198 }
199 });
200
201 eventBus.addHandler(RefreshEvent.TYPE, event -> {
202 focusModel.resetFocus();
203 model.reset();
204 Document document = view.getFrame().getContentDocument();
205 process(document);
206 if (!getState().isPreview()) {
207 focusModel.init();
208 }
209 focusModel.selectElement(getState().getSelectedElement());
210 });
211
212 eventBus.addHandler(FrameNavigationEvent.TYPE, frameUrlChangedEvent -> {
213 String path = frameUrlChangedEvent.getPath();
214 RegExp regExp = RegExp.compile("((?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*))(?:(\\?[^#]*))?(?:(#.*))?");
215 MatchResult matchResult = regExp.exec(path);
216 if (matchResult != null) {
217 path = matchResult.getGroup(1);
218
219 final String platformId = getState().getPlatformType().getId();
220 final boolean isPreview = getState().isPreview();
221 String query = matchResult.getGroup(5);
222 query = query != null ? removePageEditorParameters(query) : query;
223 query = query != null && query != "" ? query + "&" : "?";
224 query += "mgnlChannel=" + platformId;
225 query += "&mgnlPreview=" + isPreview;
226 path += query;
227
228 String fragment = matchResult.getGroup(6);
229 if (fragment != null) {
230 path += fragment;
231 }
232 }
233
234 PageEditorConnector.this.pageChange = true;
235 view.setUrl(path);
236 });
237
238 eventBus.addHandler(SelectElementEvent.TYPE, selectElementEvent -> {
239 AbstractElement selectedElement = selectElementEvent.getElement();
240 if (selectedElement instanceof PageElement) {
241 rpc.selectPage((PageElement) selectedElement);
242 } else if (selectedElement instanceof AreaElement) {
243 rpc.selectArea((AreaElement) selectedElement);
244 } else if (selectedElement instanceof ComponentElement) {
245 rpc.selectComponent((ComponentElement) selectedElement);
246 }
247 view.resetScrollTop();
248 });
249
250 eventBus.addHandler(NewAreaEvent.TYPE, newAreaEvent -> {
251 if (!getConnection().getMessageSender().hasActiveRequest()) {
252 rpc.newArea(newAreaEvent.getAreaElement());
253 }
254 });
255
256 eventBus.addHandler(NewComponentEvent.TYPE, newComponentEvent -> {
257 if (!getConnection().getMessageSender().hasActiveRequest()) {
258 rpc.newComponent(newComponentEvent.getParentAreaElement());
259 }
260 });
261
262 eventBus.addHandler(EditAreaEvent.TYPE, editAreaEvent -> {
263 if (!getConnection().getMessageSender().hasActiveRequest()) {
264 rpc.editArea(editAreaEvent.getAreaElement());
265 }
266 });
267
268 eventBus.addHandler(EditComponentEvent.TYPE, editComponentEvent -> {
269 if (!getConnection().getMessageSender().hasActiveRequest()) {
270 rpc.editComponent(editComponentEvent.getComponentElement());
271 }
272 });
273
274 eventBus.addHandler(SortComponentEvent.TYPE, sortComponentEvent -> rpc.sortComponent(sortComponentEvent.getAreaElement()));
275
276 eventBus.addHandler(ComponentStartMoveEvent.TYPE, componentStartMoveEvent -> rpc.startMoveComponent());
277
278 eventBus.addHandler(ComponentStopMoveEvent.TYPE, componentStopMoveEvent -> {
279 if (!componentStopMoveEvent.isServerSide()) {
280 rpc.stopMoveComponent();
281 }
282 if (moveWidget != null && moveWidget.isAttached()) {
283 moveWidget.detach();
284 }
285 model.setMoving(false);
286 });
287
288 eventBus.addHandler(ComponentActionEvent.TYPE, actionEvent -> rpc.onClientAction(actionEvent.getElement(), actionEvent.getActionName(), actionEvent.getParameters()));
289 }
290
291 @Override
292 protected Widget createWidget() {
293 this.view = new PageEditorViewImpl(eventBus);
294 this.view.setListener(this);
295 return view.asWidget();
296 }
297
298 @Override
299 public PageEditorState getState() {
300 return (PageEditorState) super.getState();
301 }
302
303 @Override
304 public void selectElement(Element element) {
305 focusModel.selectElement(element);
306 }
307
308 private void process(final Document document) {
309 try {
310 injectEditorStyles(document);
311 long startTime = System.currentTimeMillis();
312 processDocument(document, null);
313 processMgnlElements();
314 GWT.log("Time spent to process cms comments: " + (System.currentTimeMillis() - startTime) + "ms");
315 } catch (ProcessException e) {
316 rpc.onError(e.getErrorType(), e.getTagName());
317 GWT.log("Error while processing comment: " + e.getTagName() + " due to " + e.getErrorType());
318 consoleLog("Error while processing comment: " + e.getTagName() + " due to " + e.getErrorType());
319 }
320 }
321
322 private void injectEditorStyles(final Document document) {
323 HeadElement head = HeadElement.as(document.getElementsByTagName("head").getItem(0));
324 final NodeList<Node> childNodes = head.getChildNodes();
325 List<String> urls = new ArrayList<>();
326 for(int i = 0; i < childNodes.getLength(); i++) {
327 if (!(childNodes.getItem(i) instanceof LinkElement)) {
328 continue;
329 }
330 String url = ((LinkElement) childNodes.getItem(i)).getHref();
331 if (url != null) {
332 urls.add(url);
333 }
334 }
335 for (String uri: PageEditorCssProvider.INSTANCE.getCssLinks()) {
336 if (urls.stream().anyMatch(url -> url.contains(uri))) {
337 continue;
338 }
339 LinkElement cssLink = createLinkElement(document, getState().getContextPath() + uri);
340 head.insertFirst(cssLink);
341 }
342 }
343
344 private LinkElement createLinkElement(final Document document, final String href) {
345 LinkElement cssLink = document.createLinkElement();
346 cssLink.setType("text/css");
347 cssLink.setRel("stylesheet");
348 cssLink.setHref(href);
349 return cssLink;
350 }
351
352 private void processDocument(Node node, MgnlElement mgnlElement) throws ProcessException {
353 if (mgnlElement == null && model.getRootPage() != null) {
354 mgnlElement = model.getRootPage();
355 }
356 for (int i = 0; i < node.getChildCount(); i++) {
357 Node childNode = node.getChild(i);
358 if (childNode.getNodeType() == Comment.COMMENT_NODE) {
359 try {
360 mgnlElement = commentProcessor.process(model, eventBus, childNode, mgnlElement);
361 } catch (ProcessException e) {
362 throw e;
363 } catch (IllegalArgumentException e) {
364 GWT.log("Not CMSComment element, skipping: " + e.toString());
365 } catch (Exception e) {
366 GWT.log("Caught undefined exception: " + e.toString());
367 consoleLog("Caught undefined exception: " + e.toString());
368 }
369 } else if (childNode.getNodeType() == Node.ELEMENT_NODE) {
370 Element element = childNode.cast();
371 elementProcessor.process(element, mgnlElement, getState().isPreview());
372 }
373 processDocument(childNode, mgnlElement);
374 }
375 }
376
377 private void processMgnlElements() {
378 CmsNode root = model.getRootPage();
379 if (model.getRootPage() == null) {
380 log.warning("Could not find any Magnolia cms:page tag, this might be a static page; not injecting page-editor bars.");
381 return;
382 }
383
384 List<CmsNode> elements = root.getDescendants();
385 elements.add(root);
386 for (CmsNode element : elements) {
387 try {
388 AbstractMgnlElementProcessor processor = MgnlElementProcessorFactory.getProcessor(model, element.asMgnlElement(), getState().getI18nKeys());
389 processor.process();
390 } catch (IllegalArgumentException e) {
391 GWT.log("MgnlFactory could not instantiate class. The element is neither an area nor component.");
392 } catch (Exception e) {
393 final String errorMessage = "Error when processing editor components for '" + element.asMgnlElement().getAttribute("path") +
394 "'. It's possible that the template script for this area or for some subcomponent is incorrect. Please check that all HTML tags are closed properly.\n"
395 + e.toString();
396 GWT.log(errorMessage);
397 consoleLog(errorMessage);
398 }
399 }
400 }
401
402 void consoleLog(String message) {
403 log.info("PageEditor: " + message);
404 }
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420 private String removePageEditorParameters(String url) {
421 return url.replaceAll("(\\&{0,1})(mgnlChannel|mgnlPreview)\\=([^&]+)", "").replaceAll("(\\?$)", "").replaceAll("(\\?\\&)", "?");
422 }
423 }