diff --git a/.gitignore b/.gitignore index 9015505bc0..f90fe7f26f 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,6 @@ pubspec.lock **/resources/zips public/docs/xref-*.* _zip-output -www \ No newline at end of file +www +*.plnkr.html +plnkr.html \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index 509a2301b1..0b6fe49a7c 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -25,11 +25,13 @@ var TOOLS_PATH = './tools'; var ANGULAR_PROJECT_PATH = '../angular'; var PUBLIC_PATH = './public'; var DOCS_PATH = path.join(PUBLIC_PATH, 'docs'); +var EXAMPLES_PATH = path.join(DOCS_PATH, '_examples'); var NOT_API_DOCS_GLOB = path.join(PUBLIC_PATH, './{docs/*/latest/!(api),!(docs)}/**/*'); var RESOURCES_PATH = path.join(PUBLIC_PATH, 'resources'); var docShredder = require(path.resolve(TOOLS_PATH, 'doc-shredder/doc-shredder')); var exampleZipper = require(path.resolve(TOOLS_PATH, '_example-zipper/exampleZipper')); +var plunkerBuilder = require(path.resolve(TOOLS_PATH, 'plunker-builder/plunkerBuilder')); var _devguideShredOptions = { examplesDir: path.join(DOCS_PATH, '_examples'), @@ -49,7 +51,9 @@ var _excludeMatchers = _excludePatterns.map(function(excludePattern){ return new Minimatch(excludePattern) }); - +gulp.task('build-plunkers', function() { + return plunkerBuilder.buildPlunkers(EXAMPLES_PATH, gutil.log); +}); // Public tasks @@ -95,10 +99,6 @@ gulp.task('build-js-api-docs', ['_shred-api-examples'], function() { return buildApiDocs('js'); }); - - - - gulp.task('git-changed-examples', ['_shred-devguide-examples'], function(){ var after, sha, messageSuffix; if (argv.after) { diff --git a/package.json b/package.json index 8510a163f6..3846324464 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "indent-string": "^2.1.0", "jasmine-core": "^2.3.4", "jasmine-node": "^1.14.5", + "jsdom": "^7.0.2", "jsonfile": "^2.2.2", "karma": "^0.13.10", "karma-chrome-launcher": "^0.2.0", diff --git a/public/docs/_examples/displaying-data/ts/src/plnkr.config b/public/docs/_examples/displaying-data/ts/src/plnkr.config new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/forms/ts/src/plnkr.config b/public/docs/_examples/forms/ts/src/plnkr.config new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/pipes/ts/src/plnkr.config b/public/docs/_examples/pipes/ts/src/plnkr.config new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/quickstart/ts/src/plnkr.config b/public/docs/_examples/quickstart/ts/src/plnkr.config new file mode 100644 index 0000000000..f83d040ee9 --- /dev/null +++ b/public/docs/_examples/quickstart/ts/src/plnkr.config @@ -0,0 +1,5 @@ +{ + "description": "QuickStart Application", + "files": ["app/app.ts", "index.html"], + "tags": ["quickstart"] +} \ No newline at end of file diff --git a/public/docs/_examples/quickstart/ts/src/test.plnkr.config b/public/docs/_examples/quickstart/ts/src/test.plnkr.config new file mode 100644 index 0000000000..21329fa8a4 --- /dev/null +++ b/public/docs/_examples/quickstart/ts/src/test.plnkr.config @@ -0,0 +1,5 @@ +{ + "description": "Alternate Quickstart", + "files": ["!app/app.ts", "!*.html", "index.1.html" ], + "main": "index.1.html" +} \ No newline at end of file diff --git a/public/docs/_examples/template-syntax/ts/src/images/hero.png b/public/docs/_examples/template-syntax/ts/src/images/hero.png new file mode 100644 index 0000000000..b60c3ecc30 Binary files /dev/null and b/public/docs/_examples/template-syntax/ts/src/images/hero.png differ diff --git a/public/docs/_examples/template-syntax/ts/src/images/ng-logo.png b/public/docs/_examples/template-syntax/ts/src/images/ng-logo.png new file mode 100644 index 0000000000..7929242740 Binary files /dev/null and b/public/docs/_examples/template-syntax/ts/src/images/ng-logo.png differ diff --git a/public/docs/_examples/template-syntax/ts/src/images/villain.png b/public/docs/_examples/template-syntax/ts/src/images/villain.png new file mode 100644 index 0000000000..d1e71cf00b Binary files /dev/null and b/public/docs/_examples/template-syntax/ts/src/images/villain.png differ diff --git a/public/docs/_examples/template-syntax/ts/src/plnkr.config b/public/docs/_examples/template-syntax/ts/src/plnkr.config new file mode 100644 index 0000000000..b36cb8f9ae --- /dev/null +++ b/public/docs/_examples/template-syntax/ts/src/plnkr.config @@ -0,0 +1,4 @@ +{ + "description": "Template Syntax Collection" + +} \ No newline at end of file diff --git a/public/docs/_examples/user-input/ts/src/plnkr.config b/public/docs/_examples/user-input/ts/src/plnkr.config new file mode 100644 index 0000000000..163aef0daa --- /dev/null +++ b/public/docs/_examples/user-input/ts/src/plnkr.config @@ -0,0 +1,3 @@ +{ + "files": ["!*.json"] +} \ No newline at end of file diff --git a/public/docs/ts/latest/guide/template-syntax.jade b/public/docs/ts/latest/guide/template-syntax.jade index ca3a3e12c8..f676089131 100644 --- a/public/docs/ts/latest/guide/template-syntax.jade +++ b/public/docs/ts/latest/guide/template-syntax.jade @@ -1,5 +1,7 @@ include ../../../../_includes/_util-fns +a(href='/docs/_examples/template-syntax/ts/src/plnkr.html') Live Example 1 + :marked # Template Syntax Our Angular application manages what the user sees and does through the interaction of a Component class instance and its user-facing template. @@ -10,6 +12,8 @@ include ../../../../_includes/_util-fns We’ll cover these basic elements of Template Syntax + [Live Example 1](/docs/_examples/template-syntax/ts/src/plnkr.html) + >[HTML](#html) >[Interpolation](#interpolation) diff --git a/tools/plunker-builder/indexHtmlTranslator.js b/tools/plunker-builder/indexHtmlTranslator.js new file mode 100644 index 0000000000..5f91bf0062 --- /dev/null +++ b/tools/plunker-builder/indexHtmlTranslator.js @@ -0,0 +1,97 @@ +module.exports = { + translate: translate +}; + +var _rxRules = { + script: { + from: /.*<\/script>/, + to: '' + }, + link: { + from: '//', + to: '' + }, + config: { + from: /\s*System.config\(\{\s*packages:[\s\S]*\}\}\s*\}\);/m, + to: "\n" + + " System.config({\n" + + " transpiler: 'typescript', \n" + + " typescriptOptions: { emitDecoratorMetadata: true }, \n" + + " packages: {'app': {defaultExtension: 'ts'}} \n" + + " });" + }, + +}; + +var _rxData = [ + { + pattern: 'script', + from: 'node_modules/systemjs/dist/system.src.js', + to: ['https://rawgithub.com/systemjs/systemjs/0.19.6/dist/system.js', 'https://code.angularjs.org/tools/typescript.js'] + // to: ['https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.18.4/system.js', 'https://code.angularjs.org/tools/typescript.js' ] + }, + { + pattern: 'script', + from: 'node_modules/angular2/bundles/angular2.dev.js', + to: 'https://code.angularjs.org/2.0.0-alpha.46/angular2.dev.js' + }, + { + pattern: 'script', + from: 'node_modules/angular2/bundles/router.dev.js', + to: 'https://code.angularjs.org/2.0.0-alpha.46/router.dev.js' + }, + { + pattern: 'script', + from: 'node_modules/angular2/bundles/http.dev.js', + to: 'https://code.angularjs.org/2.0.0-alpha.46/http.dev.js' + }, + { + pattern: 'script', + from: 'node_modules/angular2/bundles/testing.js', + to: 'https://code.angularjs.org/2.0.0-alpha.46/testing.js' + }, + { + pattern: 'link', + from: 'node_modules/bootstrap/dist/css/bootstrap.min.css', + to: 'https://cdnjs.com/libraries/twitter-bootstrap/3.3.5' + // to: 'https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/css/bootstrap.min.css' + }, + { + pattern: 'config', + } +]; + + + +function translate(html) { + _rxData.forEach(function(rxDatum) { + var rxRule = _rxRules[rxDatum.pattern]; + // rxFrom is a rexexp + var rxFrom = rxRule.from; + if (rxDatum.from) { + var from = rxDatum.from.replace('/', '\/'); + var rxTemp = rxFrom.toString(); + rxTemp = rxTemp.replace('%tag%', from); + rxFrom = rxFromString(rxTemp); + } + // rxTo is a string + var rxTo = rxRule.to; + if (rxDatum.to) { + var to = rxDatum.to; + to = Array.isArray(to) ? to : [to]; + to = to.map(function (toItem) { + return rxTo.replace("%tag%", toItem); + }); + rxTo = to.join("\n "); + } + html = html.replace(rxFrom, rxTo ); + }); + + return html; +} + +function rxFromString(rxString) { + var rx = /^\/(.*)\/(.*)/; + var pieces = rx.exec(rxString); + return RegExp(pieces[1], pieces[2]); +} diff --git a/tools/plunker-builder/license.md b/tools/plunker-builder/license.md new file mode 100644 index 0000000000..c207e271d1 --- /dev/null +++ b/tools/plunker-builder/license.md @@ -0,0 +1,13 @@ +Copyright 2010-2015 Google, Inc. http://angularjs.org + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/tools/plunker-builder/plunkerBuilder.js b/tools/plunker-builder/plunkerBuilder.js new file mode 100644 index 0000000000..2cbe943c52 --- /dev/null +++ b/tools/plunker-builder/plunkerBuilder.js @@ -0,0 +1,221 @@ +// Canonical path provides a consistent path (i.e. always forward slashes) across different OSes +var path = require('canonical-path'); +var Q = require('q'); +var _ = require('lodash'); +var jsdom = require("jsdom"); +var fs = require("fs"); +var globule = require('globule'); + +var indexHtmlTranslator = require('./indexHtmlTranslator'); +var regionExtractor = require('../doc-shredder/regionExtractor'); + +module.exports = { + buildPlunkers: buildPlunkers +}; + +function buildPlunkers(basePath, errFn) { + errFn = errFn || function(e) { console.log(e); }; + var configExtns = ['plnkr.config', '*.plnkr.config']; + var gpaths = configExtns.map(function(extn) { + return path.join(basePath, '**/' + extn); + }); + var fileNames = globule.find(gpaths); + fileNames.forEach(function(configFileName) { + try { + buildPlunkerFrom(configFileName); + } catch (e) { + errFn(e); + } + }); +} + +// config has +// files: [] - optional array of globs - defaults to all js, ts, html, json, css and md files (with certain files removed) +// description: optional string - description of this plunker - defaults to the title in the index.html page. +// tags: [] - optional array of strings +// main: string - filename of what will become index.html in the plunker - defaults to index.html +function buildPlunkerFrom(configFileName ) { + // replace ending 'plnkr.config' with 'plnkr.html' to create output file name; + var outputFileName = configFileName.substr(0, configFileName.length - 'plnkr.config'.length) + 'plnkr.html'; + try { + var config = initConfigAndCollectFileNames(configFileName); + var postData = createPostData(config); + var html = createPlunkerHtml(postData); + fs.writeFileSync(outputFileName, html, 'utf-8'); + } catch (e) { + // if we fail delete the outputFile if it exists because it is an old one. + if (existsSync(outputFileName)) { + fs.unlinkSync(outputFileName); + } + throw e; + } +} + +function initConfigAndCollectFileNames(configFileName) { + var basePath = path.dirname(configFileName); + var configSrc = fs.readFileSync(configFileName, 'utf-8'); + try { + var config = (configSrc && configSrc.trim().length) ? JSON.parse(configSrc) : {}; + } catch (e) { + throw new Error("Plunker config - unable to parse json file: " + configFileName + '\n ' + e); + } + + var defaultIncludes = ['**/*.ts', '**/*.js', '**/*.css', '**/*.html', '**/*.md', '**/*.json', '**/*.png']; + if (config.files) { + if (config.files.length > 0) { + if (config.files[0].substr(0, 1) == '!') { + config.files = defaultIncludes.concat(config.files); + } + } + } else { + config.files = defaultIncludes; + } + var gpaths = config.files.map(function(fileName) { + fileName = fileName.trim(); + if (fileName.substr(0,1) == '!') { + return "!" + path.join(basePath, fileName.substr(1)); + } else { + return path.join(basePath, fileName); + } + }); + var defaultExcludes = [ '!**/typings/**','!**/tsconfig.json', '!**/plnkr.html', '!**/*.plnkr.html' ]; + Array.prototype.push.apply(gpaths, defaultExcludes); + + config.fileNames = globule.find(gpaths); + config.basePath = basePath; + return config; +} + +function createPostData(config) { + var postData = {}; + config.fileNames.forEach(function(fileName) { + var content; + var extn = path.extname(fileName); + if (extn == '.png') { + content = encodeBase64(fileName); + fileName = fileName.substr(0, fileName.length - 4) + '.base64.png' + } else { + content = fs.readFileSync(fileName, 'utf-8'); + } + // var escapedValue = escapeHtml(content); + + var relativeFileName = path.relative(config.basePath, fileName); + + if (relativeFileName == config.main) { + relativeFileName = 'index.html'; + } + + if (relativeFileName == 'index.html') { + content = indexHtmlTranslator.translate(content); + if (config.description == null) { + // set config.description to title from index.html + var matches = /(.*)<\/title>/.exec(content); + if (matches) { + config.description = matches[1]; + } + } + } + content = regionExtractor.removeDocTags(content, extn.substr(1)); + + postData['files[' + relativeFileName + ']'] = content; + }); + postData['files[license.md]'] = fs.readFileSync(path.join(__dirname, "license.md")); + + var tags = ['angular2', 'example'].concat(config.tags || []); + tags.forEach(function(tag,ix) { + postData['tags[' + ix + ']'] = tag; + }); + // postData['tags[0]'] = "angular2"; + // postData['tags[1]'] = "example"; + postData.private = true; + + postData.description = "Angular 2 Example - " + config.description; + return postData; +} + +function existsSync(filename) { + try { + fs.accessSync(filename); + return true; + } catch(ex) { + return false; + } +} + +function encodeBase64(file) { + // read binary data + var bitmap = fs.readFileSync(file); + // convert binary data to base64 encoded string + return new Buffer(bitmap).toString('base64'); +} + + +function createPlunkerHtml(postData) { + useNewWindow = false; + var baseHtml = createBasePlunkerHtml(useNewWindow); + var doc = jsdom.jsdom(baseHtml); + var form = doc.querySelector('form'); + _.forEach(postData, function(value, key) { + var ele = htmlToElement(doc, '<input type="hidden" name="' + key + '">'); + ele.setAttribute('value', value); + form.appendChild(ele) + }); + var html = doc.documentElement.outerHTML; + return html; +} + +function createBasePlunkerHtml(useNewWindow) { + var url = 'http://plnkr.co/edit/?p=preview'; + // If the form posts to target="_blank", pop-up blockers can cause it not to work. + // If a user choses to bypass pop-up blocker one time and click the link, they will arrive at + // a new default plnkr, not a plnkr with the desired template. Given this undesired behavior, + // some may still want to open the plnk in a new window by opting-in via ctrl+click. The + // newWindow param allows for this possibility. + var target = useNewWindow ? '_blank' : '_self'; + var html = '<!DOCTYPE html><html lang="en"><body>' + html += '<form id="mainForm" method="post" action="' + url + '" target="' + target + '">' + + // html += '<div class="button"><button id="formButton" type="submit">Create Plunker</button></div>' + // html += '</form><script>document.getElementById("formButton").click();</script>' + html += '</form><script>document.getElementById("mainForm").submit();</script>' + html += '</body></html>'; + return html; +} + +function htmlToElement(document, html) { + var div = document.createElement('div'); + div.innerHTML = html; + return div.firstChild; +} + +// not currently used. +function escapeHtml(unsafe) { + return unsafe + .replace(/&/g, "&") + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} + +//// Old version - no longer used +//function createPlunkerHtmlAsync(basePath, postData) { +// +// useNewWindow = false; +// jsdom.env({ +// html: createBasePlunkerHtml(useNewWindow), +// done: function (err, window) { +// var doc = window.document; +// var form = doc.querySelector('form'); +// +// _.forEach(postData, function(value, key) { +// var ele = htmlToElement(doc, '<input type="hidden" name="' + key + '">'); +// ele.setAttribute('value', value); +// form.appendChild(ele) +// }); +// var html = doc.documentElement.outerHTML; +// var outputFn = path.join(basePath, "plnkr.html"); +// fs.writeFileSync(outputFn, html, 'utf-8' ); +// } +// }); +//} \ No newline at end of file diff --git a/tools/plunker-builder/tmp.plunker.example.jade b/tools/plunker-builder/tmp.plunker.example.jade new file mode 100644 index 0000000000..5bbe6b7584 --- /dev/null +++ b/tools/plunker-builder/tmp.plunker.example.jade @@ -0,0 +1,6 @@ +.l-main-section + a(href='/docs/_examples/template-syntax/ts/src/plnkr.html') Live Example 0 + +:marked + [Live Example 1](/docs/_examples/template-syntax/ts/src/plnkr.html) + <a href='/docs/_examples/template-syntax/ts/src/plnkr.html'><code>Live Example 2</code></a>