BrightSide Workbench Full Report + Source Code
WikiEditor.java
Go to the documentation of this file.
1 /*
2  * TurrĂ³ i Cutiller Foundation. License notice.
3  * Copyright (C) 2015 Lluis TurrĂ³ Cutiller <http://www.turro.org/>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU Affero General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU Affero General Public License for more details.
14  *
15  * You should have received a copy of the GNU Affero General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 package org.turro.zkoss.text;
20 
21 import java.io.File;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.List;
25 import org.turro.command.Command;
26 import org.turro.command.Context;
27 import org.turro.elephant.context.Application;
28 import org.turro.elephant.context.ElephantContext;
29 import org.turro.elephant.context.IConstructor;
30 import org.turro.elephant.impl.util.FileUtil;
31 import org.turro.elephant.impl.util.Parser;
32 import org.turro.elephant.util.Components;
33 import org.turro.i18n.I_;
34 import org.turro.parser.wiki.WikiProcessors;
35 import org.turro.wiki.WikiParser;
36 import org.turro.wiki.WikiResult;
37 import org.turro.wiki.parsing.IWikiResolver;
38 import org.turro.zkoss.dialog.SelectionDialog;
39 import org.turro.zkoss.input.FileListbox;
40 import org.zkoss.zk.ui.Executions;
41 import org.zkoss.zk.ui.IdSpace;
42 import org.zkoss.zk.ui.annotation.ComponentAnnotation;
43 import org.zkoss.zk.ui.event.Event;
44 import org.zkoss.zk.ui.event.EventListener;
45 import org.zkoss.zk.ui.event.Events;
46 import org.zkoss.zk.ui.event.InputEvent;
47 import org.zkoss.zk.ui.select.Selectors;
48 import org.zkoss.zk.ui.select.annotation.Listen;
49 import org.zkoss.zk.ui.select.annotation.Wire;
50 import org.zkoss.zul.Div;
51 import org.zkoss.zul.Grid;
52 import org.zkoss.zul.Html;
53 import org.zkoss.zul.ListModelArray;
54 import org.zkoss.zul.Menubar;
55 import org.zkoss.zul.Menuitem;
56 import org.zkoss.zul.South;
57 import org.zkoss.zul.Textbox;
58 
63 @ComponentAnnotation({"value:@ZKBIND(ACCESS=both,SAVE_EVENT=onChange)"})
64 public class WikiEditor extends Div implements IdSpace {
65 
66  private int selectionStart, selectionEnd;
67  private String imageFolder = "/", fileFolder = "/";
68  private boolean readOnlyRepository = false, noTreeRepository = false;
69  private final List<IWikiResolver> resolvers = new ArrayList<>();
70 
71  @Wire private Menubar wikibar;
72  @Wire private WikiTextbox wikibox;
73  @Wire private Grid output;
74  @Wire private Menuitem elwiki;
75  @Wire private South outpanel;
76 
77  public WikiEditor() {
78  setSclass("wikiEditor");
79  Executions.createComponents("/WEB-INF/_zul/bs/comps/editor/wikiInput.zul", this, null);
80  Selectors.wireComponents(this, this, false);
81  Selectors.wireEventListeners(this, this);
82  wikibox.setWidgetListener("onClick",
83  "zAu.send(new zk.Event(this,\"onCaret\",zk(this.$n()).getSelectionRange()+\"\",{toServer:true}));");
84  wikibox.setWidgetListener("onBlur",
85  "zAu.send(new zk.Event(this,\"onCaret\",zk(this.$n()).getSelectionRange()+\"\",{toServer:true}));");
86  wikibox.setWidgetListener("onSelection",
87  "zAu.send(new zk.Event(this,\"onCaret\",zk(this.$n()).getSelectionRange()+\"\",{toServer:true}));");
88  wikibox.setWidgetListener("onChanging",
89  "zAu.send(new zk.Event(this,\"onCaret\",zk(this.$n()).getSelectionRange()+\"\",{toServer:true}));");
90  wikibox.addEventListener(Events.ON_CHANGE, new EventListener<InputEvent>() {
91  @Override
92  public void onEvent(InputEvent event) throws Exception {
93  Events.postEvent(WikiEditor.this, event);
94  }
95  });
96  }
97 
98  public void setValue(String value) {
99  setWiki(value);
100  elwiki.setCheckmark(WikiParser.isElWiki(value));
101  }
102 
103  public String getValue() {
104  return wikibox.getValue();
105  }
106 
107  public String getHtml() {
108  return getHtml(wikibox.getValue(), false);
109  }
110 
111  public void setImageFolder(String folder) {
112  this.imageFolder = folder;
113  }
114 
115  public String getImageFolder() {
116  return imageFolder;
117  }
118 
119  public void setFileFolder(String folder) {
120  this.fileFolder = folder;
121  }
122 
123  public String getFileFolder() {
124  return fileFolder;
125  }
126 
127  public boolean isReadonly() {
128  return wikibox.isReadonly();
129  }
130 
131  public void setReadonly(boolean value) {
132  wikibox.setReadonly(value);
133  for(Object obj : wikibar.getChildren()) {
134  if(obj instanceof Menuitem) {
135  ((Menuitem) obj).setDisabled(value);
136  }
137  }
138  }
139 
140  public boolean isReadOnlyRepository() {
141  return readOnlyRepository;
142  }
143 
144  public void setReadOnlyRepository(boolean readOnlyRepository) {
145  this.readOnlyRepository = readOnlyRepository;
146  }
147 
148  public boolean isNoTreeRepository() {
149  return noTreeRepository;
150  }
151 
152  public void setNoTreeRepository(boolean noTreeRepository) {
153  this.noTreeRepository = noTreeRepository;
154  }
155 
156  public boolean isElWiki() {
157  return elwiki.isCheckmark();
158  }
159 
160  public void addResolver(IWikiResolver resolver) {
161  if(resolver != null) resolvers.add(resolver);
162  }
163 
164  @Listen("onCaret = *")
165  public void onCaret(Event event) {
166  String v[] = ((String) event.getData()).split(",");
167  selectionStart = Integer.valueOf(v[0]);
168  selectionEnd = Integer.valueOf(v[1]);
169  }
170 
171  @Listen("onClick = #header1")
172  public void onHeader1() {
173  insertOnStartLine("---+", new String[] {
174  "---++++", "---+++", "---++", "---+"
175  });
176  }
177 
178  @Listen("onClick = #header2")
179  public void onHeader2() {
180  insertOnStartLine("---++", new String[] {
181  "---++++", "---+++", "---++", "---+"
182  });
183  }
184 
185  @Listen("onClick = #header3")
186  public void onHeader3() {
187  insertOnStartLine("---+++", new String[] {
188  "---++++", "---+++", "---++", "---+"
189  });
190  }
191 
192  @Listen("onClick = #header4")
193  public void onHeader4() {
194  insertOnStartLine("---++++", new String[] {
195  "---++++", "---+++", "---++", "---+"
196  });
197  }
198 
199  @Listen("onClick = #block")
200  public void onBlock() {
201  insert("\n[block]\n", "\n[/block]\n");
202  }
203 
204  @Listen("onClick = #span")
205  public void onSpan() {
206  insert("[span]", "[/span]");
207  }
208 
209  @Listen("onClick = #note")
210  public void onNote() {
211  insert("\n[note]", "[/note]\n");
212  }
213 
214  @Listen("onClick = #startcolumn")
215  public void onStartColumn() {
216  insert("\n[column]\n");
217  }
218 
219  @Listen("onClick = #endcolumns")
220  public void onEndColumns() {
221  insert("\n[/column]\n");
222  }
223 
224  @Listen("onClick = #tabulator")
225  public void onTabulator() {
226  insert("\n[tab(Caption1)]\n","Content1\n[tab(Caption2)]\nContent2\n[/tab]\n");
227  }
228 
229  @Listen("onClick = #blind")
230  public void onBlind() {
231  insert("\n[blind(Caption1)]\n","Content1\n[blind(Caption2)]\nContent2\n[/blind]\n");
232  }
233 
234  @Listen("onClick = #bold")
235  public void onBold() {
236  WikiElement we = new WikiElement(selectionStart, selectionEnd, wikibox, WikiElement.NON_BLOCK_ELEMENTS);
237  if(selectionStart == selectionEnd && we.inside() && !we.isTag("img")) {
238  putAttribute("font-weight", "bold");
239  } else {
240  insert("**","**", true);
241  }
242  }
243 
244  @Listen("onClick = #italic")
245  public void onItalic() {
246  WikiElement we = new WikiElement(selectionStart, selectionEnd, wikibox, WikiElement.NON_BLOCK_ELEMENTS);
247  if(selectionStart == selectionEnd && we.inside() && !we.isTag("img")) {
248  putAttribute("font-style", "italic");
249  } else {
250  insert("__","__", true);
251  }
252  }
253 
254  @Listen("onClick = #regular")
255  public void onRegular() {
256  insert("==","==", true);
257  }
258 
259  @Listen("onClick = #toWiki")
260  public void onToWiki() {
261  String v = wikibox.getValue();
262  String w = Parser.toWiki(v);
263  wikibox.setSelectedText(0, v.length(), w, false);
264  wikibox.setSelectionRange(0, 0);
265  wikibox.setFocus(true);
266  Events.postEvent(new InputEvent("onChange", this, wikibox.getValue(), null));
267  }
268 
269  @Listen("onClick = #preview")
270  public void onPreview() {
271  Div div = new Div();
272  div.setStyle("overflow:auto;");
273  Html html = new Html(getHtml(wikibox.getValue(), true));
274  div.appendChild(html);
275  SelectionDialog.showComponent(getPage(), I_.get("Preview"), div, "800px", "80%", null);
276  }
277 
278  @Listen("onClick = #html")
279  public void onHtml() {
280  Div div = new Div();
281  div.setSclass("wikiEditor");
282  Textbox html = new Textbox(getHtml(wikibox.getValue(), false));
283  html.setSclass("wikiBox");
284  html.setReadonly(true);
285  html.setMultiline(true);
286  html.setHflex("1");
287  html.setVflex("1");
288  div.appendChild(html);
289  SelectionDialog.showComponent(getPage(), "HTML", div, "800px", "80%", null);
290  }
291 
292  @Listen("onClick = #startwiki")
293  public void onStartWiki() {
294  insert("\n$wiki\n");
295  }
296 
297  @Listen("onClick = #stopwiki")
298  public void onStopWiki() {
299  insert("\n$no_wiki\n");
300  }
301 
302  @Listen("onClick = #startjava")
303  public void onStartJava() {
304  insert("\n$java\n");
305  }
306 
307  @Listen("onClick = #stopjava")
308  public void onStopJava() {
309  insert("\n$no_java\n");
310  }
311 
312  @Listen("onClick = #startxml")
313  public void onStartXml() {
314  insert("\n$xml\n");
315  }
316 
317  @Listen("onClick = #stopxml")
318  public void onStopXml() {
319  insert("\n$no_xml\n");
320  }
321 
322  @Listen("onClick = #alignleft")
323  public void onAlignLeft() {
324  putAttribute("text-align", "left");
325  }
326 
327  @Listen("onClick = #aligncenter")
328  public void onAlignCenter() {
329  putAttribute("text-align", "center");
330  }
331 
332  @Listen("onClick = #alignright")
333  public void onAlignRight() {
334  putAttribute("text-align", "right");
335  }
336 
337  @Listen("onClick = #alignjustify")
338  public void onAlignJustify() {
339  putAttribute("text-align", "justify");
340  }
341 
342  @Listen("onClick = #floatleft")
343  public void onFloatLeft() {
344  putAttribute("float", "left");
345  }
346 
347  @Listen("onClick = #floatright")
348  public void onFloatright() {
349  putAttribute("float", "right");
350  }
351 
352  @Listen("onClick = #ullist")
353  public void onUlList() {
354  insertOnStartLine(" * ", " 1 ");
355  }
356 
357  @Listen("onClick = #ollist")
358  public void onOlList() {
359  insertOnStartLine(" 1 ", " * ");
360  }
361 
362  @Listen("onClick = #thl")
363  public void onThl() {
364  insert("\n||<");
365  }
366 
367  @Listen("onClick = #thr")
368  public void onThr() {
369  insert("\n||>");
370  }
371 
372  @Listen("onClick = #trl")
373  public void onTrl() {
374  insert("\n|-<");
375  }
376 
377  @Listen("onClick = #trr")
378  public void onTrr() {
379  insert("\n|->");
380  }
381 
382  @Listen("onClick = #tcl")
383  public void onTcl() {
384  insert("\n|<");
385  }
386 
387  @Listen("onClick = #tcr")
388  public void onTcr() {
389  insert("\n|>");
390  }
391 
392  @Listen("onClick = #tet")
393  public void onTet() {
394  insert("\n-||");
395  }
396 
397  @Listen("onClick = #eraser")
398  public void onEraser() {
399  removeAttributes();
400  }
401 
402  @Listen("onChange = #color")
403  public void onColor(InputEvent event) {
404  putAttribute("color", event.getValue());
405  }
406 
407  @Listen("onChange = #background")
408  public void onBackground(InputEvent event) {
409  putAttribute("background-color", event.getValue());
410  }
411 
412  @Listen("onClick = #link")
413  public void onLink() {
414  WikiElement we = new WikiElement(selectionStart, selectionEnd, wikibox);
415  if(!we.isTag("link")) {
416  we.forgetAndCreate("link");
417  }
418  Link link = new Link(we);
419  link.setFileFolder(fileFolder);
420  link.setReadOnlyRepository(readOnlyRepository);
421  link.setNoTreeRepository(noTreeRepository);
422  SelectionDialog.getComponent(Executions.getCurrent().getDesktop().getFirstPage(), "Link", link, "500px", "300px", new Command() {
423  @Override
424  public Object execute(Context context) {
425  Link link = (Link) context.get("component");
426  if(link != null && link.renderResult()) {
427  wikibox.setSelectionRange(selectionStart, selectionStart);
428  wikibox.setFocus(true);
429  Events.postEvent(new InputEvent("onChange", WikiEditor.this, wikibox.getValue(), null));
430  }
431  return null;
432  }
433  });
434  }
435 
436  @Listen("onClick = #image")
437  public void onImage() {
438  WikiElement we = new WikiElement(selectionStart, wikibox, "img");
439 // if(!we.isTag("img")) {
440 // we.forgetAndCreate("img");
441 // }
442  final RepositoryWalker repositories = new RepositoryWalker(we);
443  repositories.selectFolder(imageFolder);
445  repositories.setReadOnlyRepository(readOnlyRepository);
446  repositories.setNoTreeRepository(noTreeRepository);
447  SelectionDialog.showComponent(getPage(), I_.get("Repository"), repositories, "90%", "90%", new Command() {
448  @Override
449  public Object execute(Context context) {
450  if(repositories.renderResult()) {
451  wikibox.setSelectionRange(selectionStart, selectionStart);
452  wikibox.setFocus(true);
453  Events.postEvent(new InputEvent("onChange", WikiEditor.this, wikibox.getValue(), null));
454  }
455  return null;
456  }
457  });
458  }
459 
460  @Listen("onClick = #template")
461  public void onTemplate() {
462  File wtmpl = new File(ElephantContext.getRealPath("/WEB-INF/elephant/wiki/templates"));
463  if(wtmpl.exists()) {
464  FileListbox fl = new FileListbox();
465  fl.setExact(true);
466  fl.setFilter(".*\\.wiki");
467  fl.setDir(wtmpl);
468  fl.addEventListener(Events.ON_DOUBLE_CLICK, (Event event) -> {
469  insert(FileUtil.getContentWithLineBreaks(fl.getObjectValue()));
470  Events.postEvent(new Event(Events.ON_CLOSE, Components.from(fl).parent(SelectionDialog.class)));
471  });
472  SelectionDialog.showComponent(getPage(), I_.get("Repository"), fl, "300px", "50%", (Context context) -> {
473  if(fl.getObjectValue() != null) {
474  wikibox.setFocus(true);
475  Events.postEvent(new InputEvent("onChange", WikiEditor.this, wikibox.getValue(), null));
476  }
477  return null;
478  });
479  }
480  }
481 
482  @Listen("onClick = #snippet")
483  public void onSnippet() {
484  File wsnip = new File(ElephantContext.getRealPath("/WEB-INF/elephant/wiki/snippets"));
485  if(wsnip.exists()) {
486  FileListbox fl = new FileListbox();
487  fl.setExact(true);
488  fl.setFilter(".*\\.wiki");
489  fl.setDir(wsnip);
490  fl.addEventListener(Events.ON_DOUBLE_CLICK, (Event event) -> {
491  insert(FileUtil.getContentWithLineBreaks(fl.getObjectValue()));
492  Events.postEvent(new Event(Events.ON_CLOSE, Components.from(fl).parent(SelectionDialog.class)));
493  });
494  SelectionDialog.showComponent(getPage(), I_.get("Repository"), fl, "300px", "50%", (Context context) -> {
495  if(fl.getObjectValue() != null) {
496  wikibox.setFocus(true);
497  Events.postEvent(new InputEvent("onChange", WikiEditor.this, wikibox.getValue(), null));
498  }
499  return null;
500  });
501  }
502  }
503 
504  @Listen("onClick = #elwiki")
505  public void onElWiki() {
506  elwiki.setCheckmark(!elwiki.isCheckmark());
507  }
508 
509  private void insert(String string) {
510  int start = selectionStart;
511  wikibox.setSelectedText(start, start, string, false);
512  start += string.length();
513  wikibox.setSelectionRange(start, start);
514  wikibox.setFocus(true);
515  Events.postEvent(new InputEvent("onChange", this, wikibox.getValue(), null));
516  }
517 
518  private void insert(String startTag, String endTag) {
519  insert(startTag, endTag, false);
520  }
521 
522  private void insert(String startTag, String endTag, boolean singleLine) {
523  int start = selectionStart, end = selectionEnd;
524  String value = wikibox.getValue();
525  if(singleLine) {
526  for(int i = start; i < end; i++) {
527  if(value.charAt(i) == '\n') {
528  end = i - 1;
529  break;
530  }
531  }
532  }
533  wikibox.setSelectedText(start, start, startTag, false);
534  start += startTag.length();
535  end += startTag.length();
536  wikibox.setSelectedText(end, end, endTag, false);
537  wikibox.setSelectionRange(start, start);
538  wikibox.setFocus(true);
539  Events.postEvent(new InputEvent("onChange", this, wikibox.getValue(), null));
540  }
541 
542  private void putAttribute(String key, String value) {
543  WikiElement we = new WikiElement(selectionStart, selectionEnd, wikibox, WikiElement.NON_BLOCK_ELEMENTS);
544  we.getAttributes().put(key, value);
545  we.render();
546  wikibox.setSelectionRange(selectionStart, selectionStart);
547  wikibox.setFocus(true);
548  Events.postEvent(new InputEvent("onChange", this, wikibox.getValue(), null));
549  }
550 
551  private void removeAttributes() {
552  WikiElement we = new WikiElement(selectionStart, selectionEnd, wikibox, WikiElement.NON_BLOCK_ELEMENTS);
553  we.removeValuedAttributes();
554  we.render();
555  wikibox.setSelectionRange(selectionStart, selectionStart);
556  wikibox.setFocus(true);
557  Events.postEvent(new InputEvent("onChange", this, wikibox.getValue(), null));
558  }
559 
560  private void insertOnStartLine(String value) {
561  insertOnStartLine(value, value);
562  }
563 
564  private void insertOnStartLine(String value, String replacement) {
565  insertOnStartLine(value, new String[] { value, replacement });
566  }
567 
568  private void insertOnStartLine(String value, String[] replacement) {
569  int start = selectionStart, end = selectionEnd;
570  String text = wikibox.getValue();
571  // Start with first line
572  for(int c = start - 1; c >= 0; c--) {
573  if(text.charAt(c) == '\n') {
574  int shift = killReplacements(text, c + 1, replacement);
575  start += shift;
576  end += shift;
577  wikibox.setSelectedText(c + 1, c + 1, value, false);
578  start += value.length();
579  end += value.length();
580  break;
581  } else if(c == 0) {
582  int shift = killReplacements(text, c, replacement);
583  start += shift;
584  end += shift;
585  wikibox.setSelectedText(c, c, value, false);
586  start += value.length();
587  end += value.length();
588  break;
589  }
590  }
591  text = wikibox.getValue();
592  // Walk through selection
593  for(int c = start; c < end; c++) {
594  if(text.charAt(c) == '\n') {
595  int shift = killReplacements(text, c + 1, replacement);
596  end += shift;
597  wikibox.setSelectedText(c + 1, c + 1, value, false);
598  end += value.length();
599  text = wikibox.getValue();
600  } else if(c == 0) {
601  int shift = killReplacements(text, c, replacement);
602  end += shift;
603  wikibox.setSelectedText(c, c, value, false);
604  end += value.length();
605  text = wikibox.getValue();
606  }
607  }
608  wikibox.setSelectionRange(start, start);
609  wikibox.setFocus(true);
610  Events.postEvent(new InputEvent("onChange", this, wikibox.getValue(), null));
611  }
612 
613  private int killReplacements(String text, int c, String[] replacement) {
614  int shift = 0;
615  for(String r : replacement) {
616  if(text.substring(c).startsWith(r)) {
617  wikibox.setSelectedText(c, c + r.length(), "", false);
618  shift -= r.length();
619  break;
620  }
621  }
622  return shift;
623  }
624 
625  private void setWiki(String source) {
626  elwiki.setCheckmark(false);
627  wikibox.setValue(source);
628  }
629 
630  private String getHtml(String source, boolean preview) {
631  IConstructor constructor = Application.getApplication().getConstructor();
632  String htmlStr;
633  output.setModel(new ListModelArray(Collections.EMPTY_LIST));
634  elwiki.setCheckmark(elwiki.isCheckmark() || WikiParser.isElWiki(source));
635  if(elwiki.isCheckmark()) {
636  WikiResult result = WikiParser.instance()
637  .onProcessor(wmacro -> WikiProcessors.instance().constructor(constructor).resolve(wmacro))
638  .addResolvers(resolvers)
639  .parse(wikibox.getValue());
640  htmlStr = result.getHtml();
641  if(!result.getErrors().isEmpty()) {
642  output.setModel(new ListModelArray<>(result.getErrors()));
643  outpanel.setOpen(true);
644  } else {
645  outpanel.setOpen(false);
646  }
647  } else {
648  Parser parser = new Parser("$wiki\n" + wikibox.getValue());
649  parser.setResolver(w -> {
650  for(IWikiResolver resolver : resolvers) {
651  w = resolver.apply(w);
652  }
653  return w;
654  });
655  htmlStr = parser.parse(constructor);
656  }
657  return (preview ? styleSheets() : "") + htmlStr;
658  }
659 
660  private String styleSheets() {
661  return "<link rel=\"stylesheet\" type=\"text/css\" href=\"" +
662  ElephantContext.getRootWebPath() + "/_internal/css/elephant-theme.css" +
663  "\"/>\n";
664  }
665 
666 }
static String toWiki(String text)
Definition: Parser.java:77
static String get(String msg)
Definition: I_.java:41
static void showComponent(Page page, String title, Component component, String width, String height, final Command command)
void setNoTreeRepository(boolean noTreeRepository)
void setReadOnlyRepository(boolean readOnlyRepository)
void setReadonly(boolean value)
void setImageFolder(String folder)
void setValue(String value)
Definition: WikiEditor.java:98
void setNoTreeRepository(boolean noTreeRepository)
void setFileFolder(String folder)
void setReadOnlyRepository(boolean readOnlyRepository)
void addResolver(IWikiResolver resolver)
void forgetAndCreate(String newTag)
static final String[] NON_BLOCK_ELEMENTS