diff --git a/src/cobalt.html.js b/src/cobalt.html.js
index b89e11b..58f7172 100644
--- a/src/cobalt.html.js
+++ b/src/cobalt.html.js
@@ -1,5 +1,11 @@
+/**
+ * Cobalt simplified HTML rules and rendering
+ */
cobalt.html = (function(html) {
+ /**
+ * These rules define the behaviour of the rendering as well as the editor.
+ */
var rules = {
block: ['h1','h2','h3','p','ol','ul','li','blockquote','br'],
inline: ['em','strong','a'],
@@ -9,7 +15,17 @@ cobalt.html = (function(html) {
},
obligatoryParent: {
'li': ['ol','ul']
- }
+ },
+ nextTag: {
+ 'h1' : 'p',
+ 'h2' : 'p',
+ 'h3' : 'p',
+ 'p' : 'p',
+ 'li' : 'li'
+ },
+ cannotHaveChildren: {
+ 'br'
+ },
};
rules.alltags = rules.block.concat(rules.inline);
rules.nesting: {
@@ -27,25 +43,153 @@ cobalt.html = (function(html) {
};
rules.toplevel = rules.block.filter(function(tag) { return tag!='li';});
+ var constraints = {
+ hasValidParent: function(entry, stack, position) {
+ var parent = stack[position] ? stack[position] : {tag:''};
+ return rules.nesting[parent.tag].includes(entry.tag);
+ },
+ hasValidChild: function(entry, stack, position) {
+ var child = stack[position+1] ? stack[position+1] : {tag:''};
+ var tag = entry.tag;
+ if ( rules.obligatoryChild(tag) ) {
+ tag = rules.obligatoryChild(entry.tag);
+ }
+ return rules.cannotHaveChildren(tag) // e.g. BR, is autoclosing, so other tags aren't contained by it
+ || rules.nesting[tag].includes(child.tag);
+ },
+ scorePosition: function(entry, stack, position) {
+ // return a number - 0 is optimal, anything larger is less optimal
+ // best position means that all parents have starting range offset <= this
+ // all children have starting range offset >= this
+ // add offsets that violate this to the score with the score the distance between this start and theirs.
+ }
+ }
+
+ function getRelativeList(annotations) {
+ if ( !annotations || !annotations.length ) { return []; }
+ var list = [];
+ annotations.foreach(function(annotation) {
+ annotation.range.foreach(function(range)
+ list.push({
+ type: 'start',
+ annotation: annotation,
+ position: range.start
+ });
+ list.push({
+ type: 'end',
+ annotation: annotation,
+ position: range.end
+ });
+ });
+ });
+ list.sort(function(a,b) {
+ return a.offset < b.offset ? -1 : 1;
+ });
+ list.reduce(function(position, entry) {
+ entry.offset = entry.position - position;
+ delete entry.position;
+ return position + entry.offset;
+ }, 0);
+ return list;
+ }
+
+ function arrangeTags(tags) {
+ // get best scoring set of tags that also validate the rules
+ // tags that start later have a bonus over tags that start earlier
+ // so more specific tags override more generic tags
+ // each tag has the same score for now, 1
+ // so more valid tags is better
+ // stack order indicates html order, parent in stack is a parent in html
+ // so nesting rules must be valid through the stack
+
+ // 0: find nearest blocklevel element, add it to the stack
+ // 1: in order, add as much block level tags as you can, start at the top of the stack, search untill
+ // a place in the stack is valid for this tag, so a valid parent and a valid child
+ // any tag that has no place is kept in a temporary list
+ // tags that have a start offset before the current tag, prefer to be in the stack above the current tag
+ // tags that start later, prefer to be lower in the stack
+ // this makes html nesting behave as expected most of the time, unless this breaks other rules
+ // so search for the optimal place in the stack first, then go up the stack, round robin over position 0,
+ // untill you reach your start position or you find a place for the tag
+ // 2: do the same for inline elements
+ // 3: foreach skipped element create a stack with that element as the first element, add as many elements
+ // as possible, inline elements need a valid block element as parent, so make sure you find one or skip
+ // the element entirely if no valid parent exists.
+ // 3: tallest stack wins
+ // note: ol en ul add an li for each subrange automatically, so you don't need to specify li in
+ // the cobalt fragment by hand, just cut the range in parts, seperated by at least 1 char, e.g. \n
+ // e.g: "0-10,12-20:ol" should be enough
+
+ // approach:
+ // create a list of functions that check constraints, returning true if the constraint is met, false if not
+ // when considering a position in the stack for a tag/element, check each constraint.
+ // update: allow numeric scores, 0 is optimal, negative numbers means invalid
+ // can this be used for a mathematically sound constraint based approach?
+ }
+
+ function getStackList(relativeList) {
+ // 1: create a list of stacks of tags - this version ignores empty elements/tags
+ var stacklist = [{offset: 0, tags: []}];
+ relativeList.forEach(function(entry) {
+ if ( entry.offset ) {
+ stacklist.push({ offset: entry.offset, tags: stacklist[ stacklist.length-1 ].tags });
+ }
+ stackentry = stacklist[ stacklist.length-1 ];
+ if ( entry.type == 'start' ) {
+ stackentry.tags.push(entry.annotation);
+ } else if ( entry.type == 'end' ) {
+ stackentry.tags = stackentry.tags.filter(function(annotation) {
+ if ( annotation == entry.annotation ) {
+ return false;
+ }
+ return true;
+ });
+ }
+ });
+ // 2: rearrange / filter tags to match the rules
+ // FIMXE: entry.tags has no information if an annotation is used more than once
+ // this means we cannot assure an html id is only used once, so all id's must be renamed to
+ // something else, e.g. data-cobalt-id=id, but this can also be done when merging
+ stacklist.forEach(function(entry) {
+ entry.tags = arrangeTags(entry.tags);
+ });
+ return stacklist;
+ }
+
+ function mergeContentAndTags(content, stacklist) {
+
+ }
html.cobaltToHtml = function(fragment) {
- var result = '';
-
- return result;
+ /*
+ TODO: find a way to break up a single cobaltToHtml call into many seperate
+ calls that can be concatenated. This allows the cobalt objects to pre-render upon creation
+ which means that only changes have to be rendered again.
+ possible way to do this is to use the stacklist as the base, each entry in there contains the
+ full stack of tags for a part of the content
+ externalise this list, make the entries immutable, and use this in the editor as the model
+ then use something like react.js to only render changes in the browser dom
+ */
+ //1: create a relative offsets list of annotation start and end points
+ var relativeList = getRelativeList(fragment.annotations);
+ //2: for each point in the relative offsets list, create a valid stack of html tags
+ var stackList = getStackList(relativeList);
+ //3: merge content and html tags
+ return mergeContentAndTags(fragment.content, stackList);
};
-
- html.cobaltToDom = function(fragment, dom) {
-
- };
-
- html.cobaltDomToHtmlDom = function(relativeFragment, dom) {
- // cobalt fragment with relative offsets and links to html nodes to html dom
- // cobalt dom serves as a shadow dom for the html dom
- };
-
+
html.htmlToCobalt = function(htmlString) {
-
+
return fragment;
};
-
+
+ html.escapeContent = function( content ) {
+ return content
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/"/g, """)
+ .replace(/'/g, "'");
+ };
+
})(cobalt.html || {});
\ No newline at end of file