diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..a6ff032f --- /dev/null +++ b/.eslintignore @@ -0,0 +1,9 @@ +# Most of these are build artifacts. +node_modules +**/*.min.js +archive +deploy +mithril.closure-compiler-externs.js + +# This is merely a dependency for the documentation. +docs/layout/lib diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 00000000..f77243f3 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,93 @@ +{ + "extends": "eslint:recommended", + + "env": { + "browser": true + }, + + "rules": { + "indent": [2, "tab"], + "no-trailing-spaces": 2, + "linebreak-style": [2, "windows"], + + "curly": [2, "multi-line"], + "dot-location": [2, "property"], + "dot-notation": 2, + "eqeqeq": [2, "allow-null"], + "no-alert": 2, + "no-caller": 2, + "guard-for-in": 2, + "no-empty-label": 2, + "no-eval": 2, + "no-extend-native": 2, + "no-extra-bind": 2, + "no-floating-decimal": 2, + "no-implied-eval": 2, + "no-invalid-this": 2, + "no-iterator": 2, + "no-lone-blocks": 2, + "no-loop-func": 2, + "no-multi-spaces": 2, + "no-multi-str": 2, + "no-native-reassign": 2, + "no-new-func": 2, + "no-new-wrappers": 2, + "no-octal-escape": 2, + "no-proto": 2, + "no-self-compare": 2, + "no-sequences": 2, + "no-throw-literal": 2, + "no-unused-expressions": 2, + "no-useless-call": 2, + "no-void": 2, + "no-warning-comments": 1, + "radix": 2, + "wrap-iife": [2, "inside"], + "strict": [2, "function"], + "no-catch-shadow": 2, + "no-label-var": 2, + "no-shadow-restricted-names": 2, + "no-undef-init": 2, + "no-use-before-define": [2, "nofunc"], + "array-bracket-spacing": 2, + "block-spacing": 2, + "brace-style": [2, "1tbs", {"allowSingleLine": true}], + "camelcase": 2, + "comma-spacing": 2, + "comma-style": 2, + "consistent-this": [2, "self"], + "eol-last": 2, + "func-style": [2, "declaration"], + "key-spacing": 2, + "max-nested-callbacks": [2, 4], + "new-parens": 2, + "no-array-constructor": 2, + "no-lonely-if": 2, + "no-new-object": 2, + "no-multiple-empty-lines": [2, {"max": 2}], + "no-spaced-func": 2, + "no-unneeded-ternary": 2, + "object-curly-spacing": 2, + "one-var": [2, {"initialized": "never"}], + "operator-assignment": 2, + "operator-linebreak": [2, "after"], + "padded-blocks": [2, "never"], + "quote-props": [2, "as-needed"], + "quotes": [2, "double", "avoid-escape"], + "semi-spacing": 0, + "semi": [2, "never"], + "space-after-keywords": 2, + "space-before-function-paren": [2, { + "anonymous": "always", + "named": "never" + }], + "space-before-keywords": 2, + "space-in-parens": 2, + "space-infix-ops": [2, {"int32Hint": false}], + "space-unary-ops": 2, + "spaced-comment": [2, "always"], + "max-depth": [2, 4], + "max-len": [2, 80, 4], + "max-statements": [2, 20] + } +} diff --git a/.travis.yml b/.travis.yml index 62c2fed2..2b42d205 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,5 @@ language: node_js node_js: - - "0.10" - - "0.12" - - "4.1" - -script: - - grunt test - - grunt teste2e + - stable sudo: false diff --git a/Gruntfile.js b/Gruntfile.js index b4dda36a..a22e6198 100755 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,11 +1,11 @@ -module.exports = function(grunt) { - var _ = require("lodash"); - var version = "0.2.1"; +/* eslint-env node */ +module.exports = function (grunt) { // eslint-disable-line + var version = "0.2.1" - var inputFolder = "./docs"; - var tempFolder = "./temp"; - var archiveFolder = "./archive"; - var outputFolder = "../mithril"; + var inputFolder = "./docs" + var tempFolder = "./temp" + var archiveFolder = "./archive" + var outputFolder = "../mithril" var guide = [ "auto-redrawing", @@ -22,7 +22,8 @@ module.exports = function(grunt) { "routing", "tools", "web-services" - ]; + ] + var api = [ "change-log", "roadmap", @@ -42,178 +43,342 @@ module.exports = function(grunt) { "mithril.trust", "mithril.withAttr", "mithril.xhr" - ]; + ] + + var md2htmlTasks = {} + function makeTasks(layout, pages) { + pages.forEach(function (name) { + var src = inputFolder + "/" + name + ".md" + var title = "" + + if (grunt.file.exists(src)) { + title = grunt.file.read(src).split(/\n/)[0].substring(3) + + " - " + } - var md2htmlTasks = {}; - var makeTasks = function(layout, pages) { - pages.map(function(name) { - var src = inputFolder + "/" + name + ".md"; - var title = (grunt.file.exists(src)) ? grunt.file.read(src).split(/\n/)[0].substring(3) + ' - ' : ''; md2htmlTasks[name] = { - options: {layout: inputFolder + "/layout/" + layout + ".html", templateData: { "topic": title }}, - files: [{src: [src], dest: tempFolder + "/" + name + ".html"}] + options: { + layout: inputFolder + "/layout/" + layout + ".html", + templateData: {topic: title} + }, + files: [{ + src: [src], + dest: tempFolder + "/" + name + ".html" + }] } }) - }; - makeTasks("guide", guide); - makeTasks("api", api); + } - var sauceBrowsers =[ - { browserName: 'firefox', version: '19', platform: 'XP' }, - { browserName: "internet explorer", platform: "XP", version: "6"}, - { browserName: "safari", platform: "OS X 10.9", version: "7"}, - { browserName: "iPad", platform: "OS X 10.9", version: "7.1"}, - { browserName: "opera", platform: "Linux", version: "12"}, - { browserName: "chrome", platform: "XP", version: "26"}, - { browserName: "chrome", platform: "Windows 8", version: "26"} - ]; + makeTasks("guide", guide) + makeTasks("api", api) - var sauceOnTestComplete = function(result, callback) { - var request = require('request'); + var currentVersionArchiveFolder = archiveFolder + "/v" + version - var user = process.env.SAUCE_USERNAME; - var pass = process.env.SAUCE_ACCESS_KEY; - - request.put({ - url: ['https://saucelabs.com/rest/v1', user, 'jobs', result.job_id].join('/'), - auth: { user: user, pass: pass }, - json: { passed: result.passed } - }, function (error, response) { - if (error) { - callback(error); - } else if (response.statusCode !== 200) { - callback(new Error('Unexpected response status: ' - + response.statusCode + "\n ")); - } else { - callback(null, result.passed); - } - }); - }; - - var sauceBaseOptions = { - username: process.env.SAUCE_USERNAME, - key: process.env.SAUCE_ACCESS_KEY, - testname: "Mithril Tests " + new Date().toJSON(), - browsers: sauceBrowsers, - sauceConfig: { - "record-video": false, - "record-screenshots": false, - }, - build: process.env.TRAVIS_JOB_ID, - onTestComplete: sauceOnTestComplete, - tunnelTimeout: 5, - }; - var sauceCustomOptions = { - testname: "Mithril Custom Tests "+ new Date().toJSON(), - urls: ["http://127.0.0.1:8000/tests/index.html"], - }; - _.assign(sauceCustomOptions, sauceBaseOptions); - var sauceQunitOptions = { - testname: "qUnit Tests "+ new Date().toJSON(), - urls: ["http://127.0.0.1:8000/tests/e2e/test.html"], - }; - _.assign(sauceQunitOptions, sauceBaseOptions); - - var currentVersionArchiveFolder = archiveFolder + "/v" + version; grunt.initConfig({ + // Keep this in sync with the .eslintignore + eslint: { + options: { + extensions: [".js"], + fix: true + }, + all: [ + "**/*.js", + "!node_modules/**", + "!**/*.min.js", + "!archive/**", + "!deploy/**", + "!mithril.closure-compiler-externs.js", + "!docs/layout/lib/**" + ] + }, + + mocha_phantomjs: { // eslint-disable-line camelcase + test: { + src: ["test/index.html"], + options: { + reporter: "dot" + } + } + }, + md2html: md2htmlTasks, + uglify: { - options: {banner: "/*\nMithril v" + version + "\nhttp://github.com/lhorie/mithril.js\n(c) Leo Horie\nLicense: MIT\n*/", sourceMap: true}, + options: { + banner: [ + "/*", + "Mithril v" + version, + "http://github.com/lhorie/mithril.js", + "(c) Leo Horie", + "License: MIT", + "*/" + ].join("\n"), + sourceMap: true + }, mithril: {src: "mithril.js", dest: "mithril.min.js"} }, - concat: { - test: {src: ["mithril.js", "./tests/test.js", "./tests/mock.js", "./tests/mithril-tests.js"], dest: currentVersionArchiveFolder + "/mithril-tests.js"} - }, + zip: { distribution: { cwd: currentVersionArchiveFolder + "/", - src: [currentVersionArchiveFolder + "/mithril.min.js", currentVersionArchiveFolder + "/mithril.min.js.map", currentVersionArchiveFolder + "/mithril.js"], + src: [ + currentVersionArchiveFolder + "/mithril.min.js", + currentVersionArchiveFolder + "/mithril.min.js.map", + currentVersionArchiveFolder + "/mithril.js" + ], dest: currentVersionArchiveFolder + "/mithril.min.zip" } }, + replace: { - options: {force: true, patterns: [{match: /\.md/g, replacement: ".html"}, {match: /\$version/g, replacement: version}]}, - links: {expand: true, flatten: true, src: [tempFolder + "/**/*.html"], dest: currentVersionArchiveFolder + "/"}, - index: {src: inputFolder + "/layout/index.html", dest: currentVersionArchiveFolder + "/index.html"}, - commonjs: {expand: true, flatten: true, src: [inputFolder + "/layout/*.json"], dest: currentVersionArchiveFolder}, - cdnjs: {src: "deploy/cdnjs-package.json", dest: "../cdnjs/ajax/libs/mithril/package.json"} + options: { + force: true, + patterns: [ + {match: /\.md/g, replacement: ".html"}, + {match: /\$version/g, replacement: version} + ] + }, + + links: { + expand: true, + flatten: true, + src: [tempFolder + "/**/*.html"], + dest: currentVersionArchiveFolder + "/" + }, + + index: { + src: inputFolder + "/layout/index.html", + dest: currentVersionArchiveFolder + "/index.html" + }, + + commonjs: { + expand: true, + flatten: true, + src: [inputFolder + "/layout/*.json"], + dest: currentVersionArchiveFolder + }, + + cdnjs: { + src: "deploy/cdnjs-package.json", + dest: "../cdnjs/ajax/libs/mithril/package.json" + } }, + copy: { - style: {src: inputFolder + "/layout/style.css", dest: currentVersionArchiveFolder + "/style.css"}, - pages: {src: inputFolder + "/layout/pages.json", dest: currentVersionArchiveFolder + "/pages.json"}, - lib: {expand: true, cwd: inputFolder + "/layout/lib/", src: "./**", dest: currentVersionArchiveFolder + "/lib/"}, - tools: {expand: true, cwd: inputFolder + "/layout/tools/", src: "./**", dest: currentVersionArchiveFolder + "/tools/"}, - comparisons: {expand: true, cwd: inputFolder + "/layout/comparisons/", src: "./**", dest: currentVersionArchiveFolder + "/comparisons/"}, - unminified: {src: "mithril.js", dest: currentVersionArchiveFolder + "/mithril.js"}, - minified: {src: "mithril.min.js", dest: currentVersionArchiveFolder + "/mithril.min.js"}, - readme: {src: "README.md", dest: currentVersionArchiveFolder + "/README.md"}, - map: {src: "mithril.min.js.map", dest: currentVersionArchiveFolder + "/mithril.min.js.map"}, - typescript: {src: "mithril.d.ts", dest: currentVersionArchiveFolder + "/mithril.d.ts"}, - publish: {expand: true, cwd: currentVersionArchiveFolder, src: "./**", dest: outputFolder}, - archive: {expand: true, cwd: currentVersionArchiveFolder, src: "./**", dest: outputFolder + "/archive/v" + version}, - }, - execute: { - tests: {src: [currentVersionArchiveFolder + "/mithril-tests.js"]} - }, - qunit: { - all: ['tests/e2e/**/*.html'] - }, - "saucelabs-custom": { - all:{ - options: sauceCustomOptions + style: { + src: inputFolder + "/layout/style.css", + dest: currentVersionArchiveFolder + "/style.css" + }, + + pages: { + src: inputFolder + "/layout/pages.json", + dest: currentVersionArchiveFolder + "/pages.json" + }, + + lib: { + expand: true, + cwd: inputFolder + "/layout/lib/", + src: "./**", + dest: currentVersionArchiveFolder + "/lib/" + }, + + tools: { + expand: true, + cwd: inputFolder + "/layout/tools/", + src: "./**", + dest: currentVersionArchiveFolder + "/tools/" + }, + + comparisons: { + expand: true, + cwd: inputFolder + "/layout/comparisons/", + src: "./**", + dest: currentVersionArchiveFolder + "/comparisons/" + }, + + unminified: { + src: "mithril.js", + dest: currentVersionArchiveFolder + "/mithril.js" + }, + + minified: { + src: "mithril.min.js", + dest: currentVersionArchiveFolder + "/mithril.min.js" + }, + + readme: { + src: "README.md", + dest: currentVersionArchiveFolder + "/README.md" + }, + + map: { + src: "mithril.min.js.map", + dest: currentVersionArchiveFolder + "/mithril.min.js.map" + }, + + typescript: { + src: "mithril.d.ts", + dest: currentVersionArchiveFolder + "/mithril.d.ts" + }, + + publish: { + expand: true, + cwd: currentVersionArchiveFolder, + src: "./**", + dest: outputFolder + }, + + archive: { + expand: true, + cwd: currentVersionArchiveFolder, + src: "./**", + dest: outputFolder + "/archive/v" + version } }, - "saucelabs-qunit": { - all:{ - options: sauceQunitOptions + + "saucelabs-browsers": { + firefox: { + filter: function (browsers) { + return browsers.filter(function (browser) { + if (browser.browserName !== "firefox") return false + var version = browser.version + return version === "dev" || version === "beta" || + +version >= 38 // The latest ESR version + }) + } + }, + + chrome: { + filter: function (browsers) { + return browsers.filter(function (browser) { + if (browser.browserName !== "chrome") return false + var version = browser.version + return version === "dev" || version === "beta" || + +version >= 41 + }) + } + }, + + ie: { + filter: function (browsers) { + return browsers.filter(function (browser) { + return browser.browserName === "internet explorer" && + !/2003/.test(browser.platform) + }) + } + }, + + edge: { + filter: function (browsers) { + return browsers.filter(function (browser) { + return browser.browserName === "microsoftedge" + }) + } + }, + + safari: { + filter: function (browsers) { + return browsers.filter(function (browser) { + return browser.browserName === "safari" + }) + } + }, + + opera: { + filter: function (browsers) { + return browsers.filter(function (browser) { + return browser.browserName === "opera" + }) + } + } + }, + + saucelabs: { + all: { + options: { + username: process.env.SAUCE_USERNAME, + key: process.env.SAUCE_ACCESS_KEY, + testname: "Mithril Tests " + new Date().toJSON(), + browsers: "<%= saucelabs.browsers %>", + urls: ["http://localhost:8000/test/index.html"], + sauceConfig: { + "record-video": false, + "record-screenshots": false + }, + build: process.env.TRAVIS_JOB_ID, + onTestComplete: function (result, callback) { + var user = process.env.SAUCE_USERNAME + var pass = process.env.SAUCE_ACCESS_KEY + + var url = [ + "https://saucelabs.com/rest/v1", user, "jobs", + result.job_id + ].join("/") + + require("request").put({ + url: url, + auth: {user: user, pass: pass}, + json: {passed: result.passed} + }, function (error, response) { + if (error) { + return callback(error) + } else if (response.statusCode !== 200) { + return callback(new Error( + "Unexpected response status: " + + response.statusCode + "\n ")) + } else { + return callback(null, result.passed) + } + }) + }, + tunnelTimeout: 5 + } } }, - watch: {}, connect: { server: { options: { port: 8888, - base: '.' + base: "." } } }, + clean: { options: {force: true}, generated: [tempFolder] - }, - jsfmt: { - default: { - files: [{ - expand: true, - src: ['mithril.js'], - cwd: '.', - dest: '.' - }] - } } - }); + }) - grunt.loadNpmTasks("grunt-contrib-clean"); - grunt.loadNpmTasks('grunt-contrib-concat'); - grunt.loadNpmTasks("grunt-contrib-copy"); - grunt.loadNpmTasks("grunt-contrib-uglify"); - grunt.loadNpmTasks('grunt-execute'); - grunt.loadNpmTasks("grunt-md2html"); - grunt.loadNpmTasks("grunt-replace"); - grunt.loadNpmTasks('grunt-zip'); - grunt.loadNpmTasks('grunt-contrib-qunit'); - grunt.loadNpmTasks('grunt-contrib-connect'); - grunt.loadNpmTasks('grunt-saucelabs'); - grunt.loadNpmTasks('grunt-jsfmt'); + grunt.loadNpmTasks("grunt-saucelabs-browsers") + grunt.loadNpmTasks("grunt-contrib-clean") + grunt.loadNpmTasks("grunt-contrib-copy") + grunt.loadNpmTasks("grunt-contrib-uglify") + grunt.loadNpmTasks("grunt-md2html") + grunt.loadNpmTasks("grunt-replace") + grunt.loadNpmTasks("grunt-zip") + grunt.loadNpmTasks("grunt-contrib-connect") + grunt.loadNpmTasks("grunt-saucelabs") + grunt.loadNpmTasks("grunt-eslint") + grunt.loadNpmTasks("grunt-mocha-phantomjs") - grunt.registerTask("build", ["test", "uglify", "zip", "md2html", "replace", "copy", "clean"]); - grunt.registerTask("testall", ["test", "teste2e"]); - grunt.registerTask("test", ["concat", "execute"]); - grunt.registerTask('teste2e', ['connect', 'qunit']); - grunt.registerTask("default", ["build"]); + grunt.registerTask("build", [ + "test", + "uglify", + "zip", + "md2html", + "replace", + "copy", + "clean" + ]) - grunt.registerTask("sauce-qunit", ["connect", "saucelabs-qunit"]); - grunt.registerTask("sauce-custom", ["connect", "saucelabs-custom"]); - grunt.registerTask("sauce-all", ["connect", "saucelabs-qunit", "saucelabs-custom"]); -}; + grunt.registerTask("test", ["eslint:all", "mocha_phantomjs"]) + grunt.registerTask("default", ["build"]) + + grunt.registerTask("sauce", [ + "saucelabs-browsers:all", + "connect", + "saucelabs" + ]) +} diff --git a/docs/layout/pages.json b/docs/layout/pages.json index e915b6ac..bfd52f9a 100644 --- a/docs/layout/pages.json +++ b/docs/layout/pages.json @@ -3,4 +3,4 @@ {"title": "Documentation", "url": "mithril.html"}, {"title": "Mithril Blog", "url": "http://lhorie.github.io/mithril-blog/"}, {"title": "Mailing List", "url": "https://groups.google.com/forum/#!forum/mithriljs"} -] \ No newline at end of file +] diff --git a/docs/layout/tools/template-converter.html b/docs/layout/tools/template-converter.html index b8c077cf..fb5d2aa0 100644 --- a/docs/layout/tools/template-converter.html +++ b/docs/layout/tools/template-converter.html @@ -1,9 +1,17 @@ -

If you already have your HTML written and want to convert it into a Mithril template, paste the HTML below and press the "Convert" button.

+ +HTML to Mithril Template Converter + +

HTML to Mithril Template Converter

+

+ If you have HTML you want to convert into a Mithril template, paste it below + and press the "Convert" button. In case you're wondering, this itself is a + Mithril app. +

\ No newline at end of file +m.mount(document.getElementById("converter"), templateConverter) + diff --git a/docs/layout/tools/template-converter.js b/docs/layout/tools/template-converter.js index b8cd01cf..55217ba3 100644 --- a/docs/layout/tools/template-converter.js +++ b/docs/layout/tools/template-converter.js @@ -1,90 +1,145 @@ -var templateConverter = {}; +/* global m: false */ +// TODO: ensure this targets the current API. +window.templateConverter = (function () { + "use strict" -templateConverter.DOMFragment = function(markup) { - if (markup.indexOf(" -1) return [new DOMParser().parseFromString(markup, "text/html").childNodes[1]] - var container = document.createElement("div"); - container.insertAdjacentHTML("beforeend", markup); - return container.childNodes; -} -templateConverter.VirtualFragment = function recurse(domFragment) { - var virtualFragment = []; - for (var i = 0, el; el = domFragment[i]; i++) { - if (el.nodeType == 3) { - virtualFragment.push(el.nodeValue); - } - else if (el.nodeType == 1) { - var attrs = {}; - for (var j = 0, attr; attr = el.attributes[j]; j++) { - attrs[attr.name] = attr.value; - } - - virtualFragment.push({tag: el.nodeName.toLowerCase(), attrs: attrs, children: recurse(el.childNodes)}); + function each(list, f) { + for (var i = 0; i < list.length; i++) { + f(list[i], i) } } - return virtualFragment; -} -templateConverter.Template = function recurse() { - if (Object.prototype.toString.call(arguments[0]) == "[object String]") { - return new recurse(new templateConverter.VirtualFragment(new templateConverter.DOMFragment(arguments[0]))); - } - - var virtualFragment = arguments[0], level = arguments[1] - if (!level) level = 1; - - var tab = "\n" + new Array(level + 1).join("\t"); - var virtuals = []; - for (var i = 0, el; el = virtualFragment[i]; i++) { - if (typeof el == "string") { - if (el.match(/\t| {2,}/g) && el.trim().length == 0) virtuals.indented = true; - else virtuals.push('"' + el.replace(/"/g, '\\"').replace(/\r/g, "\\r").replace(/\n/g, "\\n") + '"'); + + function createFragment(markup) { + if (markup.indexOf("= 0) { + return [ + new DOMParser() + .parseFromString(markup, "text/html") + .childNodes[1] + ] } - else { - var virtual = ""; - if (el.tag != "div") virtual += el.tag; - if (el.attrs["class"]) { - virtual += "." + el.attrs["class"].replace(/\t+/g, " ").split(" ").join("."); - delete el.attrs["class"]; + + var container = document.createElement("div") + container.insertAdjacentHTML("beforeend", markup) + return container.childNodes + } + + function createVirtual(fragment) { + var list = [] + + each(fragment, function (el) { + if (el.nodeType === 3) { + list.push(el.nodeValue) + } else if (el.nodeType === 1) { + var attrs = {} + + each(el.attributes, function (attr) { + attrs[attr.name] = attr.value + }) + + list.push({ + tag: el.nodeName.toLowerCase(), + attrs: attrs, + children: createVirtual(el.childNodes) + }) } - var attrNames = Object.keys(el.attrs).sort() - for (var j = 0, attrName; attrName = attrNames[j]; j++) { - if (attrName != "style") virtual += "[" + attrName + "='" + el.attrs[attrName].replace(/'/g, "\\'") + "']"; + }) + + return list + } + + function TemplateBuilder(virtual, level) { + this.virtual = virtual + this.level = level + this.virtuals = [] + this.indented = false + } + + TemplateBuilder.prototype = { + addVirtualString: function (el) { + if (/\t| {2,}/.test(el) && /^\s*/.test(el)) { + this.indented = true + } else { + this.virtuals.push('"' + el.replace(/(["\r\n])/g, "\\$1") + '"') } - if (virtual == "") virtual = "div" - virtual = '"' + virtual + '"'; - - var style = "" + }, + + addVirtualAttrs: function (el) { + var virtual = el.tag === "div" ? "" : el.tag + + if (el.attrs.class) { + virtual += "." + el.attrs.class.replace(/\s+/g, ".") + el.attrs.class = undefined + } + + each(Object.keys(el.attrs).sort(), function (attrName) { + if (attrName === "style") return + virtual += "[" + attrName + "='" + virtual += el.attrs[attrName].replace(/'/g, "\\'") + "']" + }) + + if (virtual === "") virtual = "div" + virtual = '"' + virtual + '"' + if (el.attrs.style) { - virtual += ", {style: " + ("{\"" + el.attrs.style.replace(/:/g, "\": \"").replace(/;/g, "\", \"") + "}").replace(/, "}|"}/, "}") + "}" + var style = "{\"" + el.attrs.style + .replace(/:/g, "\": \"") + .replace(/;/g, "\", \"") + "}" + virtual += ", {style: " + style.replace(/(, )"}/, "}") + "}" } - - if (el.children.length > 0) { - virtual += ", " + recurse(el.children, level + 1); + + if (el.children.length !== 0) { + var builder = new TemplateBuilder(el.children, this.level + 1) + virtual += ", " + builder.complete() + } + + this.virtuals.push("m(" + virtual + ")") + }, + + complete: function () { + var tab = "\n" + for (var i = 0; i <= this.level; i++) tab += "\t" + + each(this.virtual, function (el) { + if (typeof el === "string") { + this.addVirtualString(el) + } else { + this.addVirtualAttrs(el) + } + }.bind(this)) + + if (!this.indented) tab = "" + + if (this.virtuals.length === 1 && this.virtuals[0][0] === "\"") { + return this.virtuals.join(", ") + } else { + var body = this.virtuals.join("," + tab) + return "[" + tab + body + tab.slice(0, -1) + "]" } - virtual = "m(" + virtual + ")"; - virtuals.push(virtual); } } - if (!virtuals.indented) tab = ""; - - var isInline = virtuals.length == 1 && virtuals[0].charAt(0) == '"'; - var template = isInline ? virtuals.join(", ") : "[" + tab + virtuals.join("," + tab) + tab.slice(0, -1) + "]"; - return new String(template); -} -templateConverter.controller = function() { - this.source = m.prop(""); - this.output = m.prop(""); - - this.convert = function() { - return this.output(new templateConverter.Template(this.source())); - }; - -}; + return { + controller: function () { + this.source = m.prop("") + this.output = m.prop("") -templateConverter.view = function(ctrl) { - return m("div", [ - m("textarea", {autofocus: true, style: {width:"100%", height: "40%"}, onchange: m.withAttr("value", ctrl.source)}, ctrl.source()), - m("button", {onclick: ctrl.convert.bind(ctrl)}, "Convert"), - m("textarea", {style: {width:"100%", height: "40%"}}, ctrl.output()) - ]); -}; \ No newline at end of file + this.convert = function () { + var source = createVirtual(createFragment(this.source())) + return this.output(new TemplateBuilder(source, 1).complete()) + }.bind(this) + }, + + view: function (ctrl) { + return m("div", [ + m("textarea", { + autofocus: true, + style: {width: "100%", height: "40%"}, + onchange: m.withAttr("value", ctrl.source) + }, ctrl.source()), + m("button", {onclick: ctrl.convert}, "Convert"), + m("textarea", {style: {width: "100%", height: "40%"}}, + ctrl.output()) + ]) + } + } +})() diff --git a/mithril.d.ts b/mithril.d.ts index a5c24a6e..c7d2c6b6 100644 --- a/mithril.d.ts +++ b/mithril.d.ts @@ -20,13 +20,13 @@ declare module _mithril { * @see m.mount * @see m.component */ - ( + ( selector: string, attributes: MithrilAttributes, ...children: Array | - MithrilComponent> - ): MithrilVirtualElement; + MithrilVirtualElement | + MithrilComponent> + ): MithrilVirtualElement; /** * Creates a virtual element for use with m.render, m.mount, etc. @@ -40,12 +40,12 @@ declare module _mithril { * @see m.mount * @see m.component */ - ( + ( selector: string, ...children: Array | - MithrilComponent> - ): MithrilVirtualElement; + MithrilVirtualElement | + MithrilComponent> + ): MithrilVirtualElement; /** * Initializes a component for use with m.render, m.mount, etc. @@ -364,9 +364,9 @@ declare module _mithril { * @param forceRecreation If true, overwrite the entire tree without * diffing against it. */ - render( + render( rootElement: Element, - children: MithrilVirtualElement|MithrilVirtualElement[], + children: MithrilVirtualElement|MithrilVirtualElement[], forceRecreation?: boolean ): void; @@ -431,10 +431,10 @@ declare module _mithril { * @param defaultRoute The route to start with. * @param routes A key-value mapping of pathname to controller. */ - ( + ( rootElement: Element, defaultRoute: string, - routes: MithrilRoutes + routes: MithrilRoutes ): void; /** @@ -447,11 +447,11 @@ declare module _mithril { * m("a[href='/dashboard/alicesmith']", {config: m.route}); * ``` */ - ( + ( element: Element, isInitialized: boolean, context?: MithrilContext, - vdom?: MithrilVirtualElement + vdom?: MithrilVirtualElement ): void; /** @@ -615,7 +615,7 @@ declare module _mithril { * * @see m */ - interface MithrilVirtualElement { + interface MithrilVirtualElement { /** * A key to optionally associate with this element. */ @@ -634,7 +634,7 @@ declare module _mithril { /** * The children of this element. */ - children?: Array|MithrilComponent>; + children?: Array>; } /** @@ -686,11 +686,11 @@ declare module _mithril { * @param context The associated context for this element. * @param vdom The associated virtual element. */ - ( + ( element: Element, isInitialized: boolean, context: MithrilContext, - vdom: MithrilVirtualElement + vdom: MithrilVirtualElement ): void; } @@ -758,7 +758,7 @@ declare module _mithril { /** * Creates a view out of virtual elements. */ - (ctrl: T): MithrilVirtualElement; + (ctrl: T): MithrilVirtualElement; } /** @@ -781,7 +781,7 @@ declare module _mithril { * * @see m.component */ - view(ctrl: T): MithrilVirtualElement; + view(ctrl: T): MithrilVirtualElement; } /** @@ -805,7 +805,7 @@ declare module _mithril { * * @see m.component */ - view(ctrl: TController, arg1: T1, arg2: T2, arg3: T3, arg4: T4, ...args:any[]): MithrilVirtualElement; + view(ctrl: TController, arg1: T1, arg2: T2, arg3: T3, arg4: T4, ...args:any[]): MithrilVirtualElement; } /** @@ -876,12 +876,12 @@ declare module _mithril { /** * This represents a key-value mapping linking routes to components. */ - interface MithrilRoutes { + interface MithrilRoutes { /** * The key represents the route. The value represents the corresponding * component. */ - [key: string]: MithrilComponent; + [key: string]: MithrilComponent; } /** diff --git a/mithril.js b/mithril.js index a179d996..fc62f8c4 100644 --- a/mithril.js +++ b/mithril.js @@ -1,236 +1,386 @@ -var m = (function app(window, undefined) { - "use strict"; - var VERSION = "v0.2.1"; +(function (global, factory) { + "use strict" + /* eslint-disable no-undef */ + var m = factory(window) + if (typeof module === "object" && module != null && module.exports) { + module.exports = m + } else if (typeof define === "function" && define.amd) { + define(function () { return m }) + } else { + global.m = m + } + /* eslint-enable no-undef */ +})(this, function (window, undefined) { // eslint-disable-line + "use strict" + + var VERSION = "v0.2.1" + + // Save these two. + var type = {}.toString + var hasOwn = {}.hasOwnProperty + function isFunction(object) { - return typeof object === "function"; + return typeof object === "function" } + function isObject(object) { - return type.call(object) === "[object Object]"; + return type.call(object) === "[object Object]" } + function isString(object) { - return type.call(object) === "[object String]"; + return type.call(object) === "[object String]" } + var isArray = Array.isArray || function (object) { - return type.call(object) === "[object Array]"; - }; - var type = {}.toString; - var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g, attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/; - var voidElements = /^(AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TRACK|WBR)$/; - var noop = function () {}; + return type.call(object) === "[object Array]" + } + + function noop() {} + + function forEach(list, f) { + for (var i = 0; i < list.length && !f(list[i], i++);) { + // empty + } + } + + function forOwn(obj, f) { + for (var prop in obj) if (hasOwn.call(obj, prop)) { + if (f(obj[prop], prop)) break + } + } + + var parser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[.+?\])/g + var attrParser = /\[(.+?)(?:=("|'|)(.*?)\2)?\]/ + var voidElements = /^(AREA|BASE|BR|COL|COMMAND|EMBED|HR|IMG|INPUT|KEYGEN|LINK|META|PARAM|SOURCE|TRACK|WBR)$/ // eslint-disable-line max-len // caching commonly used variables - var $document, $location, $requestAnimationFrame, $cancelAnimationFrame; + var $document, $location, $requestAnimationFrame, $cancelAnimationFrame // self invoking function needed because of the way mocks work function initialize(window) { - $document = window.document; - $location = window.location; - $cancelAnimationFrame = window.cancelAnimationFrame || window.clearTimeout; - $requestAnimationFrame = window.requestAnimationFrame || window.setTimeout; + $document = window.document + $location = window.location + $cancelAnimationFrame = window.cancelAnimationFrame || + window.clearTimeout + $requestAnimationFrame = window.requestAnimationFrame || + window.setTimeout } - initialize(window); + initialize(window) - m.version = function() { - return VERSION; - }; + // testing API + m.deps = function (mock) { + initialize(window = mock || window) + return window + } + + m.version = function () { + return VERSION + } /** - * @typedef {String} Tag - * A string that looks like -> div.classname#id[param=one][param2=two] - * Which describes a DOM node - */ + * @typedef {String} Tag + * A string that looks like -> div.classname#id[param=one][param2=two] + * Which describes a DOM node + */ + + function checkForAttrs(pairs) { + return pairs != null && + isObject(pairs) && + !("tag" in pairs || "view" in pairs || "subtree" in pairs) + } + + function parseSelector(tag, cell) { + var classes = [] + var match + while ((match = parser.exec(tag)) != null) { + if (match[1] === "" && match[2]) { + cell.tag = match[2] + } else if (match[1] === "#") { + cell.attrs.id = match[2] + } else if (match[1] === ".") { + classes.push(match[2]) + } else if (match[3][0] === "[") { + var pair = attrParser.exec(match[3]) + cell.attrs[pair[1]] = pair[3] || (pair[2] ? "" : true) + } + } + + return classes + } + + function getChildrenFromList(hasAttrs, args) { + var children = hasAttrs ? args.slice(1) : args + if (children.length === 1 && isArray(children[0])) { + return children[0] + } else { + return children + } + } + + function assignAttrs(cell, attrs, classAttr, classes) { + forOwn(attrs, function (value, attr) { + if (attr === classAttr && + attrs[attr] != null && + attrs[attr] !== "") { + classes.push(attrs[attr]) + + // create key in correct iteration order + cell.attrs[attr] = "" + } else { + cell.attrs[attr] = attrs[attr] + } + }) + + if (classes.length) { + cell.attrs[classAttr] = classes.join(" ") + } + } /** - * - * @param {Tag} The DOM node tag - * @param {Object=[]} optional key-value pairs to be mapped to DOM attrs - * @param {...mNode=[]} Zero or more Mithril child nodes. Can be an array, or splat (optional) - * - */ + * @param {Tag} The DOM node tag + * @param {Object=[]} optional key-value pairs to be mapped to DOM attrs + * @param {...mNode=[]} Zero or more Mithril child nodes. Can be an array, + * or splat (optional) + */ function m(tag, pairs) { for (var args = [], i = 1; i < arguments.length; i++) { - args[i - 1] = arguments[i]; - } - if (isObject(tag)) return parameterize(tag, args); - var hasAttrs = pairs != null && isObject(pairs) && !("tag" in pairs || "view" in pairs || "subtree" in pairs); - var attrs = hasAttrs ? pairs : {}; - var classAttrName = "class" in attrs ? "class" : "className"; - var cell = {tag: "div", attrs: {}}; - var match, classes = []; - if (!isString(tag)) throw new Error("selector in m(selector, attrs, children) should be a string"); - while ((match = parser.exec(tag)) != null) { - if (match[1] === "" && match[2]) cell.tag = match[2]; - else if (match[1] === "#") cell.attrs.id = match[2]; - else if (match[1] === ".") classes.push(match[2]); - else if (match[3][0] === "[") { - var pair = attrParser.exec(match[3]); - cell.attrs[pair[1]] = pair[3] || (pair[2] ? "" :true); - } + args[i - 1] = arguments[i] } - var children = hasAttrs ? args.slice(1) : args; - if (children.length === 1 && isArray(children[0])) { - cell.children = children[0]; - } - else { - cell.children = children; + if (isObject(tag)) return parameterize(tag, args) + var hasAttrs = checkForAttrs(pairs) + var attrs = hasAttrs ? pairs : {} + var classAttr = "class" in attrs ? "class" : "className" + var cell = {tag: "div", attrs: {}} + + if (!isString(tag)) { + throw new Error("selector in m(selector, attrs, children) should " + + "be a string") } - for (var attrName in attrs) { - if (attrs.hasOwnProperty(attrName)) { - if (attrName === classAttrName && attrs[attrName] != null && attrs[attrName] !== "") { - classes.push(attrs[attrName]); - cell.attrs[attrName] = ""; //create key in correct iteration order - } - else cell.attrs[attrName] = attrs[attrName]; - } - } - if (classes.length) cell.attrs[classAttrName] = classes.join(" "); + var classes = parseSelector(tag, cell) + cell.children = getChildrenFromList(hasAttrs, args) - return cell; - } - function forEach(list, f) { - for (var i = 0; i < list.length && !f(list[i], i++);) {} + assignAttrs(cell, attrs, classAttr, classes) + + return cell } + function forKeys(list, f) { forEach(list, function (attrs, i) { - return (attrs = attrs && attrs.attrs) && attrs.key != null && f(attrs, i); - }); + attrs = attrs && attrs.attrs + return attrs && attrs.key != null && f(attrs, i) + }) } + // This function was causing deopts in Chrome. function dataToString(data) { - //data.toString() might throw or return null if data is the return value of Console.log in Firefox (behavior depends on version) + // data.toString() might throw or return null if data is the return + // value of Console.log in some versions of Firefox try { - if (data == null || data.toString() == null) return ""; + if (data != null && data.toString() != null) { + return data + } } catch (e) { - return ""; + // Swallow all errors here. } - return data; + + return "" } + // This function was causing deopts in Chrome. - function injectTextNode(parentElement, first, index, data) { + function injectTextNode(parent, first, index, data) { try { - insertNode(parentElement, first, index); - first.nodeValue = data; - } catch (e) {} //IE erroneously throws error when appending an empty text node after a null + insertNode(parent, first, index) + first.nodeValue = data + } catch (e) { + // IE erroneously throws error when appending an empty text node + // after a null + } } function flatten(list) { - //recursively flatten array + // recursively flatten array for (var i = 0; i < list.length; i++) { if (isArray(list[i])) { - list = list.concat.apply([], list); - //check current index again and flatten until there are no more nested arrays at that index - i--; + list = list.concat.apply([], list) + // check current index again while there is an array at this + // index. + i-- } } - return list; + + return list } - function insertNode(parentElement, node, index) { - parentElement.insertBefore(node, parentElement.childNodes[index] || null); + function insertNode(parent, node, index) { + parent.insertBefore(node, parent.childNodes[index] || null) } - var DELETION = 1, INSERTION = 2, MOVE = 3; + var DELETION = 1 + var INSERTION = 2 + var MOVE = 3 - function handleKeysDiffer(data, existing, cached, parentElement) { + function handleKeysDiffer(data, existing, cached, parent) { forKeys(data, function (key, i) { - existing[key = key.key] = existing[key] ? { - action: MOVE, - index: i, - from: existing[key].index, - element: cached.nodes[existing[key].index] || $document.createElement("div") - } : {action: INSERTION, index: i}; - }); - var actions = []; - for (var prop in existing) actions.push(existing[prop]); - var changes = actions.sort(sortChanges), newCached = new Array(cached.length); - newCached.nodes = cached.nodes.slice(); + key = key.key + if (existing[key]) { + existing[key] = { + action: MOVE, + index: i, + from: existing[key].index, + element: cached.nodes[existing[key].index] || + $document.createElement("div") + } + } else { + existing[key] = {action: INSERTION, index: i} + } + }) + + var actions = [] + + forOwn(existing, function (value) { + actions.push(value) + }) + + var changes = actions.sort(sortChanges) + var newCached = new Array(cached.length) + newCached.nodes = cached.nodes.slice() forEach(changes, function (change) { - var index = change.index; - if (change.action === DELETION) { - clear(cached[index].nodes, cached[index]); - newCached.splice(index, 1); - } - if (change.action === INSERTION) { - var dummy = $document.createElement("div"); - dummy.key = data[index].attrs.key; - insertNode(parentElement, dummy, index); + var index = change.index + + switch (change.action) { + case DELETION: + clear(cached[index].nodes, cached[index]) + newCached.splice(index, 1) + break + + case INSERTION: + var dummy = $document.createElement("div") + dummy.key = data[index].attrs.key + insertNode(parent, dummy, index) newCached.splice(index, 0, { attrs: {key: data[index].attrs.key}, nodes: [dummy] - }); - newCached.nodes[index] = dummy; - } + }) + newCached.nodes[index] = dummy + break - if (change.action === MOVE) { - var changeElement = change.element; - var maybeChanged = parentElement.childNodes[index]; + case MOVE: + var changeElement = change.element + var maybeChanged = parent.childNodes[index] if (maybeChanged !== changeElement && changeElement !== null) { - parentElement.insertBefore(changeElement, maybeChanged || null); + parent.insertBefore(changeElement, maybeChanged || null) } - newCached[index] = cached[change.from]; - newCached.nodes[index] = changeElement; + newCached[index] = cached[change.from] + newCached.nodes[index] = changeElement } - }); + }) - return newCached; + return newCached } function diffKeys(data, cached, existing, parentElement) { - var keysDiffer = data.length !== cached.length; + var keysDiffer = data.length !== cached.length + if (!keysDiffer) { forKeys(data, function (attrs, i) { - var cachedCell = cached[i]; - return keysDiffer = cachedCell && cachedCell.attrs && cachedCell.attrs.key !== attrs.key; - }); + var cachedCell = cached[i] + return keysDiffer = cachedCell && + cachedCell.attrs && + cachedCell.attrs.key !== attrs.key + }) } - return keysDiffer ? handleKeysDiffer(data, existing, cached, parentElement) : cached; + if (keysDiffer) { + return handleKeysDiffer(data, existing, cached, parentElement) + } else { + return cached + } } + // diffs the array itself function diffArray(data, cached, nodes) { - //diff the array itself - - //update the list of DOM nodes by collecting the nodes from each item + // update the list of DOM nodes by collecting the nodes from each item forEach(data, function (_, i) { - if (cached[i] != null) nodes.push.apply(nodes, cached[i].nodes); + if (cached[i] != null) nodes.push.apply(nodes, cached[i].nodes) }) - //remove items from the end of the array if the new array is shorter than the old one. if errors ever happen here, the issue is most likely - //a bug in the construction of the `cached` data structure somewhere earlier in the program + + // remove items from the end of the array if the new array is shorter + // than the old one. if errors ever happen here, the issue is most + // likely a bug in the construction of the `cached` data structure + // somewhere earlier in the program forEach(cached.nodes, function (node, i) { - if (node.parentNode != null && nodes.indexOf(node) < 0) clear([node], [cached[i]]); + if (node.parentNode != null && nodes.indexOf(node) < 0) { + clear([node], [cached[i]]) + } }) - if (data.length < cached.length) cached.length = data.length; - cached.nodes = nodes; + + if (data.length < cached.length) cached.length = data.length + + cached.nodes = nodes } function buildArrayKeys(data) { - var guid = 0; + var guid = 0 forKeys(data, function () { forEach(data, function (attrs) { - if ((attrs = attrs && attrs.attrs) && attrs.key == null) attrs.key = "__mithril__" + guid++; + attrs = attrs && attrs.attrs + if (attrs && attrs.key == null) { + attrs.key = "__mithril__" + guid++ + } }) - return 1; - }); + return true + }) + } + + // shallow array compare, sorts + function arraySortCompare(a, b) { + a.sort() + b.sort() + var len = a.length + if (len !== b.length) return false + for (var i = 0; i < len; i++) { + if (a[i] !== b[i]) return false + } + return true + } + + function elemIsDifferentEnough(data, cached, dataAttrKeys) { + if (data.tag !== cached.tag) return true + if (!arraySortCompare(dataAttrKeys, Object.keys(cached.attrs))) { + return true + } + + if (data.attrs.id !== cached.attrs.id) return true + if (data.attrs.key !== cached.attrs.key) return true + + if (m.redraw.strategy() === "all") { + return !(cached.configContext && + cached.configContext.retain === true) + } else if (m.redraw.strategy() === "diff") { + return cached.configContext && + cached.configContext.retain === false + } } function maybeRecreateObject(data, cached, dataAttrKeys) { - //if an element is different enough from the one in cache, recreate it - if (data.tag !== cached.tag || - dataAttrKeys.sort().join() !== Object.keys(cached.attrs).sort().join() || - data.attrs.id !== cached.attrs.id || - data.attrs.key !== cached.attrs.key || - (m.redraw.strategy() === "all" && (!cached.configContext || cached.configContext.retain !== true)) || - (m.redraw.strategy() === "diff" && cached.configContext && cached.configContext.retain === false)) { - if (cached.nodes.length) clear(cached.nodes); - if (cached.configContext && isFunction(cached.configContext.onunload)) cached.configContext.onunload(); + // if an element is different enough from the one in cache, recreate it + if (elemIsDifferentEnough(data, cached, dataAttrKeys)) { + if (cached.nodes.length) clear(cached.nodes) + if (cached.configContext && + isFunction(cached.configContext.onunload)) { + cached.configContext.onunload() + } + if (cached.controllers) { forEach(cached.controllers, function (controller) { - if (controller.unload) controller.onunload({preventDefault: noop}); - }); + if (controller.unload) { + controller.onunload({preventDefault: noop}) + } + }) } } } @@ -239,1178 +389,1754 @@ var m = (function app(window, undefined) { return data.attrs.xmlns ? data.attrs.xmlns : data.tag === "svg" ? "http://www.w3.org/2000/svg" : data.tag === "math" ? "http://www.w3.org/1998/Math/MathML" : - namespace; + namespace + } + + var pendingRequests = 0 + m.startComputation = function () { pendingRequests++ } + m.endComputation = function () { + if (pendingRequests > 1) { + pendingRequests-- + } else { + pendingRequests = 0 + m.redraw() + } } function unloadCachedControllers(cached, views, controllers) { if (controllers.length) { - cached.views = views; - cached.controllers = controllers; + cached.views = views + cached.controllers = controllers forEach(controllers, function (controller) { - if (controller.onunload && controller.onunload.$old) controller.onunload = controller.onunload.$old; - if (pendingRequests && controller.onunload) { - var onunload = controller.onunload; - controller.onunload = noop; - controller.onunload.$old = onunload; + if (controller.onunload && controller.onunload.$old) { + controller.onunload = controller.onunload.$old } - }); + + if (pendingRequests && controller.onunload) { + var onunload = controller.onunload + controller.onunload = noop + controller.onunload.$old = onunload + } + }) } } function scheduleConfigsToBeCalled(configs, data, node, isNew, cached) { - //schedule configs to be called. They are called after `build` - //finishes running - if (isFunction(data.attrs.config)) { - var context = cached.configContext = cached.configContext || {}; + // schedule configs to be called. They are called after `build` finishes + // running + var config = data.attrs.config + if (isFunction(config)) { + var context = cached.configContext = cached.configContext || {} - //bind - configs.push(function() { - return data.attrs.config.call(data, node, !isNew, context, cached); - }); + // bind + configs.push(function () { + return config.call(data, node, !isNew, context, cached) + }) } } - function buildUpdatedNode(cached, data, editable, hasKeys, namespace, views, configs, controllers) { - var node = cached.nodes[0]; - if (hasKeys) setAttributes(node, data.tag, data.attrs, cached.attrs, namespace); - cached.children = build(node, data.tag, undefined, undefined, data.children, cached.children, false, 0, data.attrs.contenteditable ? node : editable, namespace, configs); - cached.nodes.intact = true; + function buildUpdatedNode( + cached, + data, + editable, + hasKeys, + namespace, + views, + configs, + controllers + ) { + var node = cached.nodes[0] + if (hasKeys) { + setAttributes(node, data.tag, data.attrs, cached.attrs, namespace) + } + + cached.children = build(node, data.tag, undefined, undefined, + data.children, cached.children, false, 0, + data.attrs.contenteditable ? node : editable, namespace, configs) + + cached.nodes.intact = true if (controllers.length) { - cached.views = views; - cached.controllers = controllers; + cached.views = views + cached.controllers = controllers } - return node; + return node } - function handleNonexistentNodes(data, parentElement, index) { - var nodes; + function handleNonexistentNodes(data, parent, index) { + var nodes if (data.$trusted) { - nodes = injectHTML(parentElement, index, data); - } - else { - nodes = [$document.createTextNode(data)]; - if (!parentElement.nodeName.match(voidElements)) insertNode(parentElement, nodes[0], index); + nodes = injectHTML(parent, index, data) + } else { + nodes = [$document.createTextNode(data)] + if (!voidElements.test(parent.nodeName)) { + insertNode(parent, nodes[0], index) + } } - var cached = typeof data === "string" || typeof data === "number" || typeof data === "boolean" ? new data.constructor(data) : data; - cached.nodes = nodes; - return cached; + var cached + + if (typeof data === "string" || + typeof data === "number" || + typeof data === "boolean") { + cached = new data.constructor(data) + } else { + cached = data + } + + cached.nodes = nodes + + return cached } - function reattachNodes(data, cached, parentElement, editable, index, parentTag) { - var nodes = cached.nodes; + function reattachNodes(data, + cached, + parentElement, + editable, + index, + parentTag + ) { + var nodes = cached.nodes if (!editable || editable !== $document.activeElement) { if (data.$trusted) { - clear(nodes, cached); - nodes = injectHTML(parentElement, index, data); - } - //corner case: replacing the nodeValue of a text node that is a child of a textarea/contenteditable doesn't work - //we need to update the value property of the parent textarea or the innerHTML of the contenteditable element instead - else if (parentTag === "textarea") { - parentElement.value = data; - } - else if (editable) { - editable.innerHTML = data; - } - else { - //was a trusted string + clear(nodes, cached) + nodes = injectHTML(parentElement, index, data) + } else if (parentTag === "textarea") { + //