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