Skip to content

Commit 4498030

Browse files
committed
started work on html render algorythm
1 parent fd74b58 commit 4498030

File tree

1 file changed

+160
-16
lines changed

1 file changed

+160
-16
lines changed

src/cobalt.html.js

Lines changed: 160 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1+
/**
2+
* Cobalt simplified HTML rules and rendering
3+
*/
14
cobalt.html = (function(html) {
25

6+
/**
7+
* These rules define the behaviour of the rendering as well as the editor.
8+
*/
39
var rules = {
410
block: ['h1','h2','h3','p','ol','ul','li','blockquote','br'],
511
inline: ['em','strong','a'],
@@ -9,7 +15,17 @@ cobalt.html = (function(html) {
915
},
1016
obligatoryParent: {
1117
'li': ['ol','ul']
12-
}
18+
},
19+
nextTag: {
20+
'h1' : 'p',
21+
'h2' : 'p',
22+
'h3' : 'p',
23+
'p' : 'p',
24+
'li' : 'li'
25+
},
26+
cannotHaveChildren: {
27+
'br'
28+
},
1329
};
1430
rules.alltags = rules.block.concat(rules.inline);
1531
rules.nesting: {
@@ -27,25 +43,153 @@ cobalt.html = (function(html) {
2743
};
2844
rules.toplevel = rules.block.filter(function(tag) { return tag!='li';});
2945

46+
var constraints = {
47+
hasValidParent: function(entry, stack, position) {
48+
var parent = stack[position] ? stack[position] : {tag:''};
49+
return rules.nesting[parent.tag].includes(entry.tag);
50+
},
51+
hasValidChild: function(entry, stack, position) {
52+
var child = stack[position+1] ? stack[position+1] : {tag:''};
53+
var tag = entry.tag;
54+
if ( rules.obligatoryChild(tag) ) {
55+
tag = rules.obligatoryChild(entry.tag);
56+
}
57+
return rules.cannotHaveChildren(tag) // e.g. BR, is autoclosing, so other tags aren't contained by it
58+
|| rules.nesting[tag].includes(child.tag);
59+
},
60+
scorePosition: function(entry, stack, position) {
61+
// return a number - 0 is optimal, anything larger is less optimal
62+
// best position means that all parents have starting range offset <= this
63+
// all children have starting range offset >= this
64+
// add offsets that violate this to the score with the score the distance between this start and theirs.
65+
}
66+
}
67+
68+
function getRelativeList(annotations) {
69+
if ( !annotations || !annotations.length ) { return []; }
70+
var list = [];
71+
annotations.foreach(function(annotation) {
72+
annotation.range.foreach(function(range)
73+
list.push({
74+
type: 'start',
75+
annotation: annotation,
76+
position: range.start
77+
});
78+
list.push({
79+
type: 'end',
80+
annotation: annotation,
81+
position: range.end
82+
});
83+
});
84+
});
85+
list.sort(function(a,b) {
86+
return a.offset < b.offset ? -1 : 1;
87+
});
88+
list.reduce(function(position, entry) {
89+
entry.offset = entry.position - position;
90+
delete entry.position;
91+
return position + entry.offset;
92+
}, 0);
93+
return list;
94+
}
95+
96+
function arrangeTags(tags) {
97+
// get best scoring set of tags that also validate the rules
98+
// tags that start later have a bonus over tags that start earlier
99+
// so more specific tags override more generic tags
100+
// each tag has the same score for now, 1
101+
// so more valid tags is better
102+
// stack order indicates html order, parent in stack is a parent in html
103+
// so nesting rules must be valid through the stack
104+
105+
// 0: find nearest blocklevel element, add it to the stack
106+
// 1: in order, add as much block level tags as you can, start at the top of the stack, search untill
107+
// a place in the stack is valid for this tag, so a valid parent and a valid child
108+
// any tag that has no place is kept in a temporary list
109+
// tags that have a start offset before the current tag, prefer to be in the stack above the current tag
110+
// tags that start later, prefer to be lower in the stack
111+
// this makes html nesting behave as expected most of the time, unless this breaks other rules
112+
// so search for the optimal place in the stack first, then go up the stack, round robin over position 0,
113+
// untill you reach your start position or you find a place for the tag
114+
// 2: do the same for inline elements
115+
// 3: foreach skipped element create a stack with that element as the first element, add as many elements
116+
// as possible, inline elements need a valid block element as parent, so make sure you find one or skip
117+
// the element entirely if no valid parent exists.
118+
// 3: tallest stack wins
119+
// note: ol en ul add an li for each subrange automatically, so you don't need to specify li in
120+
// the cobalt fragment by hand, just cut the range in parts, seperated by at least 1 char, e.g. \n
121+
// e.g: "0-10,12-20:ol" should be enough
122+
123+
// approach:
124+
// create a list of functions that check constraints, returning true if the constraint is met, false if not
125+
// when considering a position in the stack for a tag/element, check each constraint.
126+
// update: allow numeric scores, 0 is optimal, negative numbers means invalid
127+
// can this be used for a mathematically sound constraint based approach?
128+
}
129+
130+
function getStackList(relativeList) {
131+
// 1: create a list of stacks of tags - this version ignores empty elements/tags
132+
var stacklist = [{offset: 0, tags: []}];
133+
relativeList.forEach(function(entry) {
134+
if ( entry.offset ) {
135+
stacklist.push({ offset: entry.offset, tags: stacklist[ stacklist.length-1 ].tags });
136+
}
137+
stackentry = stacklist[ stacklist.length-1 ];
138+
if ( entry.type == 'start' ) {
139+
stackentry.tags.push(entry.annotation);
140+
} else if ( entry.type == 'end' ) {
141+
stackentry.tags = stackentry.tags.filter(function(annotation) {
142+
if ( annotation == entry.annotation ) {
143+
return false;
144+
}
145+
return true;
146+
});
147+
}
148+
});
149+
// 2: rearrange / filter tags to match the rules
150+
// FIMXE: entry.tags has no information if an annotation is used more than once
151+
// this means we cannot assure an html id is only used once, so all id's must be renamed to
152+
// something else, e.g. data-cobalt-id=id, but this can also be done when merging
153+
stacklist.forEach(function(entry) {
154+
entry.tags = arrangeTags(entry.tags);
155+
});
156+
return stacklist;
157+
}
158+
159+
function mergeContentAndTags(content, stacklist) {
160+
161+
}
30162

31163
html.cobaltToHtml = function(fragment) {
32-
var result = '';
33-
34-
return result;
164+
/*
165+
TODO: find a way to break up a single cobaltToHtml call into many seperate
166+
calls that can be concatenated. This allows the cobalt objects to pre-render upon creation
167+
which means that only changes have to be rendered again.
168+
possible way to do this is to use the stacklist as the base, each entry in there contains the
169+
full stack of tags for a part of the content
170+
externalise this list, make the entries immutable, and use this in the editor as the model
171+
then use something like react.js to only render changes in the browser dom
172+
*/
173+
//1: create a relative offsets list of annotation start and end points
174+
var relativeList = getRelativeList(fragment.annotations);
175+
//2: for each point in the relative offsets list, create a valid stack of html tags
176+
var stackList = getStackList(relativeList);
177+
//3: merge content and html tags
178+
return mergeContentAndTags(fragment.content, stackList);
35179
};
36-
37-
html.cobaltToDom = function(fragment, dom) {
38-
39-
};
40-
41-
html.cobaltDomToHtmlDom = function(relativeFragment, dom) {
42-
// cobalt fragment with relative offsets and links to html nodes to html dom
43-
// cobalt dom serves as a shadow dom for the html dom
44-
};
45-
180+
46181
html.htmlToCobalt = function(htmlString) {
47-
182+
48183
return fragment;
49184
};
50-
185+
186+
html.escapeContent = function( content ) {
187+
return content
188+
.replace(/&/g, "&amp;")
189+
.replace(/</g, "&lt;")
190+
.replace(/>/g, "&gt;")
191+
.replace(/"/g, "&quot;")
192+
.replace(/'/g, "&#039;");
193+
};
194+
51195
})(cobalt.html || {});

0 commit comments

Comments
 (0)