├── .babelrc ├── .gitignore ├── README.md ├── assets ├── images │ ├── ad.png │ ├── amp.png │ ├── avatar.jpg │ ├── bg-brick.png │ ├── bg-checkered.jpg │ ├── bg-pick.png │ ├── heeeeey.jpg │ ├── jon.jpg │ ├── samantha.jpg │ ├── skull.png │ └── the-forum.png └── stylesheets │ └── application.css ├── dist └── bundle.js ├── gulpfile.babel.js ├── index.html ├── npm-shrinkwrap.json ├── package.json ├── server ├── db.json └── db.with.xss.json └── src ├── api.js ├── app.js ├── constants.js ├── post.js ├── ui.js └── user.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | presets: ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.swp 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ES2015 Watch Us Build 2 | 3 | *We changed the name of this series to "Watch Us Build," so you may see an occasional reference to its former name ("Soup to Bits") in this repository.* 4 | 5 | This repository contains the sample app built in the [Watch Us Build episode](https://www.codeschool.com/screencasts/build-a-forum-web-app-with-es2015) for the [ES2015: The Shape of JavaScript to Come course](https://www.codeschool.com/courses/es2015-the-shape-of-javascript-to-come). 6 | 7 | # Installing 8 | 9 | 1. `npm install` to resolve dependencies 10 | 2. `npm install -g json-server` for the API 11 | 12 | # Running 13 | 14 | First, run the API with either 15 | 16 | `json-server server/db.json ` 17 | 18 | or 19 | 20 | `json-server server/db.with.xss.json` 21 | 22 | then open up `index.html` 23 | 24 | # Developing 25 | 26 | 1. `npm install -g gulp` 27 | 2. `gulp` should transpile and trigger the watch 28 | 29 | -------------------------------------------------------------------------------- /assets/images/ad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeschool/WatchUsBuild-ForumAppWithES2015/c55887955092827b9141fa0965a8888a8283fde7/assets/images/ad.png -------------------------------------------------------------------------------- /assets/images/amp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeschool/WatchUsBuild-ForumAppWithES2015/c55887955092827b9141fa0965a8888a8283fde7/assets/images/amp.png -------------------------------------------------------------------------------- /assets/images/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeschool/WatchUsBuild-ForumAppWithES2015/c55887955092827b9141fa0965a8888a8283fde7/assets/images/avatar.jpg -------------------------------------------------------------------------------- /assets/images/bg-brick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeschool/WatchUsBuild-ForumAppWithES2015/c55887955092827b9141fa0965a8888a8283fde7/assets/images/bg-brick.png -------------------------------------------------------------------------------- /assets/images/bg-checkered.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeschool/WatchUsBuild-ForumAppWithES2015/c55887955092827b9141fa0965a8888a8283fde7/assets/images/bg-checkered.jpg -------------------------------------------------------------------------------- /assets/images/bg-pick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeschool/WatchUsBuild-ForumAppWithES2015/c55887955092827b9141fa0965a8888a8283fde7/assets/images/bg-pick.png -------------------------------------------------------------------------------- /assets/images/heeeeey.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeschool/WatchUsBuild-ForumAppWithES2015/c55887955092827b9141fa0965a8888a8283fde7/assets/images/heeeeey.jpg -------------------------------------------------------------------------------- /assets/images/jon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeschool/WatchUsBuild-ForumAppWithES2015/c55887955092827b9141fa0965a8888a8283fde7/assets/images/jon.jpg -------------------------------------------------------------------------------- /assets/images/samantha.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeschool/WatchUsBuild-ForumAppWithES2015/c55887955092827b9141fa0965a8888a8283fde7/assets/images/samantha.jpg -------------------------------------------------------------------------------- /assets/images/skull.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeschool/WatchUsBuild-ForumAppWithES2015/c55887955092827b9141fa0965a8888a8283fde7/assets/images/skull.png -------------------------------------------------------------------------------- /assets/images/the-forum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codeschool/WatchUsBuild-ForumAppWithES2015/c55887955092827b9141fa0965a8888a8283fde7/assets/images/the-forum.png -------------------------------------------------------------------------------- /assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* Global */ 2 | * { 3 | box-sizing: border-box; } 4 | 5 | body { 6 | background: url("../images/bg-brick.png"); 7 | background-size: 96px 96px; 8 | font: 700 24px/1.5 "Amatic SC", cursive; 9 | margin: 0; 10 | padding: 0; 11 | text-align: center; } 12 | 13 | button, 14 | input, 15 | textarea { 16 | font-family: "Amatic SC", cursive; 17 | font-weight: 700; } 18 | 19 | fieldset { 20 | border: 0; 21 | margin: 0; 22 | padding: 0; } 23 | 24 | h1 { 25 | color: #fff; 26 | font-size: 200%; 27 | margin-top: 0.5em; } 28 | 29 | /* Layout */ 30 | .container { 31 | display: inline-block; 32 | max-width: 600px; 33 | text-align: left; 34 | vertical-align: top; 35 | width: 87.5%; } 36 | 37 | .sidebar { 38 | background: rgba(17, 17, 17, 0.5); 39 | display: inline-block; 40 | margin: 0 0 0 2em; 41 | max-width: 260px; 42 | padding: 1em; 43 | text-align: left; 44 | vertical-align: top; 45 | width: 87.5%; } 46 | 47 | .form { 48 | background: #000; 49 | margin-top: 2em; 50 | padding: 1em; } 51 | 52 | header { 53 | background: rgba(17, 17, 17, 0.5); 54 | padding: 0 0.5em 1em 0.5em; } 55 | 56 | main { 57 | padding: 1em 0; } 58 | 59 | /* Post */ 60 | .post { 61 | background: #fff; 62 | border: 2px solid #222; 63 | border-radius: 18px 28px 12px 24px; 64 | padding: 0.5em 1.5em; 65 | position: relative; } 66 | .post:after { 67 | border: 0.125em solid #000; 68 | border-radius: 18px 28px 12px 24px; 69 | bottom: 0.25em; 70 | content: ""; 71 | left: 0.25em; 72 | position: absolute; 73 | right: 0.25em; 74 | top: 0.25em; } 75 | .post p:not(:last-child) { 76 | margin: 0 0 1em 0; } 77 | 78 | .post:not(:last-of-type) { 79 | margin-bottom: 1em; 80 | position: relative; } 81 | 82 | .post-title { 83 | font-size: 150%; 84 | font-weight: 700; 85 | margin: 0; } 86 | 87 | .post-author { 88 | background: #fc8f00; 89 | border: 2px solid #222; 90 | color: #fff; 91 | left: -2.5%; 92 | padding: 0.25em; 93 | position: absolute; 94 | -webkit-transform: rotate(-2deg); 95 | transform: rotate(-2deg); 96 | top: -2em; 97 | white-space: nowrap; 98 | z-index: 1; } 99 | 100 | .post-meta { 101 | background: #222; 102 | color: #fff; 103 | display: inline-block; 104 | font-size: 80%; 105 | font-weight: 700; 106 | margin: 0; 107 | padding: 0 0.25em; 108 | text-align: right; } 109 | 110 | /* Comment */ 111 | .comment-label { 112 | color: #fff; 113 | display: block; 114 | font-size: 125%; 115 | font-weight: 400; 116 | margin: 0; 117 | text-align: center; } 118 | 119 | .comment-input { 120 | border: 2px solid #000; 121 | display: block; 122 | font-size: 100%; 123 | padding: 0.5em; 124 | width: 100%; } 125 | 126 | .comment-button { 127 | background: linear-gradient(to top, #00aeef, #009bd6); 128 | border-radius: 0.375em; 129 | border: 0; 130 | box-shadow: 0 2px 0 #222; 131 | color: #fff; 132 | cursor: pointer; 133 | font-size: 125%; 134 | font-weight: bold; 135 | line-height: 2; 136 | margin-top: 0.5em; 137 | padding: 0 1.5em; } 138 | .comment-button:active, .comment-button:hover { 139 | background: #009bd6; } 140 | 141 | /* Counter */ 142 | .sidebar.has-counter { 143 | margin-top: 6em; } 144 | 145 | .counter-image { 146 | display: block; 147 | margin: -60% auto 0 auto; } 148 | 149 | .counter-button { 150 | background: linear-gradient(to top, #fc0400 0%, #fe2900 49%, #ff4e00 100%); 151 | border-radius: 6px; 152 | border: 0; 153 | box-shadow: 0 2px 0 #222; 154 | color: #fff; 155 | cursor: pointer; 156 | font-size: 125%; 157 | font-weight: bold; 158 | line-height: 1.5; 159 | margin-top: 0.5em; 160 | padding: 0 1.5em; } 161 | .counter-button:active, .counter-button:hover { 162 | background: #fc0400; } 163 | 164 | .counter-text { 165 | color: #fff; 166 | font-size: 400%; 167 | line-height: 1; 168 | text-shadow: 1px 1px 0 #000; } 169 | 170 | /* Thread */ 171 | .thread-meta { 172 | color: #666; } 173 | 174 | /* Active */ 175 | .active-title { 176 | color: #fff; 177 | font-size: 150%; 178 | margin: 0; 179 | text-align: center; } 180 | 181 | .active-avatar { 182 | background: url("../images/bg-pick.png"); 183 | background-size: 100px 113px; 184 | height: 113px; 185 | margin-top: 1em; 186 | padding-top: 19px; 187 | position: relative; 188 | width: 100px; } 189 | .active-avatar img { 190 | border-radius: 50%; 191 | display: block; 192 | margin: 0 auto; } 193 | .active-avatar .post-author { 194 | left: 100%; 195 | right: auto; 196 | top: 0; } 197 | 198 | /* Sponsor */ 199 | .sponsor-img { 200 | display: block; 201 | margin: 2em auto 0 auto; } 202 | 203 | /* Poll */ 204 | .poll-question { 205 | margin-bottom: 0; } 206 | 207 | .poll { 208 | margin: 0 0 2em 0; } 209 | 210 | .poll-item { 211 | color: #ffffff; } 212 | 213 | .poll-results { 214 | color: #ffffff; 215 | margin-bottom: 1em; 216 | width: 100%; } 217 | 218 | .poll-results td { 219 | padding: 5px 0; } 220 | 221 | .poll-id { 222 | width: 20px; } 223 | 224 | .poll-answer { 225 | width: 150px; } 226 | 227 | .poll-count { 228 | background: url('../images/bg-checkered.jpg'); 229 | background-size: 40px 40px; 230 | text-align: center; 231 | vertical-align: middle; } 232 | 233 | .poll-number { 234 | background-color: #000000; 235 | border-radius: 50%; 236 | display: inline-block; 237 | height: 30px; 238 | line-height: 30px; 239 | margin-top: 2px; 240 | padding-top: 1px; 241 | width: 30px; } 242 | 243 | /* * Tool Classes */ 244 | /* Text Align */ 245 | .tac { 246 | text-align: center; } 247 | -------------------------------------------------------------------------------- /dist/bundle.js: -------------------------------------------------------------------------------- 1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 8 | Adonis Fung 9 | Albert Yu 10 | */ 11 | /*jshint node: true */ 12 | 13 | exports._getPrivFilters = function () { 14 | 15 | var LT = /])/g, 21 | SPECIAL_HTML_CHARS = /[&<>"'`]/g, 22 | SPECIAL_COMMENT_CHARS = /(?:\x00|^-*!?>|--!?>|--?!?$|\]>|\]$)/g; 23 | 24 | // CSS sensitive chars: ()"'/,!*@{}:; 25 | // By CSS: (Tab|NewLine|colon|semi|lpar|rpar|apos|sol|comma|excl|ast|midast);|(quot|QUOT) 26 | // By URI_PROTOCOL: (Tab|NewLine); 27 | var SENSITIVE_HTML_ENTITIES = /&(?:#([xX][0-9A-Fa-f]+|\d+);?|(Tab|NewLine|colon|semi|lpar|rpar|apos|sol|comma|excl|ast|midast|ensp|emsp|thinsp);|(nbsp|amp|AMP|lt|LT|gt|GT|quot|QUOT);?)/g, 28 | SENSITIVE_NAMED_REF_MAP = {Tab: '\t', NewLine: '\n', colon: ':', semi: ';', lpar: '(', rpar: ')', apos: '\'', sol: '/', comma: ',', excl: '!', ast: '*', midast: '*', ensp: '\u2002', emsp: '\u2003', thinsp: '\u2009', nbsp: '\xA0', amp: '&', lt: '<', gt: '>', quot: '"', QUOT: '"'}; 29 | 30 | // var CSS_VALID_VALUE = 31 | // /^(?: 32 | // (?!-*expression)#?[-\w]+ 33 | // |[+-]?(?:\d+|\d*\.\d+)(?:em|ex|ch|rem|px|mm|cm|in|pt|pc|%|vh|vw|vmin|vmax)? 34 | // |!important 35 | // | //empty 36 | // )$/i; 37 | var CSS_VALID_VALUE = /^(?:(?!-*expression)#?[-\w]+|[+-]?(?:\d+|\d*\.\d+)(?:r?em|ex|ch|cm|mm|in|px|pt|pc|%|vh|vw|vmin|vmax)?|!important|)$/i, 38 | // TODO: prevent double css escaping by not encoding \ again, but this may require CSS decoding 39 | // \x7F and \x01-\x1F less \x09 are for Safari 5.0, added []{}/* for unbalanced quote 40 | CSS_DOUBLE_QUOTED_CHARS = /[\x00-\x1F\x7F\[\]{}\\"]/g, 41 | CSS_SINGLE_QUOTED_CHARS = /[\x00-\x1F\x7F\[\]{}\\']/g, 42 | // (, \u207D and \u208D can be used in background: 'url(...)' in IE, assumed all \ chars are encoded by QUOTED_CHARS, and null is already replaced with \uFFFD 43 | // otherwise, use this CSS_BLACKLIST instead (enhance it with url matching): /(?:\\?\(|[\u207D\u208D]|\\0{0,4}28 ?|\\0{0,2}20[78][Dd] ?)+/g 44 | CSS_BLACKLIST = /url[\(\u207D\u208D]+/g, 45 | // this assumes encodeURI() and encodeURIComponent() has escaped 1-32, 127 for IE8 46 | CSS_UNQUOTED_URL = /['\(\)]/g; // " \ treated by encodeURI() 47 | 48 | // Given a full URI, need to support "[" ( IPv6address ) "]" in URI as per RFC3986 49 | // Reference: https://tools.ietf.org/html/rfc3986 50 | var URL_IPV6 = /\/\/%5[Bb]([A-Fa-f0-9:]+)%5[Dd]/; 51 | 52 | 53 | // Reference: http://shazzer.co.uk/database/All/characters-allowd-in-html-entities 54 | // Reference: http://shazzer.co.uk/vector/Characters-allowed-after-ampersand-in-named-character-references 55 | // Reference: http://shazzer.co.uk/database/All/Characters-before-javascript-uri 56 | // Reference: http://shazzer.co.uk/database/All/Characters-after-javascript-uri 57 | // Reference: https://html.spec.whatwg.org/multipage/syntax.html#consume-a-character-reference 58 | // Reference for named characters: https://html.spec.whatwg.org/multipage/entities.json 59 | var URI_BLACKLIST_PROTOCOLS = {'javascript':1, 'data':1, 'vbscript':1, 'mhtml':1, 'x-schema':1}, 60 | URI_PROTOCOL_COLON = /(?::|&#[xX]0*3[aA];?|�*58;?|:)/, 61 | URI_PROTOCOL_WHITESPACES = /(?:^[\x00-\x20]+|[\t\n\r\x00]+)/g, 62 | URI_PROTOCOL_NAMED_REF_MAP = {Tab: '\t', NewLine: '\n'}; 63 | 64 | var x, 65 | strReplace = function (s, regexp, callback) { 66 | return s === undefined ? 'undefined' 67 | : s === null ? 'null' 68 | : s.toString().replace(regexp, callback); 69 | }, 70 | fromCodePoint = String.fromCodePoint || function(codePoint) { 71 | if (arguments.length === 0) { 72 | return ''; 73 | } 74 | if (codePoint <= 0xFFFF) { // BMP code point 75 | return String.fromCharCode(codePoint); 76 | } 77 | 78 | // Astral code point; split in surrogate halves 79 | // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae 80 | codePoint -= 0x10000; 81 | return String.fromCharCode((codePoint >> 10) + 0xD800, (codePoint % 0x400) + 0xDC00); 82 | }; 83 | 84 | 85 | function getProtocol(s) { 86 | s = s.split(URI_PROTOCOL_COLON, 2); 87 | return (s.length === 2 && s[0]) ? s[0] : null; 88 | } 89 | 90 | function htmlDecode(s, namedRefMap, reNamedRef, skipReplacement) { 91 | 92 | namedRefMap = namedRefMap || SENSITIVE_NAMED_REF_MAP; 93 | reNamedRef = reNamedRef || SENSITIVE_HTML_ENTITIES; 94 | 95 | function regExpFunction(m, num, named, named1) { 96 | if (num) { 97 | num = Number(num[0] <= '9' ? num : '0' + num); 98 | // switch(num) { 99 | // case 0x80: return '\u20AC'; // EURO SIGN (€) 100 | // case 0x82: return '\u201A'; // SINGLE LOW-9 QUOTATION MARK (‚) 101 | // case 0x83: return '\u0192'; // LATIN SMALL LETTER F WITH HOOK (ƒ) 102 | // case 0x84: return '\u201E'; // DOUBLE LOW-9 QUOTATION MARK („) 103 | // case 0x85: return '\u2026'; // HORIZONTAL ELLIPSIS (…) 104 | // case 0x86: return '\u2020'; // DAGGER (†) 105 | // case 0x87: return '\u2021'; // DOUBLE DAGGER (‡) 106 | // case 0x88: return '\u02C6'; // MODIFIER LETTER CIRCUMFLEX ACCENT (ˆ) 107 | // case 0x89: return '\u2030'; // PER MILLE SIGN (‰) 108 | // case 0x8A: return '\u0160'; // LATIN CAPITAL LETTER S WITH CARON (Š) 109 | // case 0x8B: return '\u2039'; // SINGLE LEFT-POINTING ANGLE QUOTATION MARK (‹) 110 | // case 0x8C: return '\u0152'; // LATIN CAPITAL LIGATURE OE (Œ) 111 | // case 0x8E: return '\u017D'; // LATIN CAPITAL LETTER Z WITH CARON (Ž) 112 | // case 0x91: return '\u2018'; // LEFT SINGLE QUOTATION MARK (‘) 113 | // case 0x92: return '\u2019'; // RIGHT SINGLE QUOTATION MARK (’) 114 | // case 0x93: return '\u201C'; // LEFT DOUBLE QUOTATION MARK (“) 115 | // case 0x94: return '\u201D'; // RIGHT DOUBLE QUOTATION MARK (”) 116 | // case 0x95: return '\u2022'; // BULLET (•) 117 | // case 0x96: return '\u2013'; // EN DASH (–) 118 | // case 0x97: return '\u2014'; // EM DASH (—) 119 | // case 0x98: return '\u02DC'; // SMALL TILDE (˜) 120 | // case 0x99: return '\u2122'; // TRADE MARK SIGN (™) 121 | // case 0x9A: return '\u0161'; // LATIN SMALL LETTER S WITH CARON (š) 122 | // case 0x9B: return '\u203A'; // SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (›) 123 | // case 0x9C: return '\u0153'; // LATIN SMALL LIGATURE OE (œ) 124 | // case 0x9E: return '\u017E'; // LATIN SMALL LETTER Z WITH CARON (ž) 125 | // case 0x9F: return '\u0178'; // LATIN CAPITAL LETTER Y WITH DIAERESIS (Ÿ) 126 | // } 127 | // // num >= 0xD800 && num <= 0xDFFF, and 0x0D is separately handled, as it doesn't fall into the range of x.pec() 128 | // return (num >= 0xD800 && num <= 0xDFFF) || num === 0x0D ? '\uFFFD' : x.frCoPt(num); 129 | 130 | return skipReplacement ? fromCodePoint(num) 131 | : num === 0x80 ? '\u20AC' // EURO SIGN (€) 132 | : num === 0x82 ? '\u201A' // SINGLE LOW-9 QUOTATION MARK (‚) 133 | : num === 0x83 ? '\u0192' // LATIN SMALL LETTER F WITH HOOK (ƒ) 134 | : num === 0x84 ? '\u201E' // DOUBLE LOW-9 QUOTATION MARK („) 135 | : num === 0x85 ? '\u2026' // HORIZONTAL ELLIPSIS (…) 136 | : num === 0x86 ? '\u2020' // DAGGER (†) 137 | : num === 0x87 ? '\u2021' // DOUBLE DAGGER (‡) 138 | : num === 0x88 ? '\u02C6' // MODIFIER LETTER CIRCUMFLEX ACCENT (ˆ) 139 | : num === 0x89 ? '\u2030' // PER MILLE SIGN (‰) 140 | : num === 0x8A ? '\u0160' // LATIN CAPITAL LETTER S WITH CARON (Š) 141 | : num === 0x8B ? '\u2039' // SINGLE LEFT-POINTING ANGLE QUOTATION MARK (‹) 142 | : num === 0x8C ? '\u0152' // LATIN CAPITAL LIGATURE OE (Œ) 143 | : num === 0x8E ? '\u017D' // LATIN CAPITAL LETTER Z WITH CARON (Ž) 144 | : num === 0x91 ? '\u2018' // LEFT SINGLE QUOTATION MARK (‘) 145 | : num === 0x92 ? '\u2019' // RIGHT SINGLE QUOTATION MARK (’) 146 | : num === 0x93 ? '\u201C' // LEFT DOUBLE QUOTATION MARK (“) 147 | : num === 0x94 ? '\u201D' // RIGHT DOUBLE QUOTATION MARK (”) 148 | : num === 0x95 ? '\u2022' // BULLET (•) 149 | : num === 0x96 ? '\u2013' // EN DASH (–) 150 | : num === 0x97 ? '\u2014' // EM DASH (—) 151 | : num === 0x98 ? '\u02DC' // SMALL TILDE (˜) 152 | : num === 0x99 ? '\u2122' // TRADE MARK SIGN (™) 153 | : num === 0x9A ? '\u0161' // LATIN SMALL LETTER S WITH CARON (š) 154 | : num === 0x9B ? '\u203A' // SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (›) 155 | : num === 0x9C ? '\u0153' // LATIN SMALL LIGATURE OE (œ) 156 | : num === 0x9E ? '\u017E' // LATIN SMALL LETTER Z WITH CARON (ž) 157 | : num === 0x9F ? '\u0178' // LATIN CAPITAL LETTER Y WITH DIAERESIS (Ÿ) 158 | : (num >= 0xD800 && num <= 0xDFFF) || num === 0x0D ? '\uFFFD' 159 | : x.frCoPt(num); 160 | } 161 | return namedRefMap[named || named1] || m; 162 | } 163 | 164 | return s === undefined ? 'undefined' 165 | : s === null ? 'null' 166 | : s.toString().replace(NULL, '\uFFFD').replace(reNamedRef, regExpFunction); 167 | } 168 | 169 | function cssEncode(chr) { 170 | // space after \\HEX is needed by spec 171 | return '\\' + chr.charCodeAt(0).toString(16).toLowerCase() + ' '; 172 | } 173 | function cssBlacklist(s) { 174 | return s.replace(CSS_BLACKLIST, function(m){ return '-x-' + m; }); 175 | } 176 | function cssUrl(s) { 177 | // encodeURI() in yufull() will throw error for use of the CSS_UNSUPPORTED_CODE_POINT (i.e., [\uD800-\uDFFF]) 178 | s = x.yufull(htmlDecode(s)); 179 | var protocol = getProtocol(s); 180 | 181 | // prefix ## for blacklisted protocols 182 | return (protocol && URI_BLACKLIST_PROTOCOLS[protocol.toLowerCase()]) ? '##' + s : s; 183 | } 184 | 185 | return (x = { 186 | // turn invalid codePoints and that of non-characters to \uFFFD, and then fromCodePoint() 187 | frCoPt: function(num) { 188 | return num === undefined || num === null ? '' : 189 | !isFinite(num = Number(num)) || // `NaN`, `+Infinity`, or `-Infinity` 190 | num <= 0 || // not a valid Unicode code point 191 | num > 0x10FFFF || // not a valid Unicode code point 192 | // Math.floor(num) != num || 193 | 194 | (num >= 0x01 && num <= 0x08) || 195 | (num >= 0x0E && num <= 0x1F) || 196 | (num >= 0x7F && num <= 0x9F) || 197 | (num >= 0xFDD0 && num <= 0xFDEF) || 198 | 199 | num === 0x0B || 200 | (num & 0xFFFF) === 0xFFFF || 201 | (num & 0xFFFF) === 0xFFFE ? '\uFFFD' : fromCodePoint(num); 202 | }, 203 | d: htmlDecode, 204 | /* 205 | * @param {string} s - An untrusted uri input 206 | * @returns {string} s - null if relative url, otherwise the protocol with whitespaces stripped and lower-cased 207 | */ 208 | yup: function(s) { 209 | s = getProtocol(s.replace(NULL, '')); 210 | // URI_PROTOCOL_WHITESPACES is required for left trim and remove interim whitespaces 211 | return s ? htmlDecode(s, URI_PROTOCOL_NAMED_REF_MAP, null, true).replace(URI_PROTOCOL_WHITESPACES, '').toLowerCase() : null; 212 | }, 213 | 214 | /* 215 | * @deprecated 216 | * @param {string} s - An untrusted user input 217 | * @returns {string} s - The original user input with & < > " ' ` encoded respectively as & < > " ' and `. 218 | * 219 | */ 220 | y: function(s) { 221 | return strReplace(s, SPECIAL_HTML_CHARS, function (m) { 222 | return m === '&' ? '&' 223 | : m === '<' ? '<' 224 | : m === '>' ? '>' 225 | : m === '"' ? '"' 226 | : m === "'" ? ''' 227 | : /*m === '`'*/ '`'; // in hex: 60 228 | }); 229 | }, 230 | 231 | // This filter is meant to introduce double-encoding, and should be used with extra care. 232 | ya: function(s) { 233 | return strReplace(s, AMP, '&'); 234 | }, 235 | 236 | // FOR DETAILS, refer to inHTMLData() 237 | // Reference: https://html.spec.whatwg.org/multipage/syntax.html#data-state 238 | yd: function (s) { 239 | return strReplace(s, LT, '<'); 240 | }, 241 | 242 | // FOR DETAILS, refer to inHTMLComment() 243 | // All NULL characters in s are first replaced with \uFFFD. 244 | // If s contains -->, --!>, or starts with -*>, insert a space right before > to stop state breaking at 245 | // If s ends with --!, --, or -, append a space to stop collaborative state breaking at {{{yc s}}}>, {{{yc s}}}!>, {{{yc s}}}-!>, {{{yc s}}}-> 246 | // Reference: https://html.spec.whatwg.org/multipage/syntax.html#comment-state 247 | // Reference: http://shazzer.co.uk/vector/Characters-that-close-a-HTML-comment-3 248 | // Reference: http://shazzer.co.uk/vector/Characters-that-close-a-HTML-comment 249 | // Reference: http://shazzer.co.uk/vector/Characters-that-close-a-HTML-comment-0021 250 | // If s contains ]> or ends with ], append a space after ] is verified in IE to stop IE conditional comments. 251 | // Reference: http://msdn.microsoft.com/en-us/library/ms537512%28v=vs.85%29.aspx 252 | // We do not care --\s>, which can possibly be intepreted as a valid close comment tag in very old browsers (e.g., firefox 3.6), as specified in the html4 spec 253 | // Reference: http://www.w3.org/TR/html401/intro/sgmltut.html#h-3.2.4 254 | yc: function (s) { 255 | return strReplace(s, SPECIAL_COMMENT_CHARS, function(m){ 256 | return m === '\x00' ? '\uFFFD' 257 | : m === '--!' || m === '--' || m === '-' || m === ']' ? m + ' ' 258 | :/* 259 | : m === ']>' ? '] >' 260 | : m === '-->' ? '-- >' 261 | : m === '--!>' ? '--! >' 262 | : /-*!?>/.test(m) ? */ m.slice(0, -1) + ' >'; 263 | }); 264 | }, 265 | 266 | // FOR DETAILS, refer to inDoubleQuotedAttr() 267 | // Reference: https://html.spec.whatwg.org/multipage/syntax.html#attribute-value-(double-quoted)-state 268 | yavd: function (s) { 269 | return strReplace(s, QUOT, '"'); 270 | }, 271 | 272 | // FOR DETAILS, refer to inSingleQuotedAttr() 273 | // Reference: https://html.spec.whatwg.org/multipage/syntax.html#attribute-value-(single-quoted)-state 274 | yavs: function (s) { 275 | return strReplace(s, SQUOT, '''); 276 | }, 277 | 278 | // FOR DETAILS, refer to inUnQuotedAttr() 279 | // PART A. 280 | // if s contains any state breaking chars (\t, \n, \v, \f, \r, space, and >), 281 | // they are escaped and encoded into their equivalent HTML entity representations. 282 | // Reference: http://shazzer.co.uk/database/All/Characters-which-break-attributes-without-quotes 283 | // Reference: https://html.spec.whatwg.org/multipage/syntax.html#attribute-value-(unquoted)-state 284 | // 285 | // PART B. 286 | // if s starts with ', " or `, encode it resp. as ', ", or ` to 287 | // enforce the attr value (unquoted) state 288 | // Reference: https://html.spec.whatwg.org/multipage/syntax.html#before-attribute-value-state 289 | // Reference: http://shazzer.co.uk/vector/Characters-allowed-attribute-quote 290 | // 291 | // PART C. 292 | // Inject a \uFFFD character if an empty or all null string is encountered in 293 | // unquoted attribute value state. 294 | // 295 | // Rationale 1: our belief is that developers wouldn't expect an 296 | // empty string would result in ' name="passwd"' rendered as 297 | // attribute value, even though this is how HTML5 is specified. 298 | // Rationale 2: an empty or all null string (for IE) can 299 | // effectively alter its immediate subsequent state, we choose 300 | // \uFFFD to end the unquoted attr 301 | // state, which therefore will not mess up later contexts. 302 | // Rationale 3: Since IE 6, it is verified that NULL chars are stripped. 303 | // Reference: https://html.spec.whatwg.org/multipage/syntax.html#attribute-value-(unquoted)-state 304 | // 305 | // Example: 306 | // 307 | yavu: function (s) { 308 | return strReplace(s, SPECIAL_ATTR_VALUE_UNQUOTED_CHARS, function (m) { 309 | return m === '\t' ? ' ' // in hex: 09 310 | : m === '\n' ? ' ' // in hex: 0A 311 | : m === '\x0B' ? ' ' // in hex: 0B for IE. IE<9 \v equals v, so use \x0B instead 312 | : m === '\f' ? ' ' // in hex: 0C 313 | : m === '\r' ? ' ' // in hex: 0D 314 | : m === ' ' ? ' ' // in hex: 20 315 | : m === '=' ? '=' // in hex: 3D 316 | : m === '<' ? '<' 317 | : m === '>' ? '>' 318 | : m === '"' ? '"' 319 | : m === "'" ? ''' 320 | : m === '`' ? '`' 321 | : /*empty or null*/ '\uFFFD'; 322 | }); 323 | }, 324 | 325 | yu: encodeURI, 326 | yuc: encodeURIComponent, 327 | 328 | // Notice that yubl MUST BE APPLIED LAST, and will not be used independently (expected output from encodeURI/encodeURIComponent and yavd/yavs/yavu) 329 | // This is used to disable JS execution capabilities by prefixing x- to ^javascript:, ^vbscript: or ^data: that possibly could trigger script execution in URI attribute context 330 | yubl: function (s) { 331 | return URI_BLACKLIST_PROTOCOLS[x.yup(s)] ? 'x-' + s : s; 332 | }, 333 | 334 | // This is NOT a security-critical filter. 335 | // Reference: https://tools.ietf.org/html/rfc3986 336 | yufull: function (s) { 337 | return x.yu(s).replace(URL_IPV6, function(m, p) { 338 | return '//[' + p + ']'; 339 | }); 340 | }, 341 | 342 | // chain yufull() with yubl() 343 | yublf: function (s) { 344 | return x.yubl(x.yufull(s)); 345 | }, 346 | 347 | // The design principle of the CSS filter MUST meet the following goal(s). 348 | // (1) The input cannot break out of the context (expr) and this is to fulfill the just sufficient encoding principle. 349 | // (2) The input cannot introduce CSS parsing error and this is to address the concern of UI redressing. 350 | // 351 | // term 352 | // : unary_operator? 353 | // [ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* | ANGLE S* | 354 | // TIME S* | FREQ S* ] 355 | // | STRING S* | IDENT S* | URI S* | hexcolor | function 356 | // 357 | // Reference: 358 | // * http://www.w3.org/TR/CSS21/grammar.html 359 | // * http://www.w3.org/TR/css-syntax-3/ 360 | // 361 | // NOTE: delimiter in CSS - \ _ : ; ( ) " ' / , % # ! * @ . { } 362 | // 2d 5c 5f 3a 3b 28 29 22 27 2f 2c 25 23 21 2a 40 2e 7b 7d 363 | 364 | yceu: function(s) { 365 | s = htmlDecode(s); 366 | return CSS_VALID_VALUE.test(s) ? s : ";-x:'" + cssBlacklist(s.replace(CSS_SINGLE_QUOTED_CHARS, cssEncode)) + "';-v:"; 367 | }, 368 | 369 | // string1 = \"([^\n\r\f\\"]|\\{nl}|\\[^\n\r\f0-9a-f]|\\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?)*\" 370 | yced: function(s) { 371 | return cssBlacklist(htmlDecode(s).replace(CSS_DOUBLE_QUOTED_CHARS, cssEncode)); 372 | }, 373 | 374 | // string2 = \'([^\n\r\f\\']|\\{nl}|\\[^\n\r\f0-9a-f]|\\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?)*\' 375 | yces: function(s) { 376 | return cssBlacklist(htmlDecode(s).replace(CSS_SINGLE_QUOTED_CHARS, cssEncode)); 377 | }, 378 | 379 | // for url({{{yceuu url}}} 380 | // unquoted_url = ([!#$%&*-~]|\\{h}{1,6}(\r\n|[ \t\r\n\f])?|\\[^\r\n\f0-9a-f])* (CSS 2.1 definition) 381 | // unquoted_url = ([^"'()\\ \t\n\r\f\v\u0000\u0008\u000b\u000e-\u001f\u007f]|\\{h}{1,6}(\r\n|[ \t\r\n\f])?|\\[^\r\n\f0-9a-f])* (CSS 3.0 definition) 382 | // The state machine in CSS 3.0 is more well defined - http://www.w3.org/TR/css-syntax-3/#consume-a-url-token0 383 | // CSS_UNQUOTED_URL = /['\(\)]/g; // " \ treated by encodeURI() 384 | yceuu: function(s) { 385 | return cssUrl(s).replace(CSS_UNQUOTED_URL, function (chr) { 386 | return chr === '\'' ? '\\27 ' : 387 | chr === '(' ? '%28' : 388 | /* chr === ')' ? */ '%29'; 389 | }); 390 | }, 391 | 392 | // for url("{{{yceud url}}} 393 | yceud: function(s) { 394 | return cssUrl(s); 395 | }, 396 | 397 | // for url('{{{yceus url}}} 398 | yceus: function(s) { 399 | return cssUrl(s).replace(SQUOT, '\\27 '); 400 | } 401 | }); 402 | }; 403 | 404 | // exposing privFilters 405 | // this is an undocumented feature, and please use it with extra care 406 | var privFilters = exports._privFilters = exports._getPrivFilters(); 407 | 408 | 409 | /* chaining filters */ 410 | 411 | // uriInAttr and literally uriPathInAttr 412 | // yubl is always used 413 | // Rationale: given pattern like this: 414 | // developer may expect s is always prefixed with ? or /, but an attacker can abuse it with 'javascript:alert(1)' 415 | function uriInAttr (s, yav, yu) { 416 | return privFilters.yubl(yav((yu || privFilters.yufull)(s))); 417 | } 418 | 419 | /** 420 | * Yahoo Secure XSS Filters - just sufficient output filtering to prevent XSS! 421 | * @module xss-filters 422 | */ 423 | 424 | /** 425 | * @function module:xss-filters#inHTMLData 426 | * 427 | * @param {string} s - An untrusted user input 428 | * @returns {string} The string s with '<' encoded as '&lt;' 429 | * 430 | * @description 431 | * This filter is to be placed in HTML Data context to encode all '<' characters into '&lt;' 432 | * 435 | * 436 | * @example 437 | * // output context to be applied by this filter. 438 | *
{{{inHTMLData htmlData}}}
439 | * 440 | */ 441 | exports.inHTMLData = privFilters.yd; 442 | 443 | /** 444 | * @function module:xss-filters#inHTMLComment 445 | * 446 | * @param {string} s - An untrusted user input 447 | * @returns {string} All NULL characters in s are first replaced with \uFFFD. If s contains -->, --!>, or starts with -*>, insert a space right before > to stop state breaking at . If s ends with --!, --, or -, append a space to stop collaborative state breaking at {{{yc s}}}>, {{{yc s}}}!>, {{{yc s}}}-!>, {{{yc s}}}->. If s contains ]> or ends with ], append a space after ] is verified in IE to stop IE conditional comments. 448 | * 449 | * @description 450 | * This filter is to be placed in HTML Comment context 451 | * 463 | * 464 | * @example 465 | * // output context to be applied by this filter. 466 | * 467 | * 468 | */ 469 | exports.inHTMLComment = privFilters.yc; 470 | 471 | /** 472 | * @function module:xss-filters#inSingleQuotedAttr 473 | * 474 | * @param {string} s - An untrusted user input 475 | * @returns {string} The string s with any single-quote characters encoded into '&''. 476 | * 477 | * @description 478 | *

Warning: This is NOT designed for any onX (e.g., onclick) attributes!

479 | *

Warning: If you're working on URI/components, use the more specific uri___InSingleQuotedAttr filter

480 | * This filter is to be placed in HTML Attribute Value (single-quoted) state to encode all single-quote characters into '&'' 481 | * 482 | * 485 | * 486 | * @example 487 | * // output context to be applied by this filter. 488 | * 489 | * 490 | */ 491 | exports.inSingleQuotedAttr = privFilters.yavs; 492 | 493 | /** 494 | * @function module:xss-filters#inDoubleQuotedAttr 495 | * 496 | * @param {string} s - An untrusted user input 497 | * @returns {string} The string s with any single-quote characters encoded into '&"'. 498 | * 499 | * @description 500 | *

Warning: This is NOT designed for any onX (e.g., onclick) attributes!

501 | *

Warning: If you're working on URI/components, use the more specific uri___InDoubleQuotedAttr filter

502 | * This filter is to be placed in HTML Attribute Value (double-quoted) state to encode all single-quote characters into '&"' 503 | * 504 | * 507 | * 508 | * @example 509 | * // output context to be applied by this filter. 510 | * 511 | * 512 | */ 513 | exports.inDoubleQuotedAttr = privFilters.yavd; 514 | 515 | /** 516 | * @function module:xss-filters#inUnQuotedAttr 517 | * 518 | * @param {string} s - An untrusted user input 519 | * @returns {string} If s contains any state breaking chars (\t, \n, \v, \f, \r, space, null, ', ", `, <, >, and =), they are escaped and encoded into their equivalent HTML entity representations. If the string is empty, inject a \uFFFD character. 520 | * 521 | * @description 522 | *

Warning: This is NOT designed for any onX (e.g., onclick) attributes!

523 | *

Warning: If you're working on URI/components, use the more specific uri___InUnQuotedAttr filter

524 | *

Regarding \uFFFD injection, given ,
525 | * Rationale 1: our belief is that developers wouldn't expect when id equals an 526 | * empty string would result in ' name="passwd"' rendered as 527 | * attribute value, even though this is how HTML5 is specified.
528 | * Rationale 2: an empty or all null string (for IE) can 529 | * effectively alter its immediate subsequent state, we choose 530 | * \uFFFD to end the unquoted attr 531 | * state, which therefore will not mess up later contexts.
532 | * Rationale 3: Since IE 6, it is verified that NULL chars are stripped.
533 | * Reference: https://html.spec.whatwg.org/multipage/syntax.html#attribute-value-(unquoted)-state

534 | * 540 | * 541 | * @example 542 | * // output context to be applied by this filter. 543 | * 544 | * 545 | */ 546 | exports.inUnQuotedAttr = privFilters.yavu; 547 | 548 | 549 | /** 550 | * @function module:xss-filters#uriInSingleQuotedAttr 551 | * 552 | * @param {string} s - An untrusted user input, supposedly an absolute URI 553 | * @returns {string} The string s encoded first by window.encodeURI(), then inSingleQuotedAttr(), and finally prefix the resulted string with 'x-' if it begins with 'javascript:' or 'vbscript:' that could possibly lead to script execution 554 | * 555 | * @description 556 | * This filter is to be placed in HTML Attribute Value (single-quoted) state for an absolute URI.
557 | * The correct order of encoders is thus: first window.encodeURI(), then inSingleQuotedAttr(), and finally prefix the resulted string with 'x-' if it begins with 'javascript:' or 'vbscript:' that could possibly lead to script execution 558 | * 559 | *

Notice: This filter is IPv6 friendly by not encoding '[' and ']'.

560 | * 561 | * 566 | * 567 | * @example 568 | * // output context to be applied by this filter. 569 | * link 570 | * 571 | */ 572 | exports.uriInSingleQuotedAttr = function (s) { 573 | return uriInAttr(s, privFilters.yavs); 574 | }; 575 | 576 | /** 577 | * @function module:xss-filters#uriInDoubleQuotedAttr 578 | * 579 | * @param {string} s - An untrusted user input, supposedly an absolute URI 580 | * @returns {string} The string s encoded first by window.encodeURI(), then inDoubleQuotedAttr(), and finally prefix the resulted string with 'x-' if it begins with 'javascript:' or 'vbscript:' that could possibly lead to script execution 581 | * 582 | * @description 583 | * This filter is to be placed in HTML Attribute Value (double-quoted) state for an absolute URI.
584 | * The correct order of encoders is thus: first window.encodeURI(), then inDoubleQuotedAttr(), and finally prefix the resulted string with 'x-' if it begins with 'javascript:' or 'vbscript:' that could possibly lead to script execution 585 | * 586 | *

Notice: This filter is IPv6 friendly by not encoding '[' and ']'.

587 | * 588 | * 593 | * 594 | * @example 595 | * // output context to be applied by this filter. 596 | * link 597 | * 598 | */ 599 | exports.uriInDoubleQuotedAttr = function (s) { 600 | return uriInAttr(s, privFilters.yavd); 601 | }; 602 | 603 | 604 | /** 605 | * @function module:xss-filters#uriInUnQuotedAttr 606 | * 607 | * @param {string} s - An untrusted user input, supposedly an absolute URI 608 | * @returns {string} The string s encoded first by window.encodeURI(), then inUnQuotedAttr(), and finally prefix the resulted string with 'x-' if it begins with 'javascript:' or 'vbscript:' that could possibly lead to script execution 609 | * 610 | * @description 611 | * This filter is to be placed in HTML Attribute Value (unquoted) state for an absolute URI.
612 | * The correct order of encoders is thus: first the built-in encodeURI(), then inUnQuotedAttr(), and finally prefix the resulted string with 'x-' if it begins with 'javascript:' or 'vbscript:' that could possibly lead to script execution 613 | * 614 | *

Notice: This filter is IPv6 friendly by not encoding '[' and ']'.

615 | * 616 | * 621 | * 622 | * @example 623 | * // output context to be applied by this filter. 624 | * link 625 | * 626 | */ 627 | exports.uriInUnQuotedAttr = function (s) { 628 | return uriInAttr(s, privFilters.yavu); 629 | }; 630 | 631 | /** 632 | * @function module:xss-filters#uriInHTMLData 633 | * 634 | * @param {string} s - An untrusted user input, supposedly an absolute URI 635 | * @returns {string} The string s encoded by window.encodeURI() and then inHTMLData() 636 | * 637 | * @description 638 | * This filter is to be placed in HTML Data state for an absolute URI. 639 | * 640 | *

Notice: The actual implementation skips inHTMLData(), since '<' is already encoded as '%3C' by encodeURI().

641 | *

Notice: This filter is IPv6 friendly by not encoding '[' and ']'.

642 | * 643 | * 648 | * 649 | * @example 650 | * // output context to be applied by this filter. 651 | * {{{uriInHTMLData full_uri}}} 652 | * 653 | */ 654 | exports.uriInHTMLData = privFilters.yufull; 655 | 656 | 657 | /** 658 | * @function module:xss-filters#uriInHTMLComment 659 | * 660 | * @param {string} s - An untrusted user input, supposedly an absolute URI 661 | * @returns {string} The string s encoded by window.encodeURI(), and finally inHTMLComment() 662 | * 663 | * @description 664 | * This filter is to be placed in HTML Comment state for an absolute URI. 665 | * 666 | *

Notice: This filter is IPv6 friendly by not encoding '[' and ']'.

667 | * 668 | * 674 | * 675 | * @example 676 | * // output context to be applied by this filter. 677 | * 678 | * 679 | */ 680 | exports.uriInHTMLComment = function (s) { 681 | return privFilters.yc(privFilters.yufull(s)); 682 | }; 683 | 684 | 685 | 686 | 687 | /** 688 | * @function module:xss-filters#uriPathInSingleQuotedAttr 689 | * 690 | * @param {string} s - An untrusted user input, supposedly a URI Path/Query or relative URI 691 | * @returns {string} The string s encoded first by window.encodeURI(), then inSingleQuotedAttr(), and finally prefix the resulted string with 'x-' if it begins with 'javascript:' or 'vbscript:' that could possibly lead to script execution 692 | * 693 | * @description 694 | * This filter is to be placed in HTML Attribute Value (single-quoted) state for a URI Path/Query or relative URI.
695 | * The correct order of encoders is thus: first window.encodeURI(), then inSingleQuotedAttr(), and finally prefix the resulted string with 'x-' if it begins with 'javascript:' or 'vbscript:' that could possibly lead to script execution 696 | * 697 | * 702 | * 703 | * @example 704 | * // output context to be applied by this filter. 705 | * link 706 | * link 707 | * 708 | */ 709 | exports.uriPathInSingleQuotedAttr = function (s) { 710 | return uriInAttr(s, privFilters.yavs, privFilters.yu); 711 | }; 712 | 713 | /** 714 | * @function module:xss-filters#uriPathInDoubleQuotedAttr 715 | * 716 | * @param {string} s - An untrusted user input, supposedly a URI Path/Query or relative URI 717 | * @returns {string} The string s encoded first by window.encodeURI(), then inDoubleQuotedAttr(), and finally prefix the resulted string with 'x-' if it begins with 'javascript:' or 'vbscript:' that could possibly lead to script execution 718 | * 719 | * @description 720 | * This filter is to be placed in HTML Attribute Value (double-quoted) state for a URI Path/Query or relative URI.
721 | * The correct order of encoders is thus: first window.encodeURI(), then inDoubleQuotedAttr(), and finally prefix the resulted string with 'x-' if it begins with 'javascript:' or 'vbscript:' that could possibly lead to script execution 722 | * 723 | * 728 | * 729 | * @example 730 | * // output context to be applied by this filter. 731 | * link 732 | * link 733 | * 734 | */ 735 | exports.uriPathInDoubleQuotedAttr = function (s) { 736 | return uriInAttr(s, privFilters.yavd, privFilters.yu); 737 | }; 738 | 739 | 740 | /** 741 | * @function module:xss-filters#uriPathInUnQuotedAttr 742 | * 743 | * @param {string} s - An untrusted user input, supposedly a URI Path/Query or relative URI 744 | * @returns {string} The string s encoded first by window.encodeURI(), then inUnQuotedAttr(), and finally prefix the resulted string with 'x-' if it begins with 'javascript:' or 'vbscript:' that could possibly lead to script execution 745 | * 746 | * @description 747 | * This filter is to be placed in HTML Attribute Value (unquoted) state for a URI Path/Query or relative URI.
748 | * The correct order of encoders is thus: first the built-in encodeURI(), then inUnQuotedAttr(), and finally prefix the resulted string with 'x-' if it begins with 'javascript:' or 'vbscript:' that could possibly lead to script execution 749 | * 750 | * 755 | * 756 | * @example 757 | * // output context to be applied by this filter. 758 | * link 759 | * link 760 | * 761 | */ 762 | exports.uriPathInUnQuotedAttr = function (s) { 763 | return uriInAttr(s, privFilters.yavu, privFilters.yu); 764 | }; 765 | 766 | /** 767 | * @function module:xss-filters#uriPathInHTMLData 768 | * 769 | * @param {string} s - An untrusted user input, supposedly a URI Path/Query or relative URI 770 | * @returns {string} The string s encoded by window.encodeURI() and then inHTMLData() 771 | * 772 | * @description 773 | * This filter is to be placed in HTML Data state for a URI Path/Query or relative URI. 774 | * 775 | *

Notice: The actual implementation skips inHTMLData(), since '<' is already encoded as '%3C' by encodeURI().

776 | * 777 | * 782 | * 783 | * @example 784 | * // output context to be applied by this filter. 785 | * http://example.com/{{{uriPathInHTMLData uri_path}}} 786 | * http://example.com/?{{{uriQueryInHTMLData uri_query}}} 787 | * 788 | */ 789 | exports.uriPathInHTMLData = privFilters.yu; 790 | 791 | 792 | /** 793 | * @function module:xss-filters#uriPathInHTMLComment 794 | * 795 | * @param {string} s - An untrusted user input, supposedly a URI Path/Query or relative URI 796 | * @returns {string} The string s encoded by window.encodeURI(), and finally inHTMLComment() 797 | * 798 | * @description 799 | * This filter is to be placed in HTML Comment state for a URI Path/Query or relative URI. 800 | * 801 | * 807 | * 808 | * @example 809 | * // output context to be applied by this filter. 810 | * 811 | * 812 | */ 813 | exports.uriPathInHTMLComment = function (s) { 814 | return privFilters.yc(privFilters.yu(s)); 815 | }; 816 | 817 | 818 | /** 819 | * @function module:xss-filters#uriQueryInSingleQuotedAttr 820 | * @description This is an alias of {@link module:xss-filters#uriPathInSingleQuotedAttr} 821 | * 822 | * @alias module:xss-filters#uriPathInSingleQuotedAttr 823 | */ 824 | exports.uriQueryInSingleQuotedAttr = exports.uriPathInSingleQuotedAttr; 825 | 826 | /** 827 | * @function module:xss-filters#uriQueryInDoubleQuotedAttr 828 | * @description This is an alias of {@link module:xss-filters#uriPathInDoubleQuotedAttr} 829 | * 830 | * @alias module:xss-filters#uriPathInDoubleQuotedAttr 831 | */ 832 | exports.uriQueryInDoubleQuotedAttr = exports.uriPathInDoubleQuotedAttr; 833 | 834 | /** 835 | * @function module:xss-filters#uriQueryInUnQuotedAttr 836 | * @description This is an alias of {@link module:xss-filters#uriPathInUnQuotedAttr} 837 | * 838 | * @alias module:xss-filters#uriPathInUnQuotedAttr 839 | */ 840 | exports.uriQueryInUnQuotedAttr = exports.uriPathInUnQuotedAttr; 841 | 842 | /** 843 | * @function module:xss-filters#uriQueryInHTMLData 844 | * @description This is an alias of {@link module:xss-filters#uriPathInHTMLData} 845 | * 846 | * @alias module:xss-filters#uriPathInHTMLData 847 | */ 848 | exports.uriQueryInHTMLData = exports.uriPathInHTMLData; 849 | 850 | /** 851 | * @function module:xss-filters#uriQueryInHTMLComment 852 | * @description This is an alias of {@link module:xss-filters#uriPathInHTMLComment} 853 | * 854 | * @alias module:xss-filters#uriPathInHTMLComment 855 | */ 856 | exports.uriQueryInHTMLComment = exports.uriPathInHTMLComment; 857 | 858 | 859 | 860 | /** 861 | * @function module:xss-filters#uriComponentInSingleQuotedAttr 862 | * 863 | * @param {string} s - An untrusted user input, supposedly a URI Component 864 | * @returns {string} The string s encoded first by window.encodeURIComponent(), then inSingleQuotedAttr() 865 | * 866 | * @description 867 | * This filter is to be placed in HTML Attribute Value (single-quoted) state for a URI Component.
868 | * The correct order of encoders is thus: first window.encodeURIComponent(), then inSingleQuotedAttr() 869 | * 870 | * 871 | * 876 | * 877 | * @example 878 | * // output context to be applied by this filter. 879 | * link 880 | * 881 | */ 882 | exports.uriComponentInSingleQuotedAttr = function (s) { 883 | return privFilters.yavs(privFilters.yuc(s)); 884 | }; 885 | 886 | /** 887 | * @function module:xss-filters#uriComponentInDoubleQuotedAttr 888 | * 889 | * @param {string} s - An untrusted user input, supposedly a URI Component 890 | * @returns {string} The string s encoded first by window.encodeURIComponent(), then inDoubleQuotedAttr() 891 | * 892 | * @description 893 | * This filter is to be placed in HTML Attribute Value (double-quoted) state for a URI Component.
894 | * The correct order of encoders is thus: first window.encodeURIComponent(), then inDoubleQuotedAttr() 895 | * 896 | * 897 | * 902 | * 903 | * @example 904 | * // output context to be applied by this filter. 905 | * link 906 | * 907 | */ 908 | exports.uriComponentInDoubleQuotedAttr = function (s) { 909 | return privFilters.yavd(privFilters.yuc(s)); 910 | }; 911 | 912 | 913 | /** 914 | * @function module:xss-filters#uriComponentInUnQuotedAttr 915 | * 916 | * @param {string} s - An untrusted user input, supposedly a URI Component 917 | * @returns {string} The string s encoded first by window.encodeURIComponent(), then inUnQuotedAttr() 918 | * 919 | * @description 920 | * This filter is to be placed in HTML Attribute Value (unquoted) state for a URI Component.
921 | * The correct order of encoders is thus: first the built-in encodeURIComponent(), then inUnQuotedAttr() 922 | * 923 | * 924 | * 929 | * 930 | * @example 931 | * // output context to be applied by this filter. 932 | * link 933 | * 934 | */ 935 | exports.uriComponentInUnQuotedAttr = function (s) { 936 | return privFilters.yavu(privFilters.yuc(s)); 937 | }; 938 | 939 | /** 940 | * @function module:xss-filters#uriComponentInHTMLData 941 | * 942 | * @param {string} s - An untrusted user input, supposedly a URI Component 943 | * @returns {string} The string s encoded by window.encodeURIComponent() and then inHTMLData() 944 | * 945 | * @description 946 | * This filter is to be placed in HTML Data state for a URI Component. 947 | * 948 | *

Notice: The actual implementation skips inHTMLData(), since '<' is already encoded as '%3C' by encodeURIComponent().

949 | * 950 | * 955 | * 956 | * @example 957 | * // output context to be applied by this filter. 958 | * http://example.com/?q={{{uriComponentInHTMLData uri_component}}} 959 | * http://example.com/#{{{uriComponentInHTMLData uri_fragment}}} 960 | * 961 | */ 962 | exports.uriComponentInHTMLData = privFilters.yuc; 963 | 964 | 965 | /** 966 | * @function module:xss-filters#uriComponentInHTMLComment 967 | * 968 | * @param {string} s - An untrusted user input, supposedly a URI Component 969 | * @returns {string} The string s encoded by window.encodeURIComponent(), and finally inHTMLComment() 970 | * 971 | * @description 972 | * This filter is to be placed in HTML Comment state for a URI Component. 973 | * 974 | * 980 | * 981 | * @example 982 | * // output context to be applied by this filter. 983 | * 984 | * 985 | */ 986 | exports.uriComponentInHTMLComment = function (s) { 987 | return privFilters.yc(privFilters.yuc(s)); 988 | }; 989 | 990 | 991 | // uriFragmentInSingleQuotedAttr 992 | // added yubl on top of uriComponentInAttr 993 | // Rationale: given pattern like this: 994 | // developer may expect s is always prefixed with #, but an attacker can abuse it with 'javascript:alert(1)' 995 | 996 | /** 997 | * @function module:xss-filters#uriFragmentInSingleQuotedAttr 998 | * 999 | * @param {string} s - An untrusted user input, supposedly a URI Fragment 1000 | * @returns {string} The string s encoded first by window.encodeURIComponent(), then inSingleQuotedAttr(), and finally prefix the resulted string with 'x-' if it begins with 'javascript:' or 'vbscript:' that could possibly lead to script execution 1001 | * 1002 | * @description 1003 | * This filter is to be placed in HTML Attribute Value (single-quoted) state for a URI Fragment.
1004 | * The correct order of encoders is thus: first window.encodeURIComponent(), then inSingleQuotedAttr(), and finally prefix the resulted string with 'x-' if it begins with 'javascript:' or 'vbscript:' that could possibly lead to script execution 1005 | * 1006 | * 1007 | *
1012 | * 1013 | * @example 1014 | * // output context to be applied by this filter. 1015 | * link 1016 | * 1017 | */ 1018 | exports.uriFragmentInSingleQuotedAttr = function (s) { 1019 | return privFilters.yubl(privFilters.yavs(privFilters.yuc(s))); 1020 | }; 1021 | 1022 | // uriFragmentInDoubleQuotedAttr 1023 | // added yubl on top of uriComponentInAttr 1024 | // Rationale: given pattern like this: 1025 | // developer may expect s is always prefixed with #, but an attacker can abuse it with 'javascript:alert(1)' 1026 | 1027 | /** 1028 | * @function module:xss-filters#uriFragmentInDoubleQuotedAttr 1029 | * 1030 | * @param {string} s - An untrusted user input, supposedly a URI Fragment 1031 | * @returns {string} The string s encoded first by window.encodeURIComponent(), then inDoubleQuotedAttr(), and finally prefix the resulted string with 'x-' if it begins with 'javascript:' or 'vbscript:' that could possibly lead to script execution 1032 | * 1033 | * @description 1034 | * This filter is to be placed in HTML Attribute Value (double-quoted) state for a URI Fragment.
1035 | * The correct order of encoders is thus: first window.encodeURIComponent(), then inDoubleQuotedAttr(), and finally prefix the resulted string with 'x-' if it begins with 'javascript:' or 'vbscript:' that could possibly lead to script execution 1036 | * 1037 | * 1038 | *
1043 | * 1044 | * @example 1045 | * // output context to be applied by this filter. 1046 | * link 1047 | * 1048 | */ 1049 | exports.uriFragmentInDoubleQuotedAttr = function (s) { 1050 | return privFilters.yubl(privFilters.yavd(privFilters.yuc(s))); 1051 | }; 1052 | 1053 | // uriFragmentInUnQuotedAttr 1054 | // added yubl on top of uriComponentInAttr 1055 | // Rationale: given pattern like this: 1056 | // developer may expect s is always prefixed with #, but an attacker can abuse it with 'javascript:alert(1)' 1057 | 1058 | /** 1059 | * @function module:xss-filters#uriFragmentInUnQuotedAttr 1060 | * 1061 | * @param {string} s - An untrusted user input, supposedly a URI Fragment 1062 | * @returns {string} The string s encoded first by window.encodeURIComponent(), then inUnQuotedAttr(), and finally prefix the resulted string with 'x-' if it begins with 'javascript:' or 'vbscript:' that could possibly lead to script execution 1063 | * 1064 | * @description 1065 | * This filter is to be placed in HTML Attribute Value (unquoted) state for a URI Fragment.
1066 | * The correct order of encoders is thus: first the built-in encodeURIComponent(), then inUnQuotedAttr(), and finally prefix the resulted string with 'x-' if it begins with 'javascript:' or 'vbscript:' that could possibly lead to script execution 1067 | * 1068 | *
1073 | * 1074 | * @example 1075 | * // output context to be applied by this filter. 1076 | * link 1077 | * 1078 | */ 1079 | exports.uriFragmentInUnQuotedAttr = function (s) { 1080 | return privFilters.yubl(privFilters.yavu(privFilters.yuc(s))); 1081 | }; 1082 | 1083 | 1084 | /** 1085 | * @function module:xss-filters#uriFragmentInHTMLData 1086 | * @description This is an alias of {@link module:xss-filters#uriComponentInHTMLData} 1087 | * 1088 | * @alias module:xss-filters#uriComponentInHTMLData 1089 | */ 1090 | exports.uriFragmentInHTMLData = exports.uriComponentInHTMLData; 1091 | 1092 | /** 1093 | * @function module:xss-filters#uriFragmentInHTMLComment 1094 | * @description This is an alias of {@link module:xss-filters#uriComponentInHTMLComment} 1095 | * 1096 | * @alias module:xss-filters#uriComponentInHTMLComment 1097 | */ 1098 | exports.uriFragmentInHTMLComment = exports.uriComponentInHTMLComment; 1099 | 1100 | },{}],2:[function(require,module,exports){ 1101 | "use strict"; 1102 | 1103 | Object.defineProperty(exports, "__esModule", { 1104 | value: true 1105 | }); 1106 | 1107 | var _constants = require("./constants"); 1108 | 1109 | var API = { fetch: fetch }; 1110 | 1111 | function fetch(path) { 1112 | var url = _constants.BASE_URI + "/" + path; 1113 | var request = new XMLHttpRequest(); 1114 | 1115 | return new Promise(function (resolve, reject) { 1116 | 1117 | request.open('GET', url, true); 1118 | 1119 | request.onload = function () { 1120 | if (request.status >= 200 && request.status < 400) { 1121 | resolve(JSON.parse(request.response)); 1122 | } 1123 | }; 1124 | 1125 | request.onerror = function () { 1126 | reject(new Error("Error fetching posts")); 1127 | }; 1128 | 1129 | request.send(); 1130 | }); 1131 | } 1132 | 1133 | exports.default = API; 1134 | 1135 | },{"./constants":4}],3:[function(require,module,exports){ 1136 | "use strict"; 1137 | 1138 | var _post = require("./post"); 1139 | 1140 | var _post2 = _interopRequireDefault(_post); 1141 | 1142 | var _user = require("./user"); 1143 | 1144 | var _user2 = _interopRequireDefault(_user); 1145 | 1146 | var _ui = require("./ui"); 1147 | 1148 | var _ui2 = _interopRequireDefault(_ui); 1149 | 1150 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 1151 | 1152 | _post2.default.findAll().then(_ui2.default.renderPosts); 1153 | 1154 | _user2.default.findAllActive().then(_ui2.default.renderActiveUsers); 1155 | 1156 | },{"./post":5,"./ui":6,"./user":7}],4:[function(require,module,exports){ 1157 | "use strict"; 1158 | 1159 | Object.defineProperty(exports, "__esModule", { 1160 | value: true 1161 | }); 1162 | var BASE_URI = "http://localhost:3000"; 1163 | 1164 | exports.BASE_URI = BASE_URI; 1165 | 1166 | },{}],5:[function(require,module,exports){ 1167 | "use strict"; 1168 | 1169 | Object.defineProperty(exports, "__esModule", { 1170 | value: true 1171 | }); 1172 | 1173 | var _api = require("./api"); 1174 | 1175 | var _api2 = _interopRequireDefault(_api); 1176 | 1177 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 1178 | 1179 | var Post = { findAll: findAll }; 1180 | 1181 | function findAll() { 1182 | return _api2.default.fetch("posts"); 1183 | } 1184 | 1185 | exports.default = Post; 1186 | 1187 | },{"./api":2}],6:[function(require,module,exports){ 1188 | "use strict"; 1189 | 1190 | Object.defineProperty(exports, "__esModule", { 1191 | value: true 1192 | }); 1193 | 1194 | var _xssFilters = require("xss-filters"); 1195 | 1196 | var _xssFilters2 = _interopRequireDefault(_xssFilters); 1197 | 1198 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 1199 | 1200 | var ui = { 1201 | renderPosts: function renderPosts(posts) { 1202 | var target = document.querySelector(".container"); 1203 | 1204 | var postElements = posts.map(function (post) { 1205 | return articleElement(post.title, post.lastReply); 1206 | }); 1207 | 1208 | target.innerHTML = postElements.join(""); 1209 | }, 1210 | renderActiveUsers: function renderActiveUsers(users) { 1211 | 1212 | var target = document.querySelector(".sidebar-content"); 1213 | 1214 | var elements = users.map(function (user) { 1215 | var name = user.name; 1216 | var avatar = user.avatar; 1217 | 1218 | return activeUsersTemplate(name, avatar); 1219 | }); 1220 | 1221 | target.innerHTML = elements.join(""); 1222 | } 1223 | }; 1224 | 1225 | function articleElement(title, lastReply) { 1226 | var safeTitle = _xssFilters2.default.inHTMLData(title); 1227 | var safeLastReply = _xssFilters2.default.inHTMLData(lastReply); 1228 | 1229 | return "
\n

\n " + safeTitle + "\n

\n \n
"; 1230 | } 1231 | 1232 | function activeUsersTemplate(name, avatar) { 1233 | 1234 | var safeName = _xssFilters2.default.inHTMLData(name); 1235 | var safeAvatar = _xssFilters2.default.inHTMLData(avatar); 1236 | 1237 | var template = "\n
\n \n
" + safeName + "
\n
"; 1238 | 1239 | return template; 1240 | } 1241 | 1242 | exports.default = ui; 1243 | 1244 | },{"xss-filters":1}],7:[function(require,module,exports){ 1245 | "use strict"; 1246 | 1247 | Object.defineProperty(exports, "__esModule", { 1248 | value: true 1249 | }); 1250 | 1251 | var _api = require("./api"); 1252 | 1253 | var _api2 = _interopRequireDefault(_api); 1254 | 1255 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 1256 | 1257 | var User = { findAllActive: findAllActive }; 1258 | 1259 | function findAllActive() { 1260 | return _api2.default.fetch("activeUsers"); 1261 | } 1262 | 1263 | exports.default = User; 1264 | 1265 | },{"./api":2}]},{},[3]); 1266 | -------------------------------------------------------------------------------- /gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | import gulp from "gulp"; 2 | import browserify from "browserify"; 3 | import source from "vinyl-source-stream"; 4 | 5 | gulp.task("default", ["transpile"]); 6 | 7 | gulp.task("transpile", () => { 8 | 9 | return browserify("src/app.js") 10 | .transform("babelify") 11 | .bundle() 12 | .pipe(source("bundle.js")) 13 | .pipe(gulp.dest("dist")); 14 | 15 | }); 16 | 17 | 18 | 19 | gulp.task("watch", ["transpile"], () => { 20 | gulp.watch("src/**/*", ["transpile"]); 21 | }); 22 | 23 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | The Forum 9 | 10 | 11 | 12 |
13 | 14 |
15 |
16 |
17 |
18 | 19 | 25 |
26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /npm-shrinkwrap.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-with-gulp", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "babel-core": { 6 | "version": "6.2.1", 7 | "from": "babel-core@>=6.1.21 <7.0.0", 8 | "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.2.1.tgz", 9 | "dependencies": { 10 | "babel-code-frame": { 11 | "version": "6.1.18", 12 | "from": "babel-code-frame@>=6.1.18 <7.0.0", 13 | "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.1.18.tgz", 14 | "dependencies": { 15 | "chalk": { 16 | "version": "1.1.1", 17 | "from": "chalk@>=1.1.0 <2.0.0", 18 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.1.tgz", 19 | "dependencies": { 20 | "ansi-styles": { 21 | "version": "2.1.0", 22 | "from": "ansi-styles@>=2.1.0 <3.0.0", 23 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.1.0.tgz" 24 | }, 25 | "escape-string-regexp": { 26 | "version": "1.0.3", 27 | "from": "escape-string-regexp@>=1.0.2 <2.0.0", 28 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.3.tgz" 29 | }, 30 | "has-ansi": { 31 | "version": "2.0.0", 32 | "from": "has-ansi@>=2.0.0 <3.0.0", 33 | "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", 34 | "dependencies": { 35 | "ansi-regex": { 36 | "version": "2.0.0", 37 | "from": "ansi-regex@>=2.0.0 <3.0.0", 38 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz" 39 | } 40 | } 41 | }, 42 | "strip-ansi": { 43 | "version": "3.0.0", 44 | "from": "strip-ansi@>=3.0.0 <4.0.0", 45 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.0.tgz", 46 | "dependencies": { 47 | "ansi-regex": { 48 | "version": "2.0.0", 49 | "from": "ansi-regex@>=2.0.0 <3.0.0", 50 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz" 51 | } 52 | } 53 | }, 54 | "supports-color": { 55 | "version": "2.0.0", 56 | "from": "supports-color@>=2.0.0 <3.0.0", 57 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" 58 | } 59 | } 60 | }, 61 | "esutils": { 62 | "version": "2.0.2", 63 | "from": "esutils@>=2.0.2 <3.0.0", 64 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz" 65 | }, 66 | "js-tokens": { 67 | "version": "1.0.2", 68 | "from": "js-tokens@>=1.0.1 <2.0.0", 69 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-1.0.2.tgz" 70 | }, 71 | "line-numbers": { 72 | "version": "0.2.0", 73 | "from": "line-numbers@>=0.2.0 <0.3.0", 74 | "resolved": "https://registry.npmjs.org/line-numbers/-/line-numbers-0.2.0.tgz", 75 | "dependencies": { 76 | "left-pad": { 77 | "version": "0.0.3", 78 | "from": "left-pad@0.0.3", 79 | "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-0.0.3.tgz" 80 | } 81 | } 82 | }, 83 | "repeating": { 84 | "version": "1.1.3", 85 | "from": "repeating@>=1.1.3 <2.0.0", 86 | "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz", 87 | "dependencies": { 88 | "is-finite": { 89 | "version": "1.0.1", 90 | "from": "is-finite@>=1.0.0 <2.0.0", 91 | "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.1.tgz", 92 | "dependencies": { 93 | "number-is-nan": { 94 | "version": "1.0.0", 95 | "from": "number-is-nan@>=1.0.0 <2.0.0", 96 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.0.tgz" 97 | } 98 | } 99 | } 100 | } 101 | } 102 | } 103 | }, 104 | "babel-generator": { 105 | "version": "6.2.0", 106 | "from": "babel-generator@>=6.2.0 <7.0.0", 107 | "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.2.0.tgz", 108 | "dependencies": { 109 | "detect-indent": { 110 | "version": "3.0.1", 111 | "from": "detect-indent@>=3.0.1 <4.0.0", 112 | "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-3.0.1.tgz", 113 | "dependencies": { 114 | "get-stdin": { 115 | "version": "4.0.1", 116 | "from": "get-stdin@>=4.0.1 <5.0.0", 117 | "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz" 118 | }, 119 | "minimist": { 120 | "version": "1.2.0", 121 | "from": "minimist@>=1.1.0 <2.0.0", 122 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz" 123 | } 124 | } 125 | }, 126 | "is-integer": { 127 | "version": "1.0.6", 128 | "from": "is-integer@>=1.0.4 <2.0.0", 129 | "resolved": "https://registry.npmjs.org/is-integer/-/is-integer-1.0.6.tgz", 130 | "dependencies": { 131 | "is-finite": { 132 | "version": "1.0.1", 133 | "from": "is-finite@>=1.0.0 <2.0.0", 134 | "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.1.tgz", 135 | "dependencies": { 136 | "number-is-nan": { 137 | "version": "1.0.0", 138 | "from": "number-is-nan@>=1.0.0 <2.0.0", 139 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.0.tgz" 140 | } 141 | } 142 | } 143 | } 144 | }, 145 | "repeating": { 146 | "version": "1.1.3", 147 | "from": "repeating@>=1.1.3 <2.0.0", 148 | "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz", 149 | "dependencies": { 150 | "is-finite": { 151 | "version": "1.0.1", 152 | "from": "is-finite@>=1.0.0 <2.0.0", 153 | "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.1.tgz", 154 | "dependencies": { 155 | "number-is-nan": { 156 | "version": "1.0.0", 157 | "from": "number-is-nan@>=1.0.0 <2.0.0", 158 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.0.tgz" 159 | } 160 | } 161 | } 162 | } 163 | }, 164 | "trim-right": { 165 | "version": "1.0.1", 166 | "from": "trim-right@>=1.0.1 <2.0.0", 167 | "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz" 168 | } 169 | } 170 | }, 171 | "babel-helpers": { 172 | "version": "6.1.20", 173 | "from": "babel-helpers@>=6.1.20 <7.0.0", 174 | "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.1.20.tgz" 175 | }, 176 | "babel-messages": { 177 | "version": "6.2.1", 178 | "from": "babel-messages@>=6.2.1 <7.0.0", 179 | "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.2.1.tgz" 180 | }, 181 | "babel-template": { 182 | "version": "6.2.0", 183 | "from": "babel-template@>=6.2.0 <7.0.0", 184 | "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.2.0.tgz" 185 | }, 186 | "babel-runtime": { 187 | "version": "5.8.34", 188 | "from": "babel-runtime@>=5.0.0 <6.0.0", 189 | "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.34.tgz", 190 | "dependencies": { 191 | "core-js": { 192 | "version": "1.2.6", 193 | "from": "core-js@>=1.0.0 <2.0.0", 194 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.6.tgz" 195 | } 196 | } 197 | }, 198 | "babel-register": { 199 | "version": "6.2.0", 200 | "from": "babel-register@>=6.2.0 <7.0.0", 201 | "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.2.0.tgz", 202 | "dependencies": { 203 | "core-js": { 204 | "version": "1.2.6", 205 | "from": "core-js@>=1.0.0 <2.0.0", 206 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.6.tgz" 207 | }, 208 | "home-or-tmp": { 209 | "version": "1.0.0", 210 | "from": "home-or-tmp@>=1.0.0 <2.0.0", 211 | "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-1.0.0.tgz", 212 | "dependencies": { 213 | "os-tmpdir": { 214 | "version": "1.0.1", 215 | "from": "os-tmpdir@>=1.0.1 <2.0.0", 216 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.1.tgz" 217 | }, 218 | "user-home": { 219 | "version": "1.1.1", 220 | "from": "user-home@>=1.1.1 <2.0.0", 221 | "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz" 222 | } 223 | } 224 | }, 225 | "source-map-support": { 226 | "version": "0.2.10", 227 | "from": "source-map-support@>=0.2.10 <0.3.0", 228 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.2.10.tgz", 229 | "dependencies": { 230 | "source-map": { 231 | "version": "0.1.32", 232 | "from": "source-map@0.1.32", 233 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.32.tgz", 234 | "dependencies": { 235 | "amdefine": { 236 | "version": "1.0.0", 237 | "from": "amdefine@>=0.0.4", 238 | "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.0.tgz" 239 | } 240 | } 241 | } 242 | } 243 | } 244 | } 245 | }, 246 | "babel-traverse": { 247 | "version": "6.2.0", 248 | "from": "babel-traverse@>=6.2.0 <7.0.0", 249 | "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.2.0.tgz", 250 | "dependencies": { 251 | "globals": { 252 | "version": "8.12.1", 253 | "from": "globals@>=8.3.0 <9.0.0", 254 | "resolved": "https://registry.npmjs.org/globals/-/globals-8.12.1.tgz" 255 | }, 256 | "invariant": { 257 | "version": "2.2.0", 258 | "from": "invariant@>=2.2.0 <3.0.0", 259 | "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.0.tgz", 260 | "dependencies": { 261 | "loose-envify": { 262 | "version": "1.1.0", 263 | "from": "loose-envify@>=1.0.0 <2.0.0", 264 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.1.0.tgz", 265 | "dependencies": { 266 | "js-tokens": { 267 | "version": "1.0.2", 268 | "from": "js-tokens@>=1.0.1 <2.0.0", 269 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-1.0.2.tgz" 270 | } 271 | } 272 | } 273 | } 274 | }, 275 | "repeating": { 276 | "version": "1.1.3", 277 | "from": "repeating@>=1.1.3 <2.0.0", 278 | "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz", 279 | "dependencies": { 280 | "is-finite": { 281 | "version": "1.0.1", 282 | "from": "is-finite@>=1.0.0 <2.0.0", 283 | "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.1.tgz", 284 | "dependencies": { 285 | "number-is-nan": { 286 | "version": "1.0.0", 287 | "from": "number-is-nan@>=1.0.0 <2.0.0", 288 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.0.tgz" 289 | } 290 | } 291 | } 292 | } 293 | } 294 | } 295 | }, 296 | "babel-types": { 297 | "version": "6.2.0", 298 | "from": "babel-types@>=6.2.0 <7.0.0", 299 | "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.2.0.tgz", 300 | "dependencies": { 301 | "esutils": { 302 | "version": "2.0.2", 303 | "from": "esutils@>=2.0.2 <3.0.0", 304 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz" 305 | }, 306 | "to-fast-properties": { 307 | "version": "1.0.1", 308 | "from": "to-fast-properties@>=1.0.1 <2.0.0", 309 | "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.1.tgz" 310 | } 311 | } 312 | }, 313 | "babylon": { 314 | "version": "6.2.0", 315 | "from": "babylon@>=6.2.0 <7.0.0", 316 | "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.2.0.tgz" 317 | }, 318 | "convert-source-map": { 319 | "version": "1.1.2", 320 | "from": "convert-source-map@>=1.1.0 <2.0.0", 321 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.2.tgz" 322 | }, 323 | "debug": { 324 | "version": "2.2.0", 325 | "from": "debug@>=2.1.1 <3.0.0", 326 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", 327 | "dependencies": { 328 | "ms": { 329 | "version": "0.7.1", 330 | "from": "ms@0.7.1", 331 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz" 332 | } 333 | } 334 | }, 335 | "json5": { 336 | "version": "0.4.0", 337 | "from": "json5@>=0.4.0 <0.5.0", 338 | "resolved": "https://registry.npmjs.org/json5/-/json5-0.4.0.tgz" 339 | }, 340 | "lodash": { 341 | "version": "3.10.1", 342 | "from": "lodash@>=3.10.0 <4.0.0", 343 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz" 344 | }, 345 | "minimatch": { 346 | "version": "2.0.10", 347 | "from": "minimatch@>=2.0.3 <3.0.0", 348 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", 349 | "dependencies": { 350 | "brace-expansion": { 351 | "version": "1.1.1", 352 | "from": "brace-expansion@>=1.0.0 <2.0.0", 353 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.1.tgz", 354 | "dependencies": { 355 | "balanced-match": { 356 | "version": "0.2.1", 357 | "from": "balanced-match@>=0.2.0 <0.3.0", 358 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.2.1.tgz" 359 | }, 360 | "concat-map": { 361 | "version": "0.0.1", 362 | "from": "concat-map@0.0.1", 363 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" 364 | } 365 | } 366 | } 367 | } 368 | }, 369 | "path-exists": { 370 | "version": "1.0.0", 371 | "from": "path-exists@>=1.0.0 <2.0.0", 372 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-1.0.0.tgz" 373 | }, 374 | "path-is-absolute": { 375 | "version": "1.0.0", 376 | "from": "path-is-absolute@>=1.0.0 <2.0.0", 377 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz" 378 | }, 379 | "private": { 380 | "version": "0.1.6", 381 | "from": "private@>=0.1.6 <0.2.0", 382 | "resolved": "https://registry.npmjs.org/private/-/private-0.1.6.tgz" 383 | }, 384 | "shebang-regex": { 385 | "version": "1.0.0", 386 | "from": "shebang-regex@>=1.0.0 <2.0.0", 387 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz" 388 | }, 389 | "slash": { 390 | "version": "1.0.0", 391 | "from": "slash@>=1.0.0 <2.0.0", 392 | "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz" 393 | }, 394 | "source-map": { 395 | "version": "0.5.3", 396 | "from": "source-map@>=0.5.0 <0.6.0", 397 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.3.tgz" 398 | } 399 | } 400 | } 401 | } 402 | } 403 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-with-gulp", 3 | "version": "1.0.0", 4 | "description": "Starter code for using Babel with Gulp", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Carlos Souza (http://csouza.me/)", 10 | "license": "ISC", 11 | "repository": { 12 | "type": "git", 13 | "url": "git+ssh://git@github.com/codeschool/ES2015SoupToBits" 14 | }, 15 | "keywords": [ 16 | "babel", 17 | "gulp", 18 | "es2015" 19 | ], 20 | "bugs": { 21 | "url": "https://github.com/codeschool/babel-with-gulp/issues" 22 | }, 23 | "homepage": "https://github.com/codeschool/babel-with-gulp#readme", 24 | "devDependencies": { 25 | "babel-preset-es2015": "^6.1.18", 26 | "babelify": "^7.2.0", 27 | "browserify": "^12.0.1", 28 | "gulp": "^3.9.0", 29 | "vinyl-source-stream": "^1.1.0", 30 | "xss-filters": "^1.2.6" 31 | }, 32 | "dependencies": { 33 | "babel-core": "^6.1.21" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /server/db.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": [ 3 | { 4 | "id": 1, 5 | "title": "In hybrid moments, give me a moment", 6 | "lastReply": "1997-07-15" 7 | }, 8 | { 9 | "id": 2, 10 | "title": "Sound system gonna bring me back up", 11 | "lastReply": "1991-07-01" 12 | }, 13 | { 14 | "id": 3, 15 | "title": "When I'm out walkin', I strut my stuff", 16 | "lastReply": "1983-04-19" 17 | } 18 | ], 19 | "activeUsers": [ 20 | { 21 | "name": "Sam", 22 | "avatar": "avatar.jpg" 23 | }, 24 | { 25 | "name": "Tyler", 26 | "avatar": "avatar.jpg" 27 | }, 28 | { 29 | "name": "Brook", 30 | "avatar": "avatar.jpg" 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /server/db.with.xss.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": [ 3 | { 4 | "id": 1, 5 | "title": "In hybrid moments, give me a moment", 6 | "lastReply": "" 7 | }, 8 | { 9 | "id": 2, 10 | "title": "Sound system gonna bring me back up", 11 | "lastReply": "1991-07-01" 12 | }, 13 | { 14 | "id": 3, 15 | "title": "When I'm out walkin', I strut my stuff", 16 | "lastReply": "1983-04-19" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /src/api.js: -------------------------------------------------------------------------------- 1 | import { BASE_URI } from "./constants"; 2 | 3 | let API = { fetch }; 4 | 5 | function fetch(path){ 6 | let url = `${BASE_URI}/${path}` 7 | let request = new XMLHttpRequest(); 8 | 9 | return new Promise( (resolve, reject) => { 10 | 11 | request.open('GET', url, true); 12 | 13 | request.onload = () => { 14 | if (request.status >= 200 && request.status < 400) { 15 | resolve(JSON.parse(request.response)); 16 | } 17 | }; 18 | 19 | request.onerror = () => { 20 | reject(new Error("Error fetching posts")); 21 | } 22 | 23 | request.send(); 24 | }); 25 | } 26 | 27 | export default API; 28 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import Post from "./post"; 2 | import User from "./user"; 3 | import ui from "./ui"; 4 | 5 | Post.findAll().then(ui.renderPosts); 6 | 7 | User.findAllActive().then(ui.renderActiveUsers); 8 | 9 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | const BASE_URI = "http://localhost:3000"; 2 | 3 | export { BASE_URI }; 4 | -------------------------------------------------------------------------------- /src/post.js: -------------------------------------------------------------------------------- 1 | import API from "./api"; 2 | 3 | let Post = { findAll }; 4 | 5 | function findAll(){ 6 | return API.fetch("posts"); 7 | } 8 | 9 | export default Post; 10 | -------------------------------------------------------------------------------- /src/ui.js: -------------------------------------------------------------------------------- 1 | import xss from "xss-filters"; 2 | 3 | let ui = { 4 | renderPosts(posts){ 5 | let target = document.querySelector(".container"); 6 | 7 | let postElements = posts.map( post => { 8 | return articleElement(post.title, post.lastReply); 9 | }); 10 | 11 | target.innerHTML = postElements.join(""); 12 | }, 13 | 14 | renderActiveUsers(users){ 15 | 16 | let target = document.querySelector(".sidebar-content"); 17 | 18 | let elements = users.map( (user) => { 19 | let { name, avatar } = user; 20 | return activeUsersTemplate(name, avatar); 21 | }); 22 | 23 | target.innerHTML = elements.join(""); 24 | } 25 | }; 26 | 27 | function articleElement(title, lastReply){ 28 | let safeTitle = xss.inHTMLData(title); 29 | let safeLastReply = xss.inHTMLData(lastReply); 30 | 31 | return`
32 |

33 | ${safeTitle} 34 |

35 | 38 |
`; 39 | } 40 | 41 | function activeUsersTemplate(name, avatar){ 42 | 43 | let safeName = xss.inHTMLData(name); 44 | let safeAvatar = xss.inHTMLData(avatar); 45 | 46 | let template = ` 47 |
48 | 49 | 50 |
`; 51 | 52 | return template; 53 | } 54 | 55 | export default ui; 56 | -------------------------------------------------------------------------------- /src/user.js: -------------------------------------------------------------------------------- 1 | import API from "./api"; 2 | 3 | let User = { findAllActive }; 4 | 5 | function findAllActive(){ 6 | return API.fetch("activeUsers"); 7 | } 8 | 9 | export default User; 10 | --------------------------------------------------------------------------------