(function () {
'use strict'; function capitalize(string) { return string.charAt(0).toUpperCase() + string.slice(1) } const element = document.createElement('div'); const entityCache = {}; function unescapeHtml(string){ return string.replace(/&[^\s;]+;{0,1}/gi, function(entity){ if(!entityCache[entity]){ element.innerHTML = string; entityCache[entity] = element.childNodes.length == 0 ? "" : element.childNodes[0].nodeValue; } return entityCache[entity] }) } class StringReader { constructor(string){ this.string = (string || '').toString(); } get length(){ return this.string.length } toString(){ return this.string } match(...args){ const out = this.string.match(...args); if(out){ this.string = this.string.substr(out[0].length); } return out } } const SELF_CLOSING_TAGS = [ 'area', 'base', 'br', 'embed', 'hr', 'iframe', 'img', 'input', 'link', 'meta', 'param', 'source', 'track' ]; const TEXT_ONLY_TAGS = [ 'script', 'style' ]; class CloseTag { constructor(type){ this.type = type; } } class VirtualNode { static fromString(html){ const out = new this(); out.appendHtml(html); out.normalize(); return out } constructor(parent = null, type = '#fragment', attributes = {}){ this.parent = parent; this.type = type; this.attributes = attributes; this.children = []; } appendNode(type, attributes = {}){ const out = new this.constructor(this, type, attributes); this.children.push(out); return out } appendHtml(html){ if(!(html instanceof StringReader)){ html = new StringReader(html); while(html.length > 0){ try { this.appendHtml(html); } catch(e){ if(e instanceof CloseTag); else { throw e } } } return this } while(html.length > 0){ let matches; if(matches = html.match(/^[^<]+/)){ this.appendNode('#text', {value: matches[0]}); } else if(matches = html.match(/^<!DOCTYPE[^>]*>/i)){ if(!this.parent){ this.appendNode('#doctype'); } } else if(matches = html.match(/^<!--([\s\S]*?)-->/i)){ this.appendNode('#comment', {value: matches[1]}); } else if(matches = html.match(/^<([^>\s]+)/)){ const type = matches[1].toLowerCase(); const attributes = {}; while(html.length > 0){ if(matches = html.match(/^\s*([\w-]+)\s*=\s*\"([^\">]*)\"/)){ attributes[matches[1]] = unescapeHtml(matches[2]); } else if(matches = html.match(/^\s*([\w-]+)\s*=\s*\'([^\'>]*)\'/)){ attributes[matches[1]] = unescapeHtml(matches[2]); } else if(matches = html.match(/^\s*([\w-]+)\s*=\s*([^\s>]+)/)){ attributes[matches[1]] = unescapeHtml(matches[2]); } else if(matches = html.match(/^\s*([\w-]+)/)){ attributes[matches[1]] = null; } else { html.match(/^[^>]*>/); break } } if(matches = type.match(/^\/(.*)/)){ throw new CloseTag(matches[1]) } const child = this.appendNode(type, attributes); if(SELF_CLOSING_TAGS.includes(type)); else if(TEXT_ONLY_TAGS.includes(type) && (matches = html.match(new RegExp(`^([\\s\\S]*?)<\\/${type}[^>]*>`)))){ child.appendNode('#text', {value: matches[1]}); } else if(TEXT_ONLY_TAGS.includes(type) && (matches = html.match(/^([\s\S]+)/))){ child.appendNode('#text', {value: matches[1]}); } else { try { child.appendHtml(html); } catch(e){ if(e instanceof CloseTag && e.type == type); else { throw e } } } } else if(matches = html.match(/^[\s\S]/)) { this.appendNode('#text', {value: matches[0]}); } else { break; } } } normalize(){ if(!this.parent && this.children.some(child => child.type == 'html')){ this.children = [ new this.constructor(this, '#doctype'), ...this.children.filter(child => child.type == 'html') ]; } if(this.type == '#text'){ this.attributes.value = this.attributes.value.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); } if(this.parent && this.parent.type == 'textarea' && this.type == '#text'){ this.attributes.value = this.attributes.value.replace(/^\n/, ''); } this.children.forEach(child => child.normalize()); } } class EventWrapper { static instanceFor(event){ if(!event.$p){ event.$p = new this(event); } return event.$p } constructor(event){ this.event = event; } get target(){ return NodeWrapper.instanceFor(this.event.target) } stopPropagation(){ this.event.stopPropagation(); } preventDefault(){ this.event.preventDefault(); } } const nodeWrappers = []; class NodeWrapper { static get selector(){ return `.${this.name}` } static register(){ const klass = this; nodeWrappers.unshift(klass); Object.defineProperty(NodeWrapper.prototype, klass.name, { get: function(){ let current = this.parent; while(current){ if(current instanceof klass){ return current } current = current.parent; } } }); Object.defineProperty(NodeWrapper.prototype, `is${capitalize(klass.name)}`, { get: function(){ return this instanceof klass } }); } static instanceFor(node){ if(!node.$p){ node.$p = new NodeWrapper(node); nodeWrappers.some((klass) => { if(node.$p.is(klass.selector)){ node.$p = new klass(node); return true } }); } return node.$p } constructor(node){ this.node = node; this.$registeredEventListeners = []; } get type(){ return this.node instanceof DocumentType ? '#doctype' : this.node.nodeName.toLowerCase() } get attributes(){ const out = {}; if(this.node.attributes){ for(let i = 0; i < this.node.attributes.length; i++){ out[this.node.attributes[i].name] = this.node.attributes[i].value; } } return out } get text(){ return this.node.textContent } get realParent(){ return this.node.parentNode ? this.constructor.instanceFor(this.node.parentNode) : null } get parent(){ if(this.$parent){ return this.$parent } return this.realParent } get parents(){ const out = []; let current = this; while(current.parent){ current = current.parent; out.push(parent); } return out } get children(){ return [...this.node.childNodes].map( node => this.constructor.instanceFor(node) ) } get siblings(){ if(this.parent){ return this.parent.children } else { return [this] } } get previousSibling(){ if(this.node.previousSibling){ return this.constructor.instanceFor(this.node.previousSibling) } else { return null } } get nextSibling(){ if(this.node.nextSibling){ return this.constructor.instanceFor(this.node.nextSibling) } else { return null } } get nextSiblings(){ const out = []; let current = this; while(current.nextSibling){ current = current.nextSibling; out.push(current); } return out } get previousSiblings(){ const out = []; let current = this; while(current.previousSibling){ current = current.previousSibling; out.push(current); } return out } get descendants(){ return this.find(() => true) } find(selector, out = []){ this.children.forEach((child) => { if(child.is(selector)){ out.push(child); } child.find(selector, out); }); return out; } is(selector){ if(typeof selector == 'function'){ return selector.call(this, this) } return (this.node.matches || this.node.matchesSelector || this.node.msMatchesSelector || this.node.mozMatchesSelector || this.node.webkitMatchesSelector || this.node.oMatchesSelector || (() => false)).call(this.node, selector) } on(name, ...args){ const fn = args.pop(); const selector = args.pop(); const wrapperFn = (event, ...args) => { const eventWrapper = EventWrapper.instanceFor(event); if(selector){ if(eventWrapper.target.is(selector)){ return fn.call(eventWrapper.target, eventWrapper, ...args) } } else { return fn.call(this, eventWrapper, ...args) } }; this.node.addEventListener(name, wrapperFn); this.$registeredEventListeners.push([name, wrapperFn]); return this } trigger(name, data){ if (window.CustomEvent && typeof window.CustomEvent === 'function') { var event = new CustomEvent(name, { bubbles: true, cancelable: true, detail: data } ); } else { var event = document.createEvent('CustomEvent'); event.initCustomEvent(name, true, true, data); } this.node.dispatchEvent(event); return this } remove(){ if(this.type != '#doctype'){ this.realParent.node.removeChild(this.node); } return this } addClass(name){ this.node.classList.add(name); return this } removeClass(name){ this.node.classList.remove(name); return this } patch(html){ cleanChildren.call(this); patchChildren.call(this, VirtualNode.fromString(html).children); initChildren.call(this); return this.children } append(html){ return prepend.call(this, html) } prepend(html){ return prepend.call(this, html, this.children[0]) } insertBefore(html){ return prepend.call(this.realParent, html, this) } insertAfter(html){ return prepend.call(this.realParent, html, this.nextSibling) } } function cleanChildren(){ this.children.forEach(child => clean.call(child)); } function clean(){ [...this.node.childNodes].forEach(node => node.$p && clean.call(node.$p)); while(this.$registeredEventListeners.length){ this.node.removeEventListener(...this.$registeredEventListeners.pop()); } this.node.$p = undefined; } function initChildren(){ this.children.forEach(child => initChildren.call(child)); } function prepend(html, referenceChild){ const out = []; VirtualNode.fromString(html).children.forEach((virtualChild) => { out.push(insert.call(this, virtualChild, referenceChild)); }); return out } function patch(attributes, virtualChildren){ patchAttributes.call(this, attributes); patchChildren.call(this, virtualChildren); } function patchAttributes(attributes){ if(this.type == '#text' || this.type == '#comment'){ if(this.node.textContent != attributes.value){ this.node.textContent = attributes.value; } } else if(this.type != '#doctype'){ const currentAttributes = this.attributes; Object.keys(currentAttributes).forEach((key) => { if(attributes[key] === undefined){ this.node.removeAttribute(key); } }); Object.keys(attributes).forEach((key) => { if(currentAttributes[key] != attributes[key]){ this.node.setAttribute(key, attributes[key]); } }); } } function patchChildren(virtualChildren){ const children = [...this.node.childNodes].map( node => new NodeWrapper(node) ); for(let i = 0; i < virtualChildren.length; i++){ let child = children[0]; const virtualChild = virtualChildren[i]; if(child && child.type == virtualChild.type){ patch.call(children.shift(), virtualChild.attributes, virtualChild.children); } else if(virtualChild.type == '#doctype'); else if(virtualChild.type.match(/^#(text|comment)/)){ insert.call(this, virtualChild, child); } else { while(children.length > 0 && children[0].type.match(/^#/)){ children.shift().remove(); } child = children[0]; if(child && child.type == virtualChild.type){ patch.call(children.shift(), virtualChild.attributes, virtualChild.children); } else { insert.call(this, virtualChild, child); } } } while(children.length > 0){ children.shift().remove(); } } function insert(virtualNode, referenceChild, returnNodeWrapper = true){ const { type, attributes, children } = virtualNode; let node; if(type == '#text'){ node = document.createTextNode(attributes.value); } else if(type == '#comment'){ node = document.createComment(attributes.value); } else { node = document.createElement(type); Object.keys(attributes).forEach((key) => { node.setAttribute(key, attributes[key]); }); } children.forEach(child => { insert.call(new NodeWrapper(node), child, null, false); }); this.node.insertBefore( node, referenceChild && referenceChild.node ); if(returnNodeWrapper){ return NodeWrapper.instanceFor(node) } } class Url { static fromString(url, referenceUrl){ const out = new Url(); url = new StringReader(url); if(!(referenceUrl instanceof Url)){ referenceUrl = Url.fromString(referenceUrl || window.location, new Url()); } let matches; if(matches = url.match(/^([a-z]+):\/\/([a-z\.-]+)/i)){ out.protocol = matches[1].toLowerCase(); out.host = matches[2].toLowerCase(); if(matches = url.match(/^:(\d+)/)){ out.port = parseInt(matches[1]); } } else { out.protocol = referenceUrl.protocol; out.host = referenceUrl.host; out.port = referenceUrl.port; } if(matches = url.match(/^\/[^\?\#]*/)){ out.path = normalizePath(matches[0]); } else if(matches = url.match(/^[^\?\#]+/)){ out.path = normalizePath(`${referenceUrl.path.replace(/[^\/]*$/, '')}${matches[0]}`); } else { out.path = referenceUrl.path; } if(matches = url.match(/^\?([^\#]*)/)){ matches[1].split(/&/).forEach((pair) => { const [key, value] = pair.split(/=/); out.params[decodeURIComponent(key)] = decodeURIComponent(value); }); } return out } constructor(protocol = 'http', host = 'localhost', port = 80, path = '/', params = {}){ this.protocol = protocol; this.host = host; this.port = port; this.path = path; this.params = params; } toString(){ const out = [`${this.protocol}://${this.host}`]; const defaultPort = this.protocol == 'https' ? 443 : 80; if(this.port != defaultPort){ out.push(`:${this.port}`); } out.push(this.path); const pairs = []; Object.keys(this.params).forEach((key) => { pairs.push(`${encodeURIComponent(key)}=${encodeURIComponent(this.params[key])}`); }); if(pairs.length > 0){ out.push(`?${pairs.join('&')}`); } return out.join('') } get defaultPort(){ if(this.protocol == 'https'){ return 443 } else { return 80 } } } function normalizePath(path){ const out = []; path.split(/\//).forEach((segment) => { if(segment == '..'){ out.pop(); } else if(segment != '.'){ out.push(segment); } }); return out.join('/'); } class Anchor extends NodeWrapper { static get name(){ return 'anchor' } static get selector(){ return 'a, .anchor' } constructor(...args){ super(...args); this.on('click', (event) => { if(this.url.host == this.frame.url.host && this.url.port == this.frame.url.port){ event.preventDefault(); const confirm = this.attributes['data-confirm']; const method = this.attributes['data-method'] || 'GET'; const target = this.attributes['target'] || this.attributes['data-target'] || '_top'; if(!confirm || window.confirm(confirm)){ if(target == '_modal'){ this.document.find('html').pop().addClass('is-clipped'); this.document.find('body').pop().append(`<div class="modal is-active" data-url="${this.url}"></div>`).forEach((modal) => { modal.$parent = this; modal.load({}); }); } else { this.frame.load({ $method: method, $url: this.url }); } } } }); } get url(){ if(this.$url === undefined){ this.$url = Url.fromString( this.attributes['href'] || this.attributes['data-url'], this.frame.url ); } return this.$url } set url(url){ this.$url = Url.fromString( url, this.url ); } } Anchor.register(); class Frame extends NodeWrapper { static get name(){ return 'frame' } constructor(...args){ super(...args); } get url(){ if(this.$url === undefined){ this.$url = Url.fromString( this.attributes['data-url'] || window.location, this.frame ? this.frame.url : window.location ); } return this.$url } set url(url){ this.$url = Url.fromString( url, this.url ); } load({$method = 'GET', $url = this.url.toString(), $headers = {}, ...params }){ if(this.request){ this.request.abort(); } $method = $method.toUpperCase(); this.url = $url; const isRequestBody = $method == 'POST' || $method == 'PUT' || $method == 'PATCH'; if(!isRequestBody){ this.url.params = {...this.url.params, ...params}; } this.request = new XMLHttpRequest(); this.request.open($method, this.url.toString(), true); this.request.onload = () => { if (this.request.status >= 200 && this.request.status < 400) { this.patch(this.request.response); } }; const defaultHeaders = {}; const document = this.document || this; const csrfToken = document.find('meta[name="csrf-token"').map(nodeWrapper => nodeWrapper.attributes.content).pop(); if(csrfToken){ defaultHeaders['X-CSRF-Token'] = csrfToken; } const headers = {...defaultHeaders, ...this.headers}; Object.keys(headers).forEach((name) => this.request.setRequestHeader(name, headers[name])); const formData = new FormData(); if(isRequestBody){ Object.keys(params).forEach((name) => formData.append(name, params[name])); } this.request.send(formData); } } Frame.register(); class Document extends Frame { static get name(){ return 'document' } static get selector(){ return function(){ return this.type == '#document' } } constructor(...args){ super(...args); window.onpopstate = (event) => { this.load({ $pushState: false, $url: event.state || window.location }); }; window.$p = this; } load({ $pushState = true, ...params }){ const previousUrl = this.url.toString(); super.load(params); if($pushState && params.$method == 'GET' && previousUrl != this.url.toString()){ history.pushState(this.url.toString(), null, this.url.toString()); } } } Document.register(); class Form extends NodeWrapper { static get name(){ return 'form' } static get selector(){ return 'form, .form' } constructor(...args){ super(...args); this.on('submit', (event) => { console.log('Form submit', event); event.preventDefault(); this.frame.load({$method: this.method, $url: this.url, ...this.params }); }); } get method(){ return this.attributes['method'] || this.attributes['data-method'] || 'POST' } get url(){ if(this.$url === undefined){ this.$url = Url.fromString( this.attributes['action'] || this.attributes['data-url'], this.frame.url ); } return this.$url } get inputs(){ return this.descendants.filter((descendant) => descendant.isInput) } get params(){ const out = {}; this.inputs.forEach(input => { const value = input.value; if(value !== undefined){ out[input.name] = value; } }); return out } } Form.register(); class Input extends NodeWrapper { static get name(){ return 'input' } static get selector(){ return 'input, textarea, .input' } get name(){ return this.attributes.name } get value(){ if(this.is('input[type="checkbox"], input[type="radio"]')){ return this.is(':checked') ? this.node.value : undefined } return this.node.value } } Input.register(); class Modal extends Frame { static get name(){ return 'modal' } constructor(...args){ super(...args); this.on('click', '.modal-background, .modal-close', (event) => { event.stopPropagation(); this.close(); }); } close(){ this.remove(); if(!this.document.find('body').pop().children.filter((child) => child.is('.modal')).length){ this.document.find('html').pop().removeClass('is-clipped'); } } patch(html){ return super.patch(` <div class="modal-background"></div> <div class="modal-content"> <div class="box">${html}</div> </div> <button class="modal-close is-large" aria-label="close"></button> `) } } Modal.register(); class Script extends NodeWrapper { static get name(){ return 'script' } static get selector(){ return 'script[type="pinstripe"]' } constructor(...args){ super(...args); eval(this.text); } } Script.register(); let ready = false; function initializeTree(node){ NodeWrapper.instanceFor(node).descendants; } const observer = new MutationObserver(mutations => { if(ready){ mutations.forEach( mutation => mutation.addedNodes.forEach( node => initializeTree(node) ) ); } }); observer.observe(document.documentElement, { attributes: false, childList: true, subtree: true }); setTimeout(() => { ready = true; initializeTree(document); }, 0);
}()); //# sourceMappingURL=index-bundle.js.map