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