Skip to content

Instantly share code, notes, and snippets.

@hadrienl
Created February 7, 2013 16:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hadrienl/4732118 to your computer and use it in GitHub Desktop.
Save hadrienl/4732118 to your computer and use it in GitHub Desktop.
/**
* WikiDOM tools
* @module ob/plugins/wikidom/wikidom
*/
var NS = 'ob.plugins.wikidom';
CHILD_PARAGRAPH = 'paragraph',
CHILD_HEADING = 'heading',
CHILD_PRE = 'pre',
CHILD_LIST = 'list',
CHILD_LIST_ITEM = 'listItem',
ANNOTATION_STRONG = 'textStyle/strong',
ANNOTATION_EM = 'textStyle/emphasize',
ANNOTATION_DEL = 'textStyle/delete',
ANNOTATION_LINK = 'link/external',
LIST_TYPE_BULLET = 'bullet',
LIST_TYPE_NUMBER = 'number',
CHILD_TYPES = [CHILD_PARAGRAPH, CHILD_HEADING, CHILD_PRE, CHILD_LIST, CHILD_LIST_ITEM],
CHILD_TYPES_CFG = [{
tagNames: ['p']
}, {
tagNames: ['h1', 'h2', 'h3'],
attributes: [{
level: 1
}, {
level: 2
}, {
level: 3
}]
}, {
tagNames: ['pre']
}, {
tagNames: ['ul', 'ol'],
children: [CHILD_LIST_ITEM]
}, {
tagNames: ['li'],
children: [CHILD_LIST, CHILD_PARAGRAPH]
}],
ANNOTATIONS_TYPES = [ANNOTATION_STRONG, ANNOTATION_EM, ANNOTATION_DEL, ANNOTATION_LINK],
ANNOTATIONS_TYPES_CFG = [{
tagNames: ['strong', 'b'],
hasData: false
}, {
tagNames: ['em', 'i'],
hasData: false
}, {
tagNames: ['del', 'strike'],
hasData: false
}, {
tagNames: ['a'],
hasData: true
}],
/**
* Annotation object
* @class Annotation
* @namespace Y.ob.plugins.wikidom
* @extends Y.Base
*/
Annotation = Y.Base.create('Annotation', Y.Base, [], {
asJson: function()
{
var annotation = {
type: this.get('type'),
range: this.get('range')
},
data = this.get('data');
if (data)
{
annotation.data = data;
}
return annotation;
},
/**
* Tell if this annotation need data attribute
*/
hasData: function()
{
var cfg = ANNOTATIONS_TYPES_CFG[
Y.Array.indexOf(ANNOTATIONS_TYPES, this.get('type'))];
return cfg && cfg.hasData;
},
/**
* Get the HTML tag corresponding to this annotation
* @method getTag
*
*/
getTag: function(begin)
{
var type = this.get('type'),
tag = '<';
if (!begin)
{
tag += '/';
}
tag += ANNOTATIONS_TYPES_CFG[
Y.Array.indexOf(ANNOTATIONS_TYPES, type)
].tagNames[0];
if (begin &&
ANNOTATION_LINK === type)
{
tag += ' href="';
tag += this.get('data.title');
tag +='"';
}
tag += '>';
return tag;
}
}, {
ATTRS: {
type: {
validator: function(v)
{
return ANNOTATIONS_TYPES.indexOf(v) > -1;
}
},
range: {
getter: function()
{
return {
start: this.get('range_start'),
end: this.get('range_end')
}
}
},
range_start: {
value: 0,
validator: function(v)
{
return Y.Lang.isNumber(v);
}
},
range_end: {
value: 0,
validator: function(v)
{
return Y.Lang.isNumber(v);
}
},
data: {
value: null,
validator: function(v)
{
if (ANNOTATION_LINK === this.get('type') &&
Y.Lang.isObject(v) &&
v.title)
{
return true;
}
return false;
}
}
}
}),
/**
* Content object
* @class Content
* @namespace Y.ob.plugins.wikidom
* @extends Y.Base
*/
Content = Y.Base.create('Content', Y.Base, [], {
addAnnotation: function(a)
{
if (!a.name)
{
a = new Annotation(a);
}
this.get('annotations').push(a);
},
asJson: function()
{
var content = {
text: this.get('text')
},
annotations = [];
Y.each(
this.get('annotations'),
function(a)
{
annotations.push(a.asJson());
}
);
if (annotations.length)
{
content.annotations = annotations;
}
return content;
}
}, {
ATTRS: {
text: {
value: '',
validator: function(v)
{
return Y.Lang.isString(v);
}
},
annotations: {
valueFn: function()
{
return [];
},
setter: function(v)
{
var annotations = this.get('annotations');
if (!Y.Lang.isArray(v))
{
v = [v];
}
Y.each(
v,
function(a)
{
a = new Annotation({
type: a.type,
data: a.data,
range_start: a.range.start,
range_end: a.range.end
});
annotations.push(a);
},
this
);
return annotations;
}
}
}
}),
/**
* Document object : base of WikiDOM object
* @class Document
* @namespace Y.ob.plugins.wikidom
* @extends Y.Base
*/
Document = Y.Base.create('Document', Y.Base, [], {
addChild: function(child)
{
if (!child.name ||
child.name !== 'Child')
{
child = new Child(child);
}
if (CHILD_LIST === this.get('type'))
{
child.set('attributes.styles', this.get('attributes.styles'));
}
this.get('children').push(child);
return child;
},
addChildren: function(children)
{
var ret = [];
if (!Y.Lang.isArray(children))
{
children = [children];
}
Y.each(children, function(child)
{
ret.push(
this.addChild(child)
);
}, this);
return ret;
},
/**
* Return Document transformed in a JSON chain
* @return json
*/
asJson: function()
{
var doc = {
type: this.get('type'),
children: []
};
Y.each(
this.get('children'),
function(c)
{
doc.children.push(c.asJson());
}
);
return doc;
},
hasChild: function()
{
return [CHILD_PARAGRAPH, CHILD_HEADING, CHILD_PRE, CHILD_LIST];
},
canHaveChild: function(tag)
{
var type, children;
Y.some(
CHILD_TYPES_CFG,
function(c, i)
{
if (Y.Array.indexOf(c.tagNames, tag) > -1)
{
type = CHILD_TYPES[i];
return true;
}
}
);
children = this.hasChild();
return children && Y.Array.indexOf(children, type) > -1;
}
}, {
ATTRS: {
type: {
value: 'document',
readOnly: true
},
children: {
valueFn: function()
{
return [];
},
setter: function(v)
{
// to review
var children = [];
Y.each(v, function(c)
{
if (!c)
{
return;
}
if (!c.name || 'Child' !== c.name)
{
c = new Child(c);
}
children.push(c);
});
return children;
}
}
}
}),
/**
* Child object
* @class Child
* @namespace Y.ob.plugins.wikidom
* @extends Y.ob.plugins.wikidom.Document
*/
Child = Y.Base.create('Child', Document, [], {
initializer: function(config)
{
if (config && config.content)
{
this.get('content').setAttrs(config.content);
}
},
asJson: function()
{
var child = {
type: this.get('type'),
content: this.get('content').asJson()
},
children = [],
attributes = this.get('attributes');
Y.each(
this.get('children'),
function(c)
{
children.push(c.asJson());
}
);
if (children.length)
{
child.children = children;
}
if (attributes && Y.Object.keys(attributes).length)
{
if (attributes.styles &&
CHILD_LIST_ITEM !== child.type)
{
delete attributes.styles;
}
}
if (attributes && Y.Object.keys(attributes).length)
{
child.attributes = attributes;
}
return child;
},
hasChild: function()
{
var cfg = CHILD_TYPES_CFG[
Y.Array.indexOf(CHILD_TYPES, this.get('type'))];
return cfg && cfg.children;
},
getTag: function()
{
var type = this.get('type'),
children = this.get('children'),
tag, listtype;
switch (type)
{
case CHILD_PARAGRAPH:
tag = 'p';
break;
case CHILD_HEADING:
tag = 'h';
tag += this.get('attributes.level');
break;
case CHILD_PRE:
tag = 'pre';
break;
case CHILD_LIST:
if (children.length)
{
listtype = children[0].get('attributes.styles');
listtype = listtype ? listtype[0] : null;
}
else
{
listtype = this.get('attributes.styles');
if (listtype)
{
listtype = listtype[0];
}
}
if (LIST_TYPE_NUMBER === listtype)
{
tag = 'ol';
}
else
{
tag = 'ul';
}
break;
case CHILD_LIST_ITEM:
tag = 'li';
break;
}
return tag;
}
}, {
ATTRS: {
type: {
value: CHILD_PARAGRAPH,
validator: function(v)
{
return CHILD_TYPES.indexOf(v) > -1;
},
readOnly: false
},
content: {
valueFn: function()
{
return new Content();
},
readOnly: true
},
attributes: {
valueFn: function(v)
{
return {
level: null,
styles: null
};
},
setter: function(v, key)
{
var children;
if ('attributes.styles' === key)
{
if (!Y.Lang.isArray(v.styles))
{
v.styles = [v.styles];
}
if (CHILD_LIST === this.get('type'))
{
children = this.get('children');
Y.each(
children,
function(c)
{
c.set(key, v.styles);
}
);
}
}
if (!Y.Lang.isObject(v))
{
v = {};
}
return v;
}
}
}
});
/**
* Get an annotation object corresponding to a given tag name
* @method createAnnotationFromTag
* @param {String} tag HTML tag which will be determine the Annotation type
* @class Annotation
* @static
*/
Annotation.createAnnotationFromTag = function(tag)
{
var index;
Y.some(
ANNOTATIONS_TYPES_CFG,
function(c, i)
{
if (Y.Array.indexOf(c.tagNames, tag) > -1)
{
index = i;
return true;
}
}
);
if (Y.Lang.isNull(index))
{
return;
}
return new Annotation({
type: ANNOTATIONS_TYPES[index]
});
};
/**
* Get a regexp fragment with all html tag which can be an annotation
* @method getTagsRegExp
* @class Annotation
* @static
*/
Annotation.getTagsRegExp = function()
{
return 'a|strong|b|em|i|del|strike';
};
/**
* Create a new Child object from a html tag
* @method getTagsRegExp
* @param {String} tag HTML tag which will specify the Child type
* @class Child
* @static
*/
Child.createChildFromTag = function(tag)
{
var index = null,
attributes = {},
tagindex = null;
Y.some(
CHILD_TYPES_CFG,
function(c, i)
{
tagindex = Y.Array.indexOf(c.tagNames, tag);
if (tagindex > -1)
{
index = i;
return true;
}
}
);
if ('ul' === tag)
{
attributes.styles = [LIST_TYPE_BULLET];
}
if ('ol' === tag)
{
attributes.styles = [LIST_TYPE_NUMBER];
}
if (Y.Lang.isNull(index))
{
return null;
}
if (CHILD_TYPES_CFG[index].attributes)
{
attributes = Y.mix(
CHILD_TYPES_CFG[index].attributes[tagindex],
attributes
);
}
return new Child({
type: CHILD_TYPES[index],
attributes: attributes
});
};
/**
* Get a regexp fragment with all html tag which can be a child
* @method getTagsRegExp
* @class Child
* @static
*/
Child.getTagsRegExp = function()
{
return 'pre|p|h[1-3]|ul|ol|li';
};
/**
* Export classes and constants
*/
Y.each(
{
CHILD_PARAGRAPH: CHILD_PARAGRAPH,
CHILD_HEADING: CHILD_HEADING,
CHILD_PRE: CHILD_PRE,
CHILD_LIST: CHILD_LIST,
CHILD_LIST_ITEM: CHILD_LIST_ITEM,
ANNOTATION_STRONG: ANNOTATION_STRONG,
ANNOTATION_EM: ANNOTATION_EM,
ANNOTATION_DEL: ANNOTATION_DEL,
ANNOTATION_LINK: ANNOTATION_LINK,
LIST_TYPE_BULLET: LIST_TYPE_BULLET,
LIST_TYPE_NUMBER: LIST_TYPE_NUMBER,
Annotation: Annotation,
Content: Content,
Document: Document,
Child: Child
},
function(c, n)
{
Y.namespace(NS)[n] = c;
}
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment