Files
2019-11-20 07:44:43 +01:00

3219 lines
83 KiB
JavaScript

/* Riot v2.3.15, @license MIT, (c) 2015 Muut Inc. + contributors */
;(function(window, undefined) {
'use strict';
var riot = { version: 'v2.3.15', settings: {} },
// be aware, internal usage
// ATTENTION: prefix the global dynamic variables with `__`
// counter to give a unique id to all the Tag instances
__uid = 0,
// tags instances cache
__virtualDom = [],
// tags implementation cache
__tagImpl = {},
/**
* Const
*/
// riot specific prefixes
RIOT_PREFIX = 'riot-',
RIOT_TAG = RIOT_PREFIX + 'tag',
// for typeof == '' comparisons
T_STRING = 'string',
T_OBJECT = 'object',
T_UNDEF = 'undefined',
T_FUNCTION = 'function',
// special native tags that cannot be treated like the others
SPECIAL_TAGS_REGEX = /^(?:t(?:body|head|foot|[rhd])|caption|col(?:group)?|opt(?:ion|group))$/,
RESERVED_WORDS_BLACKLIST = ['_item', '_id', '_parent', 'update', 'root', 'mount', 'unmount', 'mixin', 'isMounted', 'isLoop', 'tags', 'parent', 'opts', 'trigger', 'on', 'off', 'one'],
// version# for IE 8-11, 0 for others
IE_VERSION = (window && window.document || {}).documentMode | 0
/* istanbul ignore next */
riot.observable = function(el) {
/**
* Extend the original object or create a new empty one
* @type { Object }
*/
el = el || {}
/**
* Private variables and methods
*/
var callbacks = {},
slice = Array.prototype.slice,
onEachEvent = function(e, fn) { e.replace(/\S+/g, fn) },
defineProperty = function (key, value) {
Object.defineProperty(el, key, {
value: value,
enumerable: false,
writable: false,
configurable: false
})
}
/**
* Listen to the given space separated list of `events` and execute the `callback` each time an event is triggered.
* @param { String } events - events ids
* @param { Function } fn - callback function
* @returns { Object } el
*/
defineProperty('on', function(events, fn) {
if (typeof fn != 'function') return el
onEachEvent(events, function(name, pos) {
(callbacks[name] = callbacks[name] || []).push(fn)
fn.typed = pos > 0
})
return el
})
/**
* Removes the given space separated list of `events` listeners
* @param { String } events - events ids
* @param { Function } fn - callback function
* @returns { Object } el
*/
defineProperty('off', function(events, fn) {
if (events == '*' && !fn) callbacks = {}
else {
onEachEvent(events, function(name) {
if (fn) {
var arr = callbacks[name]
for (var i = 0, cb; cb = arr && arr[i]; ++i) {
if (cb == fn) arr.splice(i--, 1)
}
} else delete callbacks[name]
})
}
return el
})
/**
* Listen to the given space separated list of `events` and execute the `callback` at most once
* @param { String } events - events ids
* @param { Function } fn - callback function
* @returns { Object } el
*/
defineProperty('one', function(events, fn) {
function on() {
el.off(events, on)
fn.apply(el, arguments)
}
return el.on(events, on)
})
/**
* Execute all callback functions that listen to the given space separated list of `events`
* @param { String } events - events ids
* @returns { Object } el
*/
defineProperty('trigger', function(events) {
// getting the arguments
// skipping the first one
var args = slice.call(arguments, 1),
fns
onEachEvent(events, function(name) {
fns = slice.call(callbacks[name] || [], 0)
for (var i = 0, fn; fn = fns[i]; ++i) {
if (fn.busy) return
fn.busy = 1
fn.apply(el, fn.typed ? [name].concat(args) : args)
if (fns[i] !== fn) { i-- }
fn.busy = 0
}
if (callbacks['*'] && name != '*')
el.trigger.apply(el, ['*', name].concat(args))
})
return el
})
return el
}
/* istanbul ignore next */
;(function(riot) {
/**
* Simple client-side router
* @module riot-route
*/
var RE_ORIGIN = /^.+?\/+[^\/]+/,
EVENT_LISTENER = 'EventListener',
REMOVE_EVENT_LISTENER = 'remove' + EVENT_LISTENER,
ADD_EVENT_LISTENER = 'add' + EVENT_LISTENER,
HAS_ATTRIBUTE = 'hasAttribute',
REPLACE = 'replace',
POPSTATE = 'popstate',
HASHCHANGE = 'hashchange',
TRIGGER = 'trigger',
MAX_EMIT_STACK_LEVEL = 3,
win = typeof window != 'undefined' && window,
doc = typeof document != 'undefined' && document,
hist = win && history,
loc = win && (hist.location || win.location), // see html5-history-api
prot = Router.prototype, // to minify more
clickEvent = doc && doc.ontouchstart ? 'touchstart' : 'click',
started = false,
central = riot.observable(),
routeFound = false,
debouncedEmit,
base, current, parser, secondParser, emitStack = [], emitStackLevel = 0
/**
* Default parser. You can replace it via router.parser method.
* @param {string} path - current path (normalized)
* @returns {array} array
*/
function DEFAULT_PARSER(path) {
return path.split(/[/?#]/)
}
/**
* Default parser (second). You can replace it via router.parser method.
* @param {string} path - current path (normalized)
* @param {string} filter - filter string (normalized)
* @returns {array} array
*/
function DEFAULT_SECOND_PARSER(path, filter) {
var re = new RegExp('^' + filter[REPLACE](/\*/g, '([^/?#]+?)')[REPLACE](/\.\./, '.*') + '$'),
args = path.match(re)
if (args) return args.slice(1)
}
/**
* Simple/cheap debounce implementation
* @param {function} fn - callback
* @param {number} delay - delay in seconds
* @returns {function} debounced function
*/
function debounce(fn, delay) {
var t
return function () {
clearTimeout(t)
t = setTimeout(fn, delay)
}
}
/**
* Set the window listeners to trigger the routes
* @param {boolean} autoExec - see route.start
*/
function start(autoExec) {
debouncedEmit = debounce(emit, 1)
win[ADD_EVENT_LISTENER](POPSTATE, debouncedEmit)
win[ADD_EVENT_LISTENER](HASHCHANGE, debouncedEmit)
doc[ADD_EVENT_LISTENER](clickEvent, click)
if (autoExec) emit(true)
}
/**
* Router class
*/
function Router() {
this.$ = []
riot.observable(this) // make it observable
central.on('stop', this.s.bind(this))
central.on('emit', this.e.bind(this))
}
function normalize(path) {
return path[REPLACE](/^\/|\/$/, '')
}
function isString(str) {
return typeof str == 'string'
}
/**
* Get the part after domain name
* @param {string} href - fullpath
* @returns {string} path from root
*/
function getPathFromRoot(href) {
return (href || loc.href || '')[REPLACE](RE_ORIGIN, '')
}
/**
* Get the part after base
* @param {string} href - fullpath
* @returns {string} path from base
*/
function getPathFromBase(href) {
return base[0] == '#'
? (href || loc.href || '').split(base)[1] || ''
: getPathFromRoot(href)[REPLACE](base, '')
}
function emit(force) {
// the stack is needed for redirections
var isRoot = emitStackLevel == 0
if (MAX_EMIT_STACK_LEVEL <= emitStackLevel) return
emitStackLevel++
emitStack.push(function() {
var path = getPathFromBase()
if (force || path != current) {
central[TRIGGER]('emit', path)
current = path
}
})
if (isRoot) {
while (emitStack.length) {
emitStack[0]()
emitStack.shift()
}
emitStackLevel = 0
}
}
function click(e) {
if (
e.which != 1 // not left click
|| e.metaKey || e.ctrlKey || e.shiftKey // or meta keys
|| e.defaultPrevented // or default prevented
) return
var el = e.target
while (el && el.nodeName != 'A') el = el.parentNode
if (
!el || el.nodeName != 'A' // not A tag
|| el[HAS_ATTRIBUTE]('download') // has download attr
|| !el[HAS_ATTRIBUTE]('href') // has no href attr
|| el.target && el.target != '_self' // another window or frame
|| el.href.indexOf(loc.href.match(RE_ORIGIN)[0]) == -1 // cross origin
) return
if (el.href != loc.href) {
if (
el.href.split('#')[0] == loc.href.split('#')[0] // internal jump
|| base != '#' && getPathFromRoot(el.href).indexOf(base) !== 0 // outside of base
|| !go(getPathFromBase(el.href), el.title || doc.title) // route not found
) return
}
e.preventDefault()
}
/**
* Go to the path
* @param {string} path - destination path
* @param {string} title - page title
* @param {boolean} shouldReplace - use replaceState or pushState
* @returns {boolean} - route not found flag
*/
function go(path, title, shouldReplace) {
if (hist) { // if a browser
path = base + normalize(path)
title = title || doc.title
// browsers ignores the second parameter `title`
shouldReplace
? hist.replaceState(null, title, path)
: hist.pushState(null, title, path)
// so we need to set it manually
doc.title = title
routeFound = false
emit()
return routeFound
}
// Server-side usage: directly execute handlers for the path
return central[TRIGGER]('emit', getPathFromBase(path))
}
/**
* Go to path or set action
* a single string: go there
* two strings: go there with setting a title
* two strings and boolean: replace history with setting a title
* a single function: set an action on the default route
* a string/RegExp and a function: set an action on the route
* @param {(string|function)} first - path / action / filter
* @param {(string|RegExp|function)} second - title / action
* @param {boolean} third - replace flag
*/
prot.m = function(first, second, third) {
if (isString(first) && (!second || isString(second))) go(first, second, third || false)
else if (second) this.r(first, second)
else this.r('@', first)
}
/**
* Stop routing
*/
prot.s = function() {
this.off('*')
this.$ = []
}
/**
* Emit
* @param {string} path - path
*/
prot.e = function(path) {
this.$.concat('@').some(function(filter) {
var args = (filter == '@' ? parser : secondParser)(normalize(path), normalize(filter))
if (typeof args != 'undefined') {
this[TRIGGER].apply(null, [filter].concat(args))
return routeFound = true // exit from loop
}
}, this)
}
/**
* Register route
* @param {string} filter - filter for matching to url
* @param {function} action - action to register
*/
prot.r = function(filter, action) {
if (filter != '@') {
filter = '/' + normalize(filter)
this.$.push(filter)
}
this.on(filter, action)
}
var mainRouter = new Router()
var route = mainRouter.m.bind(mainRouter)
/**
* Create a sub router
* @returns {function} the method of a new Router object
*/
route.create = function() {
var newSubRouter = new Router()
// stop only this sub-router
newSubRouter.m.stop = newSubRouter.s.bind(newSubRouter)
// return sub-router's main method
return newSubRouter.m.bind(newSubRouter)
}
/**
* Set the base of url
* @param {(str|RegExp)} arg - a new base or '#' or '#!'
*/
route.base = function(arg) {
base = arg || '#'
current = getPathFromBase() // recalculate current path
}
/** Exec routing right now **/
route.exec = function() {
emit(true)
}
/**
* Replace the default router to yours
* @param {function} fn - your parser function
* @param {function} fn2 - your secondParser function
*/
route.parser = function(fn, fn2) {
if (!fn && !fn2) {
// reset parser for testing...
parser = DEFAULT_PARSER
secondParser = DEFAULT_SECOND_PARSER
}
if (fn) parser = fn
if (fn2) secondParser = fn2
}
/**
* Helper function to get url query as an object
* @returns {object} parsed query
*/
route.query = function() {
var q = {}
var href = loc.href || current
href[REPLACE](/[?&](.+?)=([^&]*)/g, function(_, k, v) { q[k] = v })
return q
}
/** Stop routing **/
route.stop = function () {
if (started) {
if (win) {
win[REMOVE_EVENT_LISTENER](POPSTATE, debouncedEmit)
win[REMOVE_EVENT_LISTENER](HASHCHANGE, debouncedEmit)
doc[REMOVE_EVENT_LISTENER](clickEvent, click)
}
central[TRIGGER]('stop')
started = false
}
}
/**
* Start routing
* @param {boolean} autoExec - automatically exec after starting if true
*/
route.start = function (autoExec) {
if (!started) {
if (win) {
if (document.readyState == 'complete') start(autoExec)
// the timeout is needed to solve
// a weird safari bug https://github.com/riot/route/issues/33
else win[ADD_EVENT_LISTENER]('load', function() {
setTimeout(function() { start(autoExec) }, 1)
})
}
started = true
}
}
/** Prepare the router **/
route.base()
route.parser()
riot.route = route
})(riot)
/* istanbul ignore next */
/**
* The riot template engine
* @version v2.3.21
*/
/**
* riot.util.brackets
*
* - `brackets ` - Returns a string or regex based on its parameter
* - `brackets.set` - Change the current riot brackets
*
* @module
*/
var brackets = (function (UNDEF) {
var
REGLOB = 'g',
R_MLCOMMS = /\/\*[^*]*\*+(?:[^*\/][^*]*\*+)*\//g,
R_STRINGS = /"[^"\\]*(?:\\[\S\s][^"\\]*)*"|'[^'\\]*(?:\\[\S\s][^'\\]*)*'/g,
S_QBLOCKS = R_STRINGS.source + '|' +
/(?:\breturn\s+|(?:[$\w\)\]]|\+\+|--)\s*(\/)(?![*\/]))/.source + '|' +
/\/(?=[^*\/])[^[\/\\]*(?:(?:\[(?:\\.|[^\]\\]*)*\]|\\.)[^[\/\\]*)*?(\/)[gim]*/.source,
FINDBRACES = {
'(': RegExp('([()])|' + S_QBLOCKS, REGLOB),
'[': RegExp('([[\\]])|' + S_QBLOCKS, REGLOB),
'{': RegExp('([{}])|' + S_QBLOCKS, REGLOB)
},
DEFAULT = '{ }'
var _pairs = [
'{', '}',
'{', '}',
/{[^}]*}/,
/\\([{}])/g,
/\\({)|{/g,
RegExp('\\\\(})|([[({])|(})|' + S_QBLOCKS, REGLOB),
DEFAULT,
/^\s*{\^?\s*([$\w]+)(?:\s*,\s*(\S+))?\s+in\s+(\S.*)\s*}/,
/(^|[^\\]){=[\S\s]*?}/
]
var
cachedBrackets = UNDEF,
_regex,
_cache = [],
_settings
function _loopback (re) { return re }
function _rewrite (re, bp) {
if (!bp) bp = _cache
return new RegExp(
re.source.replace(/{/g, bp[2]).replace(/}/g, bp[3]), re.global ? REGLOB : ''
)
}
function _create (pair) {
if (pair === DEFAULT) return _pairs
var arr = pair.split(' ')
if (arr.length !== 2 || /[\x00-\x1F<>a-zA-Z0-9'",;\\]/.test(pair)) {
throw new Error('Unsupported brackets "' + pair + '"')
}
arr = arr.concat(pair.replace(/(?=[[\]()*+?.^$|])/g, '\\').split(' '))
arr[4] = _rewrite(arr[1].length > 1 ? /{[\S\s]*?}/ : _pairs[4], arr)
arr[5] = _rewrite(pair.length > 3 ? /\\({|})/g : _pairs[5], arr)
arr[6] = _rewrite(_pairs[6], arr)
arr[7] = RegExp('\\\\(' + arr[3] + ')|([[({])|(' + arr[3] + ')|' + S_QBLOCKS, REGLOB)
arr[8] = pair
return arr
}
function _brackets (reOrIdx) {
return reOrIdx instanceof RegExp ? _regex(reOrIdx) : _cache[reOrIdx]
}
_brackets.split = function split (str, tmpl, _bp) {
// istanbul ignore next: _bp is for the compiler
if (!_bp) _bp = _cache
var
parts = [],
match,
isexpr,
start,
pos,
re = _bp[6]
isexpr = start = re.lastIndex = 0
while (match = re.exec(str)) {
pos = match.index
if (isexpr) {
if (match[2]) {
re.lastIndex = skipBraces(str, match[2], re.lastIndex)
continue
}
if (!match[3])
continue
}
if (!match[1]) {
unescapeStr(str.slice(start, pos))
start = re.lastIndex
re = _bp[6 + (isexpr ^= 1)]
re.lastIndex = start
}
}
if (str && start < str.length) {
unescapeStr(str.slice(start))
}
return parts
function unescapeStr (s) {
if (tmpl || isexpr)
parts.push(s && s.replace(_bp[5], '$1'))
else
parts.push(s)
}
function skipBraces (s, ch, ix) {
var
match,
recch = FINDBRACES[ch]
recch.lastIndex = ix
ix = 1
while (match = recch.exec(s)) {
if (match[1] &&
!(match[1] === ch ? ++ix : --ix)) break
}
return ix ? s.length : recch.lastIndex
}
}
_brackets.hasExpr = function hasExpr (str) {
return _cache[4].test(str)
}
_brackets.loopKeys = function loopKeys (expr) {
var m = expr.match(_cache[9])
return m
? { key: m[1], pos: m[2], val: _cache[0] + m[3].trim() + _cache[1] }
: { val: expr.trim() }
}
_brackets.hasRaw = function (src) {
return _cache[10].test(src)
}
_brackets.array = function array (pair) {
return pair ? _create(pair) : _cache
}
function _reset (pair) {
if ((pair || (pair = DEFAULT)) !== _cache[8]) {
_cache = _create(pair)
_regex = pair === DEFAULT ? _loopback : _rewrite
_cache[9] = _regex(_pairs[9])
_cache[10] = _regex(_pairs[10])
}
cachedBrackets = pair
}
function _setSettings (o) {
var b
o = o || {}
b = o.brackets
Object.defineProperty(o, 'brackets', {
set: _reset,
get: function () { return cachedBrackets },
enumerable: true
})
_settings = o
_reset(b)
}
Object.defineProperty(_brackets, 'settings', {
set: _setSettings,
get: function () { return _settings }
})
/* istanbul ignore next: in the browser riot is always in the scope */
_brackets.settings = typeof riot !== 'undefined' && riot.settings || {}
_brackets.set = _reset
_brackets.R_STRINGS = R_STRINGS
_brackets.R_MLCOMMS = R_MLCOMMS
_brackets.S_QBLOCKS = S_QBLOCKS
return _brackets
})()
/**
* @module tmpl
*
* tmpl - Root function, returns the template value, render with data
* tmpl.hasExpr - Test the existence of a expression inside a string
* tmpl.loopKeys - Get the keys for an 'each' loop (used by `_each`)
*/
var tmpl = (function () {
var _cache = {}
function _tmpl (str, data) {
if (!str) return str
return (_cache[str] || (_cache[str] = _create(str))).call(data, _logErr)
}
_tmpl.haveRaw = brackets.hasRaw
_tmpl.hasExpr = brackets.hasExpr
_tmpl.loopKeys = brackets.loopKeys
_tmpl.errorHandler = null
function _logErr (err, ctx) {
if (_tmpl.errorHandler) {
err.riotData = {
tagName: ctx && ctx.root && ctx.root.tagName,
_riot_id: ctx && ctx._riot_id //eslint-disable-line camelcase
}
_tmpl.errorHandler(err)
}
}
function _create (str) {
var expr = _getTmpl(str)
if (expr.slice(0, 11) !== 'try{return ') expr = 'return ' + expr
return new Function('E', expr + ';')
}
var
RE_QBLOCK = RegExp(brackets.S_QBLOCKS, 'g'),
RE_QBMARK = /\x01(\d+)~/g
function _getTmpl (str) {
var
qstr = [],
expr,
parts = brackets.split(str.replace(/\u2057/g, '"'), 1)
if (parts.length > 2 || parts[0]) {
var i, j, list = []
for (i = j = 0; i < parts.length; ++i) {
expr = parts[i]
if (expr && (expr = i & 1 ?
_parseExpr(expr, 1, qstr) :
'"' + expr
.replace(/\\/g, '\\\\')
.replace(/\r\n?|\n/g, '\\n')
.replace(/"/g, '\\"') +
'"'
)) list[j++] = expr
}
expr = j < 2 ? list[0] :
'[' + list.join(',') + '].join("")'
} else {
expr = _parseExpr(parts[1], 0, qstr)
}
if (qstr[0])
expr = expr.replace(RE_QBMARK, function (_, pos) {
return qstr[pos]
.replace(/\r/g, '\\r')
.replace(/\n/g, '\\n')
})
return expr
}
var
RE_BREND = {
'(': /[()]/g,
'[': /[[\]]/g,
'{': /[{}]/g
},
CS_IDENT = /^(?:(-?[_A-Za-z\xA0-\xFF][-\w\xA0-\xFF]*)|\x01(\d+)~):/
function _parseExpr (expr, asText, qstr) {
if (expr[0] === '=') expr = expr.slice(1)
expr = expr
.replace(RE_QBLOCK, function (s, div) {
return s.length > 2 && !div ? '\x01' + (qstr.push(s) - 1) + '~' : s
})
.replace(/\s+/g, ' ').trim()
.replace(/\ ?([[\({},?\.:])\ ?/g, '$1')
if (expr) {
var
list = [],
cnt = 0,
match
while (expr &&
(match = expr.match(CS_IDENT)) &&
!match.index
) {
var
key,
jsb,
re = /,|([[{(])|$/g
expr = RegExp.rightContext
key = match[2] ? qstr[match[2]].slice(1, -1).trim().replace(/\s+/g, ' ') : match[1]
while (jsb = (match = re.exec(expr))[1]) skipBraces(jsb, re)
jsb = expr.slice(0, match.index)
expr = RegExp.rightContext
list[cnt++] = _wrapExpr(jsb, 1, key)
}
expr = !cnt ? _wrapExpr(expr, asText) :
cnt > 1 ? '[' + list.join(',') + '].join(" ").trim()' : list[0]
}
return expr
function skipBraces (ch, re) {
var
mm,
lv = 1,
ir = RE_BREND[ch]
ir.lastIndex = re.lastIndex
while (mm = ir.exec(expr)) {
if (mm[0] === ch) ++lv
else if (!--lv) break
}
re.lastIndex = lv ? expr.length : ir.lastIndex
}
}
// istanbul ignore next: not both
var
JS_CONTEXT = '"in this?this:' + (typeof window !== 'object' ? 'global' : 'window') + ').',
JS_VARNAME = /[,{][$\w]+:|(^ *|[^$\w\.])(?!(?:typeof|true|false|null|undefined|in|instanceof|is(?:Finite|NaN)|void|NaN|new|Date|RegExp|Math)(?![$\w]))([$_A-Za-z][$\w]*)/g,
JS_NOPROPS = /^(?=(\.[$\w]+))\1(?:[^.[(]|$)/
function _wrapExpr (expr, asText, key) {
var tb
expr = expr.replace(JS_VARNAME, function (match, p, mvar, pos, s) {
if (mvar) {
pos = tb ? 0 : pos + match.length
if (mvar !== 'this' && mvar !== 'global' && mvar !== 'window') {
match = p + '("' + mvar + JS_CONTEXT + mvar
if (pos) tb = (s = s[pos]) === '.' || s === '(' || s === '['
} else if (pos) {
tb = !JS_NOPROPS.test(s.slice(pos))
}
}
return match
})
if (tb) {
expr = 'try{return ' + expr + '}catch(e){E(e,this)}'
}
if (key) {
expr = (tb ?
'function(){' + expr + '}.call(this)' : '(' + expr + ')'
) + '?"' + key + '":""'
} else if (asText) {
expr = 'function(v){' + (tb ?
expr.replace('return ', 'v=') : 'v=(' + expr + ')'
) + ';return v||v===0?v:""}.call(this)'
}
return expr
}
// istanbul ignore next: compatibility fix for beta versions
_tmpl.parse = function (s) { return s }
_tmpl.version = brackets.version = 'v2.3.21'
return _tmpl
})()
/*
lib/browser/tag/mkdom.js
Includes hacks needed for the Internet Explorer version 9 and below
See: http://kangax.github.io/compat-table/es5/#ie8
http://codeplanet.io/dropping-ie8/
*/
var mkdom = (function (checkIE) {
var
reToSrc = /<yield\s+to=(['"])?@\1\s*>([\S\s]+?)<\/yield\s*>/.source,
rootEls = { tr: 'tbody', th: 'tr', td: 'tr', col: 'colgroup' },
GENERIC = 'div'
checkIE = checkIE && checkIE < 10
var tblTags = checkIE
? SPECIAL_TAGS_REGEX : /^(?:t(?:body|head|foot|[rhd])|caption|col(?:group)?)$/
// creates any dom element in a div, table, or colgroup container
function _mkdom(templ, html) {
var match = templ && templ.match(/^\s*<([-\w]+)/),
tagName = match && match[1].toLowerCase(),
el = mkEl(GENERIC)
// replace all the yield tags with the tag inner html
templ = replaceYield(templ, html || '')
/* istanbul ignore next */
//if ((checkIE || !startsWith(tagName, 'opt')) && SPECIAL_TAGS_REGEX.test(tagName))
if (tblTags.test(tagName))
el = specialTags(el, templ, tagName)
else
el.innerHTML = templ
el.stub = true
return el
}
// creates the root element for table and select child elements
// tr/th/td/thead/tfoot/tbody/caption/col/colgroup/option/optgroup
function specialTags(el, templ, tagName) {
var
select = tagName[0] === 'o',
parent = select ? 'select>' : 'table>'
// trim() is important here, this ensures we don't have artifacts,
// so we can check if we have only one element inside the parent
el.innerHTML = '<' + parent + templ.trim() + '</' + parent
parent = el.firstChild
// returns the immediate parent if tr/th/td/col is the only element, if not
// returns the whole tree, as this can include additional elements
if (select) {
parent.selectedIndex = -1 // for IE9, compatible w/current riot behavior
} else {
var tname = rootEls[tagName]
if (tname && parent.children.length === 1) parent = $(tname, parent)
}
return parent
}
/**
* Replace the yield tag from any tag template with the innerHTML of the
* original tag in the page
* @param { String } templ - tag implementation template
* @param { String } html - original content of the tag in the DOM
* @returns { String } tag template updated without the yield tag
*/
function replaceYield(templ, html) {
// do nothing if no yield
if (!/<yield\b/i.test(templ)) return templ
// be careful with #1343 - string on the source having `$1`
var n = 0
templ = templ.replace(/<yield\s+from=['"]([-\w]+)['"]\s*(?:\/>|>\s*<\/yield\s*>)/ig,
function (str, ref) {
var m = html.match(RegExp(reToSrc.replace('@', ref), 'i'))
++n
return m && m[2] || ''
})
// yield without any "from", replace yield in templ with the innerHTML
return n ? templ : templ.replace(/<yield\s*(?:\/>|>\s*<\/yield\s*>)/gi, html)
}
return _mkdom
})(IE_VERSION)
/**
* Convert the item looped into an object used to extend the child tag properties
* @param { Object } expr - object containing the keys used to extend the children tags
* @param { * } key - value to assign to the new object returned
* @param { * } val - value containing the position of the item in the array
* @returns { Object } - new object containing the values of the original item
*
* The variables 'key' and 'val' are arbitrary.
* They depend on the collection type looped (Array, Object)
* and on the expression used on the each tag
*
*/
function mkitem(expr, key, val) {
var item = {}
item[expr.key] = key
if (expr.pos) item[expr.pos] = val
return item
}
/**
* Unmount the redundant tags
* @param { Array } items - array containing the current items to loop
* @param { Array } tags - array containing all the children tags
*/
function unmountRedundant(items, tags) {
var i = tags.length,
j = items.length,
t
while (i > j) {
t = tags[--i]
tags.splice(i, 1)
t.unmount()
}
}
/**
* Move the nested custom tags in non custom loop tags
* @param { Object } child - non custom loop tag
* @param { Number } i - current position of the loop tag
*/
function moveNestedTags(child, i) {
Object.keys(child.tags).forEach(function(tagName) {
var tag = child.tags[tagName]
if (isArray(tag))
each(tag, function (t) {
moveChildTag(t, tagName, i)
})
else
moveChildTag(tag, tagName, i)
})
}
/**
* Adds the elements for a virtual tag
* @param { Tag } tag - the tag whose root's children will be inserted or appended
* @param { Node } src - the node that will do the inserting or appending
* @param { Tag } target - only if inserting, insert before this tag's first child
*/
function addVirtual(tag, src, target) {
var el = tag._root, sib
tag._virts = []
while (el) {
sib = el.nextSibling
if (target)
src.insertBefore(el, target._root)
else
src.appendChild(el)
tag._virts.push(el) // hold for unmounting
el = sib
}
}
/**
* Move virtual tag and all child nodes
* @param { Tag } tag - first child reference used to start move
* @param { Node } src - the node that will do the inserting
* @param { Tag } target - insert before this tag's first child
* @param { Number } len - how many child nodes to move
*/
function moveVirtual(tag, src, target, len) {
var el = tag._root, sib, i = 0
for (; i < len; i++) {
sib = el.nextSibling
src.insertBefore(el, target._root)
el = sib
}
}
/**
* Manage tags having the 'each'
* @param { Object } dom - DOM node we need to loop
* @param { Tag } parent - parent tag instance where the dom node is contained
* @param { String } expr - string contained in the 'each' attribute
*/
function _each(dom, parent, expr) {
// remove the each property from the original tag
remAttr(dom, 'each')
var mustReorder = typeof getAttr(dom, 'no-reorder') !== T_STRING || remAttr(dom, 'no-reorder'),
tagName = getTagName(dom),
impl = __tagImpl[tagName] || { tmpl: dom.outerHTML },
useRoot = SPECIAL_TAGS_REGEX.test(tagName),
root = dom.parentNode,
ref = document.createTextNode(''),
child = getTag(dom),
isOption = /^option$/i.test(tagName), // the option tags must be treated differently
tags = [],
oldItems = [],
hasKeys,
isVirtual = dom.tagName == 'VIRTUAL'
// parse the each expression
expr = tmpl.loopKeys(expr)
// insert a marked where the loop tags will be injected
root.insertBefore(ref, dom)
// clean template code
parent.one('before-mount', function () {
// remove the original DOM node
dom.parentNode.removeChild(dom)
if (root.stub) root = parent.root
}).on('update', function () {
// get the new items collection
var items = tmpl(expr.val, parent),
// create a fragment to hold the new DOM nodes to inject in the parent tag
frag = document.createDocumentFragment()
// object loop. any changes cause full redraw
if (!isArray(items)) {
hasKeys = items || false
items = hasKeys ?
Object.keys(items).map(function (key) {
return mkitem(expr, key, items[key])
}) : []
}
// loop all the new items
items.forEach(function(item, i) {
// reorder only if the items are objects
var _mustReorder = mustReorder && item instanceof Object,
oldPos = oldItems.indexOf(item),
pos = ~oldPos && _mustReorder ? oldPos : i,
// does a tag exist in this position?
tag = tags[pos]
item = !hasKeys && expr.key ? mkitem(expr, item, i) : item
// new tag
if (
!_mustReorder && !tag // with no-reorder we just update the old tags
||
_mustReorder && !~oldPos || !tag // by default we always try to reorder the DOM elements
) {
tag = new Tag(impl, {
parent: parent,
isLoop: true,
hasImpl: !!__tagImpl[tagName],
root: useRoot ? root : dom.cloneNode(),
item: item
}, dom.innerHTML)
tag.mount()
if (isVirtual) tag._root = tag.root.firstChild // save reference for further moves or inserts
// this tag must be appended
if (i == tags.length) {
if (isVirtual)
addVirtual(tag, frag)
else frag.appendChild(tag.root)
}
// this tag must be insert
else {
if (isVirtual)
addVirtual(tag, root, tags[i])
else root.insertBefore(tag.root, tags[i].root)
oldItems.splice(i, 0, item)
}
tags.splice(i, 0, tag)
pos = i // handled here so no move
} else tag.update(item)
// reorder the tag if it's not located in its previous position
if (pos !== i && _mustReorder) {
// update the DOM
if (isVirtual)
moveVirtual(tag, root, tags[i], dom.childNodes.length)
else root.insertBefore(tag.root, tags[i].root)
// update the position attribute if it exists
if (expr.pos)
tag[expr.pos] = i
// move the old tag instance
tags.splice(i, 0, tags.splice(pos, 1)[0])
// move the old item
oldItems.splice(i, 0, oldItems.splice(pos, 1)[0])
// if the loop tags are not custom
// we need to move all their custom tags into the right position
if (!child && tag.tags) moveNestedTags(tag, i)
}
// cache the original item to use it in the events bound to this node
// and its children
tag._item = item
// cache the real parent tag internally
defineProperty(tag, '_parent', parent)
})
// remove the redundant tags
unmountRedundant(items, tags)
// insert the new nodes
if (isOption) root.appendChild(frag)
else root.insertBefore(frag, ref)
// set the 'tags' property of the parent tag
// if child is 'undefined' it means that we don't need to set this property
// for example:
// we don't need store the `myTag.tags['div']` property if we are looping a div tag
// but we need to track the `myTag.tags['child']` property looping a custom child node named `child`
if (child) parent.tags[tagName] = tags
// clone the items array
oldItems = items.slice()
})
}
/**
* Object that will be used to inject and manage the css of every tag instance
*/
var styleManager = (function(_riot) {
if (!window) return { // skip injection on the server
add: function () {},
inject: function () {}
}
var styleNode = (function () {
// create a new style element with the correct type
var newNode = mkEl('style')
setAttr(newNode, 'type', 'text/css')
// replace any user node or insert the new one into the head
var userNode = $('style[type=riot]')
if (userNode) {
if (userNode.id) newNode.id = userNode.id
userNode.parentNode.replaceChild(newNode, userNode)
}
else document.getElementsByTagName('head')[0].appendChild(newNode)
return newNode
})()
// Create cache and shortcut to the correct property
var cssTextProp = styleNode.styleSheet,
stylesToInject = ''
// Expose the style node in a non-modificable property
Object.defineProperty(_riot, 'styleNode', {
value: styleNode,
writable: true
})
/**
* Public api
*/
return {
/**
* Save a tag style to be later injected into DOM
* @param { String } css [description]
*/
add: function(css) {
stylesToInject += css
},
/**
* Inject all previously saved tag styles into DOM
* innerHTML seems slow: http://jsperf.com/riot-insert-style
*/
inject: function() {
if (stylesToInject) {
if (cssTextProp) cssTextProp.cssText += stylesToInject
else styleNode.innerHTML += stylesToInject
stylesToInject = ''
}
}
}
})(riot)
function parseNamedElements(root, tag, childTags, forceParsingNamed) {
walk(root, function(dom) {
if (dom.nodeType == 1) {
dom.isLoop = dom.isLoop ||
(dom.parentNode && dom.parentNode.isLoop || getAttr(dom, 'each'))
? 1 : 0
// custom child tag
if (childTags) {
var child = getTag(dom)
if (child && !dom.isLoop)
childTags.push(initChildTag(child, {root: dom, parent: tag}, dom.innerHTML, tag))
}
if (!dom.isLoop || forceParsingNamed)
setNamed(dom, tag, [])
}
})
}
function parseExpressions(root, tag, expressions) {
function addExpr(dom, val, extra) {
if (tmpl.hasExpr(val)) {
expressions.push(extend({ dom: dom, expr: val }, extra))
}
}
walk(root, function(dom) {
var type = dom.nodeType,
attr
// text node
if (type == 3 && dom.parentNode.tagName != 'STYLE') addExpr(dom, dom.nodeValue)
if (type != 1) return
/* element */
// loop
attr = getAttr(dom, 'each')
if (attr) { _each(dom, tag, attr); return false }
// attribute expressions
each(dom.attributes, function(attr) {
var name = attr.name,
bool = name.split('__')[1]
addExpr(dom, attr.value, { attr: bool || name, bool: bool })
if (bool) { remAttr(dom, name); return false }
})
// skip custom tags
if (getTag(dom)) return false
})
}
function Tag(impl, conf, innerHTML) {
var self = riot.observable(this),
opts = inherit(conf.opts) || {},
parent = conf.parent,
isLoop = conf.isLoop,
hasImpl = conf.hasImpl,
item = cleanUpData(conf.item),
expressions = [],
childTags = [],
root = conf.root,
fn = impl.fn,
tagName = root.tagName.toLowerCase(),
attr = {},
propsInSyncWithParent = [],
dom
if (fn && root._tag) root._tag.unmount(true)
// not yet mounted
this.isMounted = false
root.isLoop = isLoop
// keep a reference to the tag just created
// so we will be able to mount this tag multiple times
root._tag = this
// create a unique id to this tag
// it could be handy to use it also to improve the virtual dom rendering speed
defineProperty(this, '_riot_id', ++__uid) // base 1 allows test !t._riot_id
extend(this, { parent: parent, root: root, opts: opts, tags: {} }, item)
// grab attributes
each(root.attributes, function(el) {
var val = el.value
// remember attributes with expressions only
if (tmpl.hasExpr(val)) attr[el.name] = val
})
dom = mkdom(impl.tmpl, innerHTML)
// options
function updateOpts() {
var ctx = hasImpl && isLoop ? self : parent || self
// update opts from current DOM attributes
each(root.attributes, function(el) {
var val = el.value
opts[toCamel(el.name)] = tmpl.hasExpr(val) ? tmpl(val, ctx) : val
})
// recover those with expressions
each(Object.keys(attr), function(name) {
opts[toCamel(name)] = tmpl(attr[name], ctx)
})
}
function normalizeData(data) {
for (var key in item) {
if (typeof self[key] !== T_UNDEF && isWritable(self, key))
self[key] = data[key]
}
}
function inheritFromParent () {
if (!self.parent || !isLoop) return
each(Object.keys(self.parent), function(k) {
// some properties must be always in sync with the parent tag
var mustSync = !contains(RESERVED_WORDS_BLACKLIST, k) && contains(propsInSyncWithParent, k)
if (typeof self[k] === T_UNDEF || mustSync) {
// track the property to keep in sync
// so we can keep it updated
if (!mustSync) propsInSyncWithParent.push(k)
self[k] = self.parent[k]
}
})
}
defineProperty(this, 'update', function(data) {
// make sure the data passed will not override
// the component core methods
data = cleanUpData(data)
// inherit properties from the parent
inheritFromParent()
// normalize the tag properties in case an item object was initially passed
if (data && typeof item === T_OBJECT) {
normalizeData(data)
item = data
}
extend(self, data)
updateOpts()
self.trigger('update', data)
update(expressions, self)
// the updated event will be triggered
// once the DOM will be ready and all the reflows are completed
// this is useful if you want to get the "real" root properties
// 4 ex: root.offsetWidth ...
rAF(function() { self.trigger('updated') })
return this
})
defineProperty(this, 'mixin', function() {
each(arguments, function(mix) {
var instance
mix = typeof mix === T_STRING ? riot.mixin(mix) : mix
// check if the mixin is a function
if (isFunction(mix)) {
// create the new mixin instance
instance = new mix()
// save the prototype to loop it afterwards
mix = mix.prototype
} else instance = mix
// loop the keys in the function prototype or the all object keys
each(Object.getOwnPropertyNames(mix), function(key) {
// bind methods to self
if (key != 'init')
self[key] = isFunction(instance[key]) ?
instance[key].bind(self) :
instance[key]
})
// init method will be called automatically
if (instance.init) instance.init.bind(self)()
})
return this
})
defineProperty(this, 'mount', function() {
updateOpts()
// initialiation
if (fn) fn.call(self, opts)
// parse layout after init. fn may calculate args for nested custom tags
parseExpressions(dom, self, expressions)
// mount the child tags
toggle(true)
// update the root adding custom attributes coming from the compiler
// it fixes also #1087
if (impl.attrs || hasImpl) {
walkAttributes(impl.attrs, function (k, v) { setAttr(root, k, v) })
parseExpressions(self.root, self, expressions)
}
if (!self.parent || isLoop) self.update(item)
// internal use only, fixes #403
self.trigger('before-mount')
if (isLoop && !hasImpl) {
// update the root attribute for the looped elements
self.root = root = dom.firstChild
} else {
while (dom.firstChild) root.appendChild(dom.firstChild)
if (root.stub) self.root = root = parent.root
}
// parse the named dom nodes in the looped child
// adding them to the parent as well
if (isLoop)
parseNamedElements(self.root, self.parent, null, true)
// if it's not a child tag we can trigger its mount event
if (!self.parent || self.parent.isMounted) {
self.isMounted = true
self.trigger('mount')
}
// otherwise we need to wait that the parent event gets triggered
else self.parent.one('mount', function() {
// avoid to trigger the `mount` event for the tags
// not visible included in an if statement
if (!isInStub(self.root)) {
self.parent.isMounted = self.isMounted = true
self.trigger('mount')
}
})
})
defineProperty(this, 'unmount', function(keepRootTag) {
var el = root,
p = el.parentNode,
ptag,
tagIndex = __virtualDom.indexOf(self)
self.trigger('before-unmount')
// remove this tag instance from the global virtualDom variable
if (~tagIndex)
__virtualDom.splice(tagIndex, 1)
if (this._virts) {
each(this._virts, function(v) {
if (v.parentNode) v.parentNode.removeChild(v)
})
}
if (p) {
if (parent) {
ptag = getImmediateCustomParentTag(parent)
// remove this tag from the parent tags object
// if there are multiple nested tags with same name..
// remove this element form the array
if (isArray(ptag.tags[tagName]))
each(ptag.tags[tagName], function(tag, i) {
if (tag._riot_id == self._riot_id)
ptag.tags[tagName].splice(i, 1)
})
else
// otherwise just delete the tag instance
ptag.tags[tagName] = undefined
}
else
while (el.firstChild) el.removeChild(el.firstChild)
if (!keepRootTag)
p.removeChild(el)
else
// the riot-tag attribute isn't needed anymore, remove it
remAttr(p, 'riot-tag')
}
self.trigger('unmount')
toggle()
self.off('*')
self.isMounted = false
delete root._tag
})
function toggle(isMount) {
// mount/unmount children
each(childTags, function(child) { child[isMount ? 'mount' : 'unmount']() })
// listen/unlisten parent (events flow one way from parent to children)
if (!parent) return
var evt = isMount ? 'on' : 'off'
// the loop tags will be always in sync with the parent automatically
if (isLoop)
parent[evt]('unmount', self.unmount)
else
parent[evt]('update', self.update)[evt]('unmount', self.unmount)
}
// named elements available for fn
parseNamedElements(dom, this, childTags)
}
/**
* Attach an event to a DOM node
* @param { String } name - event name
* @param { Function } handler - event callback
* @param { Object } dom - dom node
* @param { Tag } tag - tag instance
*/
function setEventHandler(name, handler, dom, tag) {
dom[name] = function(e) {
var ptag = tag._parent,
item = tag._item,
el
if (!item)
while (ptag && !item) {
item = ptag._item
ptag = ptag._parent
}
// cross browser event fix
e = e || window.event
// override the event properties
if (isWritable(e, 'currentTarget')) e.currentTarget = dom
if (isWritable(e, 'target')) e.target = e.srcElement
if (isWritable(e, 'which')) e.which = e.charCode || e.keyCode
e.item = item
// prevent default behaviour (by default)
if (handler.call(tag, e) !== true && !/radio|check/.test(dom.type)) {
if (e.preventDefault) e.preventDefault()
e.returnValue = false
}
if (!e.preventUpdate) {
el = item ? getImmediateCustomParentTag(ptag) : tag
el.update()
}
}
}
/**
* Insert a DOM node replacing another one (used by if- attribute)
* @param { Object } root - parent node
* @param { Object } node - node replaced
* @param { Object } before - node added
*/
function insertTo(root, node, before) {
if (!root) return
root.insertBefore(before, node)
root.removeChild(node)
}
/**
* Update the expressions in a Tag instance
* @param { Array } expressions - expression that must be re evaluated
* @param { Tag } tag - tag instance
*/
function update(expressions, tag) {
each(expressions, function(expr, i) {
var dom = expr.dom,
attrName = expr.attr,
value = tmpl(expr.expr, tag),
parent = expr.dom.parentNode
if (expr.bool)
value = value ? attrName : false
else if (value == null)
value = ''
// leave out riot- prefixes from strings inside textarea
// fix #815: any value -> string
if (parent && parent.tagName == 'TEXTAREA') {
value = ('' + value).replace(/riot-/g, '')
// change textarea's value
parent.value = value
}
// no change
if (expr.value === value) return
expr.value = value
// text node
if (!attrName) {
dom.nodeValue = '' + value // #815 related
return
}
// remove original attribute
remAttr(dom, attrName)
// event handler
if (isFunction(value)) {
setEventHandler(attrName, value, dom, tag)
// if- conditional
} else if (attrName == 'if') {
var stub = expr.stub,
add = function() { insertTo(stub.parentNode, stub, dom) },
remove = function() { insertTo(dom.parentNode, dom, stub) }
// add to DOM
if (value) {
if (stub) {
add()
dom.inStub = false
// avoid to trigger the mount event if the tags is not visible yet
// maybe we can optimize this avoiding to mount the tag at all
if (!isInStub(dom)) {
walk(dom, function(el) {
if (el._tag && !el._tag.isMounted)
el._tag.isMounted = !!el._tag.trigger('mount')
})
}
}
// remove from DOM
} else {
stub = expr.stub = stub || document.createTextNode('')
// if the parentNode is defined we can easily replace the tag
if (dom.parentNode)
remove()
// otherwise we need to wait the updated event
else (tag.parent || tag).one('updated', remove)
dom.inStub = true
}
// show / hide
} else if (/^(show|hide)$/.test(attrName)) {
if (attrName == 'hide') value = !value
dom.style.display = value ? '' : 'none'
// field value
} else if (attrName == 'value') {
dom.value = value
// <img src="{ expr }">
} else if (startsWith(attrName, RIOT_PREFIX) && attrName != RIOT_TAG) {
if (value)
setAttr(dom, attrName.slice(RIOT_PREFIX.length), value)
} else {
if (expr.bool) {
dom[attrName] = value
if (!value) return
}
if (value === 0 || value && typeof value !== T_OBJECT)
setAttr(dom, attrName, value)
}
})
}
/**
* Specialized function for looping an array-like collection with `each={}`
* @param { Array } els - collection of items
* @param {Function} fn - callback function
* @returns { Array } the array looped
*/
function each(els, fn) {
var len = els ? els.length : 0
for (var i = 0, el; i < len; i++) {
el = els[i]
// return false -> current item was removed by fn during the loop
if (el != null && fn(el, i) === false) i--
}
return els
}
/**
* Detect if the argument passed is a function
* @param { * } v - whatever you want to pass to this function
* @returns { Boolean } -
*/
function isFunction(v) {
return typeof v === T_FUNCTION || false // avoid IE problems
}
/**
* Remove any DOM attribute from a node
* @param { Object } dom - DOM node we want to update
* @param { String } name - name of the property we want to remove
*/
function remAttr(dom, name) {
dom.removeAttribute(name)
}
/**
* Convert a string containing dashes to camel case
* @param { String } string - input string
* @returns { String } my-string -> myString
*/
function toCamel(string) {
return string.replace(/-(\w)/g, function(_, c) {
return c.toUpperCase()
})
}
/**
* Get the value of any DOM attribute on a node
* @param { Object } dom - DOM node we want to parse
* @param { String } name - name of the attribute we want to get
* @returns { String | undefined } name of the node attribute whether it exists
*/
function getAttr(dom, name) {
return dom.getAttribute(name)
}
/**
* Set any DOM attribute
* @param { Object } dom - DOM node we want to update
* @param { String } name - name of the property we want to set
* @param { String } val - value of the property we want to set
*/
function setAttr(dom, name, val) {
dom.setAttribute(name, val)
}
/**
* Detect the tag implementation by a DOM node
* @param { Object } dom - DOM node we need to parse to get its tag implementation
* @returns { Object } it returns an object containing the implementation of a custom tag (template and boot function)
*/
function getTag(dom) {
return dom.tagName && __tagImpl[getAttr(dom, RIOT_TAG) || dom.tagName.toLowerCase()]
}
/**
* Add a child tag to its parent into the `tags` object
* @param { Object } tag - child tag instance
* @param { String } tagName - key where the new tag will be stored
* @param { Object } parent - tag instance where the new child tag will be included
*/
function addChildTag(tag, tagName, parent) {
var cachedTag = parent.tags[tagName]
// if there are multiple children tags having the same name
if (cachedTag) {
// if the parent tags property is not yet an array
// create it adding the first cached tag
if (!isArray(cachedTag))
// don't add the same tag twice
if (cachedTag !== tag)
parent.tags[tagName] = [cachedTag]
// add the new nested tag to the array
if (!contains(parent.tags[tagName], tag))
parent.tags[tagName].push(tag)
} else {
parent.tags[tagName] = tag
}
}
/**
* Move the position of a custom tag in its parent tag
* @param { Object } tag - child tag instance
* @param { String } tagName - key where the tag was stored
* @param { Number } newPos - index where the new tag will be stored
*/
function moveChildTag(tag, tagName, newPos) {
var parent = tag.parent,
tags
// no parent no move
if (!parent) return
tags = parent.tags[tagName]
if (isArray(tags))
tags.splice(newPos, 0, tags.splice(tags.indexOf(tag), 1)[0])
else addChildTag(tag, tagName, parent)
}
/**
* Create a new child tag including it correctly into its parent
* @param { Object } child - child tag implementation
* @param { Object } opts - tag options containing the DOM node where the tag will be mounted
* @param { String } innerHTML - inner html of the child node
* @param { Object } parent - instance of the parent tag including the child custom tag
* @returns { Object } instance of the new child tag just created
*/
function initChildTag(child, opts, innerHTML, parent) {
var tag = new Tag(child, opts, innerHTML),
tagName = getTagName(opts.root),
ptag = getImmediateCustomParentTag(parent)
// fix for the parent attribute in the looped elements
tag.parent = ptag
// store the real parent tag
// in some cases this could be different from the custom parent tag
// for example in nested loops
tag._parent = parent
// add this tag to the custom parent tag
addChildTag(tag, tagName, ptag)
// and also to the real parent tag
if (ptag !== parent)
addChildTag(tag, tagName, parent)
// empty the child node once we got its template
// to avoid that its children get compiled multiple times
opts.root.innerHTML = ''
return tag
}
/**
* Loop backward all the parents tree to detect the first custom parent tag
* @param { Object } tag - a Tag instance
* @returns { Object } the instance of the first custom parent tag found
*/
function getImmediateCustomParentTag(tag) {
var ptag = tag
while (!getTag(ptag.root)) {
if (!ptag.parent) break
ptag = ptag.parent
}
return ptag
}
/**
* Helper function to set an immutable property
* @param { Object } el - object where the new property will be set
* @param { String } key - object key where the new property will be stored
* @param { * } value - value of the new property
* @param { Object } options - set the propery overriding the default options
* @returns { Object } - the initial object
*/
function defineProperty(el, key, value, options) {
Object.defineProperty(el, key, extend({
value: value,
enumerable: false,
writable: false,
configurable: false
}, options))
return el
}
/**
* Get the tag name of any DOM node
* @param { Object } dom - DOM node we want to parse
* @returns { String } name to identify this dom node in riot
*/
function getTagName(dom) {
var child = getTag(dom),
namedTag = getAttr(dom, 'name'),
tagName = namedTag && !tmpl.hasExpr(namedTag) ?
namedTag :
child ? child.name : dom.tagName.toLowerCase()
return tagName
}
/**
* Extend any object with other properties
* @param { Object } src - source object
* @returns { Object } the resulting extended object
*
* var obj = { foo: 'baz' }
* extend(obj, {bar: 'bar', foo: 'bar'})
* console.log(obj) => {bar: 'bar', foo: 'bar'}
*
*/
function extend(src) {
var obj, args = arguments
for (var i = 1; i < args.length; ++i) {
if (obj = args[i]) {
for (var key in obj) {
// check if this property of the source object could be overridden
if (isWritable(src, key))
src[key] = obj[key]
}
}
}
return src
}
/**
* Check whether an array contains an item
* @param { Array } arr - target array
* @param { * } item - item to test
* @returns { Boolean } Does 'arr' contain 'item'?
*/
function contains(arr, item) {
return ~arr.indexOf(item)
}
/**
* Check whether an object is a kind of array
* @param { * } a - anything
* @returns {Boolean} is 'a' an array?
*/
function isArray(a) { return Array.isArray(a) || a instanceof Array }
/**
* Detect whether a property of an object could be overridden
* @param { Object } obj - source object
* @param { String } key - object property
* @returns { Boolean } is this property writable?
*/
function isWritable(obj, key) {
var props = Object.getOwnPropertyDescriptor(obj, key)
return typeof obj[key] === T_UNDEF || props && props.writable
}
/**
* With this function we avoid that the internal Tag methods get overridden
* @param { Object } data - options we want to use to extend the tag instance
* @returns { Object } clean object without containing the riot internal reserved words
*/
function cleanUpData(data) {
if (!(data instanceof Tag) && !(data && typeof data.trigger == T_FUNCTION))
return data
var o = {}
for (var key in data) {
if (!contains(RESERVED_WORDS_BLACKLIST, key))
o[key] = data[key]
}
return o
}
/**
* Walk down recursively all the children tags starting dom node
* @param { Object } dom - starting node where we will start the recursion
* @param { Function } fn - callback to transform the child node just found
*/
function walk(dom, fn) {
if (dom) {
// stop the recursion
if (fn(dom) === false) return
else {
dom = dom.firstChild
while (dom) {
walk(dom, fn)
dom = dom.nextSibling
}
}
}
}
/**
* Minimize risk: only zero or one _space_ between attr & value
* @param { String } html - html string we want to parse
* @param { Function } fn - callback function to apply on any attribute found
*/
function walkAttributes(html, fn) {
var m,
re = /([-\w]+) ?= ?(?:"([^"]*)|'([^']*)|({[^}]*}))/g
while (m = re.exec(html)) {
fn(m[1].toLowerCase(), m[2] || m[3] || m[4])
}
}
/**
* Check whether a DOM node is in stub mode, useful for the riot 'if' directive
* @param { Object } dom - DOM node we want to parse
* @returns { Boolean } -
*/
function isInStub(dom) {
while (dom) {
if (dom.inStub) return true
dom = dom.parentNode
}
return false
}
/**
* Create a generic DOM node
* @param { String } name - name of the DOM node we want to create
* @returns { Object } DOM node just created
*/
function mkEl(name) {
return document.createElement(name)
}
/**
* Shorter and fast way to select multiple nodes in the DOM
* @param { String } selector - DOM selector
* @param { Object } ctx - DOM node where the targets of our search will is located
* @returns { Object } dom nodes found
*/
function $$(selector, ctx) {
return (ctx || document).querySelectorAll(selector)
}
/**
* Shorter and fast way to select a single node in the DOM
* @param { String } selector - unique dom selector
* @param { Object } ctx - DOM node where the target of our search will is located
* @returns { Object } dom node found
*/
function $(selector, ctx) {
return (ctx || document).querySelector(selector)
}
/**
* Simple object prototypal inheritance
* @param { Object } parent - parent object
* @returns { Object } child instance
*/
function inherit(parent) {
function Child() {}
Child.prototype = parent
return new Child()
}
/**
* Get the name property needed to identify a DOM node in riot
* @param { Object } dom - DOM node we need to parse
* @returns { String | undefined } give us back a string to identify this dom node
*/
function getNamedKey(dom) {
return getAttr(dom, 'id') || getAttr(dom, 'name')
}
/**
* Set the named properties of a tag element
* @param { Object } dom - DOM node we need to parse
* @param { Object } parent - tag instance where the named dom element will be eventually added
* @param { Array } keys - list of all the tag instance properties
*/
function setNamed(dom, parent, keys) {
// get the key value we want to add to the tag instance
var key = getNamedKey(dom),
isArr,
// add the node detected to a tag instance using the named property
add = function(value) {
// avoid to override the tag properties already set
if (contains(keys, key)) return
// check whether this value is an array
isArr = isArray(value)
// if the key was never set
if (!value)
// set it once on the tag instance
parent[key] = dom
// if it was an array and not yet set
else if (!isArr || isArr && !contains(value, dom)) {
// add the dom node into the array
if (isArr)
value.push(dom)
else
parent[key] = [value, dom]
}
}
// skip the elements with no named properties
if (!key) return
// check whether this key has been already evaluated
if (tmpl.hasExpr(key))
// wait the first updated event only once
parent.one('mount', function() {
key = getNamedKey(dom)
add(parent[key])
})
else
add(parent[key])
}
/**
* Faster String startsWith alternative
* @param { String } src - source string
* @param { String } str - test string
* @returns { Boolean } -
*/
function startsWith(src, str) {
return src.slice(0, str.length) === str
}
/**
* requestAnimationFrame function
* Adapted from https://gist.github.com/paulirish/1579671, license MIT
*/
var rAF = (function (w) {
var raf = w.requestAnimationFrame ||
w.mozRequestAnimationFrame || w.webkitRequestAnimationFrame
if (!raf || /iP(ad|hone|od).*OS 6/.test(w.navigator.userAgent)) { // buggy iOS6
var lastTime = 0
raf = function (cb) {
var nowtime = Date.now(), timeout = Math.max(16 - (nowtime - lastTime), 0)
setTimeout(function () { cb(lastTime = nowtime + timeout) }, timeout)
}
}
return raf
})(window || {})
/**
* Mount a tag creating new Tag instance
* @param { Object } root - dom node where the tag will be mounted
* @param { String } tagName - name of the riot tag we want to mount
* @param { Object } opts - options to pass to the Tag instance
* @returns { Tag } a new Tag instance
*/
function mountTo(root, tagName, opts) {
var tag = __tagImpl[tagName],
// cache the inner HTML to fix #855
innerHTML = root._innerHTML = root._innerHTML || root.innerHTML
// clear the inner html
root.innerHTML = ''
if (tag && root) tag = new Tag(tag, { root: root, opts: opts }, innerHTML)
if (tag && tag.mount) {
tag.mount()
// add this tag to the virtualDom variable
if (!contains(__virtualDom, tag)) __virtualDom.push(tag)
}
return tag
}
/**
* Riot public api
*/
// share methods for other riot parts, e.g. compiler
riot.util = { brackets: brackets, tmpl: tmpl }
/**
* Create a mixin that could be globally shared across all the tags
*/
riot.mixin = (function() {
var mixins = {}
/**
* Create/Return a mixin by its name
* @param { String } name - mixin name
* @param { Object } mixin - mixin logic
* @returns { Object } the mixin logic
*/
return function(name, mixin) {
if (!mixin) return mixins[name]
mixins[name] = mixin
}
})()
/**
* Create a new riot tag implementation
* @param { String } name - name/id of the new riot tag
* @param { String } html - tag template
* @param { String } css - custom tag css
* @param { String } attrs - root tag attributes
* @param { Function } fn - user function
* @returns { String } name/id of the tag just created
*/
riot.tag = function(name, html, css, attrs, fn) {
if (isFunction(attrs)) {
fn = attrs
if (/^[\w\-]+\s?=/.test(css)) {
attrs = css
css = ''
} else attrs = ''
}
if (css) {
if (isFunction(css)) fn = css
else styleManager.add(css)
}
__tagImpl[name] = { name: name, tmpl: html, attrs: attrs, fn: fn }
return name
}
/**
* Create a new riot tag implementation (for use by the compiler)
* @param { String } name - name/id of the new riot tag
* @param { String } html - tag template
* @param { String } css - custom tag css
* @param { String } attrs - root tag attributes
* @param { Function } fn - user function
* @param { string } [bpair] - brackets used in the compilation
* @returns { String } name/id of the tag just created
*/
riot.tag2 = function(name, html, css, attrs, fn, bpair) {
if (css) styleManager.add(css)
//if (bpair) riot.settings.brackets = bpair
__tagImpl[name] = { name: name, tmpl: html, attrs: attrs, fn: fn }
return name
}
/**
* Mount a tag using a specific tag implementation
* @param { String } selector - tag DOM selector
* @param { String } tagName - tag implementation name
* @param { Object } opts - tag logic
* @returns { Array } new tags instances
*/
riot.mount = function(selector, tagName, opts) {
var els,
allTags,
tags = []
// helper functions
function addRiotTags(arr) {
var list = ''
each(arr, function (e) {
if (!/[^-\w]/.test(e))
list += ',*[' + RIOT_TAG + '=' + e.trim() + ']'
})
return list
}
function selectAllTags() {
var keys = Object.keys(__tagImpl)
return keys + addRiotTags(keys)
}
function pushTags(root) {
var last
if (root.tagName) {
if (tagName && (!(last = getAttr(root, RIOT_TAG)) || last != tagName))
setAttr(root, RIOT_TAG, tagName)
var tag = mountTo(root, tagName || root.getAttribute(RIOT_TAG) || root.tagName.toLowerCase(), opts)
if (tag) tags.push(tag)
} else if (root.length)
each(root, pushTags) // assume nodeList
}
// ----- mount code -----
// inject styles into DOM
styleManager.inject()
if (typeof tagName === T_OBJECT) {
opts = tagName
tagName = 0
}
// crawl the DOM to find the tag
if (typeof selector === T_STRING) {
if (selector === '*')
// select all the tags registered
// and also the tags found with the riot-tag attribute set
selector = allTags = selectAllTags()
else
// or just the ones named like the selector
selector += addRiotTags(selector.split(','))
// make sure to pass always a selector
// to the querySelectorAll function
els = selector ? $$(selector) : []
}
else
// probably you have passed already a tag or a NodeList
els = selector
// select all the registered and mount them inside their root elements
if (tagName === '*') {
// get all custom tags
tagName = allTags || selectAllTags()
// if the root els it's just a single tag
if (els.tagName)
els = $$(tagName, els)
else {
// select all the children for all the different root elements
var nodeList = []
each(els, function (_el) {
nodeList.push($$(tagName, _el))
})
els = nodeList
}
// get rid of the tagName
tagName = 0
}
if (els.tagName)
pushTags(els)
else
each(els, pushTags)
return tags
}
/**
* Update all the tags instances created
* @returns { Array } all the tags instances
*/
riot.update = function() {
return each(__virtualDom, function(tag) {
tag.update()
})
}
/**
* Export the Tag constructor
*/
riot.Tag = Tag
/* istanbul ignore next */
/**
* @module parsers
*/
var parsers = (function () {
function _req (name) {
var parser = window[name]
if (parser) return parser
throw new Error(name + ' parser not found.')
}
function extend (obj, props) {
if (props) {
for (var prop in props) {
/* istanbul ignore next */
if (props.hasOwnProperty(prop)) {
obj[prop] = props[prop]
}
}
}
return obj
}
var _p = {
html: {
jade: function (html, opts, url) {
opts = extend({
pretty: true,
filename: url,
doctype: 'html'
}, opts)
return _req('jade').render(html, opts)
}
},
css: {
less: function (tag, css, opts, url) {
var ret
opts = extend({
sync: true,
syncImport: true,
filename: url
}, opts)
_req('less').render(css, opts, function (err, result) {
// istanbul ignore next
if (err) throw err
ret = result.css
})
return ret
}
},
js: {
es6: function (js, opts) {
opts = extend({
blacklist: ['useStrict', 'strict', 'react'],
sourceMaps: false,
comments: false
}, opts)
return _req('babel').transform(js, opts).code
},
babel: function (js, opts, url) {
return _req('babel').transform(js, extend({ filename: url }, opts)).code
},
coffee: function (js, opts) {
return _req('CoffeeScript').compile(js, extend({ bare: true }, opts))
},
livescript: function (js, opts) {
return _req('livescript').compile(js, extend({ bare: true, header: false }, opts))
},
typescript: function (js, opts) {
return _req('typescript')(js, opts)
},
none: function (js) {
return js
}
}
}
_p.js.javascript = _p.js.none
_p.js.coffeescript = _p.js.coffee
return _p
})()
riot.parsers = parsers
/**
* Compiler for riot custom tags
* @version v2.3.22
*/
var compile = (function () {
var S_LINESTR = /"[^"\n\\]*(?:\\[\S\s][^"\n\\]*)*"|'[^'\n\\]*(?:\\[\S\s][^'\n\\]*)*'/.source
var S_STRINGS = brackets.R_STRINGS.source
var HTML_ATTRS = / *([-\w:\xA0-\xFF]+) ?(?:= ?('[^']*'|"[^"]*"|\S+))?/g
var HTML_COMMS = RegExp(/<!--(?!>)[\S\s]*?-->/.source + '|' + S_LINESTR, 'g')
var HTML_TAGS = /<([-\w]+)(?:\s+([^"'\/>]*(?:(?:"[^"]*"|'[^']*'|\/[^>])[^'"\/>]*)*)|\s*)(\/?)>/g
var BOOL_ATTRS = RegExp(
'^(?:disabled|checked|readonly|required|allowfullscreen|auto(?:focus|play)|' +
'compact|controls|default|formnovalidate|hidden|ismap|itemscope|loop|' +
'multiple|muted|no(?:resize|shade|validate|wrap)?|open|reversed|seamless|' +
'selected|sortable|truespeed|typemustmatch)$')
var RIOT_ATTRS = ['style', 'src', 'd']
var VOID_TAGS = /^(?:input|img|br|wbr|hr|area|base|col|embed|keygen|link|meta|param|source|track)$/
var PRE_TAGS = /<pre(?:\s+(?:[^">]*|"[^"]*")*)?>([\S\s]+?)<\/pre\s*>/gi
var SPEC_TYPES = /^"(?:number|date(?:time)?|time|month|email|color)\b/i
var TRIM_TRAIL = /[ \t]+$/gm
var
DQ = '"',
SQ = "'"
function cleanSource (src) {
var
mm,
re = HTML_COMMS
if (~src.indexOf('\r')) {
src = src.replace(/\r\n?/g, '\n')
}
re.lastIndex = 0
while (mm = re.exec(src)) {
if (mm[0][0] === '<') {
src = RegExp.leftContext + RegExp.rightContext
re.lastIndex = mm[3] + 1
}
}
return src
}
function parseAttribs (str, pcex) {
var
list = [],
match,
type, vexp
HTML_ATTRS.lastIndex = 0
str = str.replace(/\s+/g, ' ')
while (match = HTML_ATTRS.exec(str)) {
var
k = match[1].toLowerCase(),
v = match[2]
if (!v) {
list.push(k)
} else {
if (v[0] !== DQ) {
v = DQ + (v[0] === SQ ? v.slice(1, -1) : v) + DQ
}
if (k === 'type' && SPEC_TYPES.test(v)) {
type = v
} else {
if (/\u0001\d/.test(v)) {
if (k === 'value') vexp = 1
else if (BOOL_ATTRS.test(k)) k = '__' + k
else if (~RIOT_ATTRS.indexOf(k)) k = 'riot-' + k
}
list.push(k + '=' + v)
}
}
}
if (type) {
if (vexp) type = DQ + pcex._bp[0] + SQ + type.slice(1, -1) + SQ + pcex._bp[1] + DQ
list.push('type=' + type)
}
return list.join(' ')
}
function splitHtml (html, opts, pcex) {
var _bp = pcex._bp
if (html && _bp[4].test(html)) {
var
jsfn = opts.expr && (opts.parser || opts.type) ? _compileJS : 0,
list = brackets.split(html, 0, _bp),
expr, israw
for (var i = 1; i < list.length; i += 2) {
expr = list[i]
if (expr[0] === '^') {
expr = expr.slice(1)
} else if (jsfn) {
israw = expr[0] === '='
expr = jsfn(israw ? expr.slice(1) : expr, opts).trim()
if (expr.slice(-1) === ';') expr = expr.slice(0, -1)
if (israw) expr = '=' + expr
}
list[i] = '\u0001' + (pcex.push(expr) - 1) + _bp[1]
}
html = list.join('')
}
return html
}
function restoreExpr (html, pcex) {
if (pcex.length) {
html = html
.replace(/\u0001(\d+)/g, function (_, d) {
var expr = pcex[d]
if (expr[0] === '=') {
expr = expr.replace(brackets.R_STRINGS, function (qs) {
return qs
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
})
}
return pcex._bp[0] + expr.trim().replace(/[\r\n]+/g, ' ').replace(/"/g, '\u2057')
})
}
return html
}
function _compileHTML (html, opts, pcex) {
html = splitHtml(html, opts, pcex)
.replace(HTML_TAGS, function (_, name, attr, ends) {
name = name.toLowerCase()
ends = ends && !VOID_TAGS.test(name) ? '></' + name : ''
if (attr) name += ' ' + parseAttribs(attr, pcex)
return '<' + name + ends + '>'
})
if (!opts.whitespace) {
var p = []
if (/<pre[\s>]/.test(html)) {
html = html.replace(PRE_TAGS, function (q) {
p.push(q)
return '\u0002'
})
}
html = html.trim().replace(/\s+/g, ' ')
if (p.length) html = html.replace(/\u0002/g, function () { return p.shift() })
}
if (opts.compact) html = html.replace(/>[ \t]+<([-\w\/])/g, '><$1')
return restoreExpr(html, pcex).replace(TRIM_TRAIL, '')
}
function compileHTML (html, opts, pcex) {
if (Array.isArray(opts)) {
pcex = opts
opts = {}
} else {
if (!pcex) pcex = []
if (!opts) opts = {}
}
pcex._bp = brackets.array(opts.brackets)
return _compileHTML(cleanSource(html), opts, pcex)
}
var JS_ES6SIGN = /^[ \t]*([$_A-Za-z][$\w]*)\s*\([^()]*\)\s*{/m
var JS_ES6END = RegExp('[{}]|' + brackets.S_QBLOCKS, 'g')
var JS_COMMS = RegExp(brackets.R_MLCOMMS.source + '|//[^\r\n]*|' + brackets.S_QBLOCKS, 'g')
function riotjs (js) {
var
parts = [],
match,
toes5,
pos,
name,
RE = RegExp
if (~js.indexOf('/')) js = rmComms(js, JS_COMMS)
while (match = js.match(JS_ES6SIGN)) {
parts.push(RE.leftContext)
js = RE.rightContext
pos = skipBody(js, JS_ES6END)
name = match[1]
toes5 = !/^(?:if|while|for|switch|catch|function)$/.test(name)
name = toes5 ? match[0].replace(name, 'this.' + name + ' = function') : match[0]
parts.push(name, js.slice(0, pos))
js = js.slice(pos)
if (toes5 && !/^\s*.\s*bind\b/.test(js)) parts.push('.bind(this)')
}
return parts.length ? parts.join('') + js : js
function rmComms (s, r, m) {
r.lastIndex = 0
while (m = r.exec(s)) {
if (m[0][0] === '/' && !m[1] && !m[2]) {
s = RE.leftContext + ' ' + RE.rightContext
r.lastIndex = m[3] + 1
}
}
return s
}
function skipBody (s, r) {
var m, i = 1
r.lastIndex = 0
while (i && (m = r.exec(s))) {
if (m[0] === '{') ++i
else if (m[0] === '}') --i
}
return i ? s.length : r.lastIndex
}
}
function _compileJS (js, opts, type, parserOpts, url) {
if (!/\S/.test(js)) return ''
if (!type) type = opts.type
var parser = opts.parser || (type ? parsers.js[type] : riotjs)
if (!parser) {
throw new Error('JS parser not found: "' + type + '"')
}
return parser(js, parserOpts, url).replace(/\r\n?/g, '\n').replace(TRIM_TRAIL, '')
}
function compileJS (js, opts, type, userOpts) {
if (typeof opts === 'string') {
userOpts = type
type = opts
opts = {}
}
if (type && typeof type === 'object') {
userOpts = type
type = ''
}
if (!userOpts) userOpts = {}
return _compileJS(js, opts || {}, type, userOpts.parserOptions, userOpts.url)
}
var CSS_SELECTOR = RegExp('([{}]|^)[ ;]*([^@ ;{}][^{}]*)(?={)|' + S_LINESTR, 'g')
function scopedCSS (tag, css) {
var scope = ':scope'
return css.replace(CSS_SELECTOR, function (m, p1, p2) {
if (!p2) return m
p2 = p2.replace(/[^,]+/g, function (sel) {
var s = sel.trim()
if (!s || s === 'from' || s === 'to' || s.slice(-1) === '%') {
return sel
}
if (s.indexOf(scope) < 0) {
s = tag + ' ' + s + ',[riot-tag="' + tag + '"] ' + s
} else {
s = s.replace(scope, tag) + ',' +
s.replace(scope, '[riot-tag="' + tag + '"]')
}
return sel.slice(-1) === ' ' ? s + ' ' : s
})
return p1 ? p1 + ' ' + p2 : p2
})
}
function _compileCSS (css, tag, type, opts) {
var scoped = (opts || (opts = {})).scoped
if (type) {
if (type === 'scoped-css') {
scoped = true
} else if (parsers.css[type]) {
css = parsers.css[type](tag, css, opts.parserOpts || {}, opts.url)
} else if (type !== 'css') {
throw new Error('CSS parser not found: "' + type + '"')
}
}
css = css.replace(brackets.R_MLCOMMS, '').replace(/\s+/g, ' ').trim()
if (scoped) {
if (!tag) {
throw new Error('Can not parse scoped CSS without a tagName')
}
css = scopedCSS(tag, css)
}
return css
}
function compileCSS (css, type, opts) {
if (type && typeof type === 'object') {
opts = type
type = ''
} else if (!opts) opts = {}
return _compileCSS(css, opts.tagName, type, opts)
}
var TYPE_ATTR = /\stype\s*=\s*(?:(['"])(.+?)\1|(\S+))/i
var MISC_ATTR = '\\s*=\\s*(' + S_STRINGS + '|{[^}]+}|\\S+)'
var END_TAGS = /\/>\n|^<(?:\/?[-\w]+\s*|[-\w]+\s+[-\w:\xA0-\xFF][\S\s]*?)>\n/
function _q (s, r) {
if (!s) return "''"
s = SQ + s.replace(/\\/g, '\\\\').replace(/'/g, "\\'") + SQ
return r && ~s.indexOf('\n') ? s.replace(/\n/g, '\\n') : s
}
function mktag (name, html, css, attribs, js, pcex) {
var
c = ', ',
s = '}' + (pcex.length ? ', ' + _q(pcex._bp[8]) : '') + ');'
if (js && js.slice(-1) !== '\n') s = '\n' + s
return 'riot.tag2(\'' + name + SQ +
c + _q(html, 1) +
c + _q(css) +
c + _q(attribs) + ', function(opts) {\n' + js + s
}
function splitBlocks (str) {
if (/<[-\w]/.test(str)) {
var
m,
k = str.lastIndexOf('<'),
n = str.length
while (~k) {
m = str.slice(k, n).match(END_TAGS)
if (m) {
k += m.index + m[0].length
return [str.slice(0, k), str.slice(k)]
}
n = k
k = str.lastIndexOf('<', k - 1)
}
}
return ['', str]
}
function getType (attribs) {
if (attribs) {
var match = attribs.match(TYPE_ATTR)
match = match && (match[2] || match[3])
if (match) {
return match.replace('text/', '')
}
}
return ''
}
function getAttrib (attribs, name) {
if (attribs) {
var match = attribs.match(RegExp('\\s' + name + MISC_ATTR, 'i'))
match = match && match[1]
if (match) {
return (/^['"]/).test(match) ? match.slice(1, -1) : match
}
}
return ''
}
function getParserOptions (attribs) {
var opts = getAttrib(attribs, 'options')
return opts ? JSON.parse(opts) : null
}
function getCode (code, opts, attribs, base) {
var type = getType(attribs)
return _compileJS(code, opts, type, getParserOptions(attribs), base)
}
function cssCode (code, opts, attribs, url, tag) {
var extraOpts = {
parserOpts: getParserOptions(attribs),
scoped: attribs && /\sscoped(\s|=|$)/i.test(attribs),
url: url
}
return _compileCSS(code, tag, getType(attribs) || opts.style, extraOpts)
}
function compileTemplate (html, url, lang, opts) {
var parser = parsers.html[lang]
if (!parser) {
throw new Error('Template parser not found: "' + lang + '"')
}
return parser(html, opts, url)
}
var
CUST_TAG = RegExp(/^([ \t]*)<([-\w]+)(?:\s+([^'"\/>]+(?:(?:@|\/[^>])[^'"\/>]*)*)|\s*)?(?:\/>|>[ \t]*\n?([\S\s]*)^\1<\/\2\s*>|>(.*)<\/\2\s*>)/
.source.replace('@', S_STRINGS), 'gim'),
SCRIPTS = /<script(\s+[^>]*)?>\n?([\S\s]*?)<\/script\s*>/gi,
STYLES = /<style(\s+[^>]*)?>\n?([\S\s]*?)<\/style\s*>/gi
function compile (src, opts, url) {
var
parts = [],
included
if (!opts) opts = {}
included = opts.exclude
? function (s) { return opts.exclude.indexOf(s) < 0 } : function () { return 1 }
if (!url) url = ''
var _bp = brackets.array(opts.brackets)
if (opts.template) {
src = compileTemplate(src, url, opts.template, opts.templateOptions)
}
src = cleanSource(src)
.replace(CUST_TAG, function (_, indent, tagName, attribs, body, body2) {
var
jscode = '',
styles = '',
html = '',
pcex = []
pcex._bp = _bp
tagName = tagName.toLowerCase()
attribs = attribs && included('attribs')
? restoreExpr(
parseAttribs(
splitHtml(attribs, opts, pcex),
pcex),
pcex) : ''
if ((body || (body = body2)) && /\S/.test(body)) {
if (body2) {
if (included('html')) html = _compileHTML(body2, opts, pcex)
} else {
body = body.replace(RegExp('^' + indent, 'gm'), '')
body = body.replace(STYLES, function (_m, _attrs, _style) {
if (included('css')) {
styles += (styles ? ' ' : '') + cssCode(_style, opts, _attrs, url, tagName)
}
return ''
})
body = body.replace(SCRIPTS, function (_m, _attrs, _script) {
if (included('js')) {
var code = getCode(_script, opts, _attrs, url)
if (code) jscode += (jscode ? '\n' : '') + code
}
return ''
})
var blocks = splitBlocks(body.replace(TRIM_TRAIL, ''))
if (included('html')) {
html = _compileHTML(blocks[0], opts, pcex)
}
if (included('js')) {
body = _compileJS(blocks[1], opts, null, null, url)
if (body) jscode += (jscode ? '\n' : '') + body
}
}
}
jscode = /\S/.test(jscode) ? jscode.replace(/\n{3,}/g, '\n\n') : ''
if (opts.entities) {
parts.push({
tagName: tagName,
html: html,
css: styles,
attribs: attribs,
js: jscode
})
return ''
}
return mktag(tagName, html, styles, attribs, jscode, pcex)
})
if (opts.entities) return parts
return src
}
riot.util.compiler = {
compile: compile,
html: compileHTML,
css: compileCSS,
js: compileJS,
version: 'v2.3.22'
}
return compile
})()
/*
Compilation for the browser
*/
riot.compile = (function () {
var
promise, // emits the 'ready' event and runs the first callback
ready // all the scripts were compiled?
// gets the source of an external tag with an async call
function GET (url, fn, opts) {
var req = new XMLHttpRequest()
req.onreadystatechange = function () {
if (req.readyState === 4 &&
(req.status === 200 || !req.status && req.responseText.length)) {
fn(req.responseText, opts, url)
}
}
req.open('GET', url, true)
req.send('')
}
// evaluates a compiled tag within the global context
function globalEval (js, url) {
if (typeof js === T_STRING) {
var
node = mkEl('script'),
root = document.documentElement
// make the source available in the "(no domain)" tab
// of Chrome DevTools, with a .js extension
if (url) js += '\n//# sourceURL=' + url + '.js'
node.text = js
root.appendChild(node)
root.removeChild(node)
}
}
// compiles all the internal and external tags on the page
function compileScripts (fn, xopt) {
var
scripts = $$('script[type="riot/tag"]'),
scriptsAmount = scripts.length
function done() {
promise.trigger('ready')
ready = true
if (fn) fn()
}
function compileTag (src, opts, url) {
var code = compile(src, opts, url)
globalEval(code, url)
if (!--scriptsAmount) done()
}
if (!scriptsAmount) done()
else {
for (var i = 0; i < scripts.length; ++i) {
var
script = scripts[i],
opts = extend({template: getAttr(script, 'template')}, xopt),
url = getAttr(script, 'src')
url ? GET(url, compileTag, opts) : compileTag(script.innerHTML, opts)
}
}
}
//// Entry point -----
return function (arg, fn, opts) {
if (typeof arg === T_STRING) {
// 2nd parameter is optional, but can be null
if (fn && typeof fn === T_OBJECT) {
opts = fn
fn = false
}
// `riot.compile(tag [, callback | true][, options])`
if (/^\s*</m.test(arg)) {
var js = compile(arg, opts)
if (fn !== true) globalEval(js)
if (isFunction(fn)) fn(js, arg, opts)
return js
}
// `riot.compile(url [, callback][, options])`
GET(arg, function (str, opts, url) {
var js = compile(str, opts, url)
globalEval(js, url)
if (fn) fn(js, str, opts)
})
}
else {
// `riot.compile([callback][, options])`
if (isFunction(arg)) {
opts = fn
fn = arg
} else {
opts = arg
fn = undefined
}
if (ready) {
return fn && fn()
}
if (promise) {
if (fn) promise.on('ready', fn)
} else {
promise = riot.observable()
compileScripts(fn, opts)
}
}
}
})()
// reassign mount methods -----
var mount = riot.mount
riot.mount = function (a, b, c) {
var ret
riot.compile(function () { ret = mount(a, b, c) })
return ret
}
// support CommonJS, AMD & browser
/* istanbul ignore next */
if (typeof exports === T_OBJECT)
module.exports = riot
else if (typeof define === T_FUNCTION && typeof define.amd !== T_UNDEF)
define(function() { return riot })
else
window.riot = riot
})(typeof window != 'undefined' ? window : void 0);