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.
+ ++ 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 */ -