package org.swellrt.beta.client.js.editor.annotation;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import org.swellrt.beta.client.js.JsUtils;
import org.swellrt.beta.client.js.editor.SEditorException;
import org.waveprotocol.wave.client.common.util.JsoView;
import org.waveprotocol.wave.client.doodad.annotation.GeneralAnnotationHandler;
import org.waveprotocol.wave.client.doodad.link.LinkAnnotationHandler;
import org.waveprotocol.wave.client.editor.Editor;
import org.waveprotocol.wave.client.editor.content.ContentElement;
import org.waveprotocol.wave.client.editor.content.misc.AnnotationPaint;
import org.waveprotocol.wave.client.editor.content.misc.StyleAnnotationHandler;
import org.waveprotocol.wave.client.editor.content.paragraph.Paragraph;
import org.waveprotocol.wave.client.editor.content.paragraph.Paragraph.LineStyle;
import org.waveprotocol.wave.client.editor.content.paragraph.ParagraphBehaviour;
import org.waveprotocol.wave.model.conversation.AnnotationConstants;
import org.waveprotocol.wave.model.document.util.Range;
import org.waveprotocol.wave.model.util.CollectionUtils;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsonUtils;
import jsinterop.annotations.JsIgnore;
import jsinterop.annotations.JsType;
/**
* Putting together all annotation stuff
* to ease handling in the SwellRT editor component.
* <p>
* <br>
* List of text annotation handlers:
* <p>
* <li>{@link StyleAnnotationHandler}: handler for style annotations</li>
* <li>{@link LinkAnnotationHandler}: handler for link annotations</li>
* <li>{@link GeneralAnnotationHandler}: handler for custom annotations</li>
* <p><br>
* Rendering logic for previous annotations are in following classes:
* <p>
* <li>{@link AnnotationPaint}: utility methods and constants for annotation renderers</li>
* <li>{@link AnnotationSpreadRenderer}: html renderer for style and custom annotations</li>
*
* <p><br>
* For Paragraph annotations check out {@link Paragraph} class.
*
* @author pablojan@gmail.com (Pablo Ojanguren)
*
*/
@JsType(namespace = "swellrt.Editor", name = "AnnotationRegistry")
public class AnnotationRegistry {
public static final String PARAGRAPH_HEADER = "paragraph/header";
public static final String PARAGRAPH_TEXT_ALIGN = "paragraph/textAlign";
public static final String PARAGRAPH_LIST = "paragraph/list";
public static final String PARAGRAPH_INDENT = "paragraph/indent";
public static final String STYLE_BG_COLOR = AnnotationConstants.STYLE_BG_COLOR;
public static final String STYLE_COLOR = AnnotationConstants.STYLE_COLOR;
public static final String STYLE_FONT_FAMILY = AnnotationConstants.STYLE_FONT_FAMILY;
public static final String STYLE_FONT_SIZE = AnnotationConstants.STYLE_FONT_SIZE;
public static final String STYLE_FONT_STYLE = AnnotationConstants.STYLE_FONT_STYLE;
public static final String STYLE_FONT_WEIGHT = AnnotationConstants.STYLE_FONT_WEIGHT;
public static final String STYLE_TEXT_DECORATION = AnnotationConstants.STYLE_TEXT_DECORATION;
public static final String STYLE_VERTICAL_ALIGN = AnnotationConstants.STYLE_VERTICAL_ALIGN;
public static final String LINK = AnnotationConstants.LINK_PREFIX;
private static final JsoView CANONICAL_NAMES = JsoView.create();
private final static Map<String, Annotation> store = new HashMap<String, Annotation>();
protected static boolean muteHandlers = true;
/**
* Define friendly names for annotation referencing in SEditor
*/
static {
//
// Map annotation names without prefix to their canonical name
// for the Wave system.
//
CANONICAL_NAMES.setString("header", PARAGRAPH_HEADER);
CANONICAL_NAMES.setString("textAlign", PARAGRAPH_TEXT_ALIGN);
CANONICAL_NAMES.setString("list", PARAGRAPH_LIST);
CANONICAL_NAMES.setString("indent", PARAGRAPH_INDENT);
CANONICAL_NAMES.setString("backgroundColor", STYLE_BG_COLOR);
CANONICAL_NAMES.setString("color", STYLE_COLOR);
CANONICAL_NAMES.setString("fontFamily", STYLE_FONT_FAMILY);
CANONICAL_NAMES.setString("fontSize", STYLE_FONT_SIZE);
CANONICAL_NAMES.setString("fontStyle", STYLE_FONT_STYLE);
CANONICAL_NAMES.setString("fontWeight", STYLE_FONT_WEIGHT);
CANONICAL_NAMES.setString("textDecoration", STYLE_TEXT_DECORATION);
CANONICAL_NAMES.setString("verticalAlign", STYLE_VERTICAL_ALIGN);
//
// Paragraph Headers
//
Map<String, LineStyle> m = new HashMap<String, LineStyle>();
m.put("h1", Paragraph.regularStyle("h1"));
m.put("h2", Paragraph.regularStyle("h2"));
m.put("h3", Paragraph.regularStyle("h3"));
m.put("h4", Paragraph.regularStyle("h4"));
m.put("h5", Paragraph.regularStyle("h5"));
m.put("default", Paragraph.regularStyle(""));
store.put(PARAGRAPH_HEADER, new ParagraphValueAnnotation(ParagraphBehaviour.HEADING, PARAGRAPH_HEADER, m, new Annotation.AttributeGenerator() {
@Override
public Map<String, String> generate(Range range, String styleKey) {
// This code auto generates an id for each header! so we can reference them in the DOM
Date now = new Date();
String id = String.valueOf(now.getTime()) +
String.valueOf(range.getStart()) +
String.valueOf(range.getEnd());
return CollectionUtils.<String, String> immutableMap(Paragraph.ID_ATTR, id);
}
}));
//
// Paragraph Text alignment
//
m = new HashMap<String, LineStyle>();
m.put("left", Paragraph.Alignment.LEFT);
m.put("center", Paragraph.Alignment.CENTER);
m.put("right", Paragraph.Alignment.RIGHT);
m.put("justify", Paragraph.Alignment.JUSTIFY);
m.put("default", Paragraph.Alignment.LEFT);
store.put(PARAGRAPH_TEXT_ALIGN, new ParagraphValueAnnotation(ParagraphBehaviour.DEFAULT, PARAGRAPH_TEXT_ALIGN, m, null));
//
// Paragraph List
//
m = new HashMap<String, LineStyle>();
m.put("decimal", Paragraph.listStyle(Paragraph.LIST_STYLE_DECIMAL));
m.put("unordered", Paragraph.listStyle(null));
m.put("default", Paragraph.listStyle(null));
store.put(PARAGRAPH_LIST, new ParagraphValueAnnotation(ParagraphBehaviour.LIST, PARAGRAPH_LIST, m, null));
//
// Paragraph indentation
//
Map<String, ContentElement.Action> ma = new HashMap<String, ContentElement.Action>();
ma.put("outdent", Paragraph.OUTDENTER);
ma.put("indent", Paragraph.INDENTER);
store.put(PARAGRAPH_INDENT, new ParagraphActionAnnotation(ma, Paragraph.RESET_INDENT));
//
// Style Annotations
//
store.put(AnnotationConstants.STYLE_BG_COLOR, new TextAnnotation(AnnotationConstants.STYLE_BG_COLOR));
store.put(AnnotationConstants.STYLE_COLOR, new TextAnnotation(AnnotationConstants.STYLE_COLOR));
store.put(AnnotationConstants.STYLE_FONT_FAMILY, new TextAnnotation(AnnotationConstants.STYLE_FONT_FAMILY));
store.put(AnnotationConstants.STYLE_FONT_SIZE, new TextAnnotation(AnnotationConstants.STYLE_FONT_SIZE));
store.put(AnnotationConstants.STYLE_FONT_STYLE, new TextAnnotation(AnnotationConstants.STYLE_FONT_STYLE));
store.put(AnnotationConstants.STYLE_FONT_WEIGHT, new TextAnnotation(AnnotationConstants.STYLE_FONT_WEIGHT));
store.put(AnnotationConstants.STYLE_TEXT_DECORATION, new TextAnnotation(AnnotationConstants.STYLE_TEXT_DECORATION));
store.put(AnnotationConstants.STYLE_VERTICAL_ALIGN, new TextAnnotation(AnnotationConstants.STYLE_VERTICAL_ALIGN));
store.put(AnnotationConstants.LINK_PREFIX, new TextAnnotation(AnnotationConstants.LINK_PREFIX));
}
@JsIgnore
public static Set<String> getNames() {
return store.keySet();
}
@JsIgnore
public static void forEach(BiConsumer<String, Annotation> action) {
store.forEach(action);
}
@JsIgnore
public static Annotation get(String name) {
String canonicalName = CANONICAL_NAMES.getString(name);
return store.get(canonicalName != null ? canonicalName : name);
}
protected static boolean isParagraphAnnotation(String name) {
String canonicalName = CANONICAL_NAMES.getString(name);
name = canonicalName != null ? canonicalName : name;
return name.startsWith("paragraph/");
}
/**
* Define a new custom annotation.
*
* @param name annotation's name
* @param cssClass a css class for the html container
* @param cssStyle css styles for the html container
*/
public static void define(String name, String cssClass, JavaScriptObject cssStyleObj) throws SEditorException {
if (name == null || name.startsWith("paragraph") || name.startsWith(AnnotationConstants.STYLE_PREFIX) || CANONICAL_NAMES.getString(name) != null) {
throw new SEditorException("Not valid annotation name");
}
JsoView styles = null;
if (JsUtils.isString(cssStyleObj)) {
JavaScriptObject o = JsonUtils.unsafeEval(cssStyleObj.toString());
if (o != null) {
styles = JsoView.as(o);
}
} else {
styles = JsoView.as(cssStyleObj);
}
store.put(name, new CustomTextAnnotation(name));
GeneralAnnotationHandler.register(Editor.ROOT_REGISTRIES, name, cssClass, styles);
}
/**
* Set a handler for events on one annotation type
* @param name
* @param handler
*/
public static void setHandler(String name, AnnotationInstance.Handler handler) {
Annotation antn = get(name);
if (antn != null && antn instanceof Annotation.Listenable) {
Annotation.Listenable ta = (Annotation.Listenable) antn;
ta.setHandler(handler);
}
}
/**
* Clear the handler for events on one annotation type
* @param name
* @param handler
*/
public static void unsetHandler(String name) {
Annotation antn = get(name);
if (antn != null && antn instanceof Annotation.Listenable) {
Annotation.Listenable ta = (Annotation.Listenable) antn;
ta.setHandler(null);
}
}
public static void muteHandlers(boolean mute) {
muteHandlers = mute;
}
}