Greasy Fork is available in English.
Менеджер билдов для HWM
// ==UserScript== // @name hwm_build_manager // @author Chie (http://www.heroeswm.ru/pl_info.php?id=645888) // @icon https://s.gravatar.com/avatar/5fd0059ad34d082dfbd50cfdeb9aab6a // @description Менеджер билдов для HWM // @namespace https://github.com/betula/hwm_build_manager // @homepageURL https://github.com/betula/hwm_build_manager // @include http://*heroeswm.ru/* // @include http://178.248.235.15/* // @include http://*lordswm.com/* // @version 1.0.2 // @grant none // ==/UserScript== // Build Manager 1.0.2 class ServiceContainer { constructor() { this.instances = {}; } _service(ctor) { let { name } = ctor; if (!this.instances[name]) { this.instances[name] = new ctor(this); } return this.instances[name]; } get manager() { return this._service(ManagerService) } get fraction() { return this._service(FractionService) } get inventory() { return this._service(InventoryService) } get attribute() { return this._service(AttributeService) } get army() { return this._service(ArmyService) } get skill() { return this._service(SkillService) } get current() { return this._service(CurrentService) } get change() { return this._service(ChangeService) } get import() { return this._service(ImportService) } } class ManagerService { constructor(services) { this.services = services; this._storage = new LocalStorageArrayDriver('BM_MANAGER'); this.items = []; this._restore(); } createNew() { let nextNumber = 1; for (let item of this.items) { let match = item.name.match(/^Новый билд (\d+)$/); if (match) { nextNumber = Math.max(nextNumber, (parseInt(match[1]) || 0) + 1); } } let item = { id: uniqid(), name: `Новый билд ${nextNumber}`, fraction: this.services.fraction.default.id, inventory: this.services.inventory.default.id, attribute: this.services.attribute.default, army: this.services.army.default, skill: this.services.skill.default } this.items.push(item); this._store(); return item; } remove(item) { let { founded, index } = this._searchByItem(item); if (!founded) return null; let items = this.items; items = items.slice(0, index).concat( items.slice(index + 1) ); this.items = items; this._store(); if (index === items.length) { return items[index - 1] } return items[index]; } duplicate(item) { let { founded } = this._searchByItem(item); if (!founded) return null; let duplicate = deepCopy(item); duplicate.id = uniqid(); duplicate.name += ' копия'; this.items.push(duplicate); this._store() return duplicate; } update(updatedItem) { let { founded, index } = this._searchById(updatedItem.id); if (!founded) return null; this.items[index] = updatedItem; this._store(); this.services.current.mayBeOnlyNameUpdated(updatedItem); return updatedItem; } searchEquals(item) { let { founded, index } = this._searchById(item.id); if (founded) { if (deepEquals(item, this.items[index])) { return { founded, index } } } return { founded: false } } serialize() { return JSON.stringify(this.items); } unserialize(value) { const INVALID = 'invalid'; const Checker = { string(value) { if (typeof value !== 'string' || value.length === 0) throw INVALID; }, number(value) { if (typeof value !== 'number') throw INVALID; }, array(value) { if (!Array.isArray(value)) throw INVALID; }, object(value) { if (!value || typeof value !== 'object') throw INVALID; }, keys(value, keys) { if (!keys.every({}.hasOwnProperty.bind(value))) throw INVALID; for (let key of Object.keys(value)) { Checker.enum(key, keys); } }, enum(value, values) { if (Array.isArray(value)) { for (let once of value) { Checker.enum(once, values); } } else { if (Array.isArray(values)) { if (values.indexOf(value) === -1) throw INVALID; } else { if (!values.hasOwnProperty(value)) throw INVALID; } } }, length(value, len) { if (value.length !== len) throw INVALID; } }; let items = []; try { items = JSON.parse(value); Checker.array(items); for (let item of items) { Checker.object(item); Checker.keys(item, [ 'id', 'name', 'fraction', 'inventory', 'attribute', 'army', 'skill' ]); let { id, name, fraction, inventory, attribute, army, skill } = item; Checker.string(id); Checker.string(name); Checker.enum(fraction, this.services.fraction.map); Checker.enum(inventory, this.services.inventory.map); Checker.object(attribute); Checker.keys(attribute, this.services.attribute.list); Object.values(attribute).forEach(Checker.number); Checker.array(army); Checker.length(army, 7); army.forEach(Checker.number); Checker.array(skill); Checker.enum(skill, this.services.skill.map); } } catch(e) { return false; } this.items = items; this._store(); return true; } _searchById(id) { const items = this.items; let founded = false; let index; for (index = 0; index < items.length; index++) { if (items[index].id === id) { founded = true; break; } } return founded ? { founded, index } : { founded: false }; } _searchByItem(item) { let index = this.items.indexOf(item); return index != -1 ? { founded: true, index } : { founded: false }; } _restore() { this.items = this._storage.fetch(); } _store() { this._storage.put(this.items); } } class CurrentService { constructor(services) { this.services = services; this._storage = new LocalStorageDriver('BM_CURRENT'); this._restore(); } get item() { return this._item; } mayBeOnlyNameUpdated(updatedItem) { let current = this._item; if (!current) return; if (current.id !== updatedItem.id) return; let clone = deepCopy(current); clone.name = updatedItem.name; if (!deepEquals(clone, updatedItem)) return; current.name = updatedItem.name; this._store(); } isExpired() { if (!this._item) return false; return !this.services.manager.searchEquals(this._item).founded; } change(item, force) { item = deepCopy(item); const change = () => { if (!item) return item; if (force || !this._item) { return this.services.change.force(item); } return this.services.change.diff(this._item, item); }; return this.changeQueue = (this.changeQueue || Promise.resolve()) .then(change, change) .then((item) => { this._update(item); return item; }, (e) => { this._update(); return Promise.reject(e); }); } equals(item) { return deepEquals(this._item, item); } _update(item = null) { this._item = item; this._store(); } _restore() { this._item = this._storage.fetch(); } _store() { this._storage.put(this._item); } } class ChangeService { constructor(services) { this.services = services; this.cache = {}; } force(to) { const promise = Promise.resolve() .then(() => this._fraction(to.fraction)) .then(() => this._skill(to.skill)) .then(() => this._army(to.army)) .then(() => this._inventory(to.inventory)) .then(() => this._reset()) .then(() => this._attribute(to.attribute)) .then(() => to); return PromiseMDecorator(promise); } diff(from, to) { const fractionChanged = from.fraction !== to.fraction; const skillChanged = fractionChanged || !deepEquals(from.skill, to.skill); const armyChanged = fractionChanged || !deepEquals(from.army, to.army); const inventoryChanged = from.inventory !== to.inventory; const attributeChanged = !deepEquals(from.attribute, to.attribute); let serial = Promise.resolve(); if (fractionChanged) { serial = serial.then(() => this._fraction(to.fraction, from.fraction)) } if (skillChanged) { serial = serial.then(() => this._skill(to.skill)) } if (armyChanged) { serial = serial.then(() => this._army(to.army)) } if (inventoryChanged) { serial = serial.then(() => this._inventory(to.inventory)) } if (attributeChanged) { serial = serial .then(() => this._reset()) .then(() => this._attribute(to.attribute)) } serial = serial.then(() => to); return PromiseMDecorator(serial); } _fraction(to, from) { to = this.services.fraction.map[to]; let prepare = Promise.resolve(); if (from) { from = this.services.fraction.map[from]; } else { prepare = this.services.import.getFraction().then((id) => { from = this.services.fraction.map[id]; }) } const change = (data) => { return httpPlainRequest('FORM', '/castle.php', data) } return prepare.then(() => { if (from.fract !== to.fract) { let promise = change({ fract: to.fract }); if (to.classid !== '0') { return promise.then(() => { return change({ classid: to.classid }) }); } return promise } if (from.classid !== to.classid) { return change({ classid: to.classid }) } }) } _skill(list) { let data = { rand: Math.random(), setstats: 1, } for (let i = 0; i < list.length; i++) { data[`param${i}`] = list[i]; } return httpPlainRequest('FORM', '/skillwheel.php', data); } _army(list) { let data = { rand: Math.random(), } for (let i = 0; i < list.length; i++) { data[`countv${i+1}`] = list[i]; } return httpPlainRequest('FORM', '/army_apply.php', data); } _inventory(id) { let struct = this.services.inventory.map[id]; let data = { r: Date.now() + String(Math.random()).slice(2) }; data[struct.type] = struct.value; return httpPlainRequest('GET', '/inventory.php', data); } _reset() { let resetLinkPromise = Promise.resolve(this.cache.resetLink); if (!this.cache.resetLink) { resetLinkPromise = httpPlainRequest('GET', '/home.php').then((html) => { let m = html.match(/shop\.php\?b=reset_tube&reset=2&sign=[0-9a-f]+/); if (!m) return null; return this.cache.resetLink = '/' + m[0]; }) } return resetLinkPromise.then((url) => (url ? httpPlainRequest('GET', url) : null)); } _attribute(obj) { const getTotal = () => { return httpPlainRequest('GET', '/home.php').then((html) => { let m = html.match(/href="home\.php\?increase_all=knowledge"(?:.|\n)*?(\d+)\<\/td/); if (!m) return null; return parseInt(m[1]) || null; }); } const increase = (name, count) => { let serial = Promise.resolve(); for (let i = 0; i < count; i++) { serial = serial.then(() => httpPlainRequest('GET', `/home.php?increase=${name}`)); } return serial; } const increaseAll = (name) => { return httpPlainRequest('GET', `/home.php?increase_all=${name}`); } const distribute = (total) => { total = total || 0; let list = []; for (let name of Object.keys(obj)) { list.push({ name, value: obj[name] }) } list.sort((a, b) => a.value - b.value); let serial = Promise.resolve(); let used = 0; list.slice(0, -1).forEach(({ name, value }) => { if (used >= total) return; if (value === 0) return; let v = Math.min(total - used, value); used += value; serial = serial.then(() => increase(name, v)); }); if (total > used) { let { name, value } = list[ list.length - 1 ]; if (value > 0) { if (value < total - used) { serial = serial.then(() => increase(name, value)); } else { serial = serial.then(() => increaseAll(name)); } } } return serial; } return getTotal().then(distribute); } } class ImportService { constructor(services) { this.services = services; } getInventoryNamesIfAvailable() { if (location.pathname.match(/^\/inventory\.php/)) { let list = []; let nodes = document.querySelectorAll('a[href*="inventory.php?all_on"]'); for (let node of nodes) { let [ _, type, value ] = node.getAttribute('href').match(/(all_on)=(\d)/); let name = node.innerText; list.push({ type, value, name }); } return list; } } getArmy() { return httpPlainRequest('GET', '/army.php').then((html) => { let m = html.match(/\<param name="FlashVars" value="param=\d+\|M([^"]+)/); if (!m) return null; let chunks = m[1].split(';M'); let items = []; for (let chunk of chunks) { items.push( parseInt(chunk.split(':')[1].substr(57,3)) || 0 ); } for (let i = items.length; i < this.services.army.iterator.length; i++) { items.push(0); } return items; }); } getFraction() { return httpPlainRequest('GET', '/castle.php').then((html) => { let dict = {}; for (let { fract, classid } of this.services.fraction.list) { if (!dict[fract]) { dict[fract] = {}; } dict[fract][classid] = true; } const extractFract = () => { let m = html.match(/\<select name='fract'((?:.|[\r\n])+?)\<\/select/); if (!m) return null; let available = {}; m[1].replace(/value=(\d)/g, (_, id) => { available[id] = true; }); for (let id of Object.keys(dict)) { if (!available[id]) return id; } } const extractClassid = (fract) => { let m = html.match(/\<select name='classid'((?:.|[\r\n])+?)\<\/select/); if (!m) return null; let available = {}; m[1].replace(/value=(\d)/g, (_, id) => { available[id] = true; }); for (let id of Object.keys(dict[fract])) { if (!available[id]) return id; } } let fract = extractFract(); if (!fract) return null; let classidList = Object.keys(dict[fract]); let classid; if (classidList.length === 1) { classid = classidList[0]; } else { classid = extractClassid(fract); if (!classid) return null; } return `${fract}${classid}` }) } getSkill() { return httpPlainRequest('GET', '/skillwheel.php').then((html) => { let m = html.match(/\<param name="FlashVars" value='param=.+?;builds=([^']+)/); if (!m) return null; let rows = m[1].split('$'); let items = []; for (let r of rows) { let row = r.split('|'); if (row.length != 10) continue; let id = row[0]; let has = row[8]; if (has === '1') { items.push(id); } } return items; }) } } class FractionService { constructor() { this.list = [ { fract: '1', classid: '0', name: 'Рыцарь' }, { fract: '1', classid: '1', name: 'Рыцарь света' }, { fract: '2', classid: '0', name: 'Некромант' }, { fract: '2', classid: '1', name: 'Некромант - повелитель смерти' }, { fract: '3', classid: '0', name: 'Маг' }, { fract: '3', classid: '1', name: 'Маг-разрушитель' }, { fract: '4', classid: '0', name: 'Эльф' }, { fract: '4', classid: '1', name: 'Эльф-заклинатель' }, { fract: '5', classid: '0', name: 'Варвар' }, { fract: '5', classid: '1', name: 'Варвар крови' }, { fract: '5', classid: '2', name: 'Варвар-шаман' }, { fract: '6', classid: '0', name: 'Темный эльф' }, { fract: '6', classid: '1', name: 'Темный эльф-укротитель' }, { fract: '7', classid: '0', name: 'Демон' }, { fract: '7', classid: '1', name: 'Демон тьмы' }, { fract: '8', classid: '0', name: 'Гном' }, { fract: '9', classid: '0', name: 'Степной варвар' } ]; this.map = {}; for (let item of this.list) { item.id = item.fract + item.classid; this.map[item.id] = item; } } get default() { return this.list[0]; } } class InventoryService { constructor(services) { this.services = services; this._storage = new LocalStorageArrayDriver('BM_INVENTORY'); this.loaded = false; this.list = [ { type: 'all_off', value: '100', name: 'Снять все' }, { type: 'all_on', value: '1', name: 'Набор 1' }, { type: 'all_on', value: '2', name: 'Набор 2' }, { type: 'all_on', value: '3', name: 'Набор 3' }, { type: 'all_on', value: '4', name: 'Набор 4' }, { type: 'all_on', value: '5', name: 'Набор 5' } ] this.map = {}; for (let item of this.list) { item.id = item.type + item.value; this.map[item.id] = item; } this._restore(); } _restore() { let list = this._storage.fetch(); for (let item of list) { let id = item.type + item.value; if (this.map[id]) { this.map[id].name = item.name; } } } syncNamesIfAvailable() { let list = this.services.import.getInventoryNamesIfAvailable(); if (list) { for (let { type, value, name } of list) { let id = type + value; if (this.map[id]) { this.map[id].name = name; } } this._storage.put(list); } } get default() { return this.list[0]; } } class AttributeService { get list() { return Object.keys(this.default); } get default() { return { attack: 0, defence: 0, power: 0, knowledge: 0 } } } class ArmyService { get iterator() { return this.default; } get default() { return [ 0, 0, 0, 0, 0, 0, 0 ] } } class SkillService { constructor() { this.table = [ { id: "attack", name: "Нападение", list: [ { id: "attack1", name: "Основы нападения", main: true }, { id: "attack2", name: "Развитое нападение", main: true }, { id: "attack3", name: "Искусное нападение", main: true }, { id: "battle_frenzy", name: "Боевое безумие" }, { id: "retribution", name: "Воздаяние" }, { id: "nature_wrath", name: "Лесная ярость" }, { id: "power_of_speed", name: "Мастерство скорости" }, { id: "excruciating_strike", name: "Мощный удар" }, { id: "archery", name: "Стрельба" }, { id: "tactics", name: "Тактика" }, { id: "cold_steel", name: "Холодная сталь" }, ]}, { id: "defense", name: "Защита", list: [ { id: "defense1", name: "Основы защиты", main: true }, { id: "defense2", name: "Развитая защита", main: true }, { id: "defense3", name: "Искусная защита", main: true }, { id: "last_stand", name: "Битва до последнего" }, { id: "stand_your_ground", name: "Глухая оборона" }, { id: "preparation", name: "Готовность" }, { id: "power_of_endurance", name: "Сила камня" }, { id: "resistance", name: "Сопротивление" }, { id: "protection", name: "Сопротивление магии" }, { id: "vitality", name: "Стойкость" }, { id: "evasion", name: "Уклонение" }, ]}, { id: "luck", name: "Удача", list: [ { id: "luck1", name: "Призрачная удача", main: true }, { id: "luck2", name: "Большая удача", main: true }, { id: "luck3", name: "Постоянная удача", main: true }, { id: "magic_resistance", name: "Магическое сопротивление" }, { id: "piercing_luck", name: "Пронзающая удача" }, { id: "soldier_luck", name: "Солдатская удача" }, { id: "warlock_luck", name: "Удачливый чародей" }, { id: "swarming_gate", name: "Широкие врата ада" }, { id: "elven_luck", name: "Эльфийская удача" }, ]}, { id: "leadership", name: "Лидерство", list: [ { id: "leadership1", name: "Основы лидерства", main: true }, { id: "leadership2", name: "Развитое лидерство", main: true }, { id: "leadership3", name: "Искусное лидерство", main: true }, { id: "aura_of_swiftness", name: "Аура скорости" }, { id: "divine_guidance", name: "Воодушевление" }, { id: "battle_commander", name: "Лесной лидер" }, { id: "recruitment", name: "Сбор войск" }, { id: "empathy", name: "Сопереживание" }, ]}, { id: "enlightenment", name: "Образование", list: [ { id: "enlightenment1", name: "Начальное образование", main: true }, { id: "enlightenment2", name: "Среднее образование", main: true }, { id: "enlightenment3", name: "Высшее образование", main: true }, { id: "graduate", name: "Выпускник" }, { id: "wizard_reward", name: "Колдовская награда" }, { id: "know_your_enemy", name: "Лесное коварство" }, { id: "lord_of_the_undead", name: "Повелитель мёртвых" }, { id: "intelligence", name: "Притяжение маны" }, { id: "dark_revelation", name: "Тёмное откровение" }, { id: "arcane_exaltation", name: "Хранитель тайного" }, ]}, { id: "dark", name: "Магия Тьмы", list: [ { id: "dark1", name: "Основы магии Тьмы", main: true }, { id: "dark2", name: "Сильная магия Тьмы", main: true }, { id: "dark3", name: "Искусная магия Тьмы", main: true }, { id: "weakening_strike", name: "Ослабляющий удар" }, { id: "fallen_knight", name: "Падший рыцарь" }, { id: "master_of_pain", name: "Повелитель боли" }, { id: "master_of_curses", name: "Повелитель проклятий" }, { id: "master_of_mind", name: "Повелитель разума" }, ]}, { id: "destructive", name: "Магия Хаоса", list: [ { id: "destructive1", name: "Основы магии Хаоса", main: true }, { id: "destructive2", name: "Сильная магия Хаоса", main: true }, { id: "destructive3", name: "Искусная магия Хаоса", main: true }, { id: "searing_fires", name: "Иссушающее пламя" }, { id: "sap_magic", name: "Истощение магии" }, { id: "fiery_wrath", name: "Огненная ярость" }, { id: "master_of_storms", name: "Повелитель бурь" }, { id: "master_of_fire", name: "Повелитель огня" }, { id: "master_of_ice", name: "Повелитель холода" }, { id: "secrets_of_destruction", name: "Тайны хаоса" }, ]}, { id: "light", name: "Магия Света", list: [ { id: "light1", name: "Основы магии Света", main: true }, { id: "light2", name: "Сильная магия Света", main: true }, { id: "light3", name: "Искусная магия Света", main: true }, { id: "master_of_blessings", name: "Дарующий благословение" }, { id: "master_of_abjuration", name: "Дарующий защиту" }, { id: "fire_resistance", name: "Защита от огня" }, { id: "master_of_wrath", name: "Повелитель ярости" }, { id: "twilight", name: "Сумерки" }, { id: "refined_mana", name: "Тайны света" }, ]}, { id: "summon", name: "Магия Природы", list: [ { id: "summon1", name: "Основы магии Природы", main: true }, { id: "summon2", name: "Сильная магия Природы", main: true }, { id: "summon3", name: "Искусная магия Природы", main: true }, { id: "master_of_conjuration", name: "Повелитель волшебства" }, { id: "master_of_life", name: "Повелитель жизни" }, { id: "master_of_obstacles", name: "Повелитель препятствий" }, ]}, { id: "sorcery", name: "Чародейство", list: [ { id: "sorcery1", name: "Основы чародейства", main: true }, { id: "sorcery2", name: "Развитое чародейство", main: true }, { id: "sorcery3", name: "Искусное чародейство", main: true }, { id: "mana_regeneration", name: "Восполнение маны" }, { id: "boneward", name: "Защита от магии хаоса" }, { id: "erratic_mana", name: "Изменчивая мана" }, { id: "magic_insight", name: "Мудрость" }, { id: "arcane_brillance", name: "Тайное откровение" }, { id: "arcane_excellence", name: "Тайное преимущество" }, { id: "arcane_training", name: "Тайные знания" }, ]}, { id: "special", name: "Фракция", list: [ { id: "hellfire", name: "Адское пламя" }, { id: "magic_mirror", name: "Волшебное зеркало" }, { id: "runeadv", name: "Дополнительные руны" }, { id: "necr_soul", name: "Духовная связь" }, { id: "zakarrow", name: "Заколдованная стрела" }, { id: "nomagicdamage", name: "Контроль магии" }, { id: "elf_shot", name: "Ливень из стрел" }, { id: "benediction", name: "Молитва" }, { id: "knight_mark", name: "Надзор" }, { id: "memoryblood", name: "Память нашей Крови" }, { id: "cre_master", name: "Повелитель существ" }, { id: "consumecorpse", name: "Поглощение трупов" }, { id: "barb_skill", name: "Пробивающая мощь" }, { id: "powerraise", name: "Совершенное Поднятие мертвецов" }, { id: "dark_blood", name: "Тёмная кровь" }, { id: "dark_power", name: "Тёмная сила" }, { id: "save_rage", name: "Упорство ярости" }, ]} ]; this.map = {}; for (let section of this.table) { for (let item of section.list) { this.map[item.id] = item; } } } get default() { return []; } } styles(` .mb-editor-name__box { margin: 5px 0 0 0; } .mb-editor-name__block-label { display: inline-block; width: 110px; } .mb-editor-name__block-input { width: 526px; } `); class EditorNameComponent { view({ attrs: { value, onchange } }) { return m('.mb-editor-name__box', [ m('.mb-editor-name__block', [ m('.mb-editor-name__block-label', 'Название:'), m('input.mb-editor-name__block-input', { oninput: m.withAttr('value', onchange), value }) ]) ]) } } styles(` .mb-editor-fraction__box { margin: 5px 0; } .mb-editor-fraction__block-label { display: inline-block; width: 110px; } .mb-editor-fraction__import-button { display: inline-block; margin-left: 4px; cursor: pointer; } .mb-editor-fraction__import-button:hover { text-decoration: underline; } .mb-editor-fraction__import-button--importing { animation: mb-fraction-import-animation 1s infinite linear; cursor: wait; } .mb-editor-fraction__import-button--importing:hover { text-decoration: none; } @keyframes mb-fraction-import-animation { 0% { transform: rotate(0deg); } 100% { transform: rotate(359deg); } } `); class EditorFractionComponent { constructor({ attrs: { services }}) { this.services = services; } view({ attrs: { value, onchange } }) { const importAction = () => { if (this.importing) return; this.importing = true; const finish = () => { this.importing = false }; this.services.import.getFraction() .then((val) => { if (val) { onchange(val); } }) .then(finish, finish); }; const importButton = () => { return m('.mb-editor-fraction__import-button', { onclick: importAction, class: this.importing ? 'mb-editor-fraction__import-button--importing' : '' }, '<') }; return m('.mb-editor-fraction__box', [ m('.mb-editor-fraction__block', [ m('.mb-editor-fraction__block-label', 'Фракция:'), m('select', { oninput: m.withAttr('value', onchange), value: value }, this.services.fraction.list.map(({ id, name }) => { return m('option', { key: id, value: id }, name); })), importButton() ]) ]) } } styles(` .mb-editor-inventory__box { margin: 5px 0; } .mb-editor-inventory__block-label { display: inline-block; width: 110px; } `); class EditorInventoryComponent { constructor({ attrs: { services }}) { this.services = services; } view({ attrs: { value, onchange } }) { return m('.mb-editor-inventory__box', [ m('.mb-editor-inventory__block', [ m('.mb-editor-inventory__block-label', 'Набор оружия:'), m('select', { oninput: m.withAttr('value', onchange), value: value }, this.services.inventory.list.map(({ id, name }) => { return m('option', { key: id, value: id }, name); })) ]) ]) } } styles(` .mb-editor-attribute__box { margin: 5px 0; } .mb-editor-attribute__block-label { display: inline-block; width: 110px; } .mb-editor-attribute__block-input { width: 20px; display: inline-block; } `); class EditorAttributeComponent { constructor({ attrs: { services }}) { this.services = services; } view({ attrs: { value, onchange } }) { let changeAction = (name, val) => { onchange(Object.assign({}, value, { [name]: parseInt(val) || 0 })); } return m('.mb-editor-attribute__box', [ m('.mb-editor-attribute__block', [ m('.mb-editor-attribute__block-label', 'Аттрибуты:'), this.services.attribute.list.map((name) => { return m('input.mb-editor-attribute__block-input', { key: name, oninput: m.withAttr('value', (value) => { changeAction(name, value) }), value: value[name] || 0 }); }) ]) ]) } } styles(` .mb-editor-army__box { margin: 5px 0; } .mb-editor-army__block-label { display: inline-block; width: 110px; } .mb-editor-army__block-controls { display: inline-block; } .mb-editor-army__block-input { width: 24px; display: inline-block; } .mb-editor-army__block-input:nth-child(1), .mb-editor-army__block-input:nth-child(2), .mb-editor-army__block-input:nth-child(3), .mb-editor-army__block-input:nth-child(4){ width: 30px; } .mb-editor-army__block-import-button { margin-left: 4px; display: inline-block; cursor: pointer; } .mb-editor-army__block-import-button:hover { text-decoration: underline; } .mb-editor-army__block-import-button--importing { animation: mb-army-import-animation 1s infinite linear; cursor: wait; } .mb-editor-army__block-import-button--importing:hover { text-decoration: none; } @keyframes mb-army-import-animation { 0% { transform: rotate(0deg); } 100% { transform: rotate(359deg); } } `); class EditorArmyComponent { constructor({ attrs: { services }}) { this.services = services; } view({ attrs: { value, onchange } }) { const changeAction = (index, val) => { let data = value.slice(); data[index] = parseInt(val) || 0; onchange(data); }; const importAction = () => { if (this.importing) return; this.importing = true; const finish = () => { this.importing = false }; this.services.import.getArmy() .then((val) => { if (val) { onchange(val); } }) .then(finish, finish); }; const importButton = () => { return m('.mb-editor-army__block-import-button', { onclick: importAction, class: this.importing ? 'mb-editor-army__block-import-button--importing' : '' }, '<') }; return m('.mb-editor-army__box', [ m('.mb-editor-army__block', [ m('.mb-editor-army__block-label', 'Армия:'), m('.mb-editor-army__block-controls', this.services.army.iterator.map((_, index) => { return m('input.mb-editor-army__block-input', { key: index, oninput: m.withAttr('value', (value) => { changeAction(index, value) }), value: value[index] || 0 }) }) ), importButton() ]) ]) } } styles(` .mb-editor-skill__box { margin: 5px 0; } .mb-editor-skill__select { display: inline-block; } .mb-editor-skill__option--main { font-weight: bold; } .mb-editor-skill__list-item-name { display: inline-block; margin-right: 4px; } .mb-editor-skill__list-item-button { display: inline-block; cursor: pointer; } .mb-editor-skill__list-item-button:hover { text-decoration: underline; } .mb-editor-skill__import-button { display: inline-block; margin-left: 4px; cursor: pointer; } .mb-editor-skill__import-button:hover { text-decoration: underline; } .mb-editor-skill__import-button--importing { animation: mb-skill-import-animation 1s infinite linear; cursor: wait; } .mb-editor-skill__import-button--importing:hover { text-decoration: none; } @keyframes mb-skill-import-animation { 0% { transform: rotate(0deg); } 100% { transform: rotate(359deg); } } `); class EditorSkillComponent { constructor({ attrs: { services }}) { this.services = services; } view({ attrs: { value, onchange } }) { const removeAction = (id) => { let p = value.indexOf(id); onchange(value.slice(0, p).concat( value.slice(p + 1) )); }; const importAction = () => { if (this.importing) return; this.importing = true; const finish = () => { this.importing = false }; this.services.import.getSkill() .then((val) => { if (val) { onchange(val); } }) .then(finish, finish); }; const importButton = () => { return m('.mb-editor-skill__import-button', { onclick: importAction, class: this.importing ? 'mb-editor-skill__import-button--importing' : '' }, '<') }; const list = () => { return m('.mb-editor-skill__list', value.map((id) => { return m('.mb-editor-skill__list-item', [ m('.mb-editor-skill__list-item-name', this.services.skill.map[id].name), m('.mb-editor-skill__list-item-button', { onclick: () => { removeAction(id) } }, '[х]') ]) })) } const select = () => { return m('select.mb-editor-skill__select', { oninput: m.withAttr('value', (id) => { onchange(value.concat(id)) }) }, [ m('option', 'Навыки:'), this.services.skill.table.map(({ id, name, list }) => { return m('optgroup', { key: id, label: name }, list.map(({ id, name, main }) => { if (value.indexOf(id) !== -1) return null; return m('option', { key: id, value: id, class: main ? 'mb-editor-skill__option--main': '' }, name) })) }) ]) }; return m('.mb-editor-skill__box', [ m('.mb-editor-skill__select-block', [ select(), importButton() ]), list() ]) } } styles(` .mb-editor__section { padding: 5px 6px; display: table; } .mb-editor__buttons { padding: 3px 5px 4px 5px; border-top: 1px #5D413A solid; background: #F5F3EA; height: 16px; } .mb-editor__save-button { font-weight: bold; cursor: pointer; display: inline-block; margin-right: 8px; } .mb-editor__cancel-button { cursor: pointer; display: inline-block; } .mb-editor__close-button { cursor: pointer; display: inline-block; } .mb-editor__save-button:hover, .mb-editor__cancel-button:hover, .mb-editor__close-button:hover { text-decoration: underline; } .mb-editor__section-column { float: left; margin-right: 30px; } .mb-editor__section-column:last-child { margin-right: 0; } `); class EditorComponent { constructor({ attrs: { services }}) { this.services = services; } _updateOriginItem(item) { if (this.originItem !== item) { this.originItem = item; this.item = deepCopy(item); } } cancel() { this.item = deepCopy(this.originItem); } view({ attrs: { item: originItem, onchange, onclose } }) { this._updateOriginItem(originItem); let item = this.item; let services = this.services; const closeAction = () => { onclose(); } const buttons = () => { let controls; if (deepEquals(this.item, originItem)) { controls = m('.mb-editor__close-button', { onclick: closeAction }, 'Закрыть'); } else { controls = [ m('.mb-editor__save-button', { onclick: () => { onchange(item) }}, 'Сохранить'), m('.mb-editor__cancel-button', { onclick: this.cancel.bind(this) }, 'Отменить') ]; } return m('.mb-editor__buttons', controls); }; return m('.mb-editor__box', [ m('.mb-editor__section', [ m(EditorNameComponent, { value: item.name, onchange: (value) => { item.name = value } }), m('.mb-editor__section-column', [ m(EditorFractionComponent, { services, value: item.fraction, onchange: (value) => { item.fraction = value } }), m(EditorInventoryComponent, { services, value: item.inventory, onchange: (value) => { item.inventory = value } }), m(EditorAttributeComponent, { services, value: item.attribute, onchange: (value) => { item.attribute = value } }), m(EditorArmyComponent, { services, value: item.army, onchange: (value) => { item.army = value } }), ]), m('.mb-editor__section-column', [ m(EditorSkillComponent, { services, value: item.skill, onchange: (value) => { item.skill = value } }), ]) ]), buttons() ]) } } styles(` .mb-export-popup__box { position: absolute; top: 2px; left: 2px; border: 1px #5D413A solid; } .mb-export-popup__header { background: #F5F3EA; border-bottom: 1px #5D413A solid; text-align: right; } .mb-export-popup__close-button { display: inline-block; cursor: pointer; color: rgb(89, 44, 8); padding: 4px 6px; } .mb-export-popup__close-button:hover { text-decoration: underline; } .mb-export-popup__body { background: #fff; } .mb-export-popup__body textarea { resize: none; box-sizing: border-box; width: 580px; height: 300px; font-size: 11px; padding: 3px 5px; margin: 0; border: none; } .mb-export-popup__footer { padding: 3px 5px 4px 5px; border-top: 1px #5D413A solid; background: #F5F3EA; height: 16px; } `); class ExportPopup { constructor({ attrs: { services }}) { this.services = services; } oncreate({ dom, attrs: { onclose } }) { this.releaseClickOutEventListener = nextTickAddClickOutEventListener(dom, onclose); } onbeforeremove() { this.releaseClickOutEventListener(); } view({ attrs: { onclose }}) { return m('.mb-export-popup__box', [ m('.mb-export-popup__header', [ m('.mb-export-popup__close-button', { onclick: onclose }, 'Закрыть') ]), m('.mb-export-popup__body', [ m('textarea', { readonly: true, value: this.services.manager.serialize() }) ]), m('.mb-export-popup__footer') ]) } }; styles(` .mb-import-popup__box { position: absolute; top: 2px; left: 2px; border: 1px #5D413A solid; } .mb-import-popup__header { background: #F5F3EA; border-bottom: 1px #5D413A solid; text-align: right; } .mb-import-popup__close-button, .mb-import-popup__import-button { display: inline-block; cursor: pointer; color: rgb(89, 44, 8); padding: 4px 6px; } .mb-import-popup__close-button:hover, .mb-import-popup__import-button:hover { text-decoration: underline; } .mb-import-popup__import-button { font-weight: bold; } .mb-import-popup__body { background: #fff; } .mb-import-popup__body textarea { resize: none; box-sizing: border-box; width: 580px; height: 300px; font-size: 11px; padding: 3px 5px; margin: 0; border: none; } .mb-import-popup__footer { border-top: 1px #5D413A solid; background: #F5F3EA; } .mb-import-popup__import-error-message { color: red; margin-left: 10px; display: inline-block; padding: 4px 6px; } `); class ImportPopup { constructor({ attrs: { services }}) { this.services = services; this.invalid = false; } oncreate({ dom, attrs: { onclose } }) { this.releaseClickOutEventListener = nextTickAddClickOutEventListener(dom, onclose); } onbeforeremove() { this.releaseClickOutEventListener(); } view({ attrs: { onclose, onimport }}) { const onchange = (value) => { this.data = value; this.invalid = false; } const importAction = () => { if (this.services.manager.unserialize(this.data)) { this.data = null; onimport(); } else { this.invalid = true; } } return m('.mb-import-popup__box', [ m('.mb-import-popup__header', [ m('.mb-import-popup__close-button', { onclick: onclose }, 'Закрыть') ]), m('.mb-import-popup__body', [ m('textarea', { value: this.data, oninput: m.withAttr('value', onchange) }) ]), m('.mb-import-popup__footer', [ m('.mb-import-popup__import-button', { onclick: importAction }, 'Импорт'), this.invalid ? m('.mb-import-popup__import-error-message', 'Некорректный формат данных') : null ]) ]) } }; styles(` .mb-manager__box { width: 650px; border: 1px #5D413A solid; background: #fff; position: absolute; left: 3px; top: 3px; z-index: 3; box-sizing: border-box; } .mb-manager__header { background: #F5F3EA; border-bottom: 1px #5D413A solid; position: relative; display: flex; align-items: center; justify-content: space-between; } .mb-manager__header-button { display: inline-block; cursor: pointer; color: rgb(89, 44, 8); padding: 4px 0 4px 6px; } .mb-manager__header-button:last-child { padding-right: 6px; } .mb-manager__header-button:hover { text-decoration: underline; } .mb-manager__header-space { width: 20px; display: inline-block; } .mb-manager__list { max-height: 72px; overflow: auto; border-bottom: 1px #5D413A solid; margin-bottom: -1px; white-space: nowrap; } .mb-manager__list-item { padding: 0 6px; cursor: pointer; margin: 4px 0; display: table; } .mb-manager__list-item:hover { text-decoration: underline; } .mb-manager__list-item--selected { color: rgb(255, 0, 0); text-decoration: underline; } .mb-manager__list-empty { text-align: center; border-bottom: 1px #5D413A solid; padding: 15px 0; margin-bottom: -1px; } .mb-manager__confirm-remove { height: 72px; display: flex; align-items: center; justify-content: center; flex-direction: column; } .mb-manager__confirm-remove-buttons { margin-top: 7px; } .mb-manager__confirm-remove-button { display: inline-block; margin: 4px 4px; cursor: pointer; } .mb-manager__confirm-remove-button:hover { text-decoration: underline; } .mb-manager__confirm-remove-button--no { font-weight: bold; } .mb-manager__confirm-remove-button:hover:not(.mb-manager__confirm-remove-button--no) { color: red; } .mg-manager__body { margin-top: 1px; } `); class ManagerComponent { constructor({ attrs: { services }}) { this.services = services; this._initSelected(); this.exportPopup = false; this.importPopup = false; } _initSelected() { let current = this.services.current.item; if (current) { let { founded, index } = this.services.manager.searchEquals(current); if (founded) { this.selected = this.services.manager.items[index]; return; } } this.selected = this.services.manager.items[0]; } createNew() { this.selected = this.services.manager.createNew(); } selectItem(item) { this.selected = item; } removeSelected() { this.confirmRemove = true; } confirmRemoveOk() { this.selected = this.services.manager.remove(this.selected); this.confirmRemove = false; } confirmRemoveCancel() { this.confirmRemove = false; } duplicateSelected() { this.selected = this.services.manager.duplicate(this.selected); } updateItem(item) { this.selected = this.services.manager.update(item); } view({ attrs: { onclose } }) { const closeAction = () => { onclose(); }; const exportCloseAction = () => { this.exportPopup = false; }; const importCloseAction = () => { this.importPopup = false; }; const importAction = () => { this.importPopup = false; this.selected = null; }; const headerLeft = () => { let controls = []; if (!this.confirmRemove) { controls.push( m('.mb-manager__header-button', { onclick: this.createNew.bind(this) }, 'Новый') ); if (this.selected) { controls.push( m('.mb-manager__header-button', { onclick: this.duplicateSelected.bind(this) }, 'Копия'), m('.mb-manager__header-button', { onclick: this.removeSelected.bind(this) }, 'Удалить') ) } } return m('.mb-manager__header-left', controls); }; const headerRight = () => { let controls = []; if (!this.confirmRemove) { if (this.services.manager.items.length > 0) { controls.push( m('.mb-manager__header-button', { onclick: () => { this.exportPopup = true } }, 'Экспорт') ) } controls.push( m('.mb-manager__header-button', { onclick: () => { this.importPopup = true } }, 'Импорт'), m('.mb-manager__header-space') ) } controls.push( m('.mb-manager__header-button', { onclick: closeAction }, 'Закрыть') ) return m('.mb-manager__header-right', controls); }; const confirmRemove = () => { if (!this.confirmRemove) return null; return m('.mb-manager__confirm-remove', [ m('.mb-manager__confirm-remove-message', [ `Удалить "${this.selected.name}"?` ]), m('.mb-manager__confirm-remove-buttons', [ m('.mb-manager__confirm-remove-button.mb-manager__confirm-remove-button--no', { onclick: this.confirmRemoveCancel.bind(this) }, 'Нет'), m('.mb-manager__confirm-remove-button', { onclick: this.confirmRemoveOk.bind(this) }, 'Да') ]), ]) }; const list = () => { if (this.confirmRemove) return null; let items = this.services.manager.items; if (items.length === 0) { return m('.mb-manager__list-empty', 'Список пуст') } return m('.mb-manager__list', items.map((item) => { return m('.mb-manager__list-item', { key: item.id, class: (this.selected || {}).id === item.id ? 'mb-manager__list-item--selected' : '', onclick: () => { this.selectItem(item) } }, item.name) })) }; const body = () => { if (this.confirmRemove) return null; if (!this.selected) return null; return m('.mb-manager__body', [ m(EditorComponent, { services: this.services, item: this.selected, onchange: this.updateItem.bind(this), onclose: closeAction }) ]); }; const popups = () => { if (this.confirmRemove) return null; return [ this.exportPopup ? m(ExportPopup, { services: this.services, onclose: exportCloseAction }) : null, this.importPopup ? m(ImportPopup, { services: this.services, onclose: importCloseAction, onimport: importAction }) : null ] } return m('.mb-manager__box', [ m('.mb-manager__header', [ headerLeft(), headerRight() ]), confirmRemove(), list(), body(), popups(), ]) } } styles(` .mb-selector__handler { display: flex; cursor: pointer; } .mb-selector__handler--changing { cursor: wait; } .mb-selector__info { padding: 2px 6px 4px 5px; background: #6b6b69; color: #f5c137; white-space: nowrap; border: 1px solid #f5c137; border-left: none; } .mb-selector__info-error { color: red; } .mb-selector__triangle-box { background: #6b6b69; color: #f5c137; border: 1px solid #f5c137; border-left: none; padding: 2px 8px 4px 5px; box-sizing: border-box; position: relative; } .mb-selector__triangle-box:before { content: "\\00a0"; } .mb-selector__triangle { width: 0; height: 0; border-left: 5px solid transparent; border-right: 5px solid transparent; border-top: 5px solid #f5c137; position: absolute; left: 3px; top: 8px; } .mb-selector__triangle--up { transform: rotate(180deg); } .mb-selector__list-handler { z-index: 3; position: relative; } .mb-selector__list-box { position: absolute; top: 0; left: 0; border: 1px #5D413A solid; background: #fff; } .mb-selector__list-item { padding: 0 6px; cursor: pointer; margin: 4px 0; display: table; white-space: nowrap; } .mb-selector__list-item:hover .mb-selector__list-item-name { text-decoration: underline; } .mb-selector__list-item-name--current { color: rgb(255, 0, 0); text-decoration: underline; cursor: default; } .mb-selector__list-item-name { display: inline-block; } .mb-selector__list-item-force { display: inline-block; margin-left: 5px; } .mb-selector__list-item-force:hover { text-decoration: underline; } .mb-selector__list-footer { display: block; border-top: 1px #5D413A solid; padding: 4px 6px; margin: 0; background: #F5F3EA; } .mb-selector__list-button-cancel { cursor: pointer; display: inline-block; } .mb-selector__list-button-cancel:hover { text-decoration: underline; } `); class SelectorComponent { constructor({ attrs: { services }}) { this.services = services; this.dropped = false; this.changing = false; this.error = false; } oncreate({ dom }) { this.releaseClickOutEventListener = addClickOutEventListener(dom, () => { this.dropped = false; }); } onbeforeremove() { this.releaseClickOutEventListener(); } drop() { if (this.changing) return; this.dropped = !this.dropped; } view() { let items = this.services.manager.items; if (!items.length) return null; let current = this.services.current.item; const selectAction = (item, force) => { if (item === current && !force) return; this.dropped = false; this.changing = true; this.services.current.change(item, force) .then(() => { this.changing = false; this.error = false; }, (e) => { console.error(e); this.changing = false; this.error = true; }); }; const list = () => { if (!this.dropped) return null; const box = m('.mb-selector__list-box', [ m('.mb-selector__list', items.map((item) => { return m('.mb-selector__list-item', [ m('.mb-selector__list-item-name', this.services.current.equals(item) ? { class: 'mb-selector__list-item-name--current' } : { onclick: () => { selectAction(item) } }, item.name), m('.mb-selector__list-item-force', { onclick: () => { selectAction(item, true) }}, '[*]') ]) })), current ? m('.mb-selector__list-footer', m('.mb-selector__list-button-cancel', { onclick: () => { selectAction(null); } }, 'Сбросить')) : null, ]); return m('.mb-selector__list-handler', box); }; const info = () => { if (!this.changing && !current && !this.error) return null; const text = () => { if (this.changing) { return 'Смена билда...' } else if (this.error) { return m('.mb-selector__info-error', 'Ошибка смены билда!') } else { return [ this.services.current.isExpired() ? '*' : '', current.name ] } }; return m('.mb-selector__info', text()); }; return m('.mb-selector__box', [ m('.mb-selector__handler', { onclick: this.drop.bind(this), class: this.changing ? 'mb-selector__handler--changing' : '' }, [ info(), m('.mb-selector__triangle-box', m('.mb-selector__triangle', { class: this.dropped ? 'mb-selector__triangle--up' : '' })) ]), list() ]) } } styles(` .mb-app__handler { display: flex; } .mb-app__handler-editor-button { background: #6b6b69; color: #f5c137; border: 1px solid #f5c137; padding: 2px 6px 4px 6px; cursor: pointer; } `); class AppComponent { constructor() { this.manager = false; this.services = new ServiceContainer(); this.services.inventory.syncNamesIfAvailable(); } view() { return m('.mb-app__box', [ m('.mb-app__handler', [ m('.mb-app__handler-editor-button', { onclick: () => { this.manager = true } }, '+'), m(SelectorComponent, { services: this.services }) ]), this.manager ? m(ManagerComponent, { services: this.services, onclose: () => { this.manager = false } }) : null ]); } } function mount() { let container = document.querySelector('body table table td'); if (!container) return let checkAllowedPage = document.querySelector('a[href*="home.php"]'); if (!checkAllowedPage) return; let root = document.createElement('div'); root.style.position = 'absolute'; root.style.top = '0'; root.style.left = '0'; container.style.position = 'relative'; container.appendChild(root); m.mount(root, AppComponent); } function styles(content = null, flush = false) { let task = styles.task || {}; if (content) { if (task.scheduled) { task.content += content; } else { let task = styles.task = { content, scheduled: true } task.timer = setTimeout(finish, 0, task) } } if (flush && task.scheduled) { clearInterval(task.timer); finish(task); } function finish(task) { let head = document.querySelector('head'); head.insertAdjacentHTML('beforeEnd', `<style type="text/css">${task.content}</style>`); task.scheduled = false; } } function uniqid() { return Date.now().toString(36) + Math.random().toString(36).slice(1); } function deepCopy(value) { if (!value) return value; if (value instanceof Array) { return value.map(deepCopy); } if (typeof value === 'object') { let obj = {}; for (let key of Object.keys(value)) { obj[key] = deepCopy(value[key]) } return obj; } return value; } function deepEquals(a, b) { if (a === b) return true; if (a instanceof Array && b instanceof Array) { if (a.length !== b.length) return false; for (let i = 0; i < a.length; i++) { if (!deepEquals(a[i], b[i])) return false; } return true; } if (!a || !b) return false; if (typeof a === 'object' && typeof b === 'object') { let keys = Object.keys(a); if (keys.length !== Object.keys(b).length) return false; for (let key of keys) { if (!b.hasOwnProperty(key)) return false; if (!deepEquals(a[key], b[key])) return false; } return true; } return false; } function addClickOutEventListener(dom, fn) { const listener = (event) => { let node = event.target; while(node && node.parentNode) { if (node === dom) { return; } node = node.parentNode; } fn(); m.redraw(); }; const body = document.body; body.addEventListener('click', listener); return () => { body.removeEventListener('click', listener); } } function nextTickAddClickOutEventListener(dom, fn) { let releaseEventListener = null; let timeout = setTimeout(() => { timeout = null; releaseEventListener = addClickOutEventListener(dom, fn); }); return () => { if (timeout) clearTimeout(timeout); if (releaseEventListener) releaseEventListener(); } } class LocalStorageDriver { constructor(key) { this.key = key; } fetch() { let text = localStorage.getItem(this.key); let data; try { data = JSON.parse(text); } catch(e) { data = null; } return data; } put(data) { localStorage.setItem(this.key, JSON.stringify(data)); } } class LocalStorageArrayDriver extends LocalStorageDriver { fetch() { let data = super.fetch(); if (!Array.isArray(data)) { data = []; } return data; } } function PromiseMDecorator(promise) { const proxyWithRedraw = (data) => { setTimeout(m.redraw.bind(m)); return data; } return promise.then( proxyWithRedraw, (data) => proxyWithRedraw(Promise.reject(data)) ); } function httpPlainRequest(method, url, data) { if (method === 'FORM') { let form = new FormData(); for (let key of Object.keys(data)) { form.append(key, data[key]); } data = form; method = 'POST'; } return m.request({ method, url, data, extract: ({ responseText }) => responseText }); } function main() { try { styles(null, true); mount(); } catch(e) { console.error(e); } } setTimeout(main); // Mithrill 1.1.1 ;(function() { "use strict" function Vnode(tag, key, attrs0, children, text, dom) { return {tag: tag, key: key, attrs: attrs0, children: children, text: text, dom: dom, domSize: undefined, state: undefined, _state: undefined, events: undefined, instance: undefined, skip: false} } Vnode.normalize = function(node) { if (Array.isArray(node)) return Vnode("[", undefined, undefined, Vnode.normalizeChildren(node), undefined, undefined) if (node != null && typeof node !== "object") return Vnode("#", undefined, undefined, node === false ? "" : node, undefined, undefined) return node } Vnode.normalizeChildren = function normalizeChildren(children) { for (var i = 0; i < children.length; i++) { children[i] = Vnode.normalize(children[i]) } return children } var selectorParser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g var selectorCache = {} var hasOwn = {}.hasOwnProperty function compileSelector(selector) { var match, tag = "div", classes = [], attrs = {} while (match = selectorParser.exec(selector)) { var type = match[1], value = match[2] if (type === "" && value !== "") tag = value else if (type === "#") attrs.id = value else if (type === ".") classes.push(value) else if (match[3][0] === "[") { var attrValue = match[6] if (attrValue) attrValue = attrValue.replace(/\\(["'])/g, "$1").replace(/\\\\/g, "\\") if (match[4] === "class") classes.push(attrValue) else attrs[match[4]] = attrValue === "" ? attrValue : attrValue || true } } if (classes.length > 0) attrs.className = classes.join(" ") return selectorCache[selector] = {tag: tag, attrs: attrs} } function execSelector(state, attrs, children) { var hasAttrs = false, childList, text var className = attrs.className || attrs.class for (var key in state.attrs) { if (hasOwn.call(state.attrs, key)) { attrs[key] = state.attrs[key] } } if (className !== undefined) { if (attrs.class !== undefined) { attrs.class = undefined attrs.className = className } if (state.attrs.className != null) { attrs.className = state.attrs.className + " " + className } } for (var key in attrs) { if (hasOwn.call(attrs, key) && key !== "key") { hasAttrs = true break } } if (Array.isArray(children) && children.length === 1 && children[0] != null && children[0].tag === "#") { text = children[0].children } else { childList = children } return Vnode(state.tag, attrs.key, hasAttrs ? attrs : undefined, childList, text) } function hyperscript(selector) { // Because sloppy mode sucks var attrs = arguments[1], start = 2, children if (selector == null || typeof selector !== "string" && typeof selector !== "function" && typeof selector.view !== "function") { throw Error("The selector must be either a string or a component."); } if (typeof selector === "string") { var cached = selectorCache[selector] || compileSelector(selector) } if (attrs == null) { attrs = {} } else if (typeof attrs !== "object" || attrs.tag != null || Array.isArray(attrs)) { attrs = {} start = 1 } if (arguments.length === start + 1) { children = arguments[start] if (!Array.isArray(children)) children = [children] } else { children = [] while (start < arguments.length) children.push(arguments[start++]) } var normalized = Vnode.normalizeChildren(children) if (typeof selector === "string") { return execSelector(cached, attrs, normalized) } else { return Vnode(selector, attrs.key, attrs, normalized) } } hyperscript.trust = function(html) { if (html == null) html = "" return Vnode("<", undefined, undefined, html, undefined, undefined) } hyperscript.fragment = function(attrs1, children) { return Vnode("[", attrs1.key, attrs1, Vnode.normalizeChildren(children), undefined, undefined) } var m = hyperscript /** @constructor */ var PromisePolyfill = function(executor) { if (!(this instanceof PromisePolyfill)) throw new Error("Promise must be called with `new`") if (typeof executor !== "function") throw new TypeError("executor must be a function") var self = this, resolvers = [], rejectors = [], resolveCurrent = handler(resolvers, true), rejectCurrent = handler(rejectors, false) var instance = self._instance = {resolvers: resolvers, rejectors: rejectors} var callAsync = typeof setImmediate === "function" ? setImmediate : setTimeout function handler(list, shouldAbsorb) { return function execute(value) { var then try { if (shouldAbsorb && value != null && (typeof value === "object" || typeof value === "function") && typeof (then = value.then) === "function") { if (value === self) throw new TypeError("Promise can't be resolved w/ itself") executeOnce(then.bind(value)) } else { callAsync(function() { if (!shouldAbsorb && list.length === 0) console.error("Possible unhandled promise rejection:", value) for (var i = 0; i < list.length; i++) list[i](value) resolvers.length = 0, rejectors.length = 0 instance.state = shouldAbsorb instance.retry = function() {execute(value)} }) } } catch (e) { rejectCurrent(e) } } } function executeOnce(then) { var runs = 0 function run(fn) { return function(value) { if (runs++ > 0) return fn(value) } } var onerror = run(rejectCurrent) try {then(run(resolveCurrent), onerror)} catch (e) {onerror(e)} } executeOnce(executor) } PromisePolyfill.prototype.then = function(onFulfilled, onRejection) { var self = this, instance = self._instance function handle(callback, list, next, state) { list.push(function(value) { if (typeof callback !== "function") next(value) else try {resolveNext(callback(value))} catch (e) {if (rejectNext) rejectNext(e)} }) if (typeof instance.retry === "function" && state === instance.state) instance.retry() } var resolveNext, rejectNext var promise = new PromisePolyfill(function(resolve, reject) {resolveNext = resolve, rejectNext = reject}) handle(onFulfilled, instance.resolvers, resolveNext, true), handle(onRejection, instance.rejectors, rejectNext, false) return promise } PromisePolyfill.prototype.catch = function(onRejection) { return this.then(null, onRejection) } PromisePolyfill.resolve = function(value) { if (value instanceof PromisePolyfill) return value return new PromisePolyfill(function(resolve) {resolve(value)}) } PromisePolyfill.reject = function(value) { return new PromisePolyfill(function(resolve, reject) {reject(value)}) } PromisePolyfill.all = function(list) { return new PromisePolyfill(function(resolve, reject) { var total = list.length, count = 0, values = [] if (list.length === 0) resolve([]) else for (var i = 0; i < list.length; i++) { (function(i) { function consume(value) { count++ values[i] = value if (count === total) resolve(values) } if (list[i] != null && (typeof list[i] === "object" || typeof list[i] === "function") && typeof list[i].then === "function") { list[i].then(consume, reject) } else consume(list[i]) })(i) } }) } PromisePolyfill.race = function(list) { return new PromisePolyfill(function(resolve, reject) { for (var i = 0; i < list.length; i++) { list[i].then(resolve, reject) } }) } if (typeof window !== "undefined") { if (typeof window.Promise === "undefined") window.Promise = PromisePolyfill var PromisePolyfill = window.Promise } else if (typeof global !== "undefined") { if (typeof global.Promise === "undefined") global.Promise = PromisePolyfill var PromisePolyfill = global.Promise } else { } var buildQueryString = function(object) { if (Object.prototype.toString.call(object) !== "[object Object]") return "" var args = [] for (var key0 in object) { destructure(key0, object[key0]) } return args.join("&") function destructure(key0, value) { if (Array.isArray(value)) { for (var i = 0; i < value.length; i++) { destructure(key0 + "[" + i + "]", value[i]) } } else if (Object.prototype.toString.call(value) === "[object Object]") { for (var i in value) { destructure(key0 + "[" + i + "]", value[i]) } } else args.push(encodeURIComponent(key0) + (value != null && value !== "" ? "=" + encodeURIComponent(value) : "")) } } var FILE_PROTOCOL_REGEX = new RegExp("^file://", "i") var _8 = function($window, Promise) { var callbackCount = 0 var oncompletion function setCompletionCallback(callback) {oncompletion = callback} function finalizer() { var count = 0 function complete() {if (--count === 0 && typeof oncompletion === "function") oncompletion()} return function finalize(promise0) { var then0 = promise0.then promise0.then = function() { count++ var next = then0.apply(promise0, arguments) next.then(complete, function(e) { complete() if (count === 0) throw e }) return finalize(next) } return promise0 } } function normalize(args, extra) { if (typeof args === "string") { var url = args args = extra || {} if (args.url == null) args.url = url } return args } function request(args, extra) { var finalize = finalizer() args = normalize(args, extra) var promise0 = new Promise(function(resolve, reject) { if (args.method == null) args.method = "GET" args.method = args.method.toUpperCase() var useBody = (args.method === "GET" || args.method === "TRACE") ? false : (typeof args.useBody === "boolean" ? args.useBody : true) if (typeof args.serialize !== "function") args.serialize = typeof FormData !== "undefined" && args.data instanceof FormData ? function(value) {return value} : JSON.stringify if (typeof args.deserialize !== "function") args.deserialize = deserialize if (typeof args.extract !== "function") args.extract = extract args.url = interpolate(args.url, args.data) if (useBody) args.data = args.serialize(args.data) else args.url = assemble(args.url, args.data) var xhr = new $window.XMLHttpRequest(), aborted = false, _abort = xhr.abort xhr.abort = function abort() { aborted = true _abort.call(xhr) } xhr.open(args.method, args.url, typeof args.async === "boolean" ? args.async : true, typeof args.user === "string" ? args.user : undefined, typeof args.password === "string" ? args.password : undefined) if (args.serialize === JSON.stringify && useBody) { xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8") } if (args.deserialize === deserialize) { xhr.setRequestHeader("Accept", "application/json, text/*") } if (args.withCredentials) xhr.withCredentials = args.withCredentials for (var key in args.headers) if ({}.hasOwnProperty.call(args.headers, key)) { xhr.setRequestHeader(key, args.headers[key]) } if (typeof args.config === "function") xhr = args.config(xhr, args) || xhr xhr.onreadystatechange = function() { // Don't throw errors on xhr.abort(). if(aborted) return if (xhr.readyState === 4) { try { var response = (args.extract !== extract) ? args.extract(xhr, args) : args.deserialize(args.extract(xhr, args)) if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304 || FILE_PROTOCOL_REGEX.test(args.url)) { resolve(cast(args.type, response)) } else { var error = new Error(xhr.responseText) for (var key in response) error[key] = response[key] reject(error) } } catch (e) { reject(e) } } } if (useBody && (args.data != null)) xhr.send(args.data) else xhr.send() }) return args.background === true ? promise0 : finalize(promise0) } function jsonp(args, extra) { var finalize = finalizer() args = normalize(args, extra) var promise0 = new Promise(function(resolve, reject) { var callbackName = args.callbackName || "_mithril_" + Math.round(Math.random() * 1e16) + "_" + callbackCount++ var script = $window.document.createElement("script") $window[callbackName] = function(data) { script.parentNode.removeChild(script) resolve(cast(args.type, data)) delete $window[callbackName] } script.onerror = function() { script.parentNode.removeChild(script) reject(new Error("JSONP request failed")) delete $window[callbackName] } if (args.data == null) args.data = {} args.url = interpolate(args.url, args.data) args.data[args.callbackKey || "callback"] = callbackName script.src = assemble(args.url, args.data) $window.document.documentElement.appendChild(script) }) return args.background === true? promise0 : finalize(promise0) } function interpolate(url, data) { if (data == null) return url var tokens = url.match(/:[^\/]+/gi) || [] for (var i = 0; i < tokens.length; i++) { var key = tokens[i].slice(1) if (data[key] != null) { url = url.replace(tokens[i], data[key]) } } return url } function assemble(url, data) { var querystring = buildQueryString(data) if (querystring !== "") { var prefix = url.indexOf("?") < 0 ? "?" : "&" url += prefix + querystring } return url } function deserialize(data) { try {return data !== "" ? JSON.parse(data) : null} catch (e) {throw new Error(data)} } function extract(xhr) {return xhr.responseText} function cast(type0, data) { if (typeof type0 === "function") { if (Array.isArray(data)) { for (var i = 0; i < data.length; i++) { data[i] = new type0(data[i]) } } else return new type0(data) } return data } return {request: request, jsonp: jsonp, setCompletionCallback: setCompletionCallback} } var requestService = _8(window, PromisePolyfill) var coreRenderer = function($window) { var $doc = $window.document var $emptyFragment = $doc.createDocumentFragment() var nameSpace = { svg: "http://www.w3.org/2000/svg", math: "http://www.w3.org/1998/Math/MathML" } var onevent function setEventCallback(callback) {return onevent = callback} function getNameSpace(vnode) { return vnode.attrs && vnode.attrs.xmlns || nameSpace[vnode.tag] } //create function createNodes(parent, vnodes, start, end, hooks, nextSibling, ns) { for (var i = start; i < end; i++) { var vnode = vnodes[i] if (vnode != null) { createNode(parent, vnode, hooks, ns, nextSibling) } } } function createNode(parent, vnode, hooks, ns, nextSibling) { var tag = vnode.tag if (typeof tag === "string") { vnode.state = {} if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks) switch (tag) { case "#": return createText(parent, vnode, nextSibling) case "<": return createHTML(parent, vnode, nextSibling) case "[": return createFragment(parent, vnode, hooks, ns, nextSibling) default: return createElement(parent, vnode, hooks, ns, nextSibling) } } else return createComponent(parent, vnode, hooks, ns, nextSibling) } function createText(parent, vnode, nextSibling) { vnode.dom = $doc.createTextNode(vnode.children) insertNode(parent, vnode.dom, nextSibling) return vnode.dom } function createHTML(parent, vnode, nextSibling) { var match1 = vnode.children.match(/^\s*?<(\w+)/im) || [] var parent1 = {caption: "table", thead: "table", tbody: "table", tfoot: "table", tr: "tbody", th: "tr", td: "tr", colgroup: "table", col: "colgroup"}[match1[1]] || "div" var temp = $doc.createElement(parent1) temp.innerHTML = vnode.children vnode.dom = temp.firstChild vnode.domSize = temp.childNodes.length var fragment = $doc.createDocumentFragment() var child while (child = temp.firstChild) { fragment.appendChild(child) } insertNode(parent, fragment, nextSibling) return fragment } function createFragment(parent, vnode, hooks, ns, nextSibling) { var fragment = $doc.createDocumentFragment() if (vnode.children != null) { var children = vnode.children createNodes(fragment, children, 0, children.length, hooks, null, ns) } vnode.dom = fragment.firstChild vnode.domSize = fragment.childNodes.length insertNode(parent, fragment, nextSibling) return fragment } function createElement(parent, vnode, hooks, ns, nextSibling) { var tag = vnode.tag var attrs2 = vnode.attrs var is = attrs2 && attrs2.is ns = getNameSpace(vnode) || ns var element = ns ? is ? $doc.createElementNS(ns, tag, {is: is}) : $doc.createElementNS(ns, tag) : is ? $doc.createElement(tag, {is: is}) : $doc.createElement(tag) vnode.dom = element if (attrs2 != null) { setAttrs(vnode, attrs2, ns) } insertNode(parent, element, nextSibling) if (vnode.attrs != null && vnode.attrs.contenteditable != null) { setContentEditable(vnode) } else { if (vnode.text != null) { if (vnode.text !== "") element.textContent = vnode.text else vnode.children = [Vnode("#", undefined, undefined, vnode.text, undefined, undefined)] } if (vnode.children != null) { var children = vnode.children createNodes(element, children, 0, children.length, hooks, null, ns) setLateAttrs(vnode) } } return element } function initComponent(vnode, hooks) { var sentinel if (typeof vnode.tag.view === "function") { vnode.state = Object.create(vnode.tag) sentinel = vnode.state.view if (sentinel.$$reentrantLock$$ != null) return $emptyFragment sentinel.$$reentrantLock$$ = true } else { vnode.state = void 0 sentinel = vnode.tag if (sentinel.$$reentrantLock$$ != null) return $emptyFragment sentinel.$$reentrantLock$$ = true vnode.state = (vnode.tag.prototype != null && typeof vnode.tag.prototype.view === "function") ? new vnode.tag(vnode) : vnode.tag(vnode) } vnode._state = vnode.state if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks) initLifecycle(vnode._state, vnode, hooks) vnode.instance = Vnode.normalize(vnode._state.view.call(vnode.state, vnode)) if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as argument") sentinel.$$reentrantLock$$ = null } function createComponent(parent, vnode, hooks, ns, nextSibling) { initComponent(vnode, hooks) if (vnode.instance != null) { var element = createNode(parent, vnode.instance, hooks, ns, nextSibling) vnode.dom = vnode.instance.dom vnode.domSize = vnode.dom != null ? vnode.instance.domSize : 0 insertNode(parent, element, nextSibling) return element } else { vnode.domSize = 0 return $emptyFragment } } //update function updateNodes(parent, old, vnodes, recycling, hooks, nextSibling, ns) { if (old === vnodes || old == null && vnodes == null) return else if (old == null) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, ns) else if (vnodes == null) removeNodes(old, 0, old.length, vnodes) else { if (old.length === vnodes.length) { var isUnkeyed = false for (var i = 0; i < vnodes.length; i++) { if (vnodes[i] != null && old[i] != null) { isUnkeyed = vnodes[i].key == null && old[i].key == null break } } if (isUnkeyed) { for (var i = 0; i < old.length; i++) { if (old[i] === vnodes[i]) continue else if (old[i] == null && vnodes[i] != null) createNode(parent, vnodes[i], hooks, ns, getNextSibling(old, i + 1, nextSibling)) else if (vnodes[i] == null) removeNodes(old, i, i + 1, vnodes) else updateNode(parent, old[i], vnodes[i], hooks, getNextSibling(old, i + 1, nextSibling), recycling, ns) } return } } recycling = recycling || isRecyclable(old, vnodes) if (recycling) { var pool = old.pool old = old.concat(old.pool) } var oldStart = 0, start = 0, oldEnd = old.length - 1, end = vnodes.length - 1, map while (oldEnd >= oldStart && end >= start) { var o = old[oldStart], v = vnodes[start] if (o === v && !recycling) oldStart++, start++ else if (o == null) oldStart++ else if (v == null) start++ else if (o.key === v.key) { var shouldRecycle = (pool != null && oldStart >= old.length - pool.length) || ((pool == null) && recycling) oldStart++, start++ updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), shouldRecycle, ns) if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling) } else { var o = old[oldEnd] if (o === v && !recycling) oldEnd--, start++ else if (o == null) oldEnd-- else if (v == null) start++ else if (o.key === v.key) { var shouldRecycle = (pool != null && oldEnd >= old.length - pool.length) || ((pool == null) && recycling) updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), shouldRecycle, ns) if (recycling || start < end) insertNode(parent, toFragment(o), getNextSibling(old, oldStart, nextSibling)) oldEnd--, start++ } else break } } while (oldEnd >= oldStart && end >= start) { var o = old[oldEnd], v = vnodes[end] if (o === v && !recycling) oldEnd--, end-- else if (o == null) oldEnd-- else if (v == null) end-- else if (o.key === v.key) { var shouldRecycle = (pool != null && oldEnd >= old.length - pool.length) || ((pool == null) && recycling) updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), shouldRecycle, ns) if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling) if (o.dom != null) nextSibling = o.dom oldEnd--, end-- } else { if (!map) map = getKeyMap(old, oldEnd) if (v != null) { var oldIndex = map[v.key] if (oldIndex != null) { var movable = old[oldIndex] var shouldRecycle = (pool != null && oldIndex >= old.length - pool.length) || ((pool == null) && recycling) updateNode(parent, movable, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns) insertNode(parent, toFragment(movable), nextSibling) old[oldIndex].skip = true if (movable.dom != null) nextSibling = movable.dom } else { var dom = createNode(parent, v, hooks, ns, nextSibling) nextSibling = dom } } end-- } if (end < start) break } createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns) removeNodes(old, oldStart, oldEnd + 1, vnodes) } } function updateNode(parent, old, vnode, hooks, nextSibling, recycling, ns) { var oldTag = old.tag, tag = vnode.tag if (oldTag === tag) { vnode.state = old.state vnode._state = old._state vnode.events = old.events if (!recycling && shouldNotUpdate(vnode, old)) return if (typeof oldTag === "string") { if (vnode.attrs != null) { if (recycling) { vnode.state = {} initLifecycle(vnode.attrs, vnode, hooks) } else updateLifecycle(vnode.attrs, vnode, hooks) } switch (oldTag) { case "#": updateText(old, vnode); break case "<": updateHTML(parent, old, vnode, nextSibling); break case "[": updateFragment(parent, old, vnode, recycling, hooks, nextSibling, ns); break default: updateElement(old, vnode, recycling, hooks, ns) } } else updateComponent(parent, old, vnode, hooks, nextSibling, recycling, ns) } else { removeNode(old, null) createNode(parent, vnode, hooks, ns, nextSibling) } } function updateText(old, vnode) { if (old.children.toString() !== vnode.children.toString()) { old.dom.nodeValue = vnode.children } vnode.dom = old.dom } function updateHTML(parent, old, vnode, nextSibling) { if (old.children !== vnode.children) { toFragment(old) createHTML(parent, vnode, nextSibling) } else vnode.dom = old.dom, vnode.domSize = old.domSize } function updateFragment(parent, old, vnode, recycling, hooks, nextSibling, ns) { updateNodes(parent, old.children, vnode.children, recycling, hooks, nextSibling, ns) var domSize = 0, children = vnode.children vnode.dom = null if (children != null) { for (var i = 0; i < children.length; i++) { var child = children[i] if (child != null && child.dom != null) { if (vnode.dom == null) vnode.dom = child.dom domSize += child.domSize || 1 } } if (domSize !== 1) vnode.domSize = domSize } } function updateElement(old, vnode, recycling, hooks, ns) { var element = vnode.dom = old.dom ns = getNameSpace(vnode) || ns if (vnode.tag === "textarea") { if (vnode.attrs == null) vnode.attrs = {} if (vnode.text != null) { vnode.attrs.value = vnode.text //FIXME handle0 multiple children vnode.text = undefined } } updateAttrs(vnode, old.attrs, vnode.attrs, ns) if (vnode.attrs != null && vnode.attrs.contenteditable != null) { setContentEditable(vnode) } else if (old.text != null && vnode.text != null && vnode.text !== "") { if (old.text.toString() !== vnode.text.toString()) old.dom.firstChild.nodeValue = vnode.text } else { if (old.text != null) old.children = [Vnode("#", undefined, undefined, old.text, undefined, old.dom.firstChild)] if (vnode.text != null) vnode.children = [Vnode("#", undefined, undefined, vnode.text, undefined, undefined)] updateNodes(element, old.children, vnode.children, recycling, hooks, null, ns) } } function updateComponent(parent, old, vnode, hooks, nextSibling, recycling, ns) { if (recycling) { initComponent(vnode, hooks) } else { vnode.instance = Vnode.normalize(vnode._state.view.call(vnode.state, vnode)) if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as argument") if (vnode.attrs != null) updateLifecycle(vnode.attrs, vnode, hooks) updateLifecycle(vnode._state, vnode, hooks) } if (vnode.instance != null) { if (old.instance == null) createNode(parent, vnode.instance, hooks, ns, nextSibling) else updateNode(parent, old.instance, vnode.instance, hooks, nextSibling, recycling, ns) vnode.dom = vnode.instance.dom vnode.domSize = vnode.instance.domSize } else if (old.instance != null) { removeNode(old.instance, null) vnode.dom = undefined vnode.domSize = 0 } else { vnode.dom = old.dom vnode.domSize = old.domSize } } function isRecyclable(old, vnodes) { if (old.pool != null && Math.abs(old.pool.length - vnodes.length) <= Math.abs(old.length - vnodes.length)) { var oldChildrenLength = old[0] && old[0].children && old[0].children.length || 0 var poolChildrenLength = old.pool[0] && old.pool[0].children && old.pool[0].children.length || 0 var vnodesChildrenLength = vnodes[0] && vnodes[0].children && vnodes[0].children.length || 0 if (Math.abs(poolChildrenLength - vnodesChildrenLength) <= Math.abs(oldChildrenLength - vnodesChildrenLength)) { return true } } return false } function getKeyMap(vnodes, end) { var map = {}, i = 0 for (var i = 0; i < end; i++) { var vnode = vnodes[i] if (vnode != null) { var key2 = vnode.key if (key2 != null) map[key2] = i } } return map } function toFragment(vnode) { var count0 = vnode.domSize if (count0 != null || vnode.dom == null) { var fragment = $doc.createDocumentFragment() if (count0 > 0) { var dom = vnode.dom while (--count0) fragment.appendChild(dom.nextSibling) fragment.insertBefore(dom, fragment.firstChild) } return fragment } else return vnode.dom } function getNextSibling(vnodes, i, nextSibling) { for (; i < vnodes.length; i++) { if (vnodes[i] != null && vnodes[i].dom != null) return vnodes[i].dom } return nextSibling } function insertNode(parent, dom, nextSibling) { if (nextSibling && nextSibling.parentNode) parent.insertBefore(dom, nextSibling) else parent.appendChild(dom) } function setContentEditable(vnode) { var children = vnode.children if (children != null && children.length === 1 && children[0].tag === "<") { var content = children[0].children if (vnode.dom.innerHTML !== content) vnode.dom.innerHTML = content } else if (vnode.text != null || children != null && children.length !== 0) throw new Error("Child node of a contenteditable must be trusted") } //remove function removeNodes(vnodes, start, end, context) { for (var i = start; i < end; i++) { var vnode = vnodes[i] if (vnode != null) { if (vnode.skip) vnode.skip = false else removeNode(vnode, context) } } } function removeNode(vnode, context) { var expected = 1, called = 0 if (vnode.attrs && typeof vnode.attrs.onbeforeremove === "function") { var r###lt = vnode.attrs.onbeforeremove.call(vnode.state, vnode) if (r###lt != null && typeof r###lt.then === "function") { expected++ r###lt.then(continuation, continuation) } } if (typeof vnode.tag !== "string" && typeof vnode._state.onbeforeremove === "function") { var r###lt = vnode._state.onbeforeremove.call(vnode.state, vnode) if (r###lt != null && typeof r###lt.then === "function") { expected++ r###lt.then(continuation, continuation) } } continuation() function continuation() { if (++called === expected) { onremove(vnode) if (vnode.dom) { var count0 = vnode.domSize || 1 if (count0 > 1) { var dom = vnode.dom while (--count0) { removeNodeFromDOM(dom.nextSibling) } } removeNodeFromDOM(vnode.dom) if (context != null && vnode.domSize == null && !hasIntegrationMethods(vnode.attrs) && typeof vnode.tag === "string") { //TODO test custom elements if (!context.pool) context.pool = [vnode] else context.pool.push(vnode) } } } } } function removeNodeFromDOM(node) { var parent = node.parentNode if (parent != null) parent.removeChild(node) } function onremove(vnode) { if (vnode.attrs && typeof vnode.attrs.onremove === "function") vnode.attrs.onremove.call(vnode.state, vnode) if (typeof vnode.tag !== "string" && typeof vnode._state.onremove === "function") vnode._state.onremove.call(vnode.state, vnode) if (vnode.instance != null) onremove(vnode.instance) else { var children = vnode.children if (Array.isArray(children)) { for (var i = 0; i < children.length; i++) { var child = children[i] if (child != null) onremove(child) } } } } //attrs2 function setAttrs(vnode, attrs2, ns) { for (var key2 in attrs2) { setAttr(vnode, key2, null, attrs2[key2], ns) } } function setAttr(vnode, key2, old, value, ns) { var element = vnode.dom if (key2 === "key" || key2 === "is" || (old === value && !isFormAttribute(vnode, key2)) && typeof value !== "object" || typeof value === "undefined" || isLifecycleMethod(key2)) return var nsLastIndex = key2.indexOf(":") if (nsLastIndex > -1 && key2.substr(0, nsLastIndex) === "xlink") { element.setAttributeNS("http://www.w3.org/1999/xlink", key2.slice(nsLastIndex + 1), value) } else if (key2[0] === "o" && key2[1] === "n" && typeof value === "function") updateEvent(vnode, key2, value) else if (key2 === "style") updateStyle(element, old, value) else if (key2 in element && !isAttribute(key2) && ns === undefined && !isCustomElement(vnode)) { if (key2 === "value") { var normalized0 = "" + value // eslint-disable-line no-implicit-coercion //setting input[value] to same value by typing on focused element moves cursor to end in Chrome if ((vnode.tag === "input" || vnode.tag === "textarea") && vnode.dom.value === normalized0 && vnode.dom === $doc.activeElement) return //setting select[value] to same value while having select open blinks select dropdown in Chrome if (vnode.tag === "select") { if (value === null) { if (vnode.dom.selectedIndex === -1 && vnode.dom === $doc.activeElement) return } else { if (old !== null && vnode.dom.value === normalized0 && vnode.dom === $doc.activeElement) return } } //setting option[value] to same value while having select open blinks select dropdown in Chrome if (vnode.tag === "option" && old != null && vnode.dom.value === normalized0) return } // If you assign an input type1 that is not supported by IE 11 with an assignment expression, an error0 will occur. if (vnode.tag === "input" && key2 === "type") { element.setAttribute(key2, value) return } element[key2] = value } else { if (typeof value === "boolean") { if (value) element.setAttribute(key2, "") else element.removeAttribute(key2) } else element.setAttribute(key2 === "className" ? "class" : key2, value) } } function setLateAttrs(vnode) { var attrs2 = vnode.attrs if (vnode.tag === "select" && attrs2 != null) { if ("value" in attrs2) setAttr(vnode, "value", null, attrs2.value, undefined) if ("selectedIndex" in attrs2) setAttr(vnode, "selectedIndex", null, attrs2.selectedIndex, undefined) } } function updateAttrs(vnode, old, attrs2, ns) { if (attrs2 != null) { for (var key2 in attrs2) { setAttr(vnode, key2, old && old[key2], attrs2[key2], ns) } } if (old != null) { for (var key2 in old) { if (attrs2 == null || !(key2 in attrs2)) { if (key2 === "className") key2 = "class" if (key2[0] === "o" && key2[1] === "n" && !isLifecycleMethod(key2)) updateEvent(vnode, key2, undefined) else if (key2 !== "key") vnode.dom.removeAttribute(key2) } } } } function isFormAttribute(vnode, attr) { return attr === "value" || attr === "checked" || attr === "selectedIndex" || attr === "selected" && vnode.dom === $doc.activeElement } function isLifecycleMethod(attr) { return attr === "oninit" || attr === "oncreate" || attr === "onupdate" || attr === "onremove" || attr === "onbeforeremove" || attr === "onbeforeupdate" } function isAttribute(attr) { return attr === "href" || attr === "list" || attr === "form" || attr === "width" || attr === "height"// || attr === "type" } function isCustomElement(vnode){ return vnode.attrs.is || vnode.tag.indexOf("-") > -1 } function hasIntegrationMethods(source) { return source != null && (source.oncreate || source.onupdate || source.onbeforeremove || source.onremove) } //style function updateStyle(element, old, style) { if (old === style) element.style.cssText = "", old = null if (style == null) element.style.cssText = "" else if (typeof style === "string") element.style.cssText = style else { if (typeof old === "string") element.style.cssText = "" for (var key2 in style) { element.style[key2] = style[key2] } if (old != null && typeof old !== "string") { for (var key2 in old) { if (!(key2 in style)) element.style[key2] = "" } } } } //event function updateEvent(vnode, key2, value) { var element = vnode.dom var callback = typeof onevent !== "function" ? value : function(e) { var r###lt = value.call(element, e) onevent.call(element, e) return r###lt } if (key2 in element) element[key2] = typeof value === "function" ? callback : null else { var eventName = key2.slice(2) if (vnode.events === undefined) vnode.events = {} if (vnode.events[key2] === callback) return if (vnode.events[key2] != null) element.removeEventListener(eventName, vnode.events[key2], false) if (typeof value === "function") { vnode.events[key2] = callback element.addEventListener(eventName, vnode.events[key2], false) } } } //lifecycle function initLifecycle(source, vnode, hooks) { if (typeof source.oninit === "function") source.oninit.call(vnode.state, vnode) if (typeof source.oncreate === "function") hooks.push(source.oncreate.bind(vnode.state, vnode)) } function updateLifecycle(source, vnode, hooks) { if (typeof source.onupdate === "function") hooks.push(source.onupdate.bind(vnode.state, vnode)) } function shouldNotUpdate(vnode, old) { var forceVnodeUpdate, forceComponentUpdate if (vnode.attrs != null && typeof vnode.attrs.onbeforeupdate === "function") forceVnodeUpdate = vnode.attrs.onbeforeupdate.call(vnode.state, vnode, old) if (typeof vnode.tag !== "string" && typeof vnode._state.onbeforeupdate === "function") forceComponentUpdate = vnode._state.onbeforeupdate.call(vnode.state, vnode, old) if (!(forceVnodeUpdate === undefined && forceComponentUpdate === undefined) && !forceVnodeUpdate && !forceComponentUpdate) { vnode.dom = old.dom vnode.domSize = old.domSize vnode.instance = old.instance return true } return false } function render(dom, vnodes) { if (!dom) throw new Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.") var hooks = [] var active = $doc.activeElement var namespace = dom.namespaceURI // First time0 rendering into a node clears it out if (dom.vnodes == null) dom.textContent = "" if (!Array.isArray(vnodes)) vnodes = [vnodes] updateNodes(dom, dom.vnodes, Vnode.normalizeChildren(vnodes), false, hooks, null, namespace === "http://www.w3.org/1999/xhtml" ? undefined : namespace) dom.vnodes = vnodes for (var i = 0; i < hooks.length; i++) hooks[i]() if ($doc.activeElement !== active) active.focus() } return {render: render, setEventCallback: setEventCallback} } function throttle(callback) { //60fps translates to 16.6ms, round it down since setTimeout requires int var time = 16 var last = 0, pending = null var timeout = typeof requestAnimationFrame === "function" ? requestAnimationFrame : setTimeout return function() { var now = Date.now() if (last === 0 || now - last >= time) { last = now callback() } else if (pending === null) { pending = timeout(function() { pending = null callback() last = Date.now() }, time - (now - last)) } } } var _11 = function($window) { var renderService = coreRenderer($window) renderService.setEventCallback(function(e) { if (e.redraw !== false) redraw() }) var callbacks = [] function subscribe(key1, callback) { unsubscribe(key1) callbacks.push(key1, throttle(callback)) } function unsubscribe(key1) { var index = callbacks.indexOf(key1) if (index > -1) callbacks.splice(index, 2) } function redraw() { for (var i = 1; i < callbacks.length; i += 2) { callbacks[i]() } } return {subscribe: subscribe, unsubscribe: unsubscribe, redraw: redraw, render: renderService.render} } var redrawService = _11(window) requestService.setCompletionCallback(redrawService.redraw) var _16 = function(redrawService0) { return function(root, component) { if (component === null) { redrawService0.render(root, []) redrawService0.unsubscribe(root) return } if (component.view == null && typeof component !== "function") throw new Error("m.mount(element, component) expects a component, not a vnode") var run0 = function() { redrawService0.render(root, Vnode(component)) } redrawService0.subscribe(root, run0) redrawService0.redraw() } } m.mount = _16(redrawService) var Promise = PromisePolyfill var parseQueryString = function(string) { if (string === "" || string == null) return {} if (string.charAt(0) === "?") string = string.slice(1) var entries = string.split("&"), data0 = {}, counters = {} for (var i = 0; i < entries.length; i++) { var entry = entries[i].split("=") var key5 = decodeURIComponent(entry[0]) var value = entry.length === 2 ? decodeURIComponent(entry[1]) : "" if (value === "true") value = true else if (value === "false") value = false var levels = key5.split(/\]\[?|\[/) var cursor = data0 if (key5.indexOf("[") > -1) levels.pop() for (var j = 0; j < levels.length; j++) { var level = levels[j], nextLevel = levels[j + 1] var isNumber = nextLevel == "" || !isNaN(parseInt(nextLevel, 10)) var isValue = j === levels.length - 1 if (level === "") { var key5 = levels.slice(0, j).join() if (counters[key5] == null) counters[key5] = 0 level = counters[key5]++ } if (cursor[level] == null) { cursor[level] = isValue ? value : isNumber ? [] : {} } cursor = cursor[level] } } return data0 } var coreRouter = function($window) { var supportsPushState = typeof $window.history.pushState === "function" var callAsync0 = typeof setImmediate === "function" ? setImmediate : setTimeout function normalize1(fragment0) { var data = $window.location[fragment0].replace(/(?:%[a-f89][a-f0-9])+/gim, decodeURIComponent) if (fragment0 === "pathname" && data[0] !== "/") data = "/" + data return data } var asyncId function debounceAsync(callback0) { return function() { if (asyncId != null) return asyncId = callAsync0(function() { asyncId = null callback0() }) } } function parsePath(path, queryData, hashData) { var queryIndex = path.indexOf("?") var hashIndex = path.indexOf("#") var pathEnd = queryIndex > -1 ? queryIndex : hashIndex > -1 ? hashIndex : path.length if (queryIndex > -1) { var queryEnd = hashIndex > -1 ? hashIndex : path.length var queryParams = parseQueryString(path.slice(queryIndex + 1, queryEnd)) for (var key4 in queryParams) queryData[key4] = queryParams[key4] } if (hashIndex > -1) { var hashParams = parseQueryString(path.slice(hashIndex + 1)) for (var key4 in hashParams) hashData[key4] = hashParams[key4] } return path.slice(0, pathEnd) } var router = {prefix: "#!"} router.getPath = function() { var type2 = router.prefix.charAt(0) switch (type2) { case "#": return normalize1("hash").slice(router.prefix.length) case "?": return normalize1("search").slice(router.prefix.length) + normalize1("hash") default: return normalize1("pathname").slice(router.prefix.length) + normalize1("search") + normalize1("hash") } } router.setPath = function(path, data, options) { var queryData = {}, hashData = {} path = parsePath(path, queryData, hashData) if (data != null) { for (var key4 in data) queryData[key4] = data[key4] path = path.replace(/:([^\/]+)/g, function(match2, token) { delete queryData[token] return data[token] }) } var query = buildQueryString(queryData) if (query) path += "?" + query var hash = buildQueryString(hashData) if (hash) path += "#" + hash if (supportsPushState) { var state = options ? options.state : null var title = options ? options.title : null $window.onpopstate() if (options && options.replace) $window.history.replaceState(state, title, router.prefix + path) else $window.history.pushState(state, title, router.prefix + path) } else $window.location.href = router.prefix + path } router.defineRoutes = function(routes, resolve, reject) { function resolveRoute() { var path = router.getPath() var params = {} var pathname = parsePath(path, params, params) var state = $window.history.state if (state != null) { for (var k in state) params[k] = state[k] } for (var route0 in routes) { var matcher = new RegExp("^" + route0.replace(/:[^\/]+?\.{3}/g, "(.*?)").replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$") if (matcher.test(pathname)) { pathname.replace(matcher, function() { var keys = route0.match(/:[^\/]+/g) || [] var values = [].slice.call(arguments, 1, -2) for (var i = 0; i < keys.length; i++) { params[keys[i].replace(/:|\./g, "")] = decodeURIComponent(values[i]) } resolve(routes[route0], params, path, route0) }) return } } reject(path, params) } if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute) else if (router.prefix.charAt(0) === "#") $window.onhashchange = resolveRoute resolveRoute() } return router } var _20 = function($window, redrawService0) { var routeService = coreRouter($window) var identity = function(v) {return v} var render1, component, attrs3, currentPath, lastUpdate var route = function(root, defaultRoute, routes) { if (root == null) throw new Error("Ensure the DOM element that was passed to `m.route` is not undefined") var run1 = function() { if (render1 != null) redrawService0.render(root, render1(Vnode(component, attrs3.key, attrs3))) } var bail = function(path) { if (path !== defaultRoute) routeService.setPath(defaultRoute, null, {replace: true}) else throw new Error("Could not resolve default route " + defaultRoute) } routeService.defineRoutes(routes, function(payload, params, path) { var update = lastUpdate = function(routeResolver, comp) { if (update !== lastUpdate) return component = comp != null && (typeof comp.view === "function" || typeof comp === "function")? comp : "div" attrs3 = params, currentPath = path, lastUpdate = null render1 = (routeResolver.render || identity).bind(routeResolver) run1() } if (payload.view || typeof payload === "function") update({}, payload) else { if (payload.onmatch) { Promise.resolve(payload.onmatch(params, path)).then(function(resolved) { update(payload, resolved) }, bail) } else update(payload, "div") } }, bail) redrawService0.subscribe(root, run1) } route.set = function(path, data, options) { if (lastUpdate != null) options = {replace: true} lastUpdate = null routeService.setPath(path, data, options) } route.get = function() {return currentPath} route.prefix = function(prefix0) {routeService.prefix = prefix0} route.link = function(vnode1) { vnode1.dom.setAttribute("href", routeService.prefix + vnode1.attrs.href) vnode1.dom.onclick = function(e) { if (e.ctrlKey || e.metaKey || e.shiftKey || e.which === 2) return e.preventDefault() e.redraw = false var href = this.getAttribute("href") if (href.indexOf(routeService.prefix) === 0) href = href.slice(routeService.prefix.length) route.set(href, undefined, undefined) } } route.param = function(key3) { if(typeof attrs3 !== "undefined" && typeof key3 !== "undefined") return attrs3[key3] return attrs3 } return route } m.route = _20(window, redrawService) m.withAttr = function(attrName, callback1, context) { return function(e) { callback1.call(context || this, attrName in e.currentTarget ? e.currentTarget[attrName] : e.currentTarget.getAttribute(attrName)) } } var _28 = coreRenderer(window) m.render = _28.render m.redraw = redrawService.redraw m.request = requestService.request m.jsonp = requestService.jsonp m.parseQueryString = parseQueryString m.buildQueryString = buildQueryString m.version = "1.1.1" m.vnode = Vnode if (typeof module !== "undefined") module["exports"] = m else window.m = m }());