')
.append($('')
.html($(elem).html())) },
// XXX expperimental...
json: function(context, elem){ return $('')
.html($(elem).text()
// remove JS comments...
.replace(/\s*\/\/.*$|\s*\/\*(.|[\n\r])*?\*\/\s*/mg, '')) },
// XXX
nl2br: function(context, elem){
return $('')
.html($(elem)
.html()
.replace(/\n/g, '
\n')) },
wikiword: function(context, elem){
return $('
')
.html(setWikiWords($(elem).html(), true, this.__include_marker__)) },
// XXX need to remove all on* event handlers...
noscript: function(context, elem){
return $(elem)
// remove script tags...
.find('script')
.remove()
.end()
// remove js links...
.find('[href]')
.filter(function(i, e){ return /javascript:/i.test($(e).attr('href')) })
.attr('href', '#')
.end()
.end()
// remove event handlers...
// XXX .off() will not work here as we need to remove on* handlers...
},
// XXX move this to a plugin...
markdown: function(context, elem){
var converter = new showdown.Converter({
strikethrough: true,
tables: true,
tasklists: true,
})
return $('')
.html(converter.makeHtml($(elem).html()))
// XXX add click handling to checkboxes...
.find('[checked]')
.parent()
.addClass('checked')
.end()
.end() },
},
// Post-filters...
//
// These are run on the final page.
//
// The main goal is to setup editors and other active stuff that the
// user should not have direct access to, but that should be
// configurable per instance...
//
// for tech and other details see .filter
//
post_filter: {
noscript: function(context, elem){
// XXX
return elem },
// Setup the page title and .title element...
//
// Use the text from:
// 1) set it H1 if it is the first tag in .text
// 2) set it to .location
//
// NOTE: we do not set the title tag here because this will be
// done for every included page...
title: function(context, elem){
elem = $(elem)
var title = elem.find('.text h1').first()
// show first H1 as title...
if(elem.find('.text').text().trim().indexOf(title.text().trim()) == 0){
title.detach()
elem.find('.title').html(title.html()) }
return elem },
// XXX this needs save/reload...
editor: function(context, elem){
// XXX title
// - on focus set it to .title
// XXX text
// XXX raw
// XXX checkbox
return elem },
},
// Parsing:
// 1) expand macros
// 2) apply filters
// 3) merge and parse included pages:
// 1) expand macros
// 2) apply filters
// 4) fill slots
// 5) expand post-macros
//
// NOTE: stage 4 parsing is executed on the final merged page only
// once. i.e. it is not performed on the included pages.
// NOTE: included pages are parsed in their own context.
// NOTE: slots are parsed in the context of their containing page
// and not in the location they are being placed.
//
// XXX support quoted text...
// XXX need to quote regexp chars of .__include_marker__...
// XXX include recursion is detected but path recursion is not at
// this point...
// e.g. the folowing paths resolve to the same page:
// /SomePage
// /SomePage/SomePage
// or any path matching:
// /\/(SomePage\/)+/
// XXX slow when lots of pages need to be included...
parse: function(context, text, state, skip_post, pattern){
var that = this
state = state || {}
state.filters = state.filters || []
//state.slots = state.slots || {}
state.include = state.include || []
state.seen = state.seen || []
//pattern = pattern || RegExp('@([a-zA-Z-_]+)\\(([^)]*)\\)', 'mg')
pattern = pattern
|| RegExp.apply(null, this.__macro__pattern__)
// XXX need to quote regexp chars...
var include_marker = RegExp(this.__include_marker__, 'g')
var parsed = typeof(text) == typeof('str') ?
$('').html(text)
: text
var _parseText = function(context, text, macro){
return text.replace(pattern, function(match){
// quoted macro...
if(match[0] == '\\' && macro['*'] == null){
return match.slice(1) }
//return match }
// XXX parse match...
var d = match.match(/@([a-zA-Z-_:]*)\(([^)]*)\)/)
var name = d[1]
if(name in macro || '*' in macro){
var elem = $('<'+name+'/>')
name = name in macro ?
name
: '*'
// format positional args....
var a = d[2]
.split(/((['"]).*?\2)|\s+/g)
// cleanup...
.filter(function(e){
return e
&& e != ''
&& !/^['"]$/.test(e)})
// remove quotes...
.map(function(e){
return /^(['"]).*\1$/.test(e) ?
e.slice(1, -1)
: e })
// add the attrs to the element...
name != '*'
&& a.forEach(function(e, i){
var k = ((macro[name] || {}).macro_args || [])[i]
k
&& elem.attr(k, e) })
// call macro...
var res = macro[name]
.call(that, context, elem, state,
function(elem, c){
return _parse(c || context, elem, macro) },
match)
return res instanceof jQuery ?
// merge html of the returned set of elements...
res.map(function(i, e){ return e.outerHTML })
.toArray()
.join('\n')
: typeof(res) != typeof('str') ?
res.outerHTML
: res }
return match }) }
// NOTE: this modifies parsed in-place...
var _parse = function(context, parsed, macro){
$(parsed).contents().each(function(_, e){
// #text / comment node -> parse the @... macros...
if(e.nodeType == e.TEXT_NODE
|| e.nodeType == e.COMMENT_NODE){
// get actual element content...
// NOTE: we need to do this like this to avoid
// unparsing special characters...
var text = $('').append($(e).clone()).html()
// conditional comment...
if(e.nodeType == e.COMMENT_NODE
&& /^$/.test(text)){
text = text
.replace(/^$/, '') }
$(e).replaceWith(_parseText(context, text, macro))
// node -> html-style + attrs...
} else {
var name = e.nodeName.toLowerCase()
// parse attr values...
for(var i=0; i < e.attributes.length; i++){
var attr = e.attributes[i]
attr.value = _parseText(context, attr.value, macro) }
// macro match -> call macro...
if(name in macro){
$(e).replaceWith(macro[name]
.call(that, context, e, state,
function(elem, c){
return _parse(c || context, elem, macro) }))
// normal tag -> sub-tree...
} else {
_parse(context, e, macro) } } })
return parsed }
var _filter = function(lst, filters){
lst
// unique -- leave last occurance..
.filter(function(k, i, lst){
return k[0] != '-'
// filter dupplicates...
&& lst.slice(i+1).indexOf(k) == -1
// filter disabled...
&& lst.slice(0, i).indexOf('-' + k) == -1 })
// unique -- leave first occurance..
//.filter(function(k, i, lst){ return lst.slice(0, i).indexOf(k) == -1 })
// apply the filters...
.forEach(function(f){
var k = f
// get filter aliases...
var seen = []
while(typeof(k) == typeof('str') && seen.indexOf(k) == -1){
seen.push(k)
k = filters[k] }
// could not find the filter...
if(!k){
//console.warn('Unknown filter:', f)
return }
// use the filter...
parsed = k.call(that, context, parsed) }) }
// macro stage...
_parse(context, parsed, this.macro)
// filter stage...
_filter(state.filters.concat(this.__filters__), this.filter)
// XXX DEBUG...
var t = Date.now()
console.log('>>>', context.path())
// merge includes...
parsed
.html(parsed.html()
.replace(include_marker, function(){
var page = state.include.shift()
var elem = $(page.shift())
page = page.pop()
var isolated = elem.attr('isolated') == 'true'
var seen = state.seen.slice()
if(seen.indexOf(page.path()) >= 0){
return elem.html() }
seen.push(page.path())
return page.map(function(page){
return $('
')
.append(elem
.clone()
.attr('src', page.path())
.append(that
.parse(page,
page.raw(),
{
//slots: !isolated ? state.slots : {},
templates: state.templates,
seen: seen,
},
!isolated)))
//true)))
.html()
}).join('\n') }))
// XXX DEBUG...
//console.log('<<<', context.path(),'TIME:', Date.now() - t)
// post processing...
if(!skip_post){
// fill slots...
// XXX need to prevent this from processing slots in editable
// elements...
slots = {}
// get slots...
parsed.find('slot')
.each(function(i, e){
e = $(e)
// XXX not sure about this...
// ...check if it prevents correct slot parsing
// within an isolated include...
if(e.parents('[isolated="true"]').length > 0){
return }
var n = e.attr('name')
n in slots
&& e.detach()
slots[n] = e })
// place slots...
parsed.find('slot')
.each(function(i, e){
e = $(e)
// XXX not sure about this...
// ...check if it prevents correct slot parsing
// within an isolated include...
if(e.parents('[isolated="true"]').length > 0){
return }
var n = e.attr('name')
e.replaceWith(slots[n]) })
// post-macro...
// XXX for some odd reason this clears the backslash from
// quoted macros in raw fields...
//this.post_macro
// && _parse(context, parsed, this.post_macro)
}
// post-filter stage...
// XXX get list from context.config...
_filter(this.__post_filters__, this.post_filter)
// XXX shuld we get rid of the root span???
return parsed.contents() },
}
/**********************************************************************
* vim:set ts=4 sw=4 : */ return module })