2018-09-26 08:06:57 +00:00
( function ( ) {
'use strict' ;
2018-09-26 13:14:12 +00:00
function _extends ( ) {
_extends = Object . assign || function ( target ) {
for ( var i = 1 ; i < arguments . length ; i ++ ) {
var source = arguments [ i ] ;
for ( var key in source ) {
if ( Object . prototype . hasOwnProperty . call ( source , key ) ) {
target [ key ] = source [ key ] ;
}
}
}
return target ;
} ;
return _extends . apply ( this , arguments ) ;
}
2018-09-26 08:06:57 +00:00
/ *
Possible todos :
0. Add XSLT to JML - string stylesheet ( or even vice versa )
0. IE problem : Add JsonML code to handle name attribute ( during element creation )
0. Element - specific : IE object - param handling
Todos inspired by JsonML : https : //github.com/mckamey/jsonml/blob/master/jsonml-html.js
0. duplicate attributes ?
0. expand ATTR _MAP
0. equivalent of markup , to allow strings to be embedded within an object ( e . g . , { $value : '<div>id</div>' } ) ; advantage over innerHTML in that it wouldn ' t need to work as the entire contents ( nor destroy any existing content or handlers )
0. More validation ?
0. JsonML DOM Level 0 listener
0. Whitespace trimming ?
JsonML element - specific :
0. table appending
0. canHaveChildren necessary ? ( attempts to append to script and img )
Other Todos :
0. Note to self : Integrate research from other jml notes
0. Allow Jamilih to be seeded with an existing element , so as to be able to add / modify attributes and children
0. Allow array as single first argument
0. Settle on whether need to use null as last argument to return array ( or fragment ) or other way to allow appending ? Options object at end instead to indicate whether returning array , fragment , first element , etc . ?
0. Allow building of generic XML ( pass configuration object )
0. Allow building content internally as a string ( though allowing DOM methods , etc . ? )
0. Support JsonML empty string element name to represent fragments ?
0. Redo browser testing of jml ( including ensuring IE7 can work even if test framework can ' t work )
* /
2020-01-25 02:32:24 +00:00
// istanbul ignore next
2020-07-18 11:25:50 +00:00
let win = typeof window !== 'undefined' && window ; // istanbul ignore next
2020-01-25 02:32:24 +00:00
2020-07-18 11:25:50 +00:00
let doc = typeof document !== 'undefined' && document || win && win . document ; // STATIC PROPERTIES
2018-09-26 08:06:57 +00:00
2020-07-18 11:25:50 +00:00
const possibleOptions = [ '$plugins' , // '$mode', // Todo (SVG/XML)
2020-01-29 04:09:42 +00:00
// '$state', // Used internally
2019-05-24 12:17:17 +00:00
'$map' // Add any other options here
2018-09-26 08:06:57 +00:00
] ;
2020-07-18 11:25:50 +00:00
const NS _HTML = 'http://www.w3.org/1999/xhtml' ,
hyphenForCamelCase = /-([a-z])/gu ;
const ATTR _MAP = {
2020-04-01 07:32:51 +00:00
maxlength : 'maxLength' ,
minlength : 'minLength' ,
2019-05-24 12:17:17 +00:00
readonly : 'readOnly'
2018-09-26 13:14:12 +00:00
} ; // We define separately from ATTR_DOM for clarity (and parity with JsonML) but no current need
2018-09-26 08:06:57 +00:00
// We don't set attribute esp. for boolean atts as we want to allow setting of `undefined`
// (e.g., from an empty variable) on templates to have no effect
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
const BOOL _ATTS = [ 'checked' , 'defaultChecked' , 'defaultSelected' , 'disabled' , 'indeterminate' , 'open' , // Dialog elements
2019-05-24 12:17:17 +00:00
'readOnly' , 'selected' ] ; // From JsonML
2020-07-18 11:25:50 +00:00
const ATTR _DOM = BOOL _ATTS . concat ( [ 'accessKey' , // HTMLElement
2018-09-26 08:06:57 +00:00
'async' , 'autocapitalize' , // HTMLElement
'autofocus' , 'contentEditable' , // HTMLElement through ElementContentEditable
'defaultValue' , 'defer' , 'draggable' , // HTMLElement
'formnovalidate' , 'hidden' , // HTMLElement
'innerText' , // HTMLElement
'inputMode' , // HTMLElement through ElementContentEditable
'ismap' , 'multiple' , 'novalidate' , 'pattern' , 'required' , 'spellcheck' , // HTMLElement
'translate' , // HTMLElement
2018-09-26 13:14:12 +00:00
'value' , 'willvalidate' ] ) ; // Todo: Add more to this as useful for templating
2018-09-26 08:06:57 +00:00
// to avoid setting through nullish value
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
const NULLABLES = [ 'autocomplete' , 'dir' , // HTMLElement
2020-02-01 12:44:13 +00:00
'integrity' , // script, link
2018-09-26 08:06:57 +00:00
'lang' , // HTMLElement
2020-04-01 07:32:51 +00:00
'max' , 'min' , 'minLength' , 'maxLength' , 'title' // HTMLElement
2018-09-26 08:06:57 +00:00
] ;
2020-07-18 11:25:50 +00:00
const $ = sel => doc . querySelector ( sel ) ;
2018-09-26 08:06:57 +00:00
/ * *
2019-05-24 12:17:17 +00:00
* Retrieve the ( lower - cased ) HTML name of a node .
2018-09-26 08:06:57 +00:00
* @ static
* @ param { Node } node The HTML node
2019-05-24 12:17:17 +00:00
* @ returns { string } The lower - cased node name
2018-09-26 08:06:57 +00:00
* /
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
function _getHTMLNodeName ( node ) {
2018-09-26 13:14:12 +00:00
return node . nodeName && node . nodeName . toLowerCase ( ) ;
2018-09-26 08:06:57 +00:00
}
/ * *
2019-05-24 12:17:17 +00:00
* Apply styles if this is a style tag .
2018-09-26 08:06:57 +00:00
* @ static
* @ param { Node } node The element to check whether it is a style tag
2019-05-24 12:17:17 +00:00
* @ returns { void }
2018-09-26 08:06:57 +00:00
* /
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
function _applyAnyStylesheet ( node ) {
2020-01-25 02:32:24 +00:00
// Only used in IE
// istanbul ignore else
2018-09-26 13:14:12 +00:00
if ( ! doc . createStyleSheet ) {
return ;
2020-01-25 02:32:24 +00:00
} // istanbul ignore next
2018-09-26 08:06:57 +00:00
2018-09-26 13:14:12 +00:00
if ( _getHTMLNodeName ( node ) === 'style' ) {
// IE
2020-07-18 11:25:50 +00:00
const ss = doc . createStyleSheet ( ) ; // Create a stylesheet to actually do something useful
2018-09-26 13:14:12 +00:00
ss . cssText = node . cssText ; // We continue to add the style tag, however
}
}
2018-09-26 08:06:57 +00:00
/ * *
2019-05-24 12:17:17 +00:00
* Need this function for IE since options weren ' t otherwise getting added .
2018-09-26 08:06:57 +00:00
* @ private
* @ static
2019-05-24 12:17:17 +00:00
* @ param { Element } parent The parent to which to append the element
* @ param { Node } child The element or other node to append to the parent
* @ returns { void }
2018-09-26 08:06:57 +00:00
* /
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
function _appendNode ( parent , child ) {
2020-07-18 11:25:50 +00:00
const parentName = _getHTMLNodeName ( parent ) ; // IE only
2020-01-25 02:32:24 +00:00
// istanbul ignore if
2018-09-26 13:14:12 +00:00
if ( doc . createStyleSheet ) {
if ( parentName === 'script' ) {
parent . text = child . nodeValue ;
return ;
2018-09-26 08:06:57 +00:00
}
2018-09-26 13:14:12 +00:00
if ( parentName === 'style' ) {
parent . cssText = child . nodeValue ; // This will not apply it--just make it available within the DOM cotents
return ;
2018-09-26 08:06:57 +00:00
}
2018-09-26 13:14:12 +00:00
}
if ( parentName === 'template' ) {
2019-05-24 12:17:17 +00:00
parent . content . append ( child ) ;
2018-09-26 13:14:12 +00:00
return ;
}
try {
2019-05-24 12:17:17 +00:00
parent . append ( child ) ; // IE9 is now ok with this
2018-09-26 13:14:12 +00:00
} catch ( e ) {
2020-01-25 02:32:24 +00:00
// istanbul ignore next
2020-07-18 11:25:50 +00:00
const childName = _getHTMLNodeName ( child ) ; // istanbul ignore next
2020-01-29 04:09:42 +00:00
2018-09-26 13:14:12 +00:00
if ( parentName === 'select' && childName === 'option' ) {
try {
// Since this is now DOM Level 4 standard behavior (and what IE7+ can handle), we try it first
parent . add ( child ) ;
} catch ( err ) {
// DOM Level 2 did require a second argument, so we try it too just in case the user is using an older version of Firefox, etc.
parent . add ( child , null ) ; // IE7 has a problem with this, but IE8+ is ok
}
return ;
2020-01-25 02:32:24 +00:00
} // istanbul ignore next
2018-09-26 08:06:57 +00:00
2018-09-26 13:14:12 +00:00
throw e ;
}
}
2018-09-26 08:06:57 +00:00
/ * *
2019-05-24 12:17:17 +00:00
* Attach event in a cross - browser fashion .
2018-09-26 08:06:57 +00:00
* @ static
2019-05-24 12:17:17 +00:00
* @ param { Element } el DOM element to which to attach the event
* @ param { string } type The DOM event ( without 'on' ) to attach to the element
2019-11-13 06:16:06 +00:00
* @ param { EventListener } handler The event handler to attach to the element
2019-05-24 12:17:17 +00:00
* @ param { boolean } [ capturing ] Whether or not the event should be
* capturing ( W3C - browsers only ) ; default is false ; NOT IN USE
* @ returns { void }
2018-09-26 08:06:57 +00:00
* /
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
function _addEvent ( el , type , handler , capturing ) {
2019-05-24 12:17:17 +00:00
el . addEventListener ( type , handler , Boolean ( capturing ) ) ;
2018-09-26 08:06:57 +00:00
}
/ * *
2019-05-24 12:17:17 +00:00
* Creates a text node of the result of resolving an entity or character reference .
2018-09-26 08:06:57 +00:00
* @ param { 'entity' | 'decimal' | 'hexadecimal' } type Type of reference
2019-05-24 12:17:17 +00:00
* @ param { string } prefix Text to prefix immediately after the "&"
* @ param { string } arg The body of the reference
2018-09-26 08:06:57 +00:00
* @ returns { Text } The text node of the resolved reference
* /
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
function _createSafeReference ( type , prefix , arg ) {
2020-01-25 02:32:24 +00:00
// For security reasons related to innerHTML, we ensure this string only
// contains potential entity characters
2020-07-18 11:25:50 +00:00
if ( ! arg . match ( /^\w+$/u ) ) {
throw new TypeError ( ` Bad ${ type } reference; with prefix " ${ prefix } " and arg " ${ arg } " ` ) ;
2018-09-26 13:14:12 +00:00
}
2018-09-26 08:06:57 +00:00
2020-07-18 11:25:50 +00:00
const elContainer = doc . createElement ( 'div' ) ; // Todo: No workaround for XML?
2020-01-25 02:32:24 +00:00
// eslint-disable-next-line no-unsanitized/property
2018-09-26 13:14:12 +00:00
elContainer . innerHTML = '&' + prefix + arg + ';' ;
return doc . createTextNode ( elContainer . innerHTML ) ;
}
2018-09-26 08:06:57 +00:00
/ * *
2019-05-24 12:17:17 +00:00
* @ param { string } n0 Whole expression match ( including "-" )
* @ param { string } n1 Lower - case letter match
* @ returns { string } Uppercased letter
2018-09-26 08:06:57 +00:00
* /
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
function _upperCase ( n0 , n1 ) {
2018-09-26 13:14:12 +00:00
return n1 . toUpperCase ( ) ;
2019-05-24 12:17:17 +00:00
} // Todo: Make as public utility
/ * *
2019-11-13 06:16:06 +00:00
* @ param { any } o
2019-05-24 12:17:17 +00:00
* @ returns { boolean }
* /
function _isNullish ( o ) {
return o === null || o === undefined ;
} // Todo: Make as public utility, but also return types for undefined, boolean, number, document, etc.
2018-09-26 08:06:57 +00:00
/ * *
* @ private
* @ static
2019-11-13 06:16:06 +00:00
* @ param { string | JamilihAttributes | JamilihArray | Element | DocumentFragment } item
2020-01-25 02:32:24 +00:00
* @ returns { "string" | "null" | "array" | "element" | "fragment" | "object" | "symbol" | "function" | "number" | "boolean" }
2018-09-26 08:06:57 +00:00
* /
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
function _getType ( item ) {
2020-07-18 11:25:50 +00:00
const type = typeof item ;
2018-09-26 13:14:12 +00:00
2020-01-25 02:32:24 +00:00
switch ( type ) {
case 'object' :
if ( item === null ) {
return 'null' ;
}
2018-09-26 08:06:57 +00:00
2020-01-25 02:32:24 +00:00
if ( Array . isArray ( item ) ) {
return 'array' ;
2018-09-26 13:14:12 +00:00
}
2020-01-25 02:32:24 +00:00
if ( 'nodeType' in item ) {
switch ( item . nodeType ) {
case 1 :
return 'element' ;
case 9 :
return 'document' ;
case 11 :
return 'fragment' ;
default :
return 'non-container node' ;
}
2018-09-26 13:14:12 +00:00
}
2020-01-25 02:32:24 +00:00
// Fallthrough
2018-09-26 13:14:12 +00:00
2020-01-25 02:32:24 +00:00
default :
return type ;
}
2018-09-26 13:14:12 +00:00
}
2018-09-26 08:06:57 +00:00
/ * *
* @ private
* @ static
2019-05-24 12:17:17 +00:00
* @ param { DocumentFragment } frag
* @ param { Node } node
* @ returns { DocumentFragment }
2018-09-26 08:06:57 +00:00
* /
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
function _fragReducer ( frag , node ) {
2019-05-24 12:17:17 +00:00
frag . append ( node ) ;
2018-09-26 13:14:12 +00:00
return frag ;
2018-09-26 08:06:57 +00:00
}
/ * *
* @ private
* @ static
2019-05-24 12:17:17 +00:00
* @ param { Object < { string : string } > } xmlnsObj
* @ returns { string }
2018-09-26 08:06:57 +00:00
* /
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
function _replaceDefiner ( xmlnsObj ) {
2018-09-26 13:14:12 +00:00
return function ( n0 ) {
2020-07-18 11:25:50 +00:00
let retStr = xmlnsObj [ '' ] ? ' xmlns="' + xmlnsObj [ '' ] + '"' : n0 ; // Preserve XHTML
2020-01-25 02:32:24 +00:00
2020-07-18 11:25:50 +00:00
for ( const [ ns , xmlnsVal ] of Object . entries ( xmlnsObj ) ) {
2020-01-25 02:32:24 +00:00
if ( ns !== '' ) {
retStr += ' xmlns:' + ns + '="' + xmlnsVal + '"' ;
2018-09-26 13:14:12 +00:00
}
}
return retStr ;
} ;
2018-09-26 08:06:57 +00:00
}
/ * *
2019-11-13 06:16:06 +00:00
* @ typedef { JamilihAttributes } AttributeArray
2019-05-24 12:17:17 +00:00
* @ property { string } 0 The key
* @ property { string } 1 The value
* /
/ * *
* @ callback ChildrenToJMLCallback
* @ param { JamilihArray | Jamilih } childNodeJML
* @ param { Integer } i
2020-01-25 02:32:24 +00:00
* @ returns { void }
2019-05-24 12:17:17 +00:00
* /
2018-09-26 08:06:57 +00:00
/ * *
* @ private
* @ static
2019-05-24 12:17:17 +00:00
* @ param { Node } node
* @ returns { ChildrenToJMLCallback }
2018-09-26 08:06:57 +00:00
* /
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
function _childrenToJML ( node ) {
2018-09-26 13:14:12 +00:00
return function ( childNodeJML , i ) {
2020-07-18 11:25:50 +00:00
const cn = node . childNodes [ i ] ;
const j = Array . isArray ( childNodeJML ) ? jml ( ... childNodeJML ) : jml ( childNodeJML ) ;
2020-01-29 04:09:42 +00:00
cn . replaceWith ( j ) ;
2018-09-26 13:14:12 +00:00
} ;
2018-09-26 08:06:57 +00:00
}
2019-05-24 12:17:17 +00:00
/ * *
* @ callback JamilihAppender
* @ param { JamilihArray } childJML
* @ returns { void }
* /
2018-09-26 08:06:57 +00:00
/ * *
* @ private
* @ static
2019-05-24 12:17:17 +00:00
* @ param { Node } node
* @ returns { JamilihAppender }
2018-09-26 08:06:57 +00:00
* /
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
function _appendJML ( node ) {
2018-09-26 13:14:12 +00:00
return function ( childJML ) {
2020-01-29 04:09:42 +00:00
if ( Array . isArray ( childJML ) ) {
2020-07-18 11:25:50 +00:00
node . append ( jml ( ... childJML ) ) ;
2020-01-29 04:09:42 +00:00
} else {
node . append ( jml ( childJML ) ) ;
}
2018-09-26 13:14:12 +00:00
} ;
2018-09-26 08:06:57 +00:00
}
2019-05-24 12:17:17 +00:00
/ * *
* @ callback appender
* @ param { string | JamilihArray } childJML
* @ returns { void }
* /
2018-09-26 08:06:57 +00:00
/ * *
* @ private
* @ static
2019-05-24 12:17:17 +00:00
* @ param { Node } node
* @ returns { appender }
2018-09-26 08:06:57 +00:00
* /
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
function _appendJMLOrText ( node ) {
2018-09-26 13:14:12 +00:00
return function ( childJML ) {
if ( typeof childJML === 'string' ) {
2019-05-24 12:17:17 +00:00
node . append ( childJML ) ;
2020-01-29 04:09:42 +00:00
} else if ( Array . isArray ( childJML ) ) {
2020-07-18 11:25:50 +00:00
node . append ( jml ( ... childJML ) ) ;
2020-01-29 04:09:42 +00:00
} else {
node . append ( jml ( childJML ) ) ;
2018-09-26 13:14:12 +00:00
}
} ;
2018-09-26 08:06:57 +00:00
}
/ * *
* @ private
* @ static
2020-01-08 18:27:56 +00:00
* /
2020-01-25 02:32:24 +00:00
/ *
function _DOMfromJMLOrString ( childNodeJML ) {
2019-11-13 06:16:06 +00:00
if ( typeof childNodeJML === 'string' ) {
return doc . createTextNode ( childNodeJML ) ;
}
return jml ( ... childNodeJML ) ;
2018-09-26 08:06:57 +00:00
}
* /
/ * *
2019-05-24 12:17:17 +00:00
* @ typedef { Element | DocumentFragment } JamilihReturn
* /
2019-11-13 06:16:06 +00:00
/ * *
2020-01-25 02:32:24 +00:00
* @ typedef { PlainObject < string , string > } JamilihAttributes
2019-11-13 06:16:06 +00:00
* /
2019-05-24 12:17:17 +00:00
/ * *
* @ typedef { GenericArray } JamilihArray
* @ property { string } 0 The element to create ( by lower - case name )
2019-11-13 06:16:06 +00:00
* @ property { JamilihAttributes } [ 1 ] Attributes to add with the key as the
* attribute name and value as the attribute value ; important for IE where
* the input element ' s type cannot be added later after already added to the page
2019-05-24 12:17:17 +00:00
* @ param { Element [ ] } [ children ] The optional children of this element
* ( but raw DOM elements required to be specified within arrays since
* could not otherwise be distinguished from siblings being added )
* @ param { Element } [ parent ] The optional parent to which to attach the element
* ( always the last unless followed by null , in which case it is the
* second - to - last )
* @ param { null } [ returning ] Can use null to indicate an array of elements
* should be returned
* /
2020-02-01 12:44:13 +00:00
/ * *
* @ typedef { PlainObject } JamilihOptions
* @ property { "root" | "attributeValue" | "fragment" | "children" | "fragmentChildren" } $state
* /
/ * *
* @ param { Element } elem
* @ param { string } att
* @ param { string } attVal
* @ param { JamilihOptions } opts
* @ returns { void }
* /
function checkPluginValue ( elem , att , attVal , opts ) {
opts . $state = 'attributeValue' ;
2020-07-18 11:25:50 +00:00
if ( attVal && typeof attVal === 'object' ) {
const matchingPlugin = getMatchingPlugin ( opts , Object . keys ( attVal ) [ 0 ] ) ;
2020-02-01 12:44:13 +00:00
if ( matchingPlugin ) {
return matchingPlugin . set ( {
2020-07-18 11:25:50 +00:00
opts ,
2020-02-01 12:44:13 +00:00
element : elem ,
attribute : {
name : att ,
value : attVal
}
} ) ;
}
}
return attVal ;
}
/ * *
* @ param { JamilihOptions } opts
* @ param { string } item
* @ returns { JamilihPlugin }
* /
function getMatchingPlugin ( opts , item ) {
2020-07-18 11:25:50 +00:00
return opts . $plugins && opts . $plugins . find ( p => {
2020-02-01 12:44:13 +00:00
return p . name === item ;
} ) ;
}
2019-05-24 12:17:17 +00:00
/ * *
* Creates an XHTML or HTML element ( XHTML is preferred , but only in browsers
* that support ) ; any element after element can be omitted , and any subsequent
* type or types added afterwards .
2020-01-29 04:09:42 +00:00
* @ param { ... JamilihArray } args
2019-05-24 12:17:17 +00:00
* @ returns { JamilihReturn } The newly created ( and possibly already appended )
* element or array of elements
2018-09-26 08:06:57 +00:00
* /
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
const jml = function jml ( ... args ) {
let elem = doc . createDocumentFragment ( ) ;
2019-05-24 12:17:17 +00:00
/ * *
*
* @ param { Object < { string : string } > } atts
* @ returns { void }
* /
2018-09-26 13:14:12 +00:00
function _checkAtts ( atts ) {
2020-07-18 11:25:50 +00:00
for ( let [ att , attVal ] of Object . entries ( atts ) ) {
2018-09-26 13:14:12 +00:00
att = att in ATTR _MAP ? ATTR _MAP [ att ] : att ;
if ( NULLABLES . includes ( att ) ) {
2020-02-01 12:44:13 +00:00
attVal = checkPluginValue ( elem , att , attVal , opts ) ;
2019-05-24 12:17:17 +00:00
if ( ! _isNullish ( attVal ) ) {
2018-09-26 13:14:12 +00:00
elem [ att ] = attVal ;
}
2020-02-01 12:44:13 +00:00
continue ;
2018-09-26 13:14:12 +00:00
} else if ( ATTR _DOM . includes ( att ) ) {
2020-02-01 12:44:13 +00:00
attVal = checkPluginValue ( elem , att , attVal , opts ) ;
2018-09-26 13:14:12 +00:00
elem [ att ] = attVal ;
2020-02-01 12:44:13 +00:00
continue ;
2018-09-26 13:14:12 +00:00
}
switch ( att ) {
/ *
Todos :
0. JSON mode to prevent event addition
0. { $xmlDocument : [ ] } // doc.implementation.createDocument
0. Accept array for any attribute with first item as prefix and second as value ?
0. { $ : [ 'xhtml' , 'div' ] } for prefixed elements
2019-11-13 06:16:06 +00:00
case '$' : // Element with prefix?
nodes [ nodes . length ] = elem = doc . createElementNS ( attVal [ 0 ] , attVal [ 1 ] ) ;
break ;
2018-09-26 13:14:12 +00:00
* /
case '#' :
{
// Document fragment
2020-01-29 04:09:42 +00:00
opts . $state = 'fragmentChilden' ;
nodes [ nodes . length ] = jml ( opts , attVal ) ;
2018-09-26 13:14:12 +00:00
break ;
}
case '$shadow' :
{
2020-07-18 11:25:50 +00:00
const {
open ,
closed
} = attVal ;
let {
content ,
template
} = attVal ;
const shadowRoot = elem . attachShadow ( {
2018-09-26 13:14:12 +00:00
mode : closed || open === false ? 'closed' : 'open'
} ) ;
if ( template ) {
if ( Array . isArray ( template ) ) {
if ( _getType ( template [ 0 ] ) === 'object' ) {
// Has attributes
2020-07-18 11:25:50 +00:00
template = jml ( 'template' , ... template , doc . body ) ;
2018-09-26 13:14:12 +00:00
} else {
// Array is for the children
template = jml ( 'template' , template , doc . body ) ;
}
} else if ( typeof template === 'string' ) {
template = $ ( template ) ;
}
jml ( template . content . cloneNode ( true ) , shadowRoot ) ;
} else {
if ( ! content ) {
content = open || closed ;
}
if ( content && typeof content !== 'boolean' ) {
if ( Array . isArray ( content ) ) {
jml ( {
'#' : content
} , shadowRoot ) ;
} else {
jml ( content , shadowRoot ) ;
}
}
2018-09-26 08:06:57 +00:00
}
2018-09-26 13:14:12 +00:00
break ;
}
2020-01-29 04:09:42 +00:00
case '$state' :
{
// Handled internally
break ;
}
2018-09-26 13:14:12 +00:00
case 'is' :
{
2020-01-25 02:32:24 +00:00
// Currently only in Chrome
2018-09-26 13:14:12 +00:00
// Handled during element creation
break ;
}
case '$custom' :
{
Object . assign ( elem , attVal ) ;
break ;
}
2020-01-25 02:32:24 +00:00
/* istanbul ignore next */
2018-09-26 13:14:12 +00:00
case '$define' :
{
2020-07-18 11:25:50 +00:00
const localName = elem . localName . toLowerCase ( ) ; // Note: customized built-ins sadly not working yet
2020-01-25 02:32:24 +00:00
2020-07-18 11:25:50 +00:00
const customizedBuiltIn = ! localName . includes ( '-' ) ; // We check attribute in case this is a preexisting DOM element
// const {is} = atts;
2020-01-25 02:32:24 +00:00
2020-07-18 11:25:50 +00:00
let is ;
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
if ( customizedBuiltIn ) {
is = elem . getAttribute ( 'is' ) ;
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
if ( ! is ) {
if ( ! { } . hasOwnProperty . call ( atts , 'is' ) ) {
throw new TypeError ( ` Expected \` is \` with \` $ define \` on built-in; args: ${ JSON . stringify ( args ) } ` ) ;
2020-01-25 02:32:24 +00:00
}
2020-02-01 12:44:13 +00:00
2020-07-18 11:25:50 +00:00
atts . is = checkPluginValue ( elem , 'is' , atts . is , opts ) ;
elem . setAttribute ( 'is' , atts . is ) ;
( {
is
} = atts ) ;
2018-09-26 13:14:12 +00:00
}
2020-07-18 11:25:50 +00:00
}
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
const def = customizedBuiltIn ? is : localName ;
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
if ( window . customElements . get ( def ) ) {
break ;
}
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
const getConstructor = cnstrct => {
const baseClass = options && options . extends ? doc . createElement ( options . extends ) . constructor : customizedBuiltIn ? doc . createElement ( localName ) . constructor : window . HTMLElement ;
/ * *
* Class wrapping base class .
* /
2020-02-01 12:44:13 +00:00
2020-07-18 11:25:50 +00:00
return cnstrct ? class extends baseClass {
/ * *
* Calls user constructor .
* /
constructor ( ) {
super ( ) ;
cnstrct . call ( this ) ;
}
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
} : class extends baseClass { } ;
} ;
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
let cnstrctr , options , mixin ;
if ( Array . isArray ( attVal ) ) {
if ( attVal . length <= 2 ) {
[ cnstrctr , options ] = attVal ;
if ( typeof options === 'string' ) {
// Todo: Allow creating a definition without using it;
// that may be the only reason to have a string here which
// differs from the `localName` anyways
options = {
extends : options
} ;
} else if ( options && ! { } . hasOwnProperty . call ( options , 'extends' ) ) {
mixin = options ;
}
2020-02-01 12:44:13 +00:00
2020-07-18 11:25:50 +00:00
if ( typeof cnstrctr === 'object' ) {
mixin = cnstrctr ;
cnstrctr = getConstructor ( ) ;
2020-01-25 02:32:24 +00:00
}
2020-02-01 12:44:13 +00:00
} else {
2020-07-18 11:25:50 +00:00
[ cnstrctr , mixin , options ] = attVal ;
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
if ( typeof options === 'string' ) {
options = {
extends : options
} ;
}
2020-02-01 12:44:13 +00:00
}
2020-07-18 11:25:50 +00:00
} else if ( typeof attVal === 'function' ) {
cnstrctr = attVal ;
} else {
mixin = attVal ;
cnstrctr = getConstructor ( ) ;
}
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
if ( ! cnstrctr . toString ( ) . startsWith ( 'class' ) ) {
cnstrctr = getConstructor ( cnstrctr ) ;
}
2020-01-25 02:32:24 +00:00
2020-07-18 11:25:50 +00:00
if ( ! options && customizedBuiltIn ) {
options = {
extends : localName
} ;
}
2020-01-25 02:32:24 +00:00
2020-07-18 11:25:50 +00:00
if ( mixin ) {
Object . entries ( mixin ) . forEach ( ( [ methodName , method ] ) => {
cnstrctr . prototype [ methodName ] = method ;
} ) ;
} // console.log('def', def, '::', typeof options === 'object' ? options : undefined);
2020-01-25 02:32:24 +00:00
2020-02-01 12:44:13 +00:00
2020-07-18 11:25:50 +00:00
window . customElements . define ( def , cnstrctr , typeof options === 'object' ? options : undefined ) ;
break ;
2018-09-26 13:14:12 +00:00
}
case '$symbol' :
{
2020-07-18 11:25:50 +00:00
const [ symbol , func ] = attVal ;
2018-09-26 13:14:12 +00:00
if ( typeof func === 'function' ) {
2020-07-18 11:25:50 +00:00
const funcBound = func . bind ( elem ) ;
2018-09-26 13:14:12 +00:00
if ( typeof symbol === 'string' ) {
2020-07-18 11:25:50 +00:00
elem [ Symbol . for ( symbol ) ] = funcBound ;
2018-09-26 13:14:12 +00:00
} else {
elem [ symbol ] = funcBound ;
}
} else {
2020-07-18 11:25:50 +00:00
const obj = func ;
2018-09-26 13:14:12 +00:00
obj . elem = elem ;
if ( typeof symbol === 'string' ) {
2020-07-18 11:25:50 +00:00
elem [ Symbol . for ( symbol ) ] = obj ;
2018-09-26 13:14:12 +00:00
} else {
elem [ symbol ] = obj ;
}
2018-09-26 08:06:57 +00:00
}
2018-09-26 13:14:12 +00:00
break ;
}
case '$data' :
{
setMap ( attVal ) ;
break ;
}
case '$attribute' :
{
// Attribute node
2020-07-18 11:25:50 +00:00
const node = attVal . length === 3 ? doc . createAttributeNS ( attVal [ 0 ] , attVal [ 1 ] ) : doc . createAttribute ( attVal [ 0 ] ) ;
2018-09-26 13:14:12 +00:00
node . value = attVal [ attVal . length - 1 ] ;
nodes [ nodes . length ] = node ;
break ;
}
case '$text' :
{
// Todo: Also allow as jml(['a text node']) (or should that become a fragment)?
2020-07-18 11:25:50 +00:00
const node = doc . createTextNode ( attVal ) ;
nodes [ nodes . length ] = node ;
2018-09-26 13:14:12 +00:00
break ;
}
case '$document' :
{
// Todo: Conditionally create XML document
2020-07-18 11:25:50 +00:00
const node = doc . implementation . createHTMLDocument ( ) ;
2018-09-26 13:14:12 +00:00
if ( attVal . childNodes ) {
2020-01-25 02:32:24 +00:00
// Remove any extra nodes created by createHTMLDocument().
2020-07-18 11:25:50 +00:00
const j = attVal . childNodes . length ;
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
while ( node . childNodes [ j ] ) {
const cn = node . childNodes [ j ] ;
2020-01-25 02:32:24 +00:00
cn . remove ( ) ; // `j` should stay the same as removing will cause node to be present
2020-07-25 17:57:19 +00:00
}
2020-01-25 02:32:24 +00:00
2020-07-18 11:25:50 +00:00
attVal . childNodes . forEach ( _childrenToJML ( node ) ) ;
2018-09-26 13:14:12 +00:00
} else {
if ( attVal . $DOCTYPE ) {
2020-07-18 11:25:50 +00:00
const dt = {
2018-09-26 13:14:12 +00:00
$DOCTYPE : attVal . $DOCTYPE
} ;
2020-07-18 11:25:50 +00:00
const doctype = jml ( dt ) ;
node . firstChild . replaceWith ( doctype ) ;
2018-09-26 13:14:12 +00:00
}
2020-07-18 11:25:50 +00:00
const html = node . childNodes [ 1 ] ;
const head = html . childNodes [ 0 ] ;
const body = html . childNodes [ 1 ] ;
2018-09-26 13:14:12 +00:00
if ( attVal . title || attVal . head ) {
2020-07-18 11:25:50 +00:00
const meta = doc . createElement ( 'meta' ) ;
2018-09-26 13:14:12 +00:00
meta . setAttribute ( 'charset' , 'utf-8' ) ;
2019-05-24 12:17:17 +00:00
head . append ( meta ) ;
2018-09-26 13:14:12 +00:00
2020-01-25 02:32:24 +00:00
if ( attVal . title ) {
2020-07-18 11:25:50 +00:00
node . title = attVal . title ; // Appends after meta
2020-01-25 02:32:24 +00:00
}
2018-09-26 13:14:12 +00:00
2020-01-25 02:32:24 +00:00
if ( attVal . head ) {
attVal . head . forEach ( _appendJML ( head ) ) ;
}
2018-09-26 13:14:12 +00:00
}
if ( attVal . body ) {
2020-07-18 11:25:50 +00:00
attVal . body . forEach ( _appendJMLOrText ( body ) ) ;
2018-09-26 13:14:12 +00:00
}
2018-09-26 08:06:57 +00:00
}
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
nodes [ nodes . length ] = node ;
2018-09-26 13:14:12 +00:00
break ;
}
case '$DOCTYPE' :
{
2020-07-18 11:25:50 +00:00
const node = doc . implementation . createDocumentType ( attVal . name , attVal . publicId || '' , attVal . systemId || '' ) ;
nodes [ nodes . length ] = node ;
2018-09-26 13:14:12 +00:00
break ;
}
case '$on' :
{
// Events
2020-05-26 12:44:54 +00:00
// Allow for no-op by defaulting to `{}`
2020-07-18 11:25:50 +00:00
for ( let [ p2 , val ] of Object . entries ( attVal || { } ) ) {
2020-01-25 02:32:24 +00:00
if ( typeof val === 'function' ) {
val = [ val , false ] ;
}
2018-09-26 13:14:12 +00:00
2020-01-25 02:32:24 +00:00
if ( typeof val [ 0 ] !== 'function' ) {
2020-07-18 11:25:50 +00:00
throw new TypeError ( ` Expect a function for \` $ on \` ; args: ${ JSON . stringify ( args ) } ` ) ;
2018-09-26 13:14:12 +00:00
}
2020-01-25 02:32:24 +00:00
_addEvent ( elem , p2 , val [ 0 ] , val [ 1 ] ) ; // element, event name, handler, capturing
2018-09-26 13:14:12 +00:00
}
break ;
}
case 'className' :
case 'class' :
2020-02-01 12:44:13 +00:00
attVal = checkPluginValue ( elem , att , attVal , opts ) ;
2019-05-24 12:17:17 +00:00
if ( ! _isNullish ( attVal ) ) {
2018-09-26 13:14:12 +00:00
elem . className = attVal ;
}
break ;
case 'dataset' :
{
2020-07-18 11:25:50 +00:00
// Map can be keyed with hyphenated or camel-cased properties
const recurse = ( atVal , startProp ) => {
let prop = '' ;
const pastInitialProp = startProp !== '' ;
Object . keys ( atVal ) . forEach ( key => {
const value = atVal [ key ] ;
if ( pastInitialProp ) {
prop = startProp + key . replace ( hyphenForCamelCase , _upperCase ) . replace ( /^([a-z])/u , _upperCase ) ;
} else {
prop = startProp + key . replace ( hyphenForCamelCase , _upperCase ) ;
}
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
if ( value === null || typeof value !== 'object' ) {
if ( ! _isNullish ( value ) ) {
elem . dataset [ prop ] = value ;
2018-09-26 13:14:12 +00:00
}
2020-07-18 11:25:50 +00:00
prop = startProp ;
return ;
}
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
recurse ( value , prop ) ;
} ) ;
} ;
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
recurse ( attVal , '' ) ;
break ; // Todo: Disable this by default unless configuration explicitly allows (for security)
2018-09-26 13:14:12 +00:00
}
// #if IS_REMOVE
// Don't remove this `if` block (for sake of no-innerHTML build)
case 'innerHTML' :
2019-05-24 12:17:17 +00:00
if ( ! _isNullish ( attVal ) ) {
2020-01-25 02:32:24 +00:00
// eslint-disable-next-line no-unsanitized/property
2018-09-26 13:14:12 +00:00
elem . innerHTML = attVal ;
}
break ;
// #endif
case 'htmlFor' :
case 'for' :
if ( elStr === 'label' ) {
2020-02-01 12:44:13 +00:00
attVal = checkPluginValue ( elem , att , attVal , opts ) ;
2019-05-24 12:17:17 +00:00
if ( ! _isNullish ( attVal ) ) {
2018-09-26 13:14:12 +00:00
elem . htmlFor = attVal ;
}
break ;
}
2020-02-01 12:44:13 +00:00
attVal = checkPluginValue ( elem , att , attVal , opts ) ;
2018-09-26 13:14:12 +00:00
elem . setAttribute ( att , attVal ) ;
break ;
case 'xmlns' :
// Already handled
break ;
default :
2019-05-24 12:17:17 +00:00
{
if ( att . startsWith ( 'on' ) ) {
2020-02-01 12:44:13 +00:00
attVal = checkPluginValue ( elem , att , attVal , opts ) ;
2019-05-24 12:17:17 +00:00
elem [ att ] = attVal ; // _addEvent(elem, att.slice(2), attVal, false); // This worked, but perhaps the user wishes only one event
2018-09-26 13:14:12 +00:00
break ;
}
2019-05-24 12:17:17 +00:00
if ( att === 'style' ) {
2020-02-01 12:44:13 +00:00
attVal = checkPluginValue ( elem , att , attVal , opts ) ;
2019-05-24 12:17:17 +00:00
if ( _isNullish ( attVal ) ) {
break ;
}
2020-07-18 11:25:50 +00:00
if ( typeof attVal === 'object' ) {
for ( const [ p2 , styleVal ] of Object . entries ( attVal ) ) {
2020-01-25 02:32:24 +00:00
if ( ! _isNullish ( styleVal ) ) {
2019-05-24 12:17:17 +00:00
// Todo: Handle aggregate properties like "border"
2020-07-18 11:25:50 +00:00
if ( p2 === 'float' ) {
2020-01-25 02:32:24 +00:00
elem . style . cssFloat = styleVal ;
elem . style . styleFloat = styleVal ; // Harmless though we could make conditional on older IE instead
2019-05-24 12:17:17 +00:00
} else {
2020-07-18 11:25:50 +00:00
elem . style [ p2 . replace ( hyphenForCamelCase , _upperCase ) ] = styleVal ;
2019-05-24 12:17:17 +00:00
}
2018-09-26 13:14:12 +00:00
}
2018-09-26 08:06:57 +00:00
}
2018-09-26 13:14:12 +00:00
2019-05-24 12:17:17 +00:00
break ;
} // setAttribute unfortunately erases any existing styles
2018-09-26 13:14:12 +00:00
2019-05-24 12:17:17 +00:00
elem . setAttribute ( att , attVal ) ;
/ *
// The following reorders which is troublesome for serialization, e.g., as used in our testing
if ( elem . style . cssText !== undefined ) {
2019-11-13 06:16:06 +00:00
elem . style . cssText += attVal ;
2019-05-24 12:17:17 +00:00
} else { // Opera
2019-11-13 06:16:06 +00:00
elem . style += attVal ;
2019-05-24 12:17:17 +00:00
}
* /
break ;
2018-09-26 13:14:12 +00:00
}
2020-07-18 11:25:50 +00:00
const matchingPlugin = getMatchingPlugin ( opts , att ) ;
2018-09-26 13:14:12 +00:00
2019-05-24 12:17:17 +00:00
if ( matchingPlugin ) {
matchingPlugin . set ( {
2020-07-18 11:25:50 +00:00
opts ,
2019-05-24 12:17:17 +00:00
element : elem ,
attribute : {
name : att ,
value : attVal
}
} ) ;
break ;
}
2018-09-26 13:14:12 +00:00
2020-02-01 12:44:13 +00:00
attVal = checkPluginValue ( elem , att , attVal , opts ) ;
2019-05-24 12:17:17 +00:00
elem . setAttribute ( att , attVal ) ;
2018-09-26 13:14:12 +00:00
break ;
}
}
}
}
2020-07-18 11:25:50 +00:00
const nodes = [ ] ;
let elStr ;
let opts ;
let isRoot = false ;
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
if ( _getType ( args [ 0 ] ) === 'object' && Object . keys ( args [ 0 ] ) . some ( key => possibleOptions . includes ( key ) ) ) {
2018-09-26 13:14:12 +00:00
opts = args [ 0 ] ;
2020-01-29 04:09:42 +00:00
if ( opts . $state === undefined ) {
2018-09-26 13:14:12 +00:00
isRoot = true ;
2020-01-29 04:09:42 +00:00
opts . $state = 'root' ;
2018-09-26 13:14:12 +00:00
}
if ( opts . $map && ! opts . $map . root && opts . $map . root !== false ) {
opts . $map = {
root : opts . $map
} ;
}
if ( '$plugins' in opts ) {
if ( ! Array . isArray ( opts . $plugins ) ) {
2020-07-18 11:25:50 +00:00
throw new TypeError ( ` \` $ plugins \` must be an array; args: ${ JSON . stringify ( args ) } ` ) ;
2018-09-26 13:14:12 +00:00
}
2020-07-18 11:25:50 +00:00
opts . $plugins . forEach ( pluginObj => {
if ( ! pluginObj || typeof pluginObj !== 'object' ) {
throw new TypeError ( ` Plugin must be an object; args: ${ JSON . stringify ( args ) } ` ) ;
2018-09-26 13:14:12 +00:00
}
if ( ! pluginObj . name || ! pluginObj . name . startsWith ( '$_' ) ) {
2020-07-18 11:25:50 +00:00
throw new TypeError ( ` Plugin object name must be present and begin with \` $ _ \` ; args: ${ JSON . stringify ( args ) } ` ) ;
2018-09-26 08:06:57 +00:00
}
2018-09-26 13:14:12 +00:00
if ( typeof pluginObj . set !== 'function' ) {
2020-07-18 11:25:50 +00:00
throw new TypeError ( ` Plugin object must have a \` set \` method; args: ${ JSON . stringify ( args ) } ` ) ;
2018-09-26 13:14:12 +00:00
}
} ) ;
2018-09-26 08:06:57 +00:00
}
2018-09-26 13:14:12 +00:00
args = args . slice ( 1 ) ;
2020-01-29 04:09:42 +00:00
} else {
opts = {
$state : undefined
} ;
2018-09-26 13:14:12 +00:00
}
2020-07-18 11:25:50 +00:00
const argc = args . length ;
const defaultMap = opts . $map && opts . $map . root ;
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
const setMap = dataVal => {
let map , obj ; // Boolean indicating use of default map and object
2018-09-26 13:14:12 +00:00
if ( dataVal === true ) {
2020-07-18 11:25:50 +00:00
[ map , obj ] = defaultMap ;
2018-09-26 13:14:12 +00:00
} else if ( Array . isArray ( dataVal ) ) {
// Array of strings mapping to default
if ( typeof dataVal [ 0 ] === 'string' ) {
2020-07-18 11:25:50 +00:00
dataVal . forEach ( dVal => {
2018-09-26 13:14:12 +00:00
setMap ( opts . $map [ dVal ] ) ;
2020-01-25 02:32:24 +00:00
} ) ;
return ; // Array of Map and non-map data object
}
2018-09-26 13:14:12 +00:00
2020-01-25 02:32:24 +00:00
map = dataVal [ 0 ] || defaultMap [ 0 ] ;
obj = dataVal [ 1 ] || defaultMap [ 1 ] ; // Map
2020-07-18 11:25:50 +00:00
} else if ( /^\[object (?:Weak)?Map\]$/u . test ( [ ] . toString . call ( dataVal ) ) ) {
2018-09-26 13:14:12 +00:00
map = dataVal ;
obj = defaultMap [ 1 ] ; // Non-map data object
} else {
map = defaultMap [ 0 ] ;
obj = dataVal ;
}
map . set ( elem , obj ) ;
} ;
2020-07-18 11:25:50 +00:00
for ( let i = 0 ; i < argc ; i ++ ) {
let arg = args [ i ] ;
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
const type = _getType ( arg ) ;
2020-01-25 02:32:24 +00:00
switch ( type ) {
2019-05-24 12:17:17 +00:00
default :
2020-07-18 11:25:50 +00:00
throw new TypeError ( ` Unexpected type: ${ type } ; arg: ${ arg } ; index ${ i } on args: ${ JSON . stringify ( args ) } ` ) ;
2019-05-24 12:17:17 +00:00
2018-09-26 13:14:12 +00:00
case 'null' :
// null always indicates a place-holder (only needed for last argument if want array returned)
if ( i === argc - 1 ) {
_applyAnyStylesheet ( nodes [ 0 ] ) ; // We have to execute any stylesheets even if not appending or otherwise IE will never apply them
// Todo: Fix to allow application of stylesheets of style tags within fragments?
2020-07-25 17:57:19 +00:00
return nodes . length <= 1 ? nodes [ 0 ] // eslint-disable-next-line
2019-05-24 12:17:17 +00:00
: nodes . reduce ( _fragReducer , doc . createDocumentFragment ( ) ) ; // nodes;
2018-09-26 08:06:57 +00:00
}
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
throw new TypeError ( ` \` null \` values not allowed except as final Jamilih argument; index ${ i } on args: ${ JSON . stringify ( args ) } ` ) ;
2018-09-26 13:14:12 +00:00
case 'string' :
2020-01-25 02:32:24 +00:00
// Strings normally indicate elements
2018-09-26 13:14:12 +00:00
switch ( arg ) {
case '!' :
nodes [ nodes . length ] = doc . createComment ( args [ ++ i ] ) ;
break ;
case '?' :
2019-05-24 12:17:17 +00:00
{
arg = args [ ++ i ] ;
2020-07-18 11:25:50 +00:00
let procValue = args [ ++ i ] ;
const val = procValue ;
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
if ( val && typeof val === 'object' ) {
2019-05-24 12:17:17 +00:00
procValue = [ ] ;
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
for ( const [ p , procInstVal ] of Object . entries ( val ) ) {
2020-01-25 02:32:24 +00:00
procValue . push ( p + '=' + '"' + // https://www.w3.org/TR/xml-stylesheet/#NT-PseudoAttValue
2020-07-18 11:25:50 +00:00
procInstVal . replace ( /"/gu , '"' ) + '"' ) ;
2018-09-26 08:06:57 +00:00
}
2018-09-26 13:14:12 +00:00
2019-05-24 12:17:17 +00:00
procValue = procValue . join ( ' ' ) ;
} // Firefox allows instructions with ">" in this method, but not if placed directly!
2018-09-26 13:14:12 +00:00
2019-05-24 12:17:17 +00:00
try {
nodes [ nodes . length ] = doc . createProcessingInstruction ( arg , procValue ) ;
} catch ( e ) {
// Getting NotSupportedError in IE, so we try to imitate a processing instruction with a comment
// innerHTML didn't work
// var elContainer = doc.createElement('div');
// elContainer.innerHTML = '<?' + doc.createTextNode(arg + ' ' + procValue).nodeValue + '?>';
// nodes[nodes.length] = elContainer.innerHTML;
// Todo: any other way to resolve? Just use XML?
nodes [ nodes . length ] = doc . createComment ( '?' + arg + ' ' + procValue + '?' ) ;
}
2018-09-26 13:14:12 +00:00
2019-05-24 12:17:17 +00:00
break ; // Browsers don't support doc.createEntityReference, so we just use this as a convenience
}
2018-09-26 13:14:12 +00:00
case '&' :
nodes [ nodes . length ] = _createSafeReference ( 'entity' , '' , args [ ++ i ] ) ;
break ;
case '#' :
// // Decimal character reference - ['#', '01234'] // Ӓ // probably easier to use JavaScript Unicode escapes
nodes [ nodes . length ] = _createSafeReference ( 'decimal' , arg , String ( args [ ++ i ] ) ) ;
break ;
case '#x' :
// Hex character reference - ['#x', '123a'] // ሺ // probably easier to use JavaScript Unicode escapes
nodes [ nodes . length ] = _createSafeReference ( 'hexadecimal' , arg , args [ ++ i ] ) ;
break ;
case '![' :
// '![', ['escaped <&> text'] // <![CDATA[escaped <&> text]]>
// CDATA valid in XML only, so we'll just treat as text for mutual compatibility
// Todo: config (or detection via some kind of doc.documentType property?) of whether in XML
try {
nodes [ nodes . length ] = doc . createCDATASection ( args [ ++ i ] ) ;
} catch ( e2 ) {
nodes [ nodes . length ] = doc . createTextNode ( args [ i ] ) ; // i already incremented
}
break ;
case '' :
2020-02-01 12:44:13 +00:00
nodes [ nodes . length ] = elem = doc . createDocumentFragment ( ) ; // Todo: Report to plugins
2020-01-29 04:09:42 +00:00
opts . $state = 'fragment' ;
2018-09-26 13:14:12 +00:00
break ;
default :
{
// An element
elStr = arg ;
2020-07-18 11:25:50 +00:00
const atts = args [ i + 1 ] ;
2018-09-26 13:14:12 +00:00
2019-05-24 12:17:17 +00:00
if ( _getType ( atts ) === 'object' && atts . is ) {
2020-07-18 11:25:50 +00:00
const {
is
} = atts ; // istanbul ignore else
2018-09-26 13:14:12 +00:00
if ( doc . createElementNS ) {
elem = doc . createElementNS ( NS _HTML , elStr , {
2020-07-18 11:25:50 +00:00
is
2018-09-26 13:14:12 +00:00
} ) ;
2018-09-26 08:06:57 +00:00
} else {
2018-09-26 13:14:12 +00:00
elem = doc . createElement ( elStr , {
2020-07-18 11:25:50 +00:00
is
2018-09-26 13:14:12 +00:00
} ) ;
2018-09-26 08:06:57 +00:00
}
2020-01-25 02:32:24 +00:00
} else
/* istanbul ignore else */
if ( doc . createElementNS ) {
elem = doc . createElementNS ( NS _HTML , elStr ) ;
} else {
elem = doc . createElement ( elStr ) ;
2020-02-01 12:44:13 +00:00
} // Todo: Report to plugins
2018-09-26 13:14:12 +00:00
2020-01-29 04:09:42 +00:00
opts . $state = 'element' ;
2018-09-26 13:14:12 +00:00
nodes [ nodes . length ] = elem ; // Add to parent
break ;
}
2018-09-26 08:06:57 +00:00
}
2018-09-26 13:14:12 +00:00
break ;
case 'object' :
2019-05-24 12:17:17 +00:00
{
// Non-DOM-element objects indicate attribute-value pairs
2020-07-18 11:25:50 +00:00
const atts = arg ;
2019-05-24 12:17:17 +00:00
2020-07-18 11:25:50 +00:00
if ( atts . xmlns !== undefined ) {
2019-05-24 12:17:17 +00:00
// We handle this here, as otherwise may lose events, etc.
// As namespace of element already set as XHTML, we need to change the namespace
// elem.setAttribute('xmlns', atts.xmlns); // Doesn't work
// Can't set namespaceURI dynamically, renameNode() is not supported, and setAttribute() doesn't work to change the namespace, so we resort to this hack
2020-07-18 11:25:50 +00:00
let replacer ;
2019-05-24 12:17:17 +00:00
2020-07-18 11:25:50 +00:00
if ( typeof atts . xmlns === 'object' ) {
replacer = _replaceDefiner ( atts . xmlns ) ;
2019-05-24 12:17:17 +00:00
} else {
2020-07-18 11:25:50 +00:00
replacer = ' xmlns="' + atts . xmlns + '"' ;
2019-05-24 12:17:17 +00:00
} // try {
// Also fix DOMParser to work with text/html
2020-01-25 02:32:24 +00:00
elem = nodes [ nodes . length - 1 ] = new win . DOMParser ( ) . parseFromString ( new win . XMLSerializer ( ) . serializeToString ( elem ) // Mozilla adds XHTML namespace
2020-02-01 12:44:13 +00:00
. replace ( ' xmlns="' + NS _HTML + '"' , replacer ) , 'application/xml' ) . documentElement ; // Todo: Report to plugins
2020-01-29 04:09:42 +00:00
opts . $state = 'element' ; // }catch(e) {alert(elem.outerHTML);throw e;}
2020-01-25 02:32:24 +00:00
}
2019-05-24 12:17:17 +00:00
2020-07-18 11:25:50 +00:00
_checkAtts ( atts ) ;
2019-05-24 12:17:17 +00:00
break ;
}
2018-09-26 13:14:12 +00:00
2020-01-25 02:32:24 +00:00
case 'document' :
2018-09-26 13:14:12 +00:00
case 'fragment' :
case 'element' :
/ *
1 ) Last element always the parent ( put null if don ' t want parent and want to return array ) unless only atts and children ( no other elements )
2 ) Individual elements ( DOM elements or sequences of string [ / o b j e c t / a r r a y ] ) g e t a d d e d t o p a r e n t f i r s t - i n , f i r s t - a d d e d
* /
if ( i === 0 ) {
2020-01-25 02:32:24 +00:00
// Allow wrapping of element, fragment, or document
2020-02-01 12:44:13 +00:00
elem = arg ; // Todo: Report to plugins
2020-01-29 04:09:42 +00:00
opts . $state = 'element' ;
2018-09-26 13:14:12 +00:00
}
if ( i === argc - 1 || i === argc - 2 && args [ i + 1 ] === null ) {
// parent
2020-07-18 11:25:50 +00:00
const elsl = nodes . length ;
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
for ( let k = 0 ; k < elsl ; k ++ ) {
2018-09-26 13:14:12 +00:00
_appendNode ( arg , nodes [ k ] ) ;
} // Todo: Apply stylesheets if any style tags were added elsewhere besides the first element?
_applyAnyStylesheet ( nodes [ 0 ] ) ; // We have to execute any stylesheets even if not appending or otherwise IE will never apply them
} else {
nodes [ nodes . length ] = arg ;
}
break ;
case 'array' :
2019-05-24 12:17:17 +00:00
{
// Arrays or arrays of arrays indicate child nodes
2020-07-18 11:25:50 +00:00
const child = arg ;
const cl = child . length ;
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
for ( let j = 0 ; j < cl ; j ++ ) {
2019-05-24 12:17:17 +00:00
// Go through children array container to handle elements
2020-07-18 11:25:50 +00:00
const childContent = child [ j ] ;
const childContentType = typeof childContent ;
2018-09-26 13:14:12 +00:00
2020-01-25 02:32:24 +00:00
if ( _isNullish ( childContent ) ) {
2020-07-18 11:25:50 +00:00
throw new TypeError ( ` Bad children (parent array: ${ JSON . stringify ( args ) } ; index ${ j } of child: ${ JSON . stringify ( child ) } ) ` ) ;
2019-05-24 12:17:17 +00:00
}
2018-09-26 13:14:12 +00:00
2019-05-24 12:17:17 +00:00
switch ( childContentType ) {
// Todo: determine whether null or function should have special handling or be converted to text
case 'string' :
case 'number' :
case 'boolean' :
_appendNode ( elem , doc . createTextNode ( childContent ) ) ;
2018-09-26 13:14:12 +00:00
2019-05-24 12:17:17 +00:00
break ;
2018-09-26 13:14:12 +00:00
2019-05-24 12:17:17 +00:00
default :
if ( Array . isArray ( childContent ) ) {
// Arrays representing child elements
2020-01-29 04:09:42 +00:00
opts . $state = 'children' ;
2020-07-18 11:25:50 +00:00
_appendNode ( elem , jml ( opts , ... childContent ) ) ;
2019-05-24 12:17:17 +00:00
} else if ( childContent [ '#' ] ) {
// Fragment
2020-01-29 04:09:42 +00:00
opts . $state = 'fragmentChildren' ;
_appendNode ( elem , jml ( opts , childContent [ '#' ] ) ) ;
2019-05-24 12:17:17 +00:00
} else {
// Single DOM element children
2020-07-18 11:25:50 +00:00
const newChildContent = checkPluginValue ( elem , null , childContent , opts ) ;
2020-02-01 12:44:13 +00:00
_appendNode ( elem , newChildContent ) ;
2019-05-24 12:17:17 +00:00
}
2018-09-26 13:14:12 +00:00
2019-05-24 12:17:17 +00:00
break ;
}
2018-09-26 13:14:12 +00:00
}
2019-05-24 12:17:17 +00:00
break ;
}
2018-09-26 08:06:57 +00:00
}
2018-09-26 13:14:12 +00:00
}
2020-07-18 11:25:50 +00:00
const ret = nodes [ 0 ] || elem ;
2018-09-26 13:14:12 +00:00
2020-01-29 04:09:42 +00:00
if ( isRoot && opts . $map && opts . $map . root ) {
2018-09-26 13:14:12 +00:00
setMap ( true ) ;
}
2018-09-26 08:06:57 +00:00
2018-09-26 13:14:12 +00:00
return ret ;
} ;
2018-09-26 08:06:57 +00:00
/ * *
2019-05-24 12:17:17 +00:00
* Converts a DOM object or a string of HTML into a Jamilih object ( or string ) .
2020-01-25 02:32:24 +00:00
* @ param { string | HTMLElement } dom If a string , will parse as document
2019-11-13 06:16:06 +00:00
* @ param { PlainObject } [ config ] Configuration object
2018-09-26 08:06:57 +00:00
* @ param { boolean } [ config . stringOutput = false ] Whether to output the Jamilih object as a string .
2020-01-25 02:32:24 +00:00
* @ param { boolean } [ config . reportInvalidState = true ] If true ( the default ) , will report invalid state errors
* @ param { boolean } [ config . stripWhitespace = false ] Strip whitespace for text nodes
2019-11-13 06:16:06 +00:00
* @ returns { JamilihArray | string } Array containing the elements which represent
* a Jamilih object , or , if ` stringOutput ` is true , it will be the stringified
* version of such an object
2018-09-26 08:06:57 +00:00
* /
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
jml . toJML = function ( dom , {
stringOutput = false ,
reportInvalidState = true ,
stripWhitespace = false
} = { } ) {
2018-09-26 13:14:12 +00:00
if ( typeof dom === 'string' ) {
2020-01-25 02:32:24 +00:00
dom = new win . DOMParser ( ) . parseFromString ( dom , 'text/html' ) ; // todo: Give option for XML once implemented and change JSDoc to allow for Element
2018-09-26 13:14:12 +00:00
}
2020-07-18 11:25:50 +00:00
const ret = [ ] ;
let parent = ret ;
let parentIdx = 0 ;
2019-05-24 12:17:17 +00:00
/ * *
2020-01-25 02:32:24 +00:00
* @ param { string } msg
2019-05-24 12:17:17 +00:00
* @ throws { DOMException }
* @ returns { void }
* /
2018-09-26 13:14:12 +00:00
2020-01-25 02:32:24 +00:00
function invalidStateError ( msg ) {
2018-09-26 13:14:12 +00:00
// These are probably only necessary if working with text/html
2020-02-01 12:44:13 +00:00
/* eslint-disable no-shadow, unicorn/custom-error-definition */
/ * *
* Polyfill for ` DOMException ` .
* /
2020-07-18 11:25:50 +00:00
class DOMException extends Error {
2020-02-01 12:44:13 +00:00
/* eslint-enable no-shadow, unicorn/custom-error-definition */
/ * *
* @ param { string } message
* @ param { string } name
* /
2020-07-18 11:25:50 +00:00
constructor ( message , name ) {
super ( message ) ; // eslint-disable-next-line unicorn/custom-error-definition
2020-01-25 02:32:24 +00:00
2020-07-18 11:25:50 +00:00
this . name = name ;
2020-01-25 02:32:24 +00:00
}
2020-07-18 11:25:50 +00:00
}
2020-01-25 02:32:24 +00:00
if ( reportInvalidState ) {
2018-09-26 13:14:12 +00:00
// INVALID_STATE_ERR per section 9.3 XHTML 5: http://www.w3.org/TR/html5/the-xhtml-syntax.html
2020-07-18 11:25:50 +00:00
const e = new DOMException ( msg , 'INVALID_STATE_ERR' ) ;
2018-09-26 13:14:12 +00:00
e . code = 11 ;
throw e ;
}
}
2019-05-24 12:17:17 +00:00
/ * *
*
2020-01-25 02:32:24 +00:00
* @ param { DocumentType | Entity } obj
2019-05-24 12:17:17 +00:00
* @ param { Node } node
* @ returns { void }
* /
2018-09-26 13:14:12 +00:00
function addExternalID ( obj , node ) {
if ( node . systemId . includes ( '"' ) && node . systemId . includes ( "'" ) ) {
2020-01-25 02:32:24 +00:00
invalidStateError ( 'systemId cannot have both single and double quotes.' ) ;
2018-09-26 08:06:57 +00:00
}
2020-07-18 11:25:50 +00:00
const {
publicId ,
systemId
} = node ;
2018-09-26 13:14:12 +00:00
if ( systemId ) {
obj . systemId = systemId ;
}
if ( publicId ) {
obj . publicId = publicId ;
}
}
2019-05-24 12:17:17 +00:00
/ * *
*
2019-11-13 06:16:06 +00:00
* @ param { any } val
2019-05-24 12:17:17 +00:00
* @ returns { void }
* /
2018-09-26 13:14:12 +00:00
2018-11-04 07:14:53 +00:00
function set ( val ) {
2018-09-26 13:14:12 +00:00
parent [ parentIdx ] = val ;
parentIdx ++ ;
}
2019-05-24 12:17:17 +00:00
/ * *
* @ returns { void }
* /
2018-09-26 13:14:12 +00:00
function setChildren ( ) {
2018-11-04 07:14:53 +00:00
set ( [ ] ) ;
2018-09-26 13:14:12 +00:00
parent = parent [ parentIdx - 1 ] ;
parentIdx = 0 ;
}
2019-05-24 12:17:17 +00:00
/ * *
*
* @ param { string } prop1
* @ param { string } prop2
* @ returns { void }
* /
2018-09-26 13:14:12 +00:00
function setObj ( prop1 , prop2 ) {
parent = parent [ parentIdx - 1 ] [ prop1 ] ;
parentIdx = 0 ;
if ( prop2 ) {
parent = parent [ prop2 ] ;
}
}
2019-05-24 12:17:17 +00:00
/ * *
*
* @ param { Node } node
* @ param { object < { string : string } > } namespaces
* @ returns { void }
* /
2018-09-26 13:14:12 +00:00
function parseDOM ( node , namespaces ) {
// namespaces = clone(namespaces) || {}; // Ensure we're working with a copy, so different levels in the hierarchy can treat it differently
/ *
if ( ( node . prefix && node . prefix . includes ( ':' ) ) || ( node . localName && node . localName . includes ( ':' ) ) ) {
2020-01-25 02:32:24 +00:00
invalidStateError ( 'Prefix cannot have a colon' ) ;
2018-09-26 13:14:12 +00:00
}
* /
2020-07-18 11:25:50 +00:00
const type = 'nodeType' in node ? node . nodeType : null ;
2020-05-26 12:44:54 +00:00
namespaces = _extends ( { } , namespaces ) ;
2020-07-18 11:25:50 +00:00
const xmlChars = /^([\u0009\u000A\u000D\u0020-\uD7FF\uE000-\uFFFD]|[\uD800-\uDBFF][\uDC00-\uDFFF])*$/u ; // eslint-disable-line no-control-regex
2018-09-26 13:14:12 +00:00
if ( [ 2 , 3 , 4 , 7 , 8 ] . includes ( type ) && ! xmlChars . test ( node . nodeValue ) ) {
2020-01-25 02:32:24 +00:00
invalidStateError ( 'Node has bad XML character value' ) ;
2018-09-26 13:14:12 +00:00
}
2020-07-18 11:25:50 +00:00
let tmpParent , tmpParentIdx ;
2019-05-24 12:17:17 +00:00
/ * *
* @ returns { void }
* /
2018-09-26 13:14:12 +00:00
function setTemp ( ) {
tmpParent = parent ;
tmpParentIdx = parentIdx ;
}
2019-05-24 12:17:17 +00:00
/ * *
* @ returns { void }
* /
2018-09-26 13:14:12 +00:00
function resetTemp ( ) {
parent = tmpParent ;
parentIdx = tmpParentIdx ;
parentIdx ++ ; // Increment index in parent container of this element
}
switch ( type ) {
case 1 :
2019-05-24 12:17:17 +00:00
{
// ELEMENT
setTemp ( ) ;
2020-07-18 11:25:50 +00:00
const nodeName = node . nodeName . toLowerCase ( ) ; // Todo: for XML, should not lower-case
2018-09-26 13:14:12 +00:00
2019-05-24 12:17:17 +00:00
setChildren ( ) ; // Build child array since elements are, except at the top level, encapsulated in arrays
2018-09-26 13:14:12 +00:00
2019-05-24 12:17:17 +00:00
set ( nodeName ) ;
2020-07-18 11:25:50 +00:00
const start = { } ;
let hasNamespaceDeclaration = false ;
2018-09-26 08:06:57 +00:00
2019-05-24 12:17:17 +00:00
if ( namespaces [ node . prefix || '' ] !== node . namespaceURI ) {
namespaces [ node . prefix || '' ] = node . namespaceURI ;
2018-09-26 13:14:12 +00:00
2019-05-24 12:17:17 +00:00
if ( node . prefix ) {
start [ 'xmlns:' + node . prefix ] = node . namespaceURI ;
} else if ( node . namespaceURI ) {
start . xmlns = node . namespaceURI ;
2020-01-25 02:32:24 +00:00
} else {
start . xmlns = null ;
2019-05-24 12:17:17 +00:00
}
hasNamespaceDeclaration = true ;
2018-09-26 13:14:12 +00:00
}
2019-05-24 12:17:17 +00:00
if ( node . attributes . length ) {
2020-07-18 11:25:50 +00:00
set ( [ ... node . attributes ] . reduce ( function ( obj , att ) {
2019-05-24 12:17:17 +00:00
obj [ att . name ] = att . value ; // Attr.nodeName and Attr.nodeValue are deprecated as of DOM4 as Attr no longer inherits from Node, so we can safely use name and value
2018-09-26 13:14:12 +00:00
2019-05-24 12:17:17 +00:00
return obj ;
} , start ) ) ;
} else if ( hasNamespaceDeclaration ) {
set ( start ) ;
}
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
const {
childNodes
} = node ;
2018-09-26 08:06:57 +00:00
2020-01-25 02:32:24 +00:00
if ( childNodes . length ) {
2019-05-24 12:17:17 +00:00
setChildren ( ) ; // Element children array container
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
[ ... childNodes ] . forEach ( function ( childNode ) {
2019-05-24 12:17:17 +00:00
parseDOM ( childNode , namespaces ) ;
} ) ;
}
2018-09-26 13:14:12 +00:00
2019-05-24 12:17:17 +00:00
resetTemp ( ) ;
break ;
2018-09-26 08:06:57 +00:00
}
2018-09-26 13:14:12 +00:00
2019-11-13 06:16:06 +00:00
case undefined : // Treat as attribute node until this is fixed: https://github.com/jsdom/jsdom/issues/1641 / https://github.com/jsdom/jsdom/pull/1822
2018-09-26 13:14:12 +00:00
case 2 :
// ATTRIBUTE (should only get here if passing in an attribute node)
2018-11-04 07:14:53 +00:00
set ( {
2018-09-26 13:14:12 +00:00
$attribute : [ node . namespaceURI , node . name , node . value ]
} ) ;
break ;
case 3 :
// TEXT
2020-07-18 11:25:50 +00:00
if ( stripWhitespace && /^\s+$/u . test ( node . nodeValue ) ) {
2020-01-25 02:32:24 +00:00
set ( '' ) ;
2018-09-26 13:14:12 +00:00
return ;
2018-09-26 08:06:57 +00:00
}
2018-09-26 13:14:12 +00:00
2018-11-04 07:14:53 +00:00
set ( node . nodeValue ) ;
2018-09-26 13:14:12 +00:00
break ;
case 4 :
// CDATA
if ( node . nodeValue . includes ( ']]' + '>' ) ) {
2020-01-25 02:32:24 +00:00
invalidStateError ( 'CDATA cannot end with closing ]]>' ) ;
2018-09-26 08:06:57 +00:00
}
2018-11-04 07:14:53 +00:00
set ( [ '![' , node . nodeValue ] ) ;
2018-09-26 13:14:12 +00:00
break ;
case 5 :
2020-01-25 02:32:24 +00:00
// ENTITY REFERENCE (though not in browsers (was already resolved
// anyways), ok to keep for parity with our "entity" shorthand)
2018-11-04 07:14:53 +00:00
set ( [ '&' , node . nodeName ] ) ;
2018-09-26 13:14:12 +00:00
break ;
case 7 :
// PROCESSING INSTRUCTION
2020-07-18 11:25:50 +00:00
if ( /^xml$/iu . test ( node . target ) ) {
2020-01-25 02:32:24 +00:00
invalidStateError ( 'Processing instructions cannot be "xml".' ) ;
2018-09-26 13:14:12 +00:00
}
if ( node . target . includes ( '?>' ) ) {
2020-01-25 02:32:24 +00:00
invalidStateError ( 'Processing instruction targets cannot include ?>' ) ;
2018-09-26 13:14:12 +00:00
}
if ( node . target . includes ( ':' ) ) {
2020-01-25 02:32:24 +00:00
invalidStateError ( 'The processing instruction target cannot include ":"' ) ;
2018-09-26 13:14:12 +00:00
}
if ( node . data . includes ( '?>' ) ) {
2020-01-25 02:32:24 +00:00
invalidStateError ( 'Processing instruction data cannot include ?>' ) ;
2018-09-26 13:14:12 +00:00
}
2018-11-04 07:14:53 +00:00
set ( [ '?' , node . target , node . data ] ) ; // Todo: Could give option to attempt to convert value back into object if has pseudo-attributes
2018-09-26 13:14:12 +00:00
break ;
case 8 :
// COMMENT
if ( node . nodeValue . includes ( '--' ) || node . nodeValue . length && node . nodeValue . lastIndexOf ( '-' ) === node . nodeValue . length - 1 ) {
2020-01-25 02:32:24 +00:00
invalidStateError ( 'Comments cannot include --' ) ;
2018-09-26 13:14:12 +00:00
}
2018-11-04 07:14:53 +00:00
set ( [ '!' , node . nodeValue ] ) ;
2018-09-26 13:14:12 +00:00
break ;
case 9 :
2019-05-24 12:17:17 +00:00
{
// DOCUMENT
setTemp ( ) ;
2020-07-18 11:25:50 +00:00
const docObj = {
2019-05-24 12:17:17 +00:00
$document : {
childNodes : [ ]
}
} ;
set ( docObj ) ; // doc.implementation.createHTMLDocument
// Set position to fragment's array children
2018-09-26 13:14:12 +00:00
2019-05-24 12:17:17 +00:00
setObj ( '$document' , 'childNodes' ) ;
2020-07-18 11:25:50 +00:00
const {
childNodes
} = node ;
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
if ( ! childNodes . length ) {
2020-01-25 02:32:24 +00:00
invalidStateError ( 'Documents must have a child node' ) ;
2019-05-24 12:17:17 +00:00
} // set({$xmlDocument: []}); // doc.implementation.createDocument // Todo: use this conditionally
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
[ ... childNodes ] . forEach ( function ( childNode ) {
2019-05-24 12:17:17 +00:00
// Can't just do documentElement as there may be doctype, comments, etc.
// No need for setChildren, as we have already built the container array
parseDOM ( childNode , namespaces ) ;
} ) ;
resetTemp ( ) ;
break ;
}
2018-09-26 08:06:57 +00:00
2018-09-26 13:14:12 +00:00
case 10 :
2019-05-24 12:17:17 +00:00
{
// DOCUMENT TYPE
setTemp ( ) ; // Can create directly by doc.implementation.createDocumentType
2018-09-26 08:06:57 +00:00
2020-07-18 11:25:50 +00:00
const start = {
2019-05-24 12:17:17 +00:00
$DOCTYPE : {
name : node . name
}
} ;
2020-07-18 11:25:50 +00:00
const pubIdChar = /^(\u0020|\u000D|\u000A|[a-zA-Z0-9]|[-'()+,./:=?;!*#@$_%])*$/u ; // eslint-disable-line no-control-regex
2018-09-26 08:06:57 +00:00
2019-05-24 12:17:17 +00:00
if ( ! pubIdChar . test ( node . publicId ) ) {
2020-01-25 02:32:24 +00:00
invalidStateError ( 'A publicId must have valid characters.' ) ;
2019-05-24 12:17:17 +00:00
}
2018-09-26 08:06:57 +00:00
2020-07-18 11:25:50 +00:00
addExternalID ( start . $DOCTYPE , node ) ; // Fit in internal subset along with entities?: probably don't need as these would only differ if from DTD, and we're not rebuilding the DTD
2019-05-24 12:17:17 +00:00
2020-07-18 11:25:50 +00:00
set ( start ) ; // Auto-generate the internalSubset instead?
2019-05-24 12:17:17 +00:00
resetTemp ( ) ;
break ;
}
2018-09-26 13:14:12 +00:00
case 11 :
2020-01-25 02:32:24 +00:00
{
// DOCUMENT FRAGMENT
setTemp ( ) ;
set ( {
'#' : [ ]
} ) ; // Set position to fragment's array children
2019-05-24 12:17:17 +00:00
2020-01-25 02:32:24 +00:00
setObj ( '#' ) ;
2020-07-18 11:25:50 +00:00
const {
childNodes
} = node ;
[ ... childNodes ] . forEach ( function ( childNode ) {
2020-01-25 02:32:24 +00:00
// No need for setChildren, as we have already built the container array
parseDOM ( childNode , namespaces ) ;
} ) ;
resetTemp ( ) ;
break ;
}
2018-09-26 08:06:57 +00:00
2018-09-26 13:14:12 +00:00
default :
throw new TypeError ( 'Not an XML type' ) ;
2018-09-26 08:06:57 +00:00
}
2018-09-26 13:14:12 +00:00
}
2018-09-26 08:06:57 +00:00
2018-09-26 13:14:12 +00:00
parseDOM ( dom , { } ) ;
2018-09-26 08:06:57 +00:00
2020-01-25 02:32:24 +00:00
if ( stringOutput ) {
2018-09-26 13:14:12 +00:00
return JSON . stringify ( ret [ 0 ] ) ;
}
return ret [ 0 ] ;
2018-09-26 08:06:57 +00:00
} ;
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
jml . toJMLString = function ( dom , config ) {
2018-09-26 13:14:12 +00:00
return jml . toJML ( dom , Object . assign ( config || { } , {
stringOutput : true
} ) ) ;
2018-09-26 08:06:57 +00:00
} ;
2019-05-24 12:17:17 +00:00
/ * *
*
2020-01-29 04:09:42 +00:00
* @ param { ... JamilihArray } args
2019-05-24 12:17:17 +00:00
* @ returns { JamilihReturn }
* /
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
jml . toDOM = function ( ... args ) {
2018-09-26 13:14:12 +00:00
// Alias for jml()
2020-07-18 11:25:50 +00:00
return jml ( ... args ) ;
2018-09-26 08:06:57 +00:00
} ;
2019-05-24 12:17:17 +00:00
/ * *
*
2020-01-29 04:09:42 +00:00
* @ param { ... JamilihArray } args
2019-05-24 12:17:17 +00:00
* @ returns { string }
* /
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
jml . toHTML = function ( ... args ) {
2018-09-26 13:14:12 +00:00
// Todo: Replace this with version of jml() that directly builds a string
2020-07-18 11:25:50 +00:00
const ret = jml ( ... args ) ; // Todo: deal with serialization of properties like 'selected',
2020-01-25 02:32:24 +00:00
// 'checked', 'value', 'defaultValue', 'for', 'dataset', 'on*',
// 'style'! (i.e., need to build a string ourselves)
2018-09-26 13:14:12 +00:00
return ret . outerHTML ;
2018-09-26 08:06:57 +00:00
} ;
2019-05-24 12:17:17 +00:00
/ * *
*
2020-01-29 04:09:42 +00:00
* @ param { ... JamilihArray } args
2019-05-24 12:17:17 +00:00
* @ returns { string }
* /
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
jml . toDOMString = function ( ... args ) {
2018-09-26 13:14:12 +00:00
// Alias for jml.toHTML for parity with jml.toJMLString
2020-07-18 11:25:50 +00:00
return jml . toHTML ( ... args ) ;
2018-09-26 08:06:57 +00:00
} ;
2019-05-24 12:17:17 +00:00
/ * *
*
2020-01-29 04:09:42 +00:00
* @ param { ... JamilihArray } args
2019-05-24 12:17:17 +00:00
* @ returns { string }
* /
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
jml . toXML = function ( ... args ) {
const ret = jml ( ... args ) ;
2020-01-25 02:32:24 +00:00
return new win . XMLSerializer ( ) . serializeToString ( ret ) ;
2018-09-26 08:06:57 +00:00
} ;
2019-05-24 12:17:17 +00:00
/ * *
*
2020-01-29 04:09:42 +00:00
* @ param { ... JamilihArray } args
2019-05-24 12:17:17 +00:00
* @ returns { string }
* /
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
jml . toXMLDOMString = function ( ... args ) {
2018-09-26 13:14:12 +00:00
// Alias for jml.toXML for parity with jml.toJMLString
2020-07-18 11:25:50 +00:00
return jml . toXML ( ... args ) ;
2018-09-26 08:06:57 +00:00
} ;
2020-02-01 12:44:13 +00:00
/ * *
* Element - aware wrapper for ` Map ` .
* /
2018-09-26 08:06:57 +00:00
2020-07-18 11:25:50 +00:00
class JamilihMap extends Map {
/ * *
* @ param { string | Element } elem
* @ returns { any }
* /
get ( elem ) {
elem = typeof elem === 'string' ? $ ( elem ) : elem ;
return super . get . call ( this , elem ) ;
2018-09-26 13:14:12 +00:00
}
2020-07-18 11:25:50 +00:00
/ * *
* @ param { string | Element } elem
* @ param { any } value
* @ returns { any }
* /
2018-09-26 08:06:57 +00:00
2020-07-18 11:25:50 +00:00
set ( elem , value ) {
elem = typeof elem === 'string' ? $ ( elem ) : elem ;
return super . set . call ( this , elem , value ) ;
}
/ * *
* @ param { string | Element } elem
* @ param { string } methodName
* @ param { ... any } args
* @ returns { any }
* /
2018-09-26 08:06:57 +00:00
2020-07-18 11:25:50 +00:00
invoke ( elem , methodName , ... args ) {
elem = typeof elem === 'string' ? $ ( elem ) : elem ;
return this . get ( elem ) [ methodName ] ( elem , ... args ) ;
}
2018-11-04 07:14:53 +00:00
2020-07-18 11:25:50 +00:00
}
2020-02-01 12:44:13 +00:00
/ * *
* Element - aware wrapper for ` WeakMap ` .
* /
2018-09-26 08:06:57 +00:00
2020-07-18 11:25:50 +00:00
class JamilihWeakMap extends WeakMap {
/ * *
* @ param { string | Element } elem
* @ returns { any }
* /
get ( elem ) {
elem = typeof elem === 'string' ? $ ( elem ) : elem ;
return super . get . call ( this , elem ) ;
2018-09-26 13:14:12 +00:00
}
2020-07-18 11:25:50 +00:00
/ * *
* @ param { string | Element } elem
* @ param { any } value
* @ returns { any }
* /
2018-09-26 08:06:57 +00:00
2020-07-18 11:25:50 +00:00
set ( elem , value ) {
elem = typeof elem === 'string' ? $ ( elem ) : elem ;
return super . set . call ( this , elem , value ) ;
}
/ * *
* @ param { string | Element } elem
* @ param { string } methodName
* @ param { ... any } args
* @ returns { any }
* /
2018-09-26 08:06:57 +00:00
2020-07-18 11:25:50 +00:00
invoke ( elem , methodName , ... args ) {
elem = typeof elem === 'string' ? $ ( elem ) : elem ;
return this . get ( elem ) [ methodName ] ( elem , ... args ) ;
}
2018-11-04 07:14:53 +00:00
2020-07-18 11:25:50 +00:00
}
2018-09-26 08:06:57 +00:00
jml . Map = JamilihMap ;
jml . WeakMap = JamilihWeakMap ;
2020-01-25 02:32:24 +00:00
/ * *
* @ typedef { GenericArray } MapAndElementArray
* @ property { JamilihWeakMap | JamilihMap } 0
* @ property { Element } 1
* /
/ * *
* @ param { GenericObject } obj
* @ param { ... JamilihArray } args
* @ returns { MapAndElementArray }
* /
2018-09-26 08:06:57 +00:00
2020-07-18 11:25:50 +00:00
jml . weak = function ( obj , ... args ) {
const map = new JamilihWeakMap ( ) ;
const elem = jml ( {
2018-09-26 13:14:12 +00:00
$map : [ map , obj ]
2020-07-18 11:25:50 +00:00
} , ... args ) ;
2018-09-26 13:14:12 +00:00
return [ map , elem ] ;
2018-09-26 08:06:57 +00:00
} ;
2020-01-25 02:32:24 +00:00
/ * *
* @ param { any } obj
* @ param { ... JamilihArray } args
* @ returns { MapAndElementArray }
* /
2018-09-26 08:06:57 +00:00
2020-07-18 11:25:50 +00:00
jml . strong = function ( obj , ... args ) {
const map = new JamilihMap ( ) ;
const elem = jml ( {
2018-09-26 13:14:12 +00:00
$map : [ map , obj ]
2020-07-18 11:25:50 +00:00
} , ... args ) ;
2018-09-26 13:14:12 +00:00
return [ map , elem ] ;
2018-09-26 08:06:57 +00:00
} ;
2020-01-25 02:32:24 +00:00
/ * *
* @ param { string | Element } elem If a string , will be interpreted as a selector
* @ param { symbol | string } sym If a string , will be used with ` Symbol.for `
* @ returns { any } The value associated with the symbol
* /
2018-09-26 08:06:57 +00:00
2020-07-18 11:25:50 +00:00
jml . symbol = jml . sym = jml . for = function ( elem , sym ) {
2018-09-26 13:14:12 +00:00
elem = typeof elem === 'string' ? $ ( elem ) : elem ;
2020-07-18 11:25:50 +00:00
return elem [ typeof sym === 'symbol' ? sym : Symbol . for ( sym ) ] ;
2018-09-26 08:06:57 +00:00
} ;
2020-01-25 02:32:24 +00:00
/ * *
* @ param { string | Element } elem If a string , will be interpreted as a selector
* @ param { symbol | string | Map | WeakMap } symOrMap If a string , will be used with ` Symbol.for `
* @ param { string | any } methodName Can be ` any ` if the symbol or map directly
* points to a function ( it is then used as the first argument ) .
* @ param { any [ ] } args
* @ returns { any }
* /
2018-09-26 08:06:57 +00:00
2020-07-18 11:25:50 +00:00
jml . command = function ( elem , symOrMap , methodName , ... args ) {
2018-09-26 13:14:12 +00:00
elem = typeof elem === 'string' ? $ ( elem ) : elem ;
2020-07-18 11:25:50 +00:00
let func ;
2018-09-26 08:06:57 +00:00
2020-07-18 11:25:50 +00:00
if ( [ 'symbol' , 'string' ] . includes ( typeof symOrMap ) ) {
2018-09-26 13:14:12 +00:00
func = jml . sym ( elem , symOrMap ) ;
if ( typeof func === 'function' ) {
2020-07-18 11:25:50 +00:00
return func ( methodName , ... args ) ; // Already has `this` bound to `elem`
2018-09-26 08:06:57 +00:00
}
2020-07-18 11:25:50 +00:00
return func [ methodName ] ( ... args ) ;
2019-05-24 12:17:17 +00:00
}
2018-09-26 08:06:57 +00:00
2019-05-24 12:17:17 +00:00
func = symOrMap . get ( elem ) ;
2018-09-26 08:06:57 +00:00
2019-05-24 12:17:17 +00:00
if ( typeof func === 'function' ) {
2020-07-18 11:25:50 +00:00
return func . call ( elem , methodName , ... args ) ;
2019-05-24 12:17:17 +00:00
}
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
return func [ methodName ] ( elem , ... args ) ; // return func[methodName].call(elem, ...args);
2018-09-26 08:06:57 +00:00
} ;
2020-01-25 02:32:24 +00:00
/ * *
* Expects properties ` document ` , ` XMLSerializer ` , and ` DOMParser ` .
* Also updates ` body ` with ` document.body ` .
* @ param { Window } wind
* @ returns { void }
* /
2018-09-26 08:06:57 +00:00
2020-07-18 11:25:50 +00:00
jml . setWindow = wind => {
2018-09-26 13:14:12 +00:00
win = wind ;
2020-01-25 02:32:24 +00:00
doc = win . document ;
2018-09-26 13:14:12 +00:00
2020-01-25 02:32:24 +00:00
if ( doc && doc . body ) {
2020-07-18 11:25:50 +00:00
( {
body
} = doc ) ;
2018-09-26 13:14:12 +00:00
}
2018-09-26 08:06:57 +00:00
} ;
2020-01-25 02:32:24 +00:00
/ * *
* @ returns { Window }
* /
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
2020-07-18 11:25:50 +00:00
jml . getWindow = ( ) => {
2018-09-26 13:14:12 +00:00
return win ;
2018-09-26 08:06:57 +00:00
} ;
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
2020-07-18 11:25:50 +00:00
let body = doc && doc . body ; // eslint-disable-line import/no-mutable-exports
2019-05-24 12:17:17 +00:00
2020-07-18 11:25:50 +00:00
const nbsp = '\u00A0' ; // Very commonly needed in templates
2018-09-26 08:06:57 +00:00
2018-11-04 07:38:28 +00:00
/ * *
* ISC License
*
* Copyright ( c ) 2018 , Andrea Giammarchi , @ WebReflection
*
* Permission to use , copy , modify , and / or distribute this software for any
* purpose with or without fee is hereby granted , provided that the above
* copyright notice and this permission notice appear in all copies .
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS . IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL , DIRECT ,
* INDIRECT , OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE , DATA OR PROFITS , WHETHER IN AN ACTION OF CONTRACT , NEGLIGENCE
* OR OTHER TORTIOUS ACTION , ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE .
* /
2020-07-18 11:25:50 +00:00
class QueryResult extends Array { }
const {
create ,
defineProperty
} = Object ;
const AP = Array . prototype ;
const DOM _CONTENT _LOADED = 'DOMContentLoaded' ;
const LOAD = 'load' ;
const NO _TRANSPILER _ISSUES = new QueryResult ( ) instanceof QueryResult ;
const QRP = QueryResult . prototype ; // fixes methods returning non QueryResult
2018-11-04 07:38:28 +00:00
/* istanbul ignore if */
2020-07-18 11:25:50 +00:00
if ( ! NO _TRANSPILER _ISSUES ) Object . getOwnPropertyNames ( AP ) . forEach ( name => {
const desc = Object . getOwnPropertyDescriptor ( AP , name ) ;
2018-11-04 07:38:28 +00:00
if ( typeof desc . value === 'function' ) {
2020-07-18 11:25:50 +00:00
const fn = desc . value ;
2018-11-04 07:38:28 +00:00
desc . value = function ( ) {
2020-07-18 11:25:50 +00:00
const result = fn . apply ( this , arguments ) ;
2018-11-04 07:38:28 +00:00
return result instanceof Array ? patch ( result ) : result ;
} ;
}
defineProperty ( QRP , name , desc ) ;
} ) ; // fixes badly transpiled classes
2020-07-18 11:25:50 +00:00
const patch = NO _TRANSPILER _ISSUES ? qr => qr :
2018-11-04 07:38:28 +00:00
/* istanbul ignore next */
2020-07-18 11:25:50 +00:00
qr => {
const nqr = create ( QRP ) ;
2018-11-04 07:38:28 +00:00
push . apply ( nqr , slice ( qr ) ) ;
return nqr ;
} ;
2020-07-18 11:25:50 +00:00
const push = AP . push ;
2018-11-04 07:38:28 +00:00
2020-07-18 11:25:50 +00:00
const search = ( list , el ) => {
const nodes = [ ] ;
const length = list . length ;
2018-09-26 08:06:57 +00:00
2020-07-18 11:25:50 +00:00
for ( let i = 0 ; i < length ; i ++ ) {
const css = list [ i ] . trim ( ) ;
2018-09-26 08:06:57 +00:00
2018-11-04 07:38:28 +00:00
if ( css . slice ( - 6 ) === ':first' ) {
2020-07-18 11:25:50 +00:00
const node = el . querySelector ( css . slice ( 0 , - 6 ) ) ;
2018-11-04 07:38:28 +00:00
if ( node ) push . call ( nodes , node ) ;
} else push . apply ( nodes , slice ( el . querySelectorAll ( css ) ) ) ;
2018-09-26 08:06:57 +00:00
}
2020-07-18 11:25:50 +00:00
return new QueryResult ( ... nodes ) ;
2018-09-26 08:06:57 +00:00
} ;
2020-07-18 11:25:50 +00:00
const slice = NO _TRANSPILER _ISSUES ? patch :
2018-11-04 07:38:28 +00:00
/* istanbul ignore next */
2020-07-18 11:25:50 +00:00
all => {
2018-11-04 07:38:28 +00:00
// do not use slice.call(...) due old IE gotcha
2020-07-18 11:25:50 +00:00
const nodes = [ ] ;
const length = all . length ;
2018-11-04 07:38:28 +00:00
2020-07-18 11:25:50 +00:00
for ( let i = 0 ; i < length ; i ++ ) nodes [ i ] = all [ i ] ;
2018-11-04 07:38:28 +00:00
return nodes ;
} ; // use function to avoid usage of Symbol.hasInstance
// (broken in older browsers anyway)
2020-07-18 11:25:50 +00:00
const $$1 = function $ ( CSS , parent = document ) {
switch ( typeof CSS ) {
2018-09-26 08:06:57 +00:00
case 'string' :
2018-11-04 07:38:28 +00:00
return patch ( search ( CSS . split ( ',' ) , parent ) ) ;
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
case 'object' :
2018-11-04 07:38:28 +00:00
// needed to avoid iterator dance (breaks in older IEs)
2020-07-18 11:25:50 +00:00
const nodes = [ ] ;
const all = 'nodeType' in CSS || 'postMessage' in CSS ? [ CSS ] : CSS ;
2018-11-04 07:38:28 +00:00
push . apply ( nodes , slice ( all ) ) ;
2020-07-18 11:25:50 +00:00
return patch ( new QueryResult ( ... nodes ) ) ;
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
case 'function' :
2020-07-18 11:25:50 +00:00
const $parent = $ ( parent ) ;
const $window = $ ( parent . defaultView ) ;
const handler = {
handleEvent ( event ) {
2018-09-26 08:06:57 +00:00
$parent . off ( DOM _CONTENT _LOADED , handler ) ;
$window . off ( LOAD , handler ) ;
CSS ( event ) ;
}
2020-07-18 11:25:50 +00:00
2018-09-26 08:06:57 +00:00
} ;
$parent . on ( DOM _CONTENT _LOADED , handler ) ;
$window . on ( LOAD , handler ) ;
2020-07-18 11:25:50 +00:00
const rs = parent . readyState ;
if ( rs == 'complete' || rs != 'loading' && ! parent . documentElement . doScroll ) setTimeout ( ( ) => $parent . dispatch ( DOM _CONTENT _LOADED ) ) ;
2018-11-04 07:38:28 +00:00
return $ ;
2018-09-30 05:33:00 +00:00
}
2018-11-04 07:38:28 +00:00
} ;
$$1 . prototype = QRP ;
2018-09-26 08:06:57 +00:00
2020-07-18 11:25:50 +00:00
$$1 . extend = ( key , value ) => ( defineProperty ( QRP , key , {
configurable : true ,
value
} ) , $$1 ) ; // dropped usage of for-of to avoid broken iteration dance in older IEs
2018-11-04 07:38:28 +00:00
2018-09-26 08:06:57 +00:00
2020-07-18 11:25:50 +00:00
$$1 . extend ( 'dispatch' , function dispatch ( type , init = { } ) {
const event = new CustomEvent ( type , init ) ;
const length = this . length ;
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
for ( let i = 0 ; i < length ; i ++ ) this [ i ] . dispatchEvent ( event ) ;
2018-09-26 08:06:57 +00:00
return this ;
2020-07-18 11:25:50 +00:00
} ) . extend ( 'off' , function off ( type , handler , options = false ) {
const length = this . length ;
2018-09-30 05:33:00 +00:00
2020-07-18 11:25:50 +00:00
for ( let i = 0 ; i < length ; i ++ ) this [ i ] . removeEventListener ( type , handler , options ) ;
2018-09-26 08:06:57 +00:00
2018-09-26 13:14:12 +00:00
return this ;
2020-07-18 11:25:50 +00:00
} ) . extend ( 'on' , function on ( type , handler , options = false ) {
const length = this . length ;
2018-09-30 05:33:00 +00:00
2020-07-18 11:25:50 +00:00
for ( let i = 0 ; i < length ; i ++ ) this [ i ] . addEventListener ( type , handler , options ) ;
2018-09-26 08:06:57 +00:00
return this ;
2018-09-26 13:14:12 +00:00
} ) ;
2018-09-26 08:06:57 +00:00
2020-07-18 11:25:50 +00:00
function _typeof ( obj ) {
2020-07-01 04:56:10 +00:00
"@babel/helpers - typeof" ;
if ( typeof Symbol === "function" && typeof Symbol . iterator === "symbol" ) {
2020-07-18 11:25:50 +00:00
_typeof = function ( obj ) {
2020-07-01 04:56:10 +00:00
return typeof obj ;
2018-09-26 13:14:12 +00:00
} ;
} else {
2020-07-18 11:25:50 +00:00
_typeof = function ( obj ) {
2020-07-01 04:56:10 +00:00
return obj && typeof Symbol === "function" && obj . constructor === Symbol && obj !== Symbol . prototype ? "symbol" : typeof obj ;
2018-09-26 13:14:12 +00:00
} ;
2018-09-26 08:06:57 +00:00
}
2020-07-18 11:25:50 +00:00
return _typeof ( obj ) ;
2018-09-26 13:14:12 +00:00
}
2020-07-18 11:25:50 +00:00
function _slicedToArray ( arr , i ) {
return _arrayWithHoles ( arr ) || _iterableToArrayLimit ( arr , i ) || _unsupportedIterableToArray ( arr , i ) || _nonIterableRest ( ) ;
2018-11-04 07:38:28 +00:00
}
2020-07-18 11:25:50 +00:00
function _toConsumableArray ( arr ) {
return _arrayWithoutHoles ( arr ) || _iterableToArray ( arr ) || _unsupportedIterableToArray ( arr ) || _nonIterableSpread ( ) ;
2018-09-26 13:14:12 +00:00
}
2020-07-18 11:25:50 +00:00
function _arrayWithoutHoles ( arr ) {
if ( Array . isArray ( arr ) ) return _arrayLikeToArray ( arr ) ;
2018-09-26 13:14:12 +00:00
}
2020-07-18 11:25:50 +00:00
function _arrayWithHoles ( arr ) {
2018-11-04 07:38:28 +00:00
if ( Array . isArray ( arr ) ) return arr ;
}
2020-07-18 11:25:50 +00:00
function _iterableToArray ( iter ) {
2020-07-01 04:56:10 +00:00
if ( typeof Symbol !== "undefined" && Symbol . iterator in Object ( iter ) ) return Array . from ( iter ) ;
2018-09-26 13:14:12 +00:00
}
2020-07-18 11:25:50 +00:00
function _iterableToArrayLimit ( arr , i ) {
2020-07-01 04:56:10 +00:00
if ( typeof Symbol === "undefined" || ! ( Symbol . iterator in Object ( arr ) ) ) return ;
2018-11-04 07:38:28 +00:00
var _arr = [ ] ;
var _n = true ;
var _d = false ;
var _e = undefined ;
try {
for ( var _i = arr [ Symbol . iterator ] ( ) , _s ; ! ( _n = ( _s = _i . next ( ) ) . done ) ; _n = true ) {
_arr . push ( _s . value ) ;
if ( i && _arr . length === i ) break ;
}
} catch ( err ) {
_d = true ;
_e = err ;
} finally {
try {
if ( ! _n && _i [ "return" ] != null ) _i [ "return" ] ( ) ;
} finally {
if ( _d ) throw _e ;
}
}
return _arr ;
}
2020-07-18 11:25:50 +00:00
function _unsupportedIterableToArray ( o , minLen ) {
2020-07-01 04:56:10 +00:00
if ( ! o ) return ;
2020-07-18 11:25:50 +00:00
if ( typeof o === "string" ) return _arrayLikeToArray ( o , minLen ) ;
2020-07-01 04:56:10 +00:00
var n = Object . prototype . toString . call ( o ) . slice ( 8 , - 1 ) ;
if ( n === "Object" && o . constructor ) n = o . constructor . name ;
if ( n === "Map" || n === "Set" ) return Array . from ( o ) ;
2020-07-18 11:25:50 +00:00
if ( n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/ . test ( n ) ) return _arrayLikeToArray ( o , minLen ) ;
2020-07-01 04:56:10 +00:00
}
2020-07-18 11:25:50 +00:00
function _arrayLikeToArray ( arr , len ) {
2020-07-01 04:56:10 +00:00
if ( len == null || len > arr . length ) len = arr . length ;
2020-07-18 11:25:50 +00:00
for ( var i = 0 , arr2 = new Array ( len ) ; i < len ; i ++ ) arr2 [ i ] = arr [ i ] ;
2020-07-01 04:56:10 +00:00
return arr2 ;
}
2020-07-18 11:25:50 +00:00
function _nonIterableSpread ( ) {
2020-07-01 04:56:10 +00:00
throw new TypeError ( "Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method." ) ;
2018-09-26 13:14:12 +00:00
}
2018-09-26 08:06:57 +00:00
2020-07-18 11:25:50 +00:00
function _nonIterableRest ( ) {
2020-07-01 04:56:10 +00:00
throw new TypeError ( "Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method." ) ;
2018-11-04 07:38:28 +00:00
}
function convertToString ( content , type ) {
2020-07-18 11:25:50 +00:00
switch ( _typeof ( content ) ) {
2018-09-26 08:06:57 +00:00
case 'object' :
{
if ( ! content ) {
throw new TypeError ( 'Cannot supply `null`' ) ;
}
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
switch ( content . nodeType ) {
case 1 :
{
// ELEMENT
return content . outerHTML ;
}
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
case 3 :
{
// TEXT
return content . nodeValue ;
}
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
case 11 :
{
// DOCUMENT_FRAGMENT_NODE
2020-07-18 11:25:50 +00:00
return _toConsumableArray ( content . childNodes ) . reduce ( function ( s , node ) {
2018-11-04 07:38:28 +00:00
return s + convertToString ( node , type ) ;
2018-09-26 08:06:57 +00:00
} , '' ) ;
}
2018-09-26 13:14:12 +00:00
2018-11-04 07:38:28 +00:00
case undefined :
2019-12-26 23:32:39 +00:00
// Array of nodes, QueryResult objects
// if (Array.isArray(content)) {
if ( typeof content . reduce === 'function' ) {
return content . reduce ( function ( s , node ) {
return s + convertToString ( node , type ) ;
} , '' ) ;
2018-11-04 07:38:28 +00:00
}
2019-12-26 23:32:39 +00:00
break ;
2018-11-04 07:38:28 +00:00
}
2018-09-26 13:14:12 +00:00
2019-12-26 23:32:39 +00:00
return undefined ;
2018-09-26 08:06:57 +00:00
}
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
case 'string' :
{
return content ;
}
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
default :
2020-07-18 11:25:50 +00:00
throw new TypeError ( 'Bad content for ' + type + '; type: ' + _typeof ( content ) ) ;
2018-09-26 08:06:57 +00:00
}
}
2018-09-26 13:14:12 +00:00
2018-11-04 07:38:28 +00:00
function convertToDOM ( content , type , avoidClone ) {
2020-07-18 11:25:50 +00:00
switch ( _typeof ( content ) ) {
2018-09-26 08:06:57 +00:00
case 'object' :
{
if ( ! content ) {
throw new TypeError ( 'Cannot supply `null`' ) ;
}
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
if ( [ 1 , // ELEMENT
3 , // TEXT
11 // Document fragment
] . includes ( content . nodeType ) ) {
return avoidClone ? content : content . cloneNode ( true ) ;
2018-11-04 07:38:28 +00:00
}
2018-09-26 13:14:12 +00:00
2018-11-04 07:38:28 +00:00
if ( typeof content . reduce !== 'function' ) {
throw new TypeError ( 'Unrecognized type of object for conversion to DOM' ) ;
} // Array of nodes, QueryResult objects
2018-09-26 13:14:12 +00:00
2018-11-04 07:38:28 +00:00
return avoidClone ? content : content . map ( function ( node ) {
if ( ! node || ! node . cloneNode ) {
// Allows for arrays of HTML strings
return convertToDOM ( node , type , false ) ;
}
return node . cloneNode ( true ) ;
} ) ;
2018-09-26 08:06:57 +00:00
}
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
case 'string' :
{
2020-07-01 04:56:10 +00:00
var div = document . createElement ( 'div' ) ; // eslint-disable-next-line no-unsanitized/property
2018-09-26 08:06:57 +00:00
div . innerHTML = content ;
return div . firstElementChild || div . firstChild ;
}
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
default :
2020-07-18 11:25:50 +00:00
throw new TypeError ( 'Bad content for ' + type + '; type: ' + _typeof ( content ) ) ;
2018-09-26 08:06:57 +00:00
}
}
function insert ( type ) {
return function ( ) {
var _this = this ;
2018-09-26 13:14:12 +00:00
for ( var _len = arguments . length , args = new Array ( _len ) , _key = 0 ; _key < _len ; _key ++ ) {
2018-09-26 08:06:57 +00:00
args [ _key ] = arguments [ _key ] ;
}
var cbOrContent = args [ 0 ] ;
2020-07-18 11:25:50 +00:00
switch ( _typeof ( cbOrContent ) ) {
2018-09-26 08:06:57 +00:00
case 'function' :
{
this . forEach ( function ( node , i ) {
var ret = cbOrContent . call ( _this , i , node . textContent ) ;
node [ type ] ( ret ) ;
} ) ;
break ;
}
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
default :
{
2018-11-04 07:38:28 +00:00
this . forEach ( function ( node , i , arr ) {
2020-07-18 11:25:50 +00:00
node [ type ] . apply ( node , _toConsumableArray ( args . flatMap ( function ( content ) {
2018-11-04 07:38:28 +00:00
return convertToDOM ( content , type , i === arr . length - 1 ) ;
2018-09-26 08:06:57 +00:00
} ) ) ) ;
} ) ;
break ;
}
}
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
return this ;
} ;
}
function insertText ( type ) {
return function ( cbOrContent ) {
var _this2 = this ;
2020-07-18 11:25:50 +00:00
switch ( _typeof ( cbOrContent ) ) {
2018-09-26 08:06:57 +00:00
case 'function' :
{
this . forEach ( function ( node , i ) {
var ret = cbOrContent . call ( _this2 , i , node [ type ] ) ;
2018-11-04 07:38:28 +00:00
node [ type ] = convertToString ( ret , type ) ;
2018-09-26 08:06:57 +00:00
} ) ;
break ;
}
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
default :
{
this . forEach ( function ( node ) {
2018-11-04 07:38:28 +00:00
node [ type ] = convertToString ( cbOrContent , type ) ;
2018-09-26 08:06:57 +00:00
} ) ;
break ;
}
}
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
return this ;
} ;
}
var after = insert ( 'after' ) ;
var before = insert ( 'before' ) ;
var append = insert ( 'append' ) ;
var prepend = insert ( 'prepend' ) ;
var html = insertText ( 'innerHTML' ) ;
var text = insertText ( 'textContent' ) ;
2018-11-04 07:38:28 +00:00
/ *
// Todo:
export const val = function ( valueOrFunc ) {
} ;
* /
// Given that these types require a selector engine and
// in order to avoid the absence of optimization of `document.querySelectorAll`
// for `:first-child` and different behavior in different contexts,
// and to avoid making a mutual dependency with query-result,
// exports of this type accept a QueryResult instance;
// if selected without a second argument, we do default to
// `document.querySelectorAll`, however.
var insertTo = function insertTo ( method ) {
var $ = arguments . length > 1 && arguments [ 1 ] !== undefined ? arguments [ 1 ] : function ( sel ) {
2020-07-18 11:25:50 +00:00
return _toConsumableArray ( document . querySelectorAll ( sel ) ) ;
2018-11-04 07:38:28 +00:00
} ;
var type = {
appendTo : 'append' ,
prependTo : 'prepend' ,
insertAfter : 'after' ,
insertBefore : 'before'
} [ method ] || 'append' ;
return function ( target ) {
var toType = type + 'To' ;
this . forEach ( function ( node , i , arr ) {
if ( typeof target === 'string' && target . charAt ( 0 ) !== '<' ) {
target = $ ( target ) ;
}
target = Array . isArray ( target ) ? target : [ target ] ;
2020-07-18 11:25:50 +00:00
node [ type ] . apply ( node , _toConsumableArray ( target . flatMap ( function ( content ) {
2018-11-04 07:38:28 +00:00
return convertToDOM ( content , toType , i === arr . length - 1 ) ;
} ) ) ) ;
} ) ;
return this ;
} ;
} ; // Todo: optional `withDataAndEvents` and `deepWithDataAndEvents` arguments?
var clone = function clone ( ) {
return this . map ( function ( node ) {
// Still a QueryResult with such a map
return node . cloneNode ( true ) ;
} ) ;
} ;
var empty = function empty ( ) {
this . forEach ( function ( node ) {
node . textContent = '' ;
} ) ;
} ;
var remove = function remove ( selector ) {
if ( selector ) {
this . forEach ( function ( node ) {
if ( node . matches ( selector ) ) {
// Todo: Use query-result instead?
node . remove ( ) ;
}
} ) ;
} else {
this . forEach ( function ( node ) {
node . remove ( ) ;
} ) ;
}
return this ;
} ;
/ *
// Todo:
export const detach = function ( selector ) {
// Should preserve attached data
return remove ( selector ) ;
} ;
* /
var attr = function attr ( attributeNameOrAtts , valueOrCb ) {
var _this3 = this ;
if ( valueOrCb === undefined ) {
2020-07-18 11:25:50 +00:00
switch ( _typeof ( attributeNameOrAtts ) ) {
2018-11-04 07:38:28 +00:00
case 'string' :
{
return this [ 0 ] . hasAttribute ( attributeNameOrAtts ) ? this [ 0 ] . getAttribute ( attributeNameOrAtts ) : undefined ;
}
case 'object' :
{
if ( attributeNameOrAtts ) {
this . forEach ( function ( node , i ) {
Object . entries ( attributeNameOrAtts ) . forEach ( function ( _ref ) {
2020-07-18 11:25:50 +00:00
var _ref2 = _slicedToArray ( _ref , 2 ) ,
2018-11-04 07:38:28 +00:00
att = _ref2 [ 0 ] ,
val = _ref2 [ 1 ] ;
node . setAttribute ( att , val ) ;
} ) ;
} ) ;
return this ;
}
}
// Fallthrough
default :
{
2020-07-18 11:25:50 +00:00
throw new TypeError ( 'Unexpected type for attribute name: ' + _typeof ( attributeNameOrAtts ) ) ;
2018-11-04 07:38:28 +00:00
}
}
}
2020-07-18 11:25:50 +00:00
switch ( _typeof ( valueOrCb ) ) {
2018-11-04 07:38:28 +00:00
case 'function' :
{
this . forEach ( function ( node , i ) {
var ret = valueOrCb . call ( _this3 , i , node . getAttribute ( valueOrCb ) ) ;
if ( ret === null ) {
node . removeAttribute ( attributeNameOrAtts ) ;
} else {
node . setAttribute ( attributeNameOrAtts , ret ) ;
}
} ) ;
break ;
}
case 'string' :
{
this . forEach ( function ( node , i ) {
node . setAttribute ( attributeNameOrAtts , valueOrCb ) ;
} ) ;
break ;
}
case 'object' :
{
if ( ! valueOrCb ) {
// `null`
return removeAttr . call ( this , attributeNameOrAtts ) ;
}
}
// Fallthrough
default :
{
2020-07-18 11:25:50 +00:00
throw new TypeError ( 'Unexpected type for attribute name: ' + _typeof ( attributeNameOrAtts ) ) ;
2018-11-04 07:38:28 +00:00
}
}
return this ;
} ;
var removeAttr = function removeAttr ( attributeName ) {
if ( typeof attributeName !== 'string' ) {
2020-07-18 11:25:50 +00:00
throw new TypeError ( 'Unexpected type for attribute name: ' + _typeof ( attributeName ) ) ;
2018-11-04 07:38:28 +00:00
}
this . forEach ( function ( node ) {
node . removeAttribute ( attributeName ) ;
} ) ;
} ;
function classAttManipulation ( type ) {
return function ( cbOrContent ) {
var _this4 = this ;
2020-07-18 11:25:50 +00:00
switch ( _typeof ( cbOrContent ) ) {
2018-11-04 07:38:28 +00:00
case 'function' :
{
this . forEach ( function ( node , i ) {
var _node$classList ;
var ret = cbOrContent . call ( _this4 , i , node . className ) ;
2020-07-18 11:25:50 +00:00
( _node$classList = node . classList ) [ type ] . apply ( _node$classList , _toConsumableArray ( ret . split ( ' ' ) ) ) ;
2018-11-04 07:38:28 +00:00
} ) ;
break ;
}
default :
{
if ( type === 'remove' && ! cbOrContent ) {
this . forEach ( function ( node ) {
node . className = '' ;
} ) ;
break ;
}
this . forEach ( function ( node ) {
var _node$classList2 ;
2020-07-18 11:25:50 +00:00
( _node$classList2 = node . classList ) [ type ] . apply ( _node$classList2 , _toConsumableArray ( cbOrContent . split ( ' ' ) ) ) ;
2018-11-04 07:38:28 +00:00
} ) ;
break ;
}
}
return this ;
} ;
}
var addClass = classAttManipulation ( 'add' ) ;
var removeClass = classAttManipulation ( 'remove' ) ;
var hasClass = function hasClass ( className ) {
return this . some ( function ( node ) {
return node . classList . contains ( className ) ;
} ) ;
} ;
var toggleClass = function toggleClass ( classNameOrCb , state ) {
var _this5 = this ;
2020-07-18 11:25:50 +00:00
switch ( typeof cbOrContent === "undefined" ? "undefined" : _typeof ( cbOrContent ) ) {
2018-11-04 07:38:28 +00:00
case 'function' :
{
if ( typeof state === 'boolean' ) {
this . forEach ( function ( node , i ) {
var _node$classList3 ;
var ret = classNameOrCb . call ( _this5 , i , node . className , state ) ;
2020-07-18 11:25:50 +00:00
( _node$classList3 = node . classList ) . toggle . apply ( _node$classList3 , _toConsumableArray ( ret . split ( ' ' ) ) . concat ( [ state ] ) ) ;
2018-11-04 07:38:28 +00:00
} ) ;
} else {
this . forEach ( function ( node , i ) {
var _node$classList4 ;
var ret = classNameOrCb . call ( _this5 , i , node . className , state ) ;
2020-07-18 11:25:50 +00:00
( _node$classList4 = node . classList ) . toggle . apply ( _node$classList4 , _toConsumableArray ( ret . split ( ' ' ) ) ) ;
2018-11-04 07:38:28 +00:00
} ) ;
}
break ;
}
case 'string' :
{
if ( typeof state === 'boolean' ) {
this . forEach ( function ( node ) {
var _node$classList5 ;
2020-07-18 11:25:50 +00:00
( _node$classList5 = node . classList ) . toggle . apply ( _node$classList5 , _toConsumableArray ( classNameOrCb . split ( ' ' ) ) . concat ( [ state ] ) ) ;
2018-11-04 07:38:28 +00:00
} ) ;
} else {
this . forEach ( function ( node ) {
var _node$classList6 ;
2020-07-18 11:25:50 +00:00
( _node$classList6 = node . classList ) . toggle . apply ( _node$classList6 , _toConsumableArray ( classNameOrCb . split ( ' ' ) ) ) ;
2018-11-04 07:38:28 +00:00
} ) ;
}
break ;
}
}
} ;
2018-09-26 08:06:57 +00:00
2018-09-26 13:14:12 +00:00
var methods = {
after : after ,
before : before ,
append : append ,
prepend : prepend ,
html : html ,
2018-11-04 07:38:28 +00:00
text : text ,
clone : clone ,
empty : empty ,
remove : remove ,
// detach
attr : attr ,
removeAttr : removeAttr ,
addClass : addClass ,
hasClass : hasClass ,
removeClass : removeClass ,
toggleClass : toggleClass
2018-09-26 13:14:12 +00:00
} ;
2018-09-26 08:06:57 +00:00
var manipulation = function manipulation ( $ , jml ) {
2018-11-04 07:38:28 +00:00
[ 'after' , 'before' , 'append' , 'prepend' , 'html' , 'text' , 'clone' , 'empty' , 'remove' , // 'detach'
'attr' , 'removeAttr' , 'addClass' , 'hasClass' , 'removeClass' , 'toggleClass' ] . forEach ( function ( method ) {
2018-09-26 08:06:57 +00:00
$ . extend ( method , methods [ method ] ) ;
} ) ;
2018-11-04 07:38:28 +00:00
[ 'appendTo' , 'prependTo' , 'insertAfter' , 'insertBefore' ] . forEach ( function ( method ) {
$ . extend ( method , insertTo ( method , $ ) ) ;
} ) ;
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
if ( jml ) {
$ . extend ( 'jml' , function ( ) {
2018-11-04 07:38:28 +00:00
var _this6 = this ;
2018-09-26 08:06:57 +00:00
2018-09-26 13:14:12 +00:00
for ( var _len2 = arguments . length , args = new Array ( _len2 ) , _key2 = 0 ; _key2 < _len2 ; _key2 ++ ) {
2018-09-26 08:06:57 +00:00
args [ _key2 ] = arguments [ _key2 ] ;
}
this . forEach ( function ( node ) {
while ( node . hasChildNodes ( ) ) {
node . firstChild . remove ( ) ;
}
2018-09-26 13:14:12 +00:00
var n = jml . apply ( void 0 , args ) ;
2018-11-04 07:38:28 +00:00
return append . call ( _this6 , n ) ;
2018-09-26 08:06:57 +00:00
} ) ;
} ) ;
}
2018-09-26 13:14:12 +00:00
2018-09-26 08:06:57 +00:00
return $ ;
} ;
2018-09-26 13:14:12 +00:00
manipulation ( $$1 , jml ) ;
2020-07-18 11:25:50 +00:00
const baseAPIURL = 'https://openclipart.org/search/json/' ;
const jsVoid = 'javascript: void(0);' ; // eslint-disable-line no-script-url
2018-11-08 06:48:01 +00:00
/ * *
* Shows results after query submission .
* @ param { string } url
2019-05-22 15:37:27 +00:00
* @ returns { Promise < void > }
2018-11-08 06:48:01 +00:00
* /
2018-09-26 13:14:12 +00:00
2020-07-18 11:25:50 +00:00
async function processResults ( url ) {
/ * *
* @ param { string } query
* @ returns { external : JamilihArray }
* /
function queryLink ( query ) {
return [ 'a' , {
href : jsVoid ,
dataset : {
value : query
} ,
$on : {
click ( e ) {
e . preventDefault ( ) ;
const {
value
} = this . dataset ;
$$1 ( '#query' ) [ 0 ] . $set ( value ) ;
$$1 ( '#openclipart' ) [ 0 ] . $submit ( ) ;
}
2019-12-19 08:59:45 +00:00
2020-07-18 11:25:50 +00:00
}
} , [ query ] ] ;
}
2019-12-19 08:59:45 +00:00
2020-07-18 11:25:50 +00:00
const r = await fetch ( url ) ;
const json = await r . json ( ) ; // console.log('json', json);
2019-12-19 08:59:45 +00:00
2020-07-18 11:25:50 +00:00
if ( ! json || json . msg !== 'success' ) {
// Todo: This could use a generic alert library instead
alert ( 'There was a problem downloading the results' ) ; // eslint-disable-line no-alert
2019-12-19 08:59:45 +00:00
2020-07-18 11:25:50 +00:00
return ;
}
2019-12-19 08:59:45 +00:00
2020-07-18 11:25:50 +00:00
const {
payload ,
info : {
results : numResults ,
pages ,
current _page : currentPage
}
} = json ; // $('#page')[0].value = currentPage;
// $('#page')[0].max = pages;
// Unused properties:
// - `svg_filesize` always 0?
// - `dimensions: {
// png_thumb: {width, height},
// png_full_lossy: {width, height}
// }` object of relevance?
// - No need for `tags` with `tags_array`
// - `svg`'s: `png_thumb`, `png_full_lossy`, `png_2400px`
const semiColonSep = '; ' + nbsp ;
$$1 ( '#results' ) . jml ( 'div' , [ [ 'span' , [ 'Number of results: ' , numResults ] ] , semiColonSep , [ 'span' , [ 'page ' , currentPage , ' out of ' , pages ] ] , ... payload . map ( ( {
title ,
description ,
id ,
uploader ,
created ,
svg : {
url : svgURL
} ,
detail _link : detailLink ,
tags _array : tagsArray ,
downloaded _by : downloadedBy ,
total _favorites : totalFavorites
} ) => {
const imgHW = '100px' ;
const colonSep = ': ' + nbsp ;
return [ 'div' , [ [ 'button' , {
style : 'margin-right: 8px; border: 2px solid black;' ,
dataset : {
id ,
value : svgURL
} ,
$on : {
async click ( e ) {
e . preventDefault ( ) ;
const {
value : svgurl
} = this . dataset ; // console.log('this', id, svgurl);
const post = message => {
// Todo: Make origin customizable as set by opening window
// Todo: If dropping IE9, avoid stringifying
window . parent . postMessage ( JSON . stringify ( _extends ( {
namespace : 'imagelib'
} , message ) ) , '*' ) ;
} ; // Send metadata (also indicates file is about to be sent)
post ( {
name : title ,
id : svgurl
} ) ;
const result = await fetch ( svgurl ) ;
const svg = await result . text ( ) ; // console.log('url and svg', svgurl, svg);
2018-09-26 08:06:57 +00:00
2020-07-18 11:25:50 +00:00
post ( {
href : svgurl ,
data : svg
} ) ;
}
2018-09-26 08:06:57 +00:00
2020-07-18 11:25:50 +00:00
}
} , [ // If we wanted interactive versions despite security risk:
// ['object', {data: svgURL, type: 'image/svg+xml'}]
[ 'img' , {
src : svgURL ,
style : ` width: ${ imgHW } ; height: ${ imgHW } ; `
} ] ] ] , [ 'b' , [ title ] ] , ' ' , [ 'i' , [ description ] ] , ' ' , [ 'span' , [ '(ID: ' , [ 'a' , {
href : jsVoid ,
dataset : {
value : id
} ,
$on : {
click ( e ) {
e . preventDefault ( ) ;
const {
value
} = this . dataset ;
$$1 ( '#byids' ) [ 0 ] . $set ( value ) ;
$$1 ( '#openclipart' ) [ 0 ] . $submit ( ) ;
2019-12-19 08:59:45 +00:00
}
2020-07-18 11:25:50 +00:00
2018-09-26 08:06:57 +00:00
}
2020-07-18 11:25:50 +00:00
} , [ id ] ] , ')' ] ] , ' ' , [ 'i' , [ [ 'a' , {
href : detailLink ,
target : '_blank'
} , [ 'Details' ] ] ] ] , [ 'br' ] , [ 'span' , [ [ 'u' , [ 'Uploaded by' ] ] , colonSep , queryLink ( uploader ) , semiColonSep ] ] , [ 'span' , [ [ 'u' , [ 'Download count' ] ] , colonSep , downloadedBy , semiColonSep ] ] , [ 'span' , [ [ 'u' , [ 'Times used as favorite' ] ] , colonSep , totalFavorites , semiColonSep ] ] , [ 'span' , [ [ 'u' , [ 'Created date' ] ] , colonSep , created ] ] , [ 'br' ] , [ 'u' , [ 'Tags' ] ] , colonSep , ... tagsArray . map ( tag => {
return [ 'span' , [ ' ' , queryLink ( tag ) ] ] ;
} ) ] ] ;
} ) , [ 'br' ] , [ 'br' ] , currentPage === 1 || pages <= 2 ? '' : [ 'span' , [ [ 'a' , {
href : jsVoid ,
$on : {
click ( e ) {
e . preventDefault ( ) ;
$$1 ( '#page' ) [ 0 ] . value = 1 ;
$$1 ( '#openclipart' ) [ 0 ] . $submit ( ) ;
}
}
} , [ 'First' ] ] , ' ' ] ] , currentPage === 1 ? '' : [ 'span' , [ [ 'a' , {
href : jsVoid ,
$on : {
click ( e ) {
e . preventDefault ( ) ;
$$1 ( '#page' ) [ 0 ] . value = currentPage - 1 ;
$$1 ( '#openclipart' ) [ 0 ] . $submit ( ) ;
}
}
} , [ 'Prev' ] ] , ' ' ] ] , currentPage === pages ? '' : [ 'span' , [ [ 'a' , {
href : jsVoid ,
$on : {
click ( e ) {
e . preventDefault ( ) ;
$$1 ( '#page' ) [ 0 ] . value = currentPage + 1 ;
$$1 ( '#openclipart' ) [ 0 ] . $submit ( ) ;
}
}
} , [ 'Next' ] ] , ' ' ] ] , currentPage === pages || pages <= 2 ? '' : [ 'span' , [ [ 'a' , {
href : jsVoid ,
$on : {
click ( e ) {
e . preventDefault ( ) ;
$$1 ( '#page' ) [ 0 ] . value = pages ;
$$1 ( '#openclipart' ) [ 0 ] . $submit ( ) ;
}
}
} , [ 'Last' ] ] , ' ' ] ] ] ) ;
2018-09-26 13:14:12 +00:00
}
2018-09-26 08:06:57 +00:00
2020-07-18 11:25:50 +00:00
jml ( 'div' , [ [ 'style' , [ ` .control {
padding - top : 10 px ;
} ` ]], ['form', {
2018-09-26 08:06:57 +00:00
id : 'openclipart' ,
$custom : {
2020-07-18 11:25:50 +00:00
async $submit ( ) {
const url = new URL ( baseAPIURL ) ;
[ 'query' , 'sort' , 'amount' , 'page' , 'byids' ] . forEach ( prop => {
const {
value
} = $$1 ( '#' + prop ) [ 0 ] ;
if ( value ) {
url . searchParams . set ( prop , value ) ;
}
} ) ;
await processResults ( url ) ;
2020-02-01 12:44:13 +00:00
}
2020-07-18 11:25:50 +00:00
2018-09-26 08:06:57 +00:00
} ,
$on : {
2020-07-18 11:25:50 +00:00
submit ( e ) {
2018-09-26 08:06:57 +00:00
e . preventDefault ( ) ;
this . $submit ( ) ;
}
2020-07-18 11:25:50 +00:00
2018-09-26 08:06:57 +00:00
}
2018-09-26 13:14:12 +00:00
} , [ // Todo: i18nize
[ 'fieldset' , [ [ 'legend' , [ 'Search terms' ] ] , [ 'div' , {
2020-07-18 11:25:50 +00:00
class : 'control'
2018-09-26 13:14:12 +00:00
} , [ [ 'label' , [ 'Query (Title, description, uploader, or tag): ' , [ 'input' , {
id : 'query' ,
name : 'query' ,
placeholder : 'cat' ,
$custom : {
2020-07-18 11:25:50 +00:00
$set ( value ) {
2018-09-26 08:06:57 +00:00
$$1 ( '#byids' ) [ 0 ] . value = '' ;
this . value = value ;
}
2020-07-18 11:25:50 +00:00
2018-09-26 13:14:12 +00:00
} ,
$on : {
2020-07-18 11:25:50 +00:00
change ( ) {
2018-09-26 08:06:57 +00:00
$$1 ( '#byids' ) [ 0 ] . value = '' ;
}
2020-07-18 11:25:50 +00:00
2018-09-26 13:14:12 +00:00
}
} ] ] ] ] ] , [ 'br' ] , ' OR ' , [ 'br' ] , [ 'div' , {
2020-07-18 11:25:50 +00:00
class : 'control'
2018-09-26 13:14:12 +00:00
} , [ [ 'label' , [ 'IDs (single or comma-separated): ' , [ 'input' , {
id : 'byids' ,
name : 'ids' ,
placeholder : '271380, 265741' ,
$custom : {
2020-07-18 11:25:50 +00:00
$set ( value ) {
2018-09-26 08:06:57 +00:00
$$1 ( '#query' ) [ 0 ] . value = '' ;
this . value = value ;
}
2020-07-18 11:25:50 +00:00
2018-09-26 13:14:12 +00:00
} ,
$on : {
2020-07-18 11:25:50 +00:00
change ( ) {
2018-09-26 08:06:57 +00:00
$$1 ( '#query' ) [ 0 ] . value = '' ;
}
2020-07-18 11:25:50 +00:00
2018-09-26 13:14:12 +00:00
}
} ] ] ] ] ] ] ] , [ 'fieldset' , [ [ 'legend' , [ 'Configuring results' ] ] , [ 'div' , {
2020-07-18 11:25:50 +00:00
class : 'control'
2018-09-26 13:14:12 +00:00
} , [ [ 'label' , [ 'Sort by: ' , [ 'select' , {
id : 'sort'
} , [ // Todo: i18nize first values
2020-07-18 11:25:50 +00:00
[ 'Date' , 'date' ] , [ 'Downloads' , 'downloads' ] , [ 'Favorited' , 'favorites' ] ] . map ( ( [ text , value = text ] ) => {
2018-09-26 13:14:12 +00:00
return [ 'option' , {
2020-07-18 11:25:50 +00:00
value
2019-02-21 11:41:09 +00:00
} , [ text ] ] ;
2018-09-26 13:14:12 +00:00
} ) ] ] ] ] ] , [ 'div' , {
2020-07-18 11:25:50 +00:00
class : 'control'
2018-09-26 13:14:12 +00:00
} , [ [ 'label' , [ 'Results per page: ' , [ 'input' , {
id : 'amount' ,
name : 'amount' ,
value : 10 ,
type : 'number' ,
min : 1 ,
max : 200 ,
step : 1 ,
pattern : '\\d+'
} ] ] ] ] ] , [ 'div' , {
2020-07-18 11:25:50 +00:00
class : 'control'
2018-09-26 13:14:12 +00:00
} , [ [ 'label' , [ 'Page number: ' , [ 'input' , {
2018-09-26 08:06:57 +00:00
// max: 1, // We'll change this based on available results
2018-09-26 13:14:12 +00:00
id : 'page' ,
name : 'page' ,
value : 1 ,
style : 'width: 40px;' ,
type : 'number' ,
min : 1 ,
step : 1 ,
pattern : '\\d+'
} ] ] ] ] ] ] ] , [ 'div' , {
2020-07-18 11:25:50 +00:00
class : 'control'
2018-09-26 13:14:12 +00:00
} , [ [ 'input' , {
type : 'submit'
} ] ] ] ] ] , [ 'div' , {
id : 'results'
} ] ] , body ) ;
2018-09-26 08:06:57 +00:00
} ( ) ) ;