├── .gitignore ├── 404.html ├── CNAME ├── README.md ├── codekit-config.json ├── css ├── .gitkeep ├── _functions.styl ├── _mixins.styl ├── _variables.styl ├── _vertical-rhythm.styl ├── bones.css ├── bones.styl ├── monokai_sublime.min.css ├── normalize.css ├── normalize.styl ├── skin.css └── skin.styl ├── docs └── index.html ├── example ├── index.html └── sendgrid.html ├── favicon.ico ├── fireform.js ├── fireform.min.js ├── img ├── .gitkeep ├── collect-data-with-fireform.png ├── fireform-logo-white.png ├── fireform-logo.png ├── generate-fireform-code.png ├── loading.gif ├── paste-fireform-code-into-html.png ├── sf-dev-labs.png └── spark.png ├── index.html ├── index.js ├── js ├── app.js ├── config.js ├── controllers-ck.js ├── controllers.js ├── directives.js ├── filters.js ├── module.routeSecurity.js ├── module.waitForAuth.js ├── routes.js ├── service.firebase.js ├── service.login.js ├── services.js └── vendor │ ├── highlight.min.js │ └── underscore-min.js ├── list └── index.html ├── package.json ├── package.jsoncop.json └── partials ├── .gitkeep ├── account.html ├── add.html ├── app-header.html ├── docs.html ├── footer.html ├── list-view.html ├── lists.html ├── login.html ├── public-header.html ├── public-home.html ├── signup.html ├── snippet ├── boiler-confirmation.html ├── boiler-notification-confirmation.html ├── boiler-notification.html ├── boiler.html ├── default-confirmation.html ├── default-notification-confirmation.html ├── default-notification.html └── default.html └── snippetBoil.html /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | app/node_modules 3 | node_modules 4 | -------------------------------------------------------------------------------- /404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Fireform 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | fireform.org -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Fireform 2 | ======== 3 | 4 | [Visit the docs](http://fireform.org/ "Fireform") 5 | 6 | Forms for your static webpages. 7 | -------------------------------------------------------------------------------- /css/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SFDevLabs/fireform/9b00ce76cbc0f7a5ceb56629bfb85b0eb10b7862/css/.gitkeep -------------------------------------------------------------------------------- /css/_functions.styl: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Functions from https://github.com/jenius/axis 3 | // ---------------------------------------------------------------------------- 4 | 5 | // @public unitless() function borrowed from SASS 6 | // TODO: move this to a utilities file 7 | -unitless(number) 8 | unit(number) is '' 9 | 10 | // @public Function. 11 | // True if a number has a relative unit. 12 | -relative-unit(number) 13 | unit(number) is '%' or unit(number) is 'em' or unit(number) is 'rem' 14 | 15 | // @public Function. 16 | // True if a number has an absolute unit. 17 | -absolute-unit(number) 18 | not (-relative-unit(number) or -unitless(number)) 19 | 20 | // @public function - Calculate rhythm units. 21 | -rhythm(lines = 1, font-size = base-font-size, offset = 0) 22 | if (not relative-font-sizing) and (font-size is not base-font-size) 23 | warn('relative-font-sizing is false but a relative font size was passed to the rhythm function') 24 | rhythm = font-unit * (lines * base-line-height - offset) / font-size 25 | // Round the pixels down to nearest integer. 26 | if unit(rhythm) is 'px' 27 | rhythm = floor(rhythm) 28 | return rhythm 29 | 30 | // @private Function - Calculate the minimum multiple of rhythm units needed to contain the font-size. 31 | -lines-for-font-size(font-size) 32 | lines = round-to-nearest-half-line ? ceil(2 * font-size / base-line-height) / 2 : ceil(font-size / base-line-height) 33 | if lines * base-line-height - font-size < min-line-padding * 2 34 | lines = lines + (round-to-nearest-half-line ? 0.5 : 1) 35 | return unit(lines, '') -------------------------------------------------------------------------------- /css/_mixins.styl: -------------------------------------------------------------------------------- 1 | @import "_variables" 2 | 3 | 4 | 5 | // ---------------------------------------------------------------------------- 6 | // Mixins from https://github.com/jenius/axis 7 | // ---------------------------------------------------------------------------- 8 | 9 | if (relative-font-sizing) and (not -relative-unit(font-unit)) 10 | warn('relative-font-sizing is true but font-unit is set to ' + font-unit + ' which is not a relative unit.') 11 | 12 | establish-baseline(fs = base-font-size) 13 | if fs is a 'unit' 14 | // IE 6 refuses to resize fonts set in pixels and it weirdly resizes fonts 15 | // whose root is set in ems. So we set the root font size in percentages of 16 | // the default font size. 17 | * html 18 | font-size: 100% * (fs / browser-default-font-size) 19 | html 20 | font-size: fs 21 | adjust-leading-to: 1, relative-font-sizing ? fs : base-font-size 22 | 23 | else if (fs is a 'string') and (fs is 'normalize') 24 | baseline-normalize() 25 | 26 | else 27 | warn('Invalid parameter ' + fs + ' passed to establish-baseline().') 28 | 29 | 30 | // Show a background image that can be used to debug your alignments. 31 | // Requires internet connection 32 | // TODO: Make a better version of this 33 | debug-vertical-alignment() 34 | background-image: url('http://basehold.it/i/' + unit(base-line-height, '')) 35 | 36 | // Adjust a block to have a different font size and line height to maintain the 37 | // rhythm. $lines specifies how many multiples of the baseline rhythm each line 38 | // of this font should use up. It does not have to be an integer, but it 39 | // defaults to the smallest integer that is large enough to fit the font. 40 | // Use $from-size to adjust from a font-size other than the base font-size. 41 | adjust-font-size-to(to-size, lines = -lines-for-font-size(to-size), from-size = base-font-size) 42 | if (not relative-font-sizing) and (from-size is not base-font-size) 43 | warn('relative-font-sizing is false but a relative font size was passed to adjust-font-size-to') 44 | font-size: font-unit * (to-size / from-size) 45 | adjust-leading-to: lines, relative-font-sizing ? to-size : base-font-size 46 | 47 | // Adjust a block to have different line height to maintain the rhythm. 48 | // $lines specifies how many multiples of the baseline rhythm each line of this 49 | // font should use up. It does not have to be an integer, but it defaults to the 50 | // smallest integer that is large enough to fit the font. 51 | adjust-leading-to(lines, font-size = base-font-size) 52 | line-height: -rhythm(lines, font-size) 53 | 54 | // Apply leading whitespace. The $property can be margin or padding. 55 | leader(lines = 1, font-size = base-font-size, property = margin) 56 | {property}-top: -rhythm(lines, font-size) 57 | 58 | // Apply leading whitespace as padding. 59 | padding-leader(lines = 1, font-size = base-font-size) 60 | padding-top: -rhythm(lines, font-size) 61 | 62 | // Apply leading whitespace as margin. 63 | margin-leader(lines = 1, font-size = base-font-size) 64 | margin-top: -rhythm(lines, font-size) 65 | 66 | // Apply trailing whitespace. The $property can be margin or padding. 67 | trailer(lines = 1, font-size = base-font-size, property = margin) 68 | {property}-bottom: -rhythm(lines, font-size) 69 | 70 | // Apply trailing whitespace as padding. 71 | padding-trailer(lines = 1, font-size = base-font-size) 72 | padding-bottom: -rhythm(lines, font-size) 73 | 74 | // Apply trailing whitespace as margin. 75 | margin-trailer(lines = 1, font-size = base-font-size) 76 | margin-bottom: -rhythm(lines, font-size) 77 | 78 | // Shorthand mixin to apply whitespace for top and bottom margins and padding. 79 | rhythm(l = 0, pl = 0, pt = 0, t = 0, font-size = base-font-size) 80 | leader: l, font-size 81 | padding-leader: pl, font-size 82 | padding-trailer: pt, font-size 83 | trailer: t, font-size 84 | 85 | // Apply a border and whitespace to any side without destroying the vertical 86 | // rhythm. The whitespace must be greater than the width of the border. 87 | apply-side-rhythm-border(side, w = 1px, lines = 1, font-size = base-font-size, bs = default-rhythm-border-style) 88 | if (not relative-font-sizing) and (font-size is not base-font-size) 89 | warn('relative-font-sizing is false but a relative font size was passed to apply-side-rhythm-border') 90 | border-{side}-style: bs 91 | border-{side}-width: font-unit * (w / font-size) 92 | padding-{side}: -rhythm(lines, font-size, offset = w) 93 | 94 | // Apply borders and whitespace equally to all sides. 95 | rhythm-borders(w = 1px, lines = 1, font-size = base-font-size, bs = default-rhythm-border-style) 96 | if (not relative-font-sizing) and (font-size is not base-font-size) 97 | warn('relative-font-sizing is false but a relative font size was passed to rhythm-borders') 98 | border-style: bs 99 | border-width: font-unit * (w / font-size) 100 | padding: -rhythm(lines, font-size, offset = w) 101 | 102 | // Apply a leading border. 103 | leading-border(width = 1px, lines = 1, font-size = base-font-size, border-style = default-rhythm-border-style) 104 | apply-side-rhythm-border: top, width, lines, font-size, border-style 105 | 106 | // Apply a trailing border. 107 | trailing-border(width = 1px, lines = 1, font-size = base-font-size, border-style = default-rhythm-border-style) 108 | apply-side-rhythm-border: bottom, width, lines, font-size, border-style 109 | 110 | // Apply both leading and trailing borders. 111 | horizontal-borders(width = 1px, lines = 1, font-size = base-font-size, border-style = default-rhythm-border-style) 112 | leading-border: width, lines, font-size, border-style 113 | trailing-border: width, lines, font-size, border-style 114 | 115 | baseline-normalize() 116 | h1-font-size = 2 * base-font-size 117 | h2-font-size = 1.5 * base-font-size 118 | h3-font-size = 1.17 * base-font-size 119 | h4-font-size = 1 * base-font-size 120 | h5-font-size = 0.83 * base-font-size 121 | h6-font-size = 0.67 * base-font-size 122 | base-font-family = sans-serif 123 | indent-amount = 40px -------------------------------------------------------------------------------- /css/_variables.styl: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // Variables from https://github.com/jenius/axis 3 | // ---------------------------------------------------------------------------- 4 | 5 | 6 | font-size = 16 7 | 8 | // vertical rhythm 9 | base-line-height = 20px 10 | default-rhythm-border-style = solid 11 | relative-font-sizing = true 12 | round-to-nearest-half-line = false 13 | min-line-padding = 2px 14 | 15 | // The base font size. 16 | base-font-size ?= unit(font-size, px) 17 | 18 | // The base line height determines the basic unit of vertical rhythm. 19 | base-line-height ?= 20px 20 | 21 | // Set the default border style for rhythm borders. 22 | default-rhythm-border-style ?= solid 23 | 24 | // The default font size in all browsers. 25 | browser-default-font-size = 16px 26 | 27 | // Set to false if you want to use absolute pixels in sizing your typography. 28 | relative-font-sizing ?= true 29 | 30 | // Allows the `adjust-font-size-to` mixin and the `-lines-for-font-size` function 31 | // to round the line height to the nearest half line height instead of the 32 | // nearest integral line height to avoid large spacing between lines. 33 | round-to-nearest-half-line ?= false 34 | 35 | // Ensure there is at least this many pixels 36 | // of vertical padding above and below the text. 37 | min-line-padding ?= 2px 38 | 39 | // $base-font-size but in your output unit of choice. 40 | // Defaults to 1em when `$relative-font-sizing` is true. 41 | font-unit ?= relative-font-sizing ? 1em : base-font-size 42 | 43 | // The basic unit of font rhythm. 44 | base-rhythm-unit = font-unit * base-line-height / base-font-size 45 | 46 | // The leader is the amount of whitespace in a line. 47 | // It might be useful in your calculations. 48 | base-leader = font-unit / base-font-size * (base-line-height - base-font-size) 49 | 50 | // The half-leader is the amount of whitespace above and below a line. 51 | // It might be useful in your calculations. 52 | base-half-leader = base-leader / 2 -------------------------------------------------------------------------------- /css/_vertical-rhythm.styl: -------------------------------------------------------------------------------- 1 | // Vertical Rhythm ported from SASS/Compass 2 | // Works exactly as before, with four exceptions: 3 | // 1. rhythm() is a mixin, -rhythm() is a function. Stylus doesn't differentiate between same-name mixins and functions 4 | // 2. All of the variables you're used to lack the dollar sign ($) prepend. 5 | // 3. debug-vertical-alignment uses a temporary online image solution via http://basehold.it 6 | // 4. There is no h-borders alias. Use horizonatal-borders instead. 7 | 8 | // The base font size. 9 | base-font-size ?= unit(font-size, px) 10 | 11 | // The base line height determines the basic unit of vertical rhythm. 12 | base-line-height ?= 24px 13 | 14 | // Set the default border style for rhythm borders. 15 | default-rhythm-border-style ?= solid 16 | 17 | // The default font size in all browsers. 18 | browser-default-font-size = 16px 19 | 20 | // Set to false if you want to use absolute pixels in sizing your typography. 21 | relative-font-sizing ?= true 22 | 23 | // Allows the `adjust-font-size-to` mixin and the `-lines-for-font-size` function 24 | // to round the line height to the nearest half line height instead of the 25 | // nearest integral line height to avoid large spacing between lines. 26 | round-to-nearest-half-line ?= false 27 | 28 | // Ensure there is at least this many pixels 29 | // of vertical padding above and below the text. 30 | min-line-padding ?= 2px 31 | 32 | // $base-font-size but in your output unit of choice. 33 | // Defaults to 1em when `$relative-font-sizing` is true. 34 | font-unit ?= relative-font-sizing ? 1em : base-font-size 35 | 36 | // The basic unit of font rhythm. 37 | base-rhythm-unit = font-unit * base-line-height / base-font-size 38 | 39 | // The leader is the amount of whitespace in a line. 40 | // It might be useful in your calculations. 41 | base-leader = font-unit / base-font-size * (base-line-height - base-font-size) 42 | 43 | // The half-leader is the amount of whitespace above and below a line. 44 | // It might be useful in your calculations. 45 | base-half-leader = base-leader / 2 46 | 47 | // @public unitless() function borrowed from SASS 48 | // TODO: move this to a utilities file 49 | -unitless(number) 50 | unit(number) is '' 51 | 52 | // @public Function. 53 | // True if a number has a relative unit. 54 | -relative-unit(number) 55 | unit(number) is '%' or unit(number) is 'em' or unit(number) is 'rem' 56 | 57 | // @public Function. 58 | // True if a number has an absolute unit. 59 | -absolute-unit(number) 60 | not (-relative-unit(number) or -unitless(number)) 61 | 62 | if (relative-font-sizing) and (not -relative-unit(font-unit)) 63 | warn('relative-font-sizing is true but font-unit is set to ' + font-unit + ' which is not a relative unit.') 64 | 65 | // Mixin 66 | // Establishes a font baseline for the given font-size. 67 | establish-baseline(fs = base-font-size) 68 | if fs is a 'unit' 69 | // IE 6 refuses to resize fonts set in pixels and it weirdly resizes fonts 70 | // whose root is set in ems. So we set the root font size in percentages of 71 | // the default font size. 72 | * html 73 | font-size: 100% * (fs / browser-default-font-size) 74 | html 75 | font-size: fs 76 | adjust-leading-to: 1, relative-font-sizing ? fs : base-font-size 77 | 78 | else if (fs is a 'string') and (fs is 'normalize') 79 | baseline-normalize() 80 | 81 | else 82 | warn('Invalid parameter ' + fs + ' passed to establish-baseline().') 83 | 84 | // Show a background image that can be used to debug your alignments. 85 | // Requires internet connection 86 | // TODO: Make a better version of this 87 | debug-vertical-alignment() 88 | background-image: url('http://basehold.it/i/' + unit(base-line-height, '')) 89 | 90 | // Adjust a block to have a different font size and line height to maintain the 91 | // rhythm. $lines specifies how many multiples of the baseline rhythm each line 92 | // of this font should use up. It does not have to be an integer, but it 93 | // defaults to the smallest integer that is large enough to fit the font. 94 | // Use $from-size to adjust from a font-size other than the base font-size. 95 | adjust-font-size-to(to-size, lines = -lines-for-font-size(to-size), from-size = base-font-size) 96 | if (not relative-font-sizing) and (from-size is not base-font-size) 97 | warn('relative-font-sizing is false but a relative font size was passed to adjust-font-size-to') 98 | font-size: font-unit * (to-size / from-size) 99 | adjust-leading-to: lines, relative-font-sizing ? to-size : base-font-size 100 | 101 | // Adjust a block to have different line height to maintain the rhythm. 102 | // $lines specifies how many multiples of the baseline rhythm each line of this 103 | // font should use up. It does not have to be an integer, but it defaults to the 104 | // smallest integer that is large enough to fit the font. 105 | adjust-leading-to(lines, font-size = base-font-size) 106 | line-height: -rhythm(lines, font-size) 107 | 108 | // @public function - Calculate rhythm units. 109 | -rhythm(lines = 1, font-size = base-font-size, offset = 0) 110 | if (not relative-font-sizing) and (font-size is not base-font-size) 111 | warn('relative-font-sizing is false but a relative font size was passed to the rhythm function') 112 | rhythm = font-unit * (lines * base-line-height - offset) / font-size 113 | // Round the pixels down to nearest integer. 114 | if unit(rhythm) is 'px' 115 | rhythm = floor(rhythm) 116 | return rhythm 117 | 118 | // @private Function - Calculate the minimum multiple of rhythm units needed to contain the font-size. 119 | -lines-for-font-size(font-size) 120 | lines = round-to-nearest-half-line ? ceil(2 * font-size / base-line-height) / 2 : ceil(font-size / base-line-height) 121 | if lines * base-line-height - font-size < min-line-padding * 2 122 | lines = lines + (round-to-nearest-half-line ? 0.5 : 1) 123 | return unit(lines, '') 124 | 125 | // Apply leading whitespace. The $property can be margin or padding. 126 | leader(lines = 1, font-size = base-font-size, property = margin) 127 | {property}-top: -rhythm(lines, font-size) 128 | 129 | // Apply leading whitespace as padding. 130 | padding-leader(lines = 1, font-size = base-font-size) 131 | padding-top: -rhythm(lines, font-size) 132 | 133 | // Apply leading whitespace as margin. 134 | margin-leader(lines = 1, font-size = base-font-size) 135 | margin-top: -rhythm(lines, font-size) 136 | 137 | // Apply trailing whitespace. The $property can be margin or padding. 138 | trailer(lines = 1, font-size = base-font-size, property = margin) 139 | {property}-bottom: -rhythm(lines, font-size) 140 | 141 | // Apply trailing whitespace as padding. 142 | padding-trailer(lines = 1, font-size = base-font-size) 143 | padding-bottom: -rhythm(lines, font-size) 144 | 145 | // Apply trailing whitespace as margin. 146 | margin-trailer(lines = 1, font-size = base-font-size) 147 | margin-bottom: -rhythm(lines, font-size) 148 | 149 | // Shorthand mixin to apply whitespace for top and bottom margins and padding. 150 | rhythm(l = 0, pl = 0, pt = 0, t = 0, font-size = base-font-size) 151 | leader: l, font-size 152 | padding-leader: pl, font-size 153 | padding-trailer: pt, font-size 154 | trailer: t, font-size 155 | 156 | // Apply a border and whitespace to any side without destroying the vertical 157 | // rhythm. The whitespace must be greater than the width of the border. 158 | apply-side-rhythm-border(side, w = 1px, lines = 1, font-size = base-font-size, bs = default-rhythm-border-style) 159 | if (not relative-font-sizing) and (font-size is not base-font-size) 160 | warn('relative-font-sizing is false but a relative font size was passed to apply-side-rhythm-border') 161 | border-{side}-style: bs 162 | border-{side}-width: font-unit * (w / font-size) 163 | padding-{side}: -rhythm(lines, font-size, offset = w) 164 | 165 | // Apply borders and whitespace equally to all sides. 166 | rhythm-borders(w = 1px, lines = 1, font-size = base-font-size, bs = default-rhythm-border-style) 167 | if (not relative-font-sizing) and (font-size is not base-font-size) 168 | warn('relative-font-sizing is false but a relative font size was passed to rhythm-borders') 169 | border-style: bs 170 | border-width: font-unit * (w / font-size) 171 | padding: -rhythm(lines, font-size, offset = w) 172 | 173 | // Apply a leading border. 174 | leading-border(width = 1px, lines = 1, font-size = base-font-size, border-style = default-rhythm-border-style) 175 | apply-side-rhythm-border: top, width, lines, font-size, border-style 176 | 177 | // Apply a trailing border. 178 | trailing-border(width = 1px, lines = 1, font-size = base-font-size, border-style = default-rhythm-border-style) 179 | apply-side-rhythm-border: bottom, width, lines, font-size, border-style 180 | 181 | // Apply both leading and trailing borders. 182 | horizontal-borders(width = 1px, lines = 1, font-size = base-font-size, border-style = default-rhythm-border-style) 183 | leading-border: width, lines, font-size, border-style 184 | trailing-border: width, lines, font-size, border-style 185 | 186 | // --------------------------------------------- 187 | // @private Mixin - Internal Baseline Normalize 188 | // --------------------------------------------- 189 | 190 | baseline-normalize() 191 | h1-font-size = 2 * base-font-size 192 | h2-font-size = 1.5 * base-font-size 193 | h3-font-size = 1.17 * base-font-size 194 | h4-font-size = 1 * base-font-size 195 | h5-font-size = 0.83 * base-font-size 196 | h6-font-size = 0.67 * base-font-size 197 | base-font-family = sans-serif 198 | indent-amount = 40px 199 | 200 | article 201 | aside 202 | details 203 | figcaption 204 | figure 205 | footer 206 | header 207 | hgroup 208 | main 209 | nav 210 | section 211 | summary 212 | display: block 213 | 214 | audio 215 | canvas 216 | video 217 | display: inline-block 218 | if support-for-ie 219 | *display: inline 220 | *zoom: 1 221 | 222 | audio:not([controls]) 223 | display: none 224 | height: 0 225 | [hidden] 226 | display: none 227 | 228 | html 229 | font-family: base-font-family 230 | -webkit-text-size-adjust: 100% 231 | -ms-text-size-adjust: 100% 232 | font-size: 100% * (base-font-size / 16px) 233 | adjust-leading-to: 1 234 | 235 | if support-for-ie 236 | button 237 | input 238 | select 239 | textarea 240 | font-family: base-font-family 241 | 242 | body 243 | margin: 0 244 | 245 | a:focus 246 | outline: thin dotted 247 | 248 | a:active 249 | a:hover 250 | outline: 0 251 | 252 | p 253 | pre 254 | margin: -rhythm(1) 0 255 | 256 | blockquote 257 | margin: -rhythm(1) indent-amount 258 | 259 | h1 260 | adjust-font-size-to: h1-font-size 261 | leader: 1, h1-font-size 262 | trailer: 1, h1-font-size 263 | 264 | h2 265 | adjust-font-size-to: h2-font-size 266 | leader: 1, h2-font-size 267 | trailer: 1, h2-font-size 268 | 269 | h3 270 | adjust-font-size-to: h3-font-size 271 | leader: 1, h3-font-size 272 | trailer: 1, h3-font-size 273 | 274 | h4 275 | adjust-font-size-to: h4-font-size 276 | leader: 1, h4-font-size 277 | trailer: 1, h4-font-size 278 | 279 | h5 280 | adjust-font-size-to: h5-font-size 281 | leader: 1, h5-font-size 282 | trailer: 1, h5-font-size 283 | 284 | h6 285 | adjust-font-size-to: h6-font-size 286 | leader: 1, h6-font-size 287 | trailer: 1, h6-font-size 288 | 289 | abbr[title] 290 | border-bottom: 1px dotted 291 | 292 | b 293 | strong 294 | font-weight: bold 295 | 296 | dfn 297 | font-style: italic 298 | 299 | hr 300 | -moz-box-sizing: content-box 301 | box-sizing: content-box 302 | height: 0 303 | 304 | mark 305 | background: #ff0 306 | color: #000 307 | 308 | code 309 | kbd 310 | pre 311 | samp 312 | font-family: monospace, serif 313 | if support-for-ie 314 | _font-family: 'courier new', monospace 315 | adjust-font-size-to: 1 * base-font-size 316 | 317 | pre 318 | white-space: pre-wrap 319 | 320 | q 321 | quotes: "\201C" "\201D" "\2018" "\2019" 322 | 323 | small 324 | font-size: 80% 325 | 326 | sub 327 | sup 328 | font-size: 75% 329 | line-height: 0 330 | position: relative 331 | vertical-align: baseline 332 | 333 | sup 334 | top: -0.5em 335 | 336 | sub 337 | bottom: -0.25em 338 | 339 | dl 340 | menu 341 | ol 342 | ul 343 | margin: -rhythm(1) 0 344 | padding: 0 0 0 indent-amount 345 | 346 | dl 347 | padding: 0 348 | 349 | dd 350 | margin: 0 0 0 indent-amount 351 | 352 | if support-for-ie 353 | nav 354 | ul 355 | ol 356 | list-style: none 357 | list-style-image: none 358 | 359 | img 360 | border: 0 361 | if support-for-ie 362 | -ms-interpolation-mode: bicubic 363 | 364 | svg:not(:root) 365 | overflow: hidden 366 | 367 | figure 368 | margin: 0 369 | 370 | if support-for-ie 371 | form 372 | margin: 0 373 | 374 | fieldset 375 | border-color: #c0c0c0 376 | margin: 0 2px 377 | apply-side-rhythm-border: top, width = 1px, lines = 0.35 378 | apply-side-rhythm-border: bottom, width = 1px, lines = 0.65 379 | apply-side-rhythm-border: left, width = 1px, lines = 0.625 380 | apply-side-rhythm-border: right, width = 1px, lines = 0.625 381 | 382 | legend 383 | border: 0 384 | padding: 0 385 | if support-for-ie 386 | *margin-left: -7px 387 | 388 | button 389 | input 390 | select 391 | textarea 392 | font-family: inherit 393 | font-size: 100% 394 | margin: 0 395 | if support-for-ie 396 | vertical-align: baseline 397 | *vertical-align: middle 398 | 399 | button 400 | input 401 | line-height: normal 402 | 403 | button 404 | select 405 | text-transform: none 406 | 407 | button, html input[type="button"], input[type="reset"], input[type="submit"] 408 | -webkit-appearance: button 409 | cursor: pointer 410 | if support-for-ie 411 | *overflow: visible 412 | 413 | button[disabled], html input[disabled] 414 | cursor: default 415 | 416 | input[type="checkbox"], input[type="radio"] 417 | box-sizing: border-box 418 | padding: 0 419 | if support-for-ie 420 | *height: 13px 421 | *width: 13px 422 | 423 | input[type="search"] 424 | -webkit-appearance: textfield 425 | box-sizing: content-box 426 | 427 | input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration 428 | -webkit-appearance: none 429 | 430 | button::-moz-focus-inner 431 | input::-moz-focus-inner 432 | border: 0 433 | padding: 0 434 | 435 | textarea 436 | overflow: auto 437 | vertical-align: top 438 | 439 | table 440 | border-collapse: collapse 441 | border-spacing: 0 442 | -------------------------------------------------------------------------------- /css/bones.css: -------------------------------------------------------------------------------- 1 | .alert .checkbox li, 2 | .modal .checkbox li, 3 | .sidebar, 4 | .content, 5 | .two-up li, 6 | .three-up li, 7 | .col { 8 | border: 0px solid rgba(0,0,0,0); 9 | float: left; 10 | box-sizing: border-box; 11 | -moz-background-clip: padding-box !important; 12 | -webkit-background-clip: padding-box !important; 13 | background-clip: padding-box !important; 14 | } 15 | .container { 16 | margin: 0 auto; 17 | } 18 | .left, 19 | #left { 20 | float: left; 21 | } 22 | .right, 23 | #right { 24 | float: right; 25 | } 26 | .center, 27 | #center { 28 | float: none; 29 | margin: 0 auto; 30 | } 31 | @media screen and (min-width: 0em) and (max-width: 43.75em) { 32 | .container { 33 | padding: 2em 10%; 34 | } 35 | .container.shell { 36 | padding: 0; 37 | } 38 | .wrapped li { 39 | padding: 0 5%; 40 | } 41 | nav#left ul li, 42 | .sidebar.right, 43 | .content.left, 44 | .three-up li, 45 | .two-up li, 46 | .col { 47 | float: none; 48 | width: 100%; 49 | } 50 | .right { 51 | float: left; 52 | } 53 | } 54 | @media screen and (min-width: 43.75em) and (max-width: 71.1875em) { 55 | .container { 56 | padding: 2em 10%; 57 | } 58 | .container.shell { 59 | padding: 0 10%; 60 | } 61 | .three-up li, 62 | .col { 63 | margin-right: 4%; 64 | } 65 | .col, 66 | .wrapped li { 67 | padding: 0 2%; 68 | } 69 | .three-up li:last-child, 70 | .row .col:last-child { 71 | margin-right: 0; 72 | } 73 | .span_1 { 74 | width: 13.3333333333%; 75 | } 76 | .three-up li, 77 | .span_2 { 78 | width: 30.6666666667%; 79 | } 80 | .span_3 { 81 | width: 48%; 82 | } 83 | .span_4 { 84 | width: 65.3333333333%; 85 | } 86 | .span_5 { 87 | width: 82.6666666667%; 88 | } 89 | .sidebar.right, 90 | .content.left, 91 | .full, 92 | .span_6 { 93 | margin-left: 0; 94 | width: 100%; 95 | clear: both; 96 | } 97 | } 98 | @media screen and (min-width: 71.25em) { 99 | .container { 100 | max-width: 62.5em; 101 | padding: 2em 0; 102 | } 103 | .container.shell { 104 | padding: 0; 105 | } 106 | .alert .checkbox li:first-child, 107 | .modal .checkbox li:first-child, 108 | .two-up li, 109 | .three-up li, 110 | .col { 111 | margin-right: 4%; 112 | } 113 | .col, 114 | .wrapped li { 115 | padding: 0 2%; 116 | } 117 | .two-up li:last-child, 118 | .three-up li:last-child, 119 | .col:last-child { 120 | margin-right: 0; 121 | } 122 | .span_1 { 123 | width: 4.66666666667%; 124 | } 125 | .span_2 { 126 | width: 13.3333333333%; 127 | } 128 | nav#left, 129 | .span_3 { 130 | width: 22%; 131 | } 132 | .sidebar.right, 133 | .three-up li, 134 | .span_4 { 135 | width: 30.6666666667%; 136 | } 137 | .span_5 { 138 | width: 39.3333333333%; 139 | } 140 | .center.two-up, 141 | .alert .checkbox li, 142 | .modal .checkbox li, 143 | #center, 144 | .two-up li, 145 | .account #left, 146 | .account #right, 147 | .span_6 { 148 | width: 48%; 149 | } 150 | .span_7 { 151 | width: 56.6666666667%; 152 | } 153 | .content.left, 154 | .span_8 { 155 | width: 65.3333333333%; 156 | } 157 | .public.home #center, 158 | section#right, 159 | .span_9 { 160 | width: 74%; 161 | } 162 | .span_10 { 163 | width: 82.6666666667%; 164 | } 165 | .span_11 { 166 | width: 91.3333333333%; 167 | } 168 | .full, 169 | .span_12 { 170 | margin-left: 0; 171 | width: 100%; 172 | clear: both; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /css/bones.styl: -------------------------------------------------------------------------------- 1 | .alert .checkbox li 2 | .modal .checkbox li 3 | .sidebar 4 | .content 5 | .two-up li 6 | .three-up li 7 | .col 8 | border: 0px solid rgba(0,0,0,0) 9 | float: left 10 | box-sizing: border-box 11 | -moz-background-clip: padding-box !important 12 | -webkit-background-clip: padding-box !important 13 | background-clip: padding-box !important 14 | 15 | .full 16 | .container 17 | .row 18 | clearfix() 19 | 20 | .container 21 | margin: 0 auto 22 | 23 | .left 24 | #left 25 | float: left 26 | 27 | .right 28 | #right 29 | float: right 30 | 31 | .center 32 | #center 33 | float: none 34 | margin: 0 auto 35 | 36 | 37 | @media screen and (min-width: 0em) and (max-width: 43.75em) 38 | 39 | .container 40 | padding: 2em 10% 41 | 42 | .container.shell 43 | padding: 0 44 | 45 | .wrapped li 46 | padding: 0 5% 47 | 48 | nav#left ul li 49 | .sidebar.right 50 | .content.left 51 | .three-up li 52 | .two-up li 53 | .col 54 | float: none 55 | width: 100% 56 | 57 | .right 58 | float: left 59 | 60 | 61 | @media screen and (min-width: 43.75em) and (max-width: 71.1875em) 62 | 63 | .container 64 | padding: 2em 10% 65 | 66 | .container.shell 67 | padding: 0 10% 68 | 69 | .three-up li, 70 | .col 71 | margin-right: 4% 72 | 73 | .col 74 | .wrapped li 75 | padding: 0 2% 76 | 77 | .three-up li:last-child 78 | .row .col:last-child 79 | margin-right: 0 80 | 81 | 82 | .span_1 83 | width: 13.3333333333% 84 | 85 | .three-up li, 86 | .span_2 87 | width: 30.6666666667% 88 | 89 | .span_3 90 | width: 48.0% 91 | 92 | .span_4 93 | width: 65.3333333333% 94 | 95 | .span_5 96 | width: 82.6666666667% 97 | 98 | .sidebar.right 99 | .content.left 100 | .full 101 | .span_6 102 | margin-left: 0 103 | width: 100% 104 | clear: both 105 | 106 | 107 | @media screen and (min-width: 71.25em) 108 | 109 | .container 110 | max-width: 62.5em 111 | padding: 2em 0 112 | 113 | .container.shell 114 | padding: 0 115 | 116 | .alert .checkbox li:first-child 117 | .modal .checkbox li:first-child 118 | .two-up li 119 | .three-up li 120 | .col 121 | margin-right: 4% 122 | 123 | .col 124 | .wrapped li 125 | padding: 0 2% 126 | 127 | .two-up li:last-child 128 | .three-up li:last-child 129 | .col:last-child 130 | margin-right: 0 131 | 132 | .span_1 133 | width: 4.66666666667% 134 | 135 | .span_2 136 | width: 13.3333333333% 137 | 138 | nav#left 139 | .span_3 140 | width: 22.0% 141 | 142 | .sidebar.right 143 | .three-up li, 144 | .span_4 145 | width: 30.6666666667% 146 | 147 | .span_5 148 | width: 39.3333333333% 149 | 150 | .center.two-up 151 | .alert .checkbox li 152 | .modal .checkbox li 153 | #center 154 | .two-up li 155 | .account #left 156 | .account #right 157 | .span_6 158 | width: 48.0% 159 | 160 | .span_7 161 | width: 56.6666666667% 162 | 163 | .content.left 164 | .span_8 165 | width: 65.3333333333% 166 | 167 | .public.home #center 168 | section#right 169 | .span_9 170 | width: 74.0% 171 | 172 | .span_10 173 | width: 82.6666666667% 174 | 175 | .span_11 176 | width: 91.3333333333% 177 | 178 | .full 179 | .span_12 180 | margin-left: 0 181 | width: 100% 182 | clear: both 183 | 184 | 185 | -------------------------------------------------------------------------------- /css/monokai_sublime.min.css: -------------------------------------------------------------------------------- 1 | .hljs{display:block;padding:.5em;background:#23241f}.hljs,.hljs-tag,.css .hljs-rules,.css .hljs-value,.css .hljs-function .hljs-preprocessor,.hljs-pragma{color:#f8f8f2}.hljs-strongemphasis,.hljs-strong,.hljs-emphasis{color:#a8a8a2}.hljs-bullet,.hljs-blockquote,.hljs-horizontal_rule,.hljs-number,.hljs-regexp,.alias .hljs-keyword,.hljs-literal,.hljs-hexcolor{color:#ae81ff}.hljs-tag .hljs-value,.hljs-code,.hljs-title,.css .hljs-class,.hljs-class .hljs-title:last-child{color:#a6e22e}.hljs-link_url{font-size:80%}.hljs-strong,.hljs-strongemphasis{font-weight:bold}.hljs-emphasis,.hljs-strongemphasis,.hljs-class .hljs-title:last-child{font-style:italic}.hljs-keyword,.hljs-function,.hljs-change,.hljs-winutils,.hljs-flow,.lisp .hljs-title,.clojure .hljs-built_in,.nginx .hljs-title,.tex .hljs-special,.hljs-header,.hljs-attribute,.hljs-symbol,.hljs-symbol .hljs-string,.hljs-tag .hljs-title,.hljs-value,.alias .hljs-keyword:first-child,.css .hljs-tag,.css .unit,.css .hljs-important{color:#f92672}.hljs-function .hljs-keyword,.hljs-class .hljs-keyword:first-child,.hljs-constant,.css .hljs-attribute{color:#66d9ef}.hljs-variable,.hljs-params,.hljs-class .hljs-title{color:#f8f8f2}.hljs-string,.css .hljs-id,.hljs-subst,.haskell .hljs-type,.ruby .hljs-class .hljs-parent,.hljs-built_in,.sql .hljs-aggregate,.django .hljs-template_tag,.django .hljs-variable,.smalltalk .hljs-class,.django .hljs-filter .hljs-argument,.smalltalk .hljs-localvars,.smalltalk .hljs-array,.hljs-attr_selector,.hljs-pseudo,.hljs-addition,.hljs-stream,.hljs-envvar,.apache .hljs-tag,.apache .hljs-cbracket,.tex .hljs-command,.hljs-prompt,.hljs-link_label,.hljs-link_url{color:#e6db74}.hljs-comment,.hljs-javadoc,.java .hljs-annotation,.python .hljs-decorator,.hljs-template_comment,.hljs-pi,.hljs-doctype,.hljs-deletion,.hljs-shebang,.apache .hljs-sqbracket,.tex .hljs-formula{color:#75715e}.coffeescript .javascript,.javascript .xml,.tex .hljs-formula,.xml .javascript,.xml .vbscript,.xml .css,.xml .hljs-cdata,.xml .php,.php .xml{opacity:.5} -------------------------------------------------------------------------------- /css/normalize.css: -------------------------------------------------------------------------------- 1 | article, 2 | aside, 3 | details, 4 | figcaption, 5 | figure, 6 | footer, 7 | header, 8 | hgroup, 9 | main, 10 | nav, 11 | section, 12 | summary { 13 | display: block; 14 | } 15 | audio, 16 | canvas, 17 | video { 18 | display: inline-block; 19 | *display: inline; 20 | *zoom: 1; 21 | } 22 | audio:not([controls]) { 23 | display: none; 24 | height: 0; 25 | } 26 | [hidden] { 27 | display: none; 28 | } 29 | html { 30 | font-family: base-font-family; 31 | -webkit-text-size-adjust: 100%; 32 | -ms-text-size-adjust: 100%; 33 | font-size: 100%; 34 | line-height: 1.25em; 35 | } 36 | button, 37 | input, 38 | select, 39 | textarea { 40 | font-family: base-font-family; 41 | font-family: inherit; 42 | font-size: 100%; 43 | margin: 0; 44 | vertical-align: baseline; 45 | *vertical-align: middle; 46 | } 47 | body { 48 | margin: 0; 49 | line-height: 1; 50 | color: #000; 51 | background: #fff; 52 | } 53 | a:focus { 54 | outline: thin dotted; 55 | } 56 | a img { 57 | border: none; 58 | } 59 | a:active, 60 | a:hover { 61 | outline: 0; 62 | } 63 | p, 64 | pre { 65 | margin: 1.25em 0; 66 | } 67 | blockquote { 68 | margin: 1.25em indent-amount; 69 | } 70 | abbr[title] { 71 | border-bottom: 1px dotted; 72 | } 73 | b, 74 | strong { 75 | font-weight: bold; 76 | } 77 | dfn { 78 | font-style: italic; 79 | } 80 | hr { 81 | -moz-box-sizing: content-box; 82 | -webkit-box-sizing: content-box; 83 | -moz-box-sizing: content-box; 84 | box-sizing: content-box; 85 | height: 0; 86 | } 87 | mark { 88 | background: #ff0; 89 | color: #000; 90 | } 91 | code, 92 | kbd, 93 | pre, 94 | samp { 95 | font-family: monospace, serif; 96 | _font-family: 'courier new', monospace; 97 | font-size: 1em; 98 | line-height: 1.25em; 99 | } 100 | pre { 101 | white-space: pre-wrap; 102 | } 103 | q { 104 | quotes: "\201C" "\201D" "\2018" "\2019"; 105 | } 106 | small { 107 | font-size: 80%; 108 | } 109 | sub, 110 | sup { 111 | font-size: 75%; 112 | line-height: 0; 113 | position: relative; 114 | vertical-align: baseline; 115 | } 116 | sup { 117 | top: -0.5em; 118 | } 119 | sub { 120 | bottom: -0.25em; 121 | } 122 | dl, 123 | menu, 124 | ol, 125 | ul { 126 | margin: 1.25em 0; 127 | padding: 0 0 0 indent-amount; 128 | } 129 | dl { 130 | padding: 0; 131 | } 132 | dd { 133 | margin: 0 0 0 indent-amount; 134 | } 135 | nav ul, 136 | nav ol { 137 | list-style: none; 138 | list-style-image: none; 139 | } 140 | img { 141 | border: 0; 142 | -ms-interpolation-mode: bicubic; 143 | } 144 | svg:not(:root) { 145 | overflow: hidden; 146 | } 147 | figure { 148 | margin: 0; 149 | } 150 | form { 151 | margin: 0; 152 | } 153 | fieldset { 154 | border-color: #c0c0c0; 155 | margin: 0 2px; 156 | border-top-style: solid; 157 | border-top-width: 0.0625em; 158 | padding-top: 1.1875em; 159 | border-bottom-style: solid; 160 | border-bottom-width: 0.0625em; 161 | padding-bottom: 1.1875em; 162 | border-left-style: solid; 163 | border-left-width: 0.0625em; 164 | padding-left: 1.1875em; 165 | border-right-style: solid; 166 | border-right-width: 0.0625em; 167 | padding-right: 1.1875em; 168 | } 169 | legend { 170 | border: 0; 171 | padding: 0; 172 | *margin-left: -7px; 173 | } 174 | button, 175 | input { 176 | line-height: normal; 177 | } 178 | button, 179 | select { 180 | text-transform: none; 181 | } 182 | button, 183 | html input[type="button"], 184 | input[type="reset"], 185 | input[type="submit"] { 186 | -webkit-appearance: button; 187 | cursor: pointer; 188 | *overflow: visible; 189 | } 190 | button[disabled], 191 | html input[disabled] { 192 | cursor: default; 193 | } 194 | input[type="checkbox"], 195 | input[type="radio"] { 196 | -webkit-box-sizing: border-box; 197 | -moz-box-sizing: border-box; 198 | box-sizing: border-box; 199 | padding: 0; 200 | *height: 13px; 201 | *width: 13px; 202 | } 203 | input[type="search"] { 204 | -webkit-appearance: textfield; 205 | -webkit-box-sizing: content-box; 206 | -moz-box-sizing: content-box; 207 | box-sizing: content-box; 208 | } 209 | input[type="search"]::-webkit-search-cancel-button, 210 | input[type="search"]::-webkit-search-decoration { 211 | -webkit-appearance: none; 212 | } 213 | button::-moz-focus-inner, 214 | input::-moz-focus-inner { 215 | border: 0; 216 | padding: 0; 217 | } 218 | textarea { 219 | overflow: auto; 220 | vertical-align: top; 221 | } 222 | table { 223 | border-collapse: collapse; 224 | border-spacing: 0; 225 | border-collapse: separate; 226 | border-spacing: 0; 227 | vertical-align: middle; 228 | } 229 | nav, 230 | article, 231 | section, 232 | ul, 233 | div, 234 | p { 235 | zoom: 1; 236 | } 237 | nav:before, 238 | article:before, 239 | section:before, 240 | div:before, 241 | p:before, 242 | nav:after, 243 | article:after, 244 | section:after, 245 | div:after, 246 | p:after { 247 | content: ""; 248 | display: table; 249 | } 250 | nav:after, 251 | article:after, 252 | section:after, 253 | div:after, 254 | p:after { 255 | clear: both; 256 | } 257 | html, 258 | body, 259 | div, 260 | span, 261 | applet, 262 | object, 263 | iframe, 264 | h1, 265 | h2, 266 | h3, 267 | h4, 268 | h5, 269 | h6, 270 | p, 271 | blockquote, 272 | pre, 273 | a, 274 | abbr, 275 | acronym, 276 | address, 277 | big, 278 | cite, 279 | code, 280 | del, 281 | dfn, 282 | em, 283 | img, 284 | ins, 285 | kbd, 286 | q, 287 | s, 288 | samp, 289 | small, 290 | strike, 291 | strong, 292 | sub, 293 | sup, 294 | tt, 295 | var, 296 | dl, 297 | dt, 298 | dd, 299 | ol, 300 | ul, 301 | li, 302 | fieldset, 303 | form, 304 | label, 305 | legend, 306 | table, 307 | caption, 308 | tbody, 309 | tfoot, 310 | thead, 311 | tr, 312 | th, 313 | td { 314 | margin: 0; 315 | padding: 0; 316 | border: 0; 317 | outline: 0; 318 | font-weight: inherit; 319 | font-style: inherit; 320 | font-family: inherit; 321 | font-size: 100%; 322 | vertical-align: baseline; 323 | } 324 | ol, 325 | ul { 326 | list-style: none; 327 | } 328 | caption, 329 | th, 330 | td { 331 | text-align: left; 332 | font-weight: normal; 333 | vertical-align: middle; 334 | } 335 | -------------------------------------------------------------------------------- /css/normalize.styl: -------------------------------------------------------------------------------- 1 | article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary 2 | display block 3 | 4 | audio,canvas,video 5 | display inline-block 6 | *display inline 7 | *zoom 1 8 | 9 | audio 10 | &:not([controls]) 11 | display none 12 | height 0 13 | 14 | [hidden] 15 | display none 16 | 17 | html 18 | font-family base-font-family 19 | -webkit-text-size-adjust 100% 20 | -ms-text-size-adjust 100% 21 | font-size 100% 22 | line-height 1.25em 23 | 24 | button,input,select,textarea 25 | font-family base-font-family 26 | font-family inherit 27 | font-size 100% 28 | margin 0 29 | vertical-align baseline 30 | *vertical-align middle 31 | 32 | body 33 | margin 0 34 | line-height 1 35 | color #000 36 | background #fff 37 | 38 | a 39 | &:focus 40 | outline thin dotted 41 | img 42 | border none 43 | 44 | a:active,a:hover 45 | outline 0 46 | 47 | p,pre 48 | margin 1.25em 0 49 | 50 | blockquote 51 | margin 1.25em indent-amount 52 | 53 | abbr[title] 54 | border-bottom 1px dotted 55 | 56 | b,strong 57 | font-weight bold 58 | 59 | dfn 60 | font-style italic 61 | 62 | hr 63 | -moz-box-sizing content-box 64 | -webkit-box-sizing content-box 65 | -moz-box-sizing content-box 66 | box-sizing content-box 67 | height 0 68 | 69 | mark 70 | background #ff0 71 | color #000 72 | 73 | code,kbd,pre,samp 74 | font-family monospace,serif 75 | _font-family 'courier new',monospace 76 | font-size 1em 77 | line-height 1.25em 78 | 79 | pre 80 | white-space pre-wrap 81 | 82 | q 83 | quotes "\201C" "\201D" "\2018" "\2019" 84 | 85 | small 86 | font-size 80% 87 | 88 | sub,sup 89 | font-size 75% 90 | line-height 0 91 | position relative 92 | vertical-align baseline 93 | 94 | sup 95 | top -.5em 96 | 97 | sub 98 | bottom -.25em 99 | 100 | dl,menu,ol,ul 101 | margin 1.25em 0 102 | padding 0 0 0 indent-amount 103 | 104 | dl 105 | padding 0 106 | 107 | dd 108 | margin 0 0 0 indent-amount 109 | 110 | nav ul,nav ol 111 | list-style none 112 | list-style-image none 113 | 114 | img 115 | border 0 116 | -ms-interpolation-mode bicubic 117 | 118 | svg 119 | &:not(:root) 120 | overflow hidden 121 | 122 | figure 123 | margin 0 124 | 125 | form 126 | margin 0 127 | 128 | fieldset 129 | border-color #c0c0c0 130 | margin 0 2px 131 | border-top-style solid 132 | border-top-width .0625em 133 | padding-top 1.1875em 134 | border-bottom-style solid 135 | border-bottom-width .0625em 136 | padding-bottom 1.1875em 137 | border-left-style solid 138 | border-left-width .0625em 139 | padding-left 1.1875em 140 | border-right-style solid 141 | border-right-width .0625em 142 | padding-right 1.1875em 143 | 144 | legend 145 | border 0 146 | padding 0 147 | *margin-left -7px 148 | 149 | button,input 150 | line-height normal 151 | 152 | button,select 153 | text-transform none 154 | 155 | button,html input[type="button"],input[type="reset"],input[type="submit"] 156 | -webkit-appearance button 157 | cursor pointer 158 | *overflow visible 159 | 160 | button[disabled],html input[disabled] 161 | cursor default 162 | 163 | input[type="checkbox"],input[type="radio"] 164 | -webkit-box-sizing border-box 165 | -moz-box-sizing border-box 166 | box-sizing border-box 167 | padding 0 168 | *height 13px 169 | *width 13px 170 | 171 | input[type="search"] 172 | -webkit-appearance textfield 173 | -webkit-box-sizing content-box 174 | -moz-box-sizing content-box 175 | box-sizing content-box 176 | 177 | input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration 178 | -webkit-appearance none 179 | 180 | button::-moz-focus-inner,input::-moz-focus-inner 181 | border 0 182 | padding 0 183 | 184 | textarea 185 | overflow auto 186 | vertical-align top 187 | 188 | table 189 | border-collapse collapse 190 | border-spacing 0 191 | border-collapse separate 192 | border-spacing 0 193 | vertical-align middle 194 | 195 | nav,article,section,ul,div,p 196 | zoom 1 197 | 198 | nav:before,article:before,section:before,div:before,p:before,nav:after,article:after,section:after,div:after,p:after 199 | content "" 200 | display table 201 | 202 | nav:after,article:after,section:after,div:after,p:after 203 | clear both 204 | 205 | html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td 206 | margin 0 207 | padding 0 208 | border 0 209 | outline 0 210 | font-weight inherit 211 | font-style inherit 212 | font-family inherit 213 | font-size 100% 214 | vertical-align baseline 215 | 216 | ol,ul 217 | list-style none 218 | 219 | caption,th,td 220 | text-align left 221 | font-weight normal 222 | vertical-align middle 223 | 224 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Fireform 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Fireform Example 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 |

Fireform Example: Contact Form

30 | 31 | 32 | 40 | 41 | 42 |
43 |
44 |
    45 |
  • 46 | 47 | 48 |
  • 49 |
  • 50 | 51 | 52 |
  • 53 |
  • 54 |

    Neighborhood

    55 |
      56 |
    • 57 | 58 | 59 |
    • 60 |
    • 61 | 62 | 63 |
    • 64 |
    • 65 | 66 | 67 |
    • 68 |
    • 69 | 70 | 71 |
    • 72 |
    • 73 | 74 | 75 |
    • 76 |
    77 |
  • 78 |
  • 79 |

    Which of our events have you attended?

    80 |
      81 |
    • 82 | 83 | 84 |
    • 85 |
    • 86 | 87 | 88 |
    • 89 |
    • 90 | 91 | 92 |
    • 93 |
    94 |
  • 95 |
  • 96 | 97 | 98 |
  • 99 |
100 |

101 | 102 |

103 |

104 | Success, your form was submitted. Check it out here! 105 |

106 |

107 | Dang Nabit, the form was not completely filled out.
But Fireform does have simple validation. See the Docs! 108 |

109 | 110 | 111 |
112 |
113 | 114 | 115 |
116 |

Form Code

117 |
                    
118 |                     
119 |                         <ul>
120 |                             <li>
121 |                                 <label for="first-name">First Name</label>
122 |                                 <input type="text" name="FirstName" id="first-name" />
123 |                             </li>
124 |                             <li>
125 |                                 <label for="last-name">Last Name</label>
126 |                                 <input type="text" name="LastName" id="last-name" />
127 |                             </li>
128 |                             <li class="multiple">
129 |                                 <p>Neighborhood</p>
130 |                                 <ul>
131 |                                     <li>
132 |                                         <input type="radio" name="Neighborhood" id="south-of-market" value="South of Market" />
133 |                                         <label for="south-of-market">South of Market</label>
134 |                                     </li>
135 |                                     <li>
136 |                                         <input type="radio" name="Neighborhood" id="tenderloin" value="Tenderloin" />
137 |                                         <label for="tenderloin">Tenderloin</label>
138 |                                     </li>
139 |                                     <li>
140 |                                         <input type="radio" name="Neighborhood" id="nob-hill" value="Nob Hill" />
141 |                                         <label for="nob-hill">Nob Hill</label>
142 |                                     </li>
143 |                                     <li>
144 |                                         <input type="radio" name="Neighborhood" id="north-beach" value="North Beach" />
145 |                                         <label for="north-beach">North Beach</label>
146 |                                     </li>
147 |                                     <li>
148 |                                         <input type="radio" name="Neighborhood" id="russian-hill" value="Russian Hill" />
149 |                                         <label for="russian-hill">Russian Hill</label>
150 |                                     </li>
151 |                                 </ul>
152 |                             </li>
153 |                             <li class="multiple">
154 |                                 <p>Which of our events have you attended?</p>
155 |                                 <ul>
156 |                                     <li>
157 |                                         <input type="checkbox" name="BeerFriday" id="beer-friday" />
158 |                                         <label for="beer-friday">Beer Friday</label>
159 |                                     </li>
160 |                                     <li>
161 |                                         <input type="checkbox" name="WineWednesday" id="wine-wednesday" />
162 |                                         <label for="wine-wednesday">Wine Wednesday</label>
163 |                                     </li>
164 |                                     <li>
165 |                                         <input type="checkbox" name="MilkshakeMonday" id="milkshake-monday" />
166 |                                         <label for="milkshake-monday">Milkshake Monday</label>
167 |                                     </li>
168 |                                 </ul>
169 |                             </li>
170 |                             <li>
171 |                                 <label for="message">Any comments?</label>
172 |                                 <textarea name="Message" id="message"></textarea>
173 |                             </li>
174 |                         </ul>
175 |                         <p class="submit">
176 |                             <input type="submit" value="Submit Form" />
177 |                         </p>
178 |                       <script type="application/javascript" src="//src.fireform.org/fireform.min.js"></script>
179 |                       <script type="application/javascript">
180 |                             new Fireform('#MySelector', 'http://fireform/publicexample');
181 |                       </script>
182 |                     
183 |                 
184 |
185 |
186 | 187 | 188 | 196 | 197 | 198 | -------------------------------------------------------------------------------- /example/sendgrid.html: -------------------------------------------------------------------------------- 1 | sendgrid -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SFDevLabs/fireform/9b00ce76cbc0f7a5ceb56629bfb85b0eb10b7862/favicon.ico -------------------------------------------------------------------------------- /fireform.js: -------------------------------------------------------------------------------- 1 | /*2014 SFDevLabs.com, MIT License*/ 2 | Fireform = function (selector, fireBaseRepo, options){ 3 | that=this; 4 | if (window.location.href.indexOf('file')===0){ console.warn('WARNING: Please run Fireform on page served from a web server to garentee functionality.')} 5 | //here is where we define our various options that we wull use 6 | this.error=function(text){console.error(text)}; 7 | var formDOMObject, 8 | inputs, 9 | submitElement, 10 | successClass = options && options.successClass? options.successClass:"submit-success", 11 | failedClass = options && options.failedClass? options.failedClass:"submit-failed", 12 | formValidationClass = options && options.formValidationClass? options.formValidationClass:"form-validation-failed", 13 | inputValidationClass = options && options.inputValidationClass? options.inputValidationClass:"input-validation-failed", 14 | simpleValidation = options && options.simpleValidation? options.simpleValidation:false; 15 | that.emailNotification = options && options.emailNotification? options.emailNotification:undefined; 16 | that.emailConfirmationName = options && options.emailConfirmationName? options.emailConfirmationName:undefined; 17 | that.emailConfirmationFrom = options && options.emailConfirmationFrom? options.emailConfirmationFrom:undefined; 18 | that.emailConfirmationSubject = options && options.emailConfirmationSubject? options.emailConfirmationSubject:undefined; 19 | that.emailConfirmationBodyHTML = options && options.emailConfirmationBodyHTML? options.emailConfirmationBodyHTML:undefined; 20 | that.emailConfirmationBodyText = options && options.emailConfirmationBodyText? options.emailConfirmationBodyText:undefined; 21 | that.user=undefined; 22 | 23 | 24 | if (typeof selector!=="string"){ 25 | formDOMObject = selector; 26 | }else if ( selector.search(/^\./)===0 ) { 27 | formDOMObject = document.getElementsByClassName(selector.slice(1))[0] 28 | console.warn("We will default to the first form matching this class selector"); 29 | } 30 | else if ( selector.search(/^\#/)===0 ){ 31 | formDOMObject = document.getElementById(selector.slice(1)); 32 | }else if (!formDOMObject){ 33 | this.error('Please use a Class or Id Selector. This mean your string should begin with a "." or "#". You man also pass in a Dom elment object') 34 | return 35 | } 36 | if (!formDOMObject.tagName){ 37 | this.error('No DOM object found!') 38 | return 39 | }else if(formDOMObject.tagName!=="FORM"){ 40 | this.error('DOM elments is not a
') 41 | return 42 | } 43 | 44 | if (!fireBaseRepo || fireBaseRepo.length==-0){ 45 | this.error('No Fireform URL entered') 46 | return 47 | } 48 | 49 | this.formDOMObject=formDOMObject 50 | 51 | this.inputs=inputs=formDOMObject.elements 52 | 53 | for (var i = this.inputs.length - 1; i >= 0; i--) { 54 | var type; 55 | type = inputs[i].getAttribute('type'); 56 | if (type==="submit"){ submitElement=inputs[i]; break;} 57 | }; 58 | if (!submitElement){this.error('Please add a submit button with a attr"'); return;} 59 | 60 | this.submitElement=submitElement; 61 | 62 | this.submit = function(e){ 63 | var validation=true; 64 | var validationRadio; 65 | 66 | if (e && e.preventDefault){e.preventDefault();} 67 | else if(event && event.preventDefault){event.preventDefault();} 68 | 69 | var payLoad={}; 70 | payLoad._time={} 71 | payLoad._time.name = '_time';// add the time 72 | payLoad._time.type = '_time';// add the time 73 | payLoad._time.value = new Date().toString();// add the time 74 | for (var i = that.inputs.length - 1; i >= 0; i--) { 75 | var name, type; 76 | name = that.inputs[i].name ? inputs[i].name : 'input_'+String(i); 77 | type = inputs[i].type 78 | value=inputs[i].value; 79 | 80 | 81 | if (inputs[i].getAttribute("email-confirmation")==='true' || that.emailConfirmationName===name) 82 | that.emailConfirmation = value; 83 | 84 | if (inputs[i].getAttribute("email-confirmation-from")==='true') 85 | that.emailConfirmationFrom = value; 86 | 87 | 88 | if (inputs[i].getAttribute("email-confirmation-from")==='true') 89 | that.emailConfirmationFrom = value; 90 | 91 | 92 | if (inputs[i].getAttribute("email-confirmation-subject")==='true') 93 | that.emailConfirmationSubject = value; 94 | 95 | if (inputs[i].getAttribute("email-confirmation-body-html")==='true') 96 | that.emailConfirmationBodyHTML = value; 97 | 98 | 99 | if (inputs[i].getAttribute("email-confirmation-body-text")==='true') 100 | that.emailConfirmationBodyText = value; 101 | 102 | if (inputs[i].getAttribute("email-notification")==='true' && !that.emailNotification) 103 | that.emailNotification = value; 104 | 105 | 106 | if (type==="radio" && inputs[i].checked===true){ 107 | payLoad[name]={}; 108 | payLoad[name].value = inputs[i].checked? value:""; 109 | payLoad[name].type=inputs[i].type; 110 | payLoad[name].name=inputs[i].name; 111 | validationRadio= (inputs[i].checked || validationRadio)? true:false;//flip it to true if checked or keep it true 112 | } else if (type!=="submit" && type!=="radio"){ 113 | payLoad[name]={}; 114 | payLoad[name].value=value 115 | payLoad[name].type=inputs[i].type 116 | payLoad[name].name=inputs[i].name 117 | payLoad[name].checked=inputs[i].checked 118 | } 119 | 120 | if (type==="radio" && !validationRadio && simpleValidation) 121 | inputs[i].className += " "+inputValidationClass; 122 | else if (type!=="submit" && value==="" && simpleValidation) 123 | inputs[i].className += " "+inputValidationClass, validation=false; 124 | else if(simpleValidation) 125 | inputs[i].className=inputs[i].className.replace(new RegExp(inputValidationClass, 'g'),""); 126 | } 127 | if ( (validation && validationRadio)|| !simpleValidation){ 128 | that.submitForm(fireBaseRepo, payLoad), 129 | formDOMObject.className=formDOMObject.className.replace(new RegExp(formValidationClass, 'g'),""); 130 | }else{ 131 | formDOMObject.className += " "+formValidationClass, 132 | that.error('Validation Failed. Classname '+formValidationClass+' Added to inputs'); 133 | }; 134 | return false; 135 | 136 | } 137 | 138 | this.getRepo=function(url){ 139 | var user_repo_tuple, source_tuple, source, user_repo_tuple, user, repo 140 | if ( url.match("fireform.org/publicexample") || url.match("fireform/publicexample") ) {return "https://fireform.firebaseio.com/example/formPosts.json" }//check for example url 141 | source_tuple=url.split("://")[1].split('/list/') 142 | source=source_tuple[0].split('.org')[0]//split the .com 143 | user_repo_tuple=source_tuple[1].split('/') 144 | user= that.user = user_repo_tuple[0] 145 | if (!isNaN(Number(user))){ 146 | user= that.user = 'simplelogin:'+user 147 | } 148 | repo=that.repo=user_repo_tuple[1] 149 | return "https://"+source+".firebaseio.com/users/"+user+"/lists/"+repo+"/formPosts.json" 150 | } 151 | 152 | this.getEmailRepo=function(url){ 153 | var user_repo_tuple, source_tuple, source, user_repo_tuple, user, repo 154 | if ( url.match("fireform.org/publicexample") ) {return null }//check for example url 155 | source_tuple=url.split("://")[1].split('/list/') 156 | source=source_tuple[0].split('.org')[0]//split the .com 157 | user_repo_tuple=source_tuple[1].split('/') 158 | user=that.user=user_repo_tuple[0] 159 | repo=user_repo_tuple[1] 160 | return "https://"+source+".firebaseio.com/users/"+user+"/" 161 | } 162 | 163 | 164 | this.submitForm=function(fireBaseRepo, payLoad){ 165 | var xmlhttp = new XMLHttpRequest, url=this.getRepo(fireBaseRepo); 166 | xmlhttp.open("POST",url,true); 167 | xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); 168 | xmlhttp.send( JSON.stringify(payLoad) ); 169 | 170 | var emailPayload={}; 171 | emailPayload.form=payLoad; 172 | payloadText=''; 173 | payloadHTML="" 174 | for (var key in emailPayload.form) { 175 | payloadText+=' \r\n '+key+" : "+emailPayload.form[key].value 176 | payloadHTML+=' ' 177 | }; 178 | payloadText+='
'+key+" "+emailPayload.form[key].value+'
' 179 | 180 | emailPayload.fireBaseRepo=fireBaseRepo; 181 | emailPayload.payloadText=payloadText; 182 | emailPayload.payloadHTML=payloadHTML; 183 | emailPayload.uid = that.user?that.user:undefined; 184 | emailPayload.fireFormRepo=that.repo; 185 | 186 | 187 | if (that.emailConfirmation){ 188 | emailPayload.emailConfirmation= that.emailConfirmation? that.emailConfirmation:undefined; 189 | emailPayload.emailConfirmationFrom=that.emailConfirmationFrom? that.emailConfirmationFrom:'no-reply@'+window.location.host; 190 | emailPayload.emailConfirmationSubject=that.emailConfirmationSubject? that.emailConfirmationSubject:'Confirming your submition to'+window.location.host; 191 | emailPayload.emailConfirmationBodyText=that.emailConfirmationBodyText? that.emailConfirmationBodyText:'Thanks for you submition!'; 192 | emailPayload.emailConfirmationBodyHTML=that.emailConfirmationBodyHTML? '
'+that.emailConfirmationBodyHTML:'

Thanks for your submition!

'; //front div is a hack for zapier 193 | var urlEmailC = 'https://fireform.firebaseio.com/emailQConfirmation.json'//this.getEmailRepo(fireBaseRepo) 194 | var xmlhttpEmailC = new XMLHttpRequest; //this.getRepo(fireBaseRepo); 195 | xmlhttpEmailC.open("POST",urlEmailC,true); 196 | xmlhttpEmailC.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); 197 | xmlhttpEmailC.send( JSON.stringify(emailPayload) ); 198 | } 199 | 200 | 201 | if (that.emailNotification){ 202 | emailPayload.emailNotification= that.emailNotification? that.emailNotification:undefined; 203 | var urlEmailN = 'https://fireform.firebaseio.com/emailQNotification.json'//this.getEmailRepo(fireBaseRepo) 204 | var xmlhttpEmailN = new XMLHttpRequest; //this.getRepo(fireBaseRepo); 205 | xmlhttpEmailN.open("POST",urlEmailN,true); 206 | xmlhttpEmailN.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); 207 | xmlhttpEmailN.send( JSON.stringify(emailPayload) ); 208 | }; 209 | 210 | xmlhttp.onreadystatechange=function(){ 211 | if (xmlhttp.readyState == 4) { 212 | formDOMObject.className += " "+successClass 213 | if (!options || !options.disableInput) that.disableInput(that.submitElement); 214 | if (options && options.callback) options.callback(null,{url:url}); 215 | } 216 | } 217 | } 218 | 219 | this.disableInput=function(submit){ 220 | var att=document.createAttribute("disabled"); 221 | att.value="true"; 222 | submit.setAttributeNode(att); 223 | } 224 | 225 | submitElement.onclick=this.submit; 226 | return this; 227 | }; -------------------------------------------------------------------------------- /fireform.min.js: -------------------------------------------------------------------------------- 1 | Fireform=function(t,e,i){that=this,0===window.location.href.indexOf("file")&&console.warn("WARNING: Please run Fireform on page served from a web server to garentee functionality."),this.error=function(t){console.error(t)};var a,o,n,r=i&&i.successClass?i.successClass:"submit-success",m=(i&&i.failedClass?i.failedClass:"submit-failed",i&&i.formValidationClass?i.formValidationClass:"form-validation-failed"),l=i&&i.inputValidationClass?i.inputValidationClass:"input-validation-failed",s=i&&i.simpleValidation?i.simpleValidation:!1;if(that.emailNotification=i&&i.emailNotification?i.emailNotification:void 0,that.emailConfirmationName=i&&i.emailConfirmationName?i.emailConfirmationName:void 0,that.emailConfirmationFrom=i&&i.emailConfirmationFrom?i.emailConfirmationFrom:void 0,that.emailConfirmationSubject=i&&i.emailConfirmationSubject?i.emailConfirmationSubject:void 0,that.emailConfirmationBodyHTML=i&&i.emailConfirmationBodyHTML?i.emailConfirmationBodyHTML:void 0,that.emailConfirmationBodyText=i&&i.emailConfirmationBodyText?i.emailConfirmationBodyText:void 0,that.user=void 0,"string"!=typeof t)a=t;else if(0===t.search(/^\./))a=document.getElementsByClassName(t.slice(1))[0],console.warn("We will default to the first form matching this class selector");else if(0===t.search(/^\#/))a=document.getElementById(t.slice(1));else if(!a)return void this.error('Please use a Class or Id Selector. This mean your string should begin with a "." or "#". You man also pass in a Dom elment object');if(!a.tagName)return void this.error("No DOM object found!");if("FORM"!==a.tagName)return void this.error("DOM elments is not a ");if(!e||e.length==-0)return void this.error("No Fireform URL entered");this.formDOMObject=a,this.inputs=o=a.elements;for(var f=this.inputs.length-1;f>=0;f--){var u;if(u=o[f].getAttribute("type"),"submit"===u){n=o[f];break}}return n?(this.submitElement=n,this.submit=function(t){var i,n=!0;t&&t.preventDefault?t.preventDefault():event&&event.preventDefault&&event.preventDefault();var r={};r._time={},r._time.name="_time",r._time.type="_time",r._time.value=(new Date).toString();for(var f=that.inputs.length-1;f>=0;f--){var u,d;u=that.inputs[f].name?o[f].name:"input_"+String(f),d=o[f].type,value=o[f].value,("true"===o[f].getAttribute("email-confirmation")||that.emailConfirmationName===u)&&(that.emailConfirmation=value),"true"===o[f].getAttribute("email-confirmation-from")&&(that.emailConfirmationFrom=value),"true"===o[f].getAttribute("email-confirmation-from")&&(that.emailConfirmationFrom=value),"true"===o[f].getAttribute("email-confirmation-subject")&&(that.emailConfirmationSubject=value),"true"===o[f].getAttribute("email-confirmation-body-html")&&(that.emailConfirmationBodyHTML=value),"true"===o[f].getAttribute("email-confirmation-body-text")&&(that.emailConfirmationBodyText=value),"true"!==o[f].getAttribute("email-notification")||that.emailNotification||(that.emailNotification=value),"radio"===d&&o[f].checked===!0?(r[u]={},r[u].value=o[f].checked?value:"",r[u].type=o[f].type,r[u].name=o[f].name,i=o[f].checked||i?!0:!1):"submit"!==d&&"radio"!==d&&(r[u]={},r[u].value=value,r[u].type=o[f].type,r[u].name=o[f].name,r[u].checked=o[f].checked),"radio"===d&&!i&&s?o[f].className+=" "+l:"submit"!==d&&""===value&&s?(o[f].className+=" "+l,n=!1):s&&(o[f].className=o[f].className.replace(new RegExp(l,"g"),""))}return n&&i||!s?(that.submitForm(e,r),a.className=a.className.replace(new RegExp(m,"g"),"")):(a.className+=" "+m,that.error("Validation Failed. Classname "+m+" Added to inputs")),!1},this.getRepo=function(t){var e,i,a,e,o,n;return t.match("fireform.org/publicexample")||t.match("fireform/publicexample")?"https://fireform.firebaseio.com/example/formPosts.json":(i=t.split("://")[1].split("/list/"),a=i[0].split(".org")[0],e=i[1].split("/"),o=that.user=e[0],isNaN(Number(o))||(o=that.user="simplelogin:"+o),n=that.repo=e[1],"https://"+a+".firebaseio.com/users/"+o+"/lists/"+n+"/formPosts.json")},this.getEmailRepo=function(t){var e,i,a,e,o,n;return t.match("fireform.org/publicexample")?null:(i=t.split("://")[1].split("/list/"),a=i[0].split(".org")[0],e=i[1].split("/"),o=that.user=e[0],n=e[1],"https://"+a+".firebaseio.com/users/"+o+"/")},this.submitForm=function(t,e){var o=new XMLHttpRequest,n=this.getRepo(t);o.open("POST",n,!0),o.setRequestHeader("Content-type","application/x-www-form-urlencoded"),o.send(JSON.stringify(e));var m={};m.form=e,payloadText="",payloadHTML="";for(var l in m.form)payloadText+=" \r\n "+l+" : "+m.form[l].value,payloadHTML+=" ";if(payloadText+="
"+l+" "+m.form[l].value+"
",m.fireBaseRepo=t,m.payloadText=payloadText,m.payloadHTML=payloadHTML,m.uid=that.user?that.user:void 0,m.fireFormRepo=that.repo,that.emailConfirmation){m.emailConfirmation=that.emailConfirmation?that.emailConfirmation:void 0,m.emailConfirmationFrom=that.emailConfirmationFrom?that.emailConfirmationFrom:"no-reply@"+window.location.host,m.emailConfirmationSubject=that.emailConfirmationSubject?that.emailConfirmationSubject:"Confirming your submition to"+window.location.host,m.emailConfirmationBodyText=that.emailConfirmationBodyText?that.emailConfirmationBodyText:"Thanks for you submition!",m.emailConfirmationBodyHTML=that.emailConfirmationBodyHTML?"

"+that.emailConfirmationBodyHTML:"

Thanks for your submition!

";var s="https://fireform.firebaseio.com/emailQConfirmation.json",f=new XMLHttpRequest;f.open("POST",s,!0),f.setRequestHeader("Content-type","application/x-www-form-urlencoded"),f.send(JSON.stringify(m))}if(that.emailNotification){m.emailNotification=that.emailNotification?that.emailNotification:void 0;var u="https://fireform.firebaseio.com/emailQNotification.json",d=new XMLHttpRequest;d.open("POST",u,!0),d.setRequestHeader("Content-type","application/x-www-form-urlencoded"),d.send(JSON.stringify(m))}o.onreadystatechange=function(){4==o.readyState&&(a.className+=" "+r,i&&i.disableInput||that.disableInput(that.submitElement),i&&i.callback&&i.callback(null,{url:n}))}},this.disableInput=function(t){var e=document.createAttribute("disabled");e.value="true",t.setAttributeNode(e)},n.onclick=this.submit,this):void this.error('Please add a submit button with a attr"')}; -------------------------------------------------------------------------------- /img/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SFDevLabs/fireform/9b00ce76cbc0f7a5ceb56629bfb85b0eb10b7862/img/.gitkeep -------------------------------------------------------------------------------- /img/collect-data-with-fireform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SFDevLabs/fireform/9b00ce76cbc0f7a5ceb56629bfb85b0eb10b7862/img/collect-data-with-fireform.png -------------------------------------------------------------------------------- /img/fireform-logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SFDevLabs/fireform/9b00ce76cbc0f7a5ceb56629bfb85b0eb10b7862/img/fireform-logo-white.png -------------------------------------------------------------------------------- /img/fireform-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SFDevLabs/fireform/9b00ce76cbc0f7a5ceb56629bfb85b0eb10b7862/img/fireform-logo.png -------------------------------------------------------------------------------- /img/generate-fireform-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SFDevLabs/fireform/9b00ce76cbc0f7a5ceb56629bfb85b0eb10b7862/img/generate-fireform-code.png -------------------------------------------------------------------------------- /img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SFDevLabs/fireform/9b00ce76cbc0f7a5ceb56629bfb85b0eb10b7862/img/loading.gif -------------------------------------------------------------------------------- /img/paste-fireform-code-into-html.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SFDevLabs/fireform/9b00ce76cbc0f7a5ceb56629bfb85b0eb10b7862/img/paste-fireform-code-into-html.png -------------------------------------------------------------------------------- /img/sf-dev-labs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SFDevLabs/fireform/9b00ce76cbc0f7a5ceb56629bfb85b0eb10b7862/img/sf-dev-labs.png -------------------------------------------------------------------------------- /img/spark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SFDevLabs/fireform/9b00ce76cbc0f7a5ceb56629bfb85b0eb10b7862/img/spark.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Fireform 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |

26 | 27 | 28 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var connect = require('connect'), 2 | nib = require('nib'), 3 | fs = require('fs'), 4 | stylus = require('stylus'), 5 | jade = require('jade'), 6 | connect = require('connect'), 7 | http = require('http'); 8 | 9 | var http = require("http"), 10 | url = require("url"), 11 | path = require("path"), 12 | fs = require("fs") 13 | port = process.argv[2] || 8000; 14 | 15 | http.createServer(function(request, response) { 16 | 17 | var uri = url.parse(request.url).pathname, 18 | filename = path.join(process.cwd(), uri); 19 | 20 | path.exists(filename, function(exists) { 21 | if (!exists) { 22 | response.writeHead(404, { 23 | "Content-Type": "text/plain" 24 | }); 25 | response.write("404 Not Found\n"); 26 | response.end(); 27 | return; 28 | } 29 | 30 | if (fs.statSync(filename).isDirectory()) filename += '/index.html'; 31 | 32 | fs.readFile(filename, "binary", function(err, file) { 33 | if (err) { 34 | response.writeHead(500, { 35 | "Content-Type": "text/plain" 36 | }); 37 | response.write(err + "\n"); 38 | response.end(); 39 | return; 40 | } 41 | 42 | response.writeHead(200); 43 | response.write(file, "binary"); 44 | response.end(); 45 | }); 46 | }); 47 | }).listen(parseInt(port, 10)); 48 | 49 | console.log("Static file server running at\n => http://localhost:" + port + "/\nCTRL + C to shutdown"); 50 | // fs.readFile('/Users/sirpunchallot/fireform/app/index2.jade', 'utf8', function(err, str) { 51 | 52 | 53 | // console.log(__dirname); 54 | 55 | // //console.log(stylus(str).use(nib())); 56 | 57 | // var fn = jade.compile(str, { 58 | // filename: '/Users/sirpunchallot/fireform/app/index2.jade' 59 | // }); 60 | // var htmlOutput = fn({ 61 | // maintainer: { 62 | // name: 'Forbes Lindesay', 63 | // twitter: '@ForbesLindesay', 64 | // blog: 'forbeslindesay.co.uk' 65 | // } 66 | // }); 67 | 68 | // return 69 | 70 | // stylus(str) 71 | // .set('filename', 'nesting.css') 72 | // .include(__dirname + '/css') 73 | // .render(function(err, css) { 74 | 75 | // console.log(err); 76 | 77 | 78 | // fs.writeFile("/Users/sirpunchallot/fireform/app/css/normalize.css", css, function(err) { 79 | // if (err) { 80 | // console.log(err); 81 | // } else { 82 | // console.log("The file was saved!"); 83 | // } 84 | // }); 85 | 86 | // }); 87 | 88 | 89 | 90 | 91 | 92 | // }); 93 | 94 | 95 | 96 | 97 | 98 | 99 | // Require 100 | var watchr = require('watchr'); 101 | 102 | // Watch a directory or file 103 | watchr.watch({ 104 | paths: [__dirname], 105 | listeners: { 106 | log: function(logLevel, b, c) { 107 | 108 | //console.log(b.match("Watch triggered on: ")); 109 | //console.log(b.match(" for event error")); 110 | 111 | if (b.match("Watch triggered on: ") !== null) { 112 | var file = b.replace("Watch triggered on: ", "").replace(" for event error", ""); 113 | console.log(true); 114 | fileParse(file); 115 | } 116 | 117 | 118 | }, 119 | error: function(err) { 120 | // console.log('an error occured:', err); 121 | }, 122 | watching: function(err, watcherInstance, isWatching) { 123 | if (err) { 124 | console.log("watching the path " + watcherInstance.path + " failed with error", err); 125 | } else { 126 | // console.log("watching the path " + watcherInstance.path + " completed"); 127 | } 128 | }, 129 | change: function(changeType, filePath, fileCurrentStat, filePreviousStat) { 130 | console.log('change:', filePath.match(/\.([^\.]+)$/)[0]); 131 | 132 | }, 133 | next: function(err, watchers) { 134 | 135 | 136 | // if (err) { 137 | // return console.log("watching everything failed with error", err); 138 | // } else { 139 | // console.log('watching everything completed', watchers); 140 | // } 141 | 142 | // Close watchers after 60 seconds 143 | // setTimeout(function() { 144 | // var i; 145 | // console.log('Stop watching our paths'); 146 | // for (i = 0; i < watchers.length; i++) { 147 | // watchers[i].close(); 148 | // } 149 | // }, 60 * 1000); 150 | } 151 | } 152 | 153 | }); 154 | 155 | var fileParse = function(filePath) { 156 | //console.log('a change event occured:', arguments); 157 | 158 | console.log('fileparse:', filePath.match(/\.([^\.]+)$/)[0]); 159 | var fileEnd = filePath.match(/\.([^\.]+)$/)[0]; 160 | 161 | 162 | if (fileEnd === ".styl") { 163 | 164 | 165 | console.log(filePath); 166 | 167 | fs.readFile(filePath, 'utf8', function(err, str) { 168 | 169 | 170 | 171 | stylus(str) 172 | .set('filename', filePath) 173 | .set('compress', true) 174 | .use(nib()) 175 | .import('nib') 176 | .include(__dirname + '/css') 177 | .render(function(err, css) { 178 | 179 | console.log(css); 180 | 181 | console.log(err); 182 | 183 | newCSSpathfilePath = filePath.slice(0, -5).concat('.css') 184 | 185 | console.log('writeAttempt', newCSSpathfilePath, css); 186 | 187 | fs.writeFile(newCSSpathfilePath, css, function(err) { 188 | console.log('write', newCSSpathfilePath); 189 | 190 | if (err) { 191 | console.log(err); 192 | } else { 193 | console.log("The file was saved!"); 194 | } 195 | }); 196 | 197 | 198 | 199 | }); 200 | 201 | 202 | 203 | 204 | }); 205 | 206 | 207 | } else if (fileEnd === ".jade") { 208 | 209 | 210 | fs.readFile(filePath, 'utf8', function(err, str) { 211 | 212 | console.log(filePath); 213 | console.log(str); 214 | 215 | var fn = jade.compile(str, { 216 | filename: filePath 217 | }); 218 | var htmlOutput = fn({ 219 | maintainer: { 220 | name: 'Forbes Lindesay', 221 | twitter: '@ForbesLindesay', 222 | blog: 'forbeslindesay.co.uk' 223 | } 224 | }); 225 | 226 | 227 | newHTMLpathfilePath = filePath.slice(0, -5).concat('.html'); 228 | 229 | 230 | fs.writeFile(newHTMLpathfilePath, htmlOutput, function(err) { 231 | 232 | if (err) { 233 | console.log(err); 234 | } else { 235 | console.log("The file was saved!"); 236 | } 237 | }); 238 | 239 | 240 | 241 | 242 | 243 | }); 244 | 245 | 246 | 247 | 248 | 249 | } 250 | 251 | 252 | 253 | 254 | } -------------------------------------------------------------------------------- /js/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Declare app level module which depends on filters, and services 4 | angular.module('myApp', 5 | ['myApp.config', 'myApp.routes', 'myApp.filters', 'myApp.services', 'myApp.directives', 'myApp.controllers', 6 | 'waitForAuth', 'routeSecurity'] 7 | ) 8 | 9 | .run(['loginService', '$rootScope','$timeout', 'FBURL', '$location', function(loginService, $rootScope, $timeout, FBURL, $location) { 10 | if( FBURL === 'https://INSTANCE.firebaseio.com' ) { 11 | // double-check that the app has been configured 12 | angular.element(document.body).html('

Please configure app/js/config.js before running!

'); 13 | setTimeout(function() { 14 | angular.element(document.body).removeClass('hide'); 15 | }, 250); 16 | } 17 | else { 18 | // establish authentication 19 | $rootScope.auth = loginService.init('/login'); 20 | $rootScope.FBURL = FBURL; 21 | } 22 | 23 | //set root scopefunctions 24 | $rootScope.logout = function() { 25 | loginService.logout(); 26 | $location.path('/'); 27 | }; 28 | 29 | $rootScope.bodyClassClick = function(bodyClass) { 30 | $rootScope.bodyClassModal=bodyClass; 31 | }; 32 | 33 | $rootScope.getTmplUrlforAuth= function(loggedin, loggedout){ 34 | return $rootScope.auth.user ? loggedin : loggedout; 35 | } 36 | $rootScope.ffContact= function(loggedin, loggedout){ 37 | var options={ 38 | emailNotification:"team@sfdevlabs.com", 39 | emailConfirmationName:"email", //The form input we get the email from. 40 | emailConfirmationFrom:"team@sfdevlabs.com", //Email appears as sent by this address. 41 | emailConfirmationSubject:"Fireform Contact Verification", 42 | emailConfirmationBodyHTML:"

Thanks for reaching out!

We will be in touch shortly.

", 43 | emailConfirmationBodyText:"Thanks for reaching out! We will be in touch shortly" 44 | } 45 | new Fireform('#FireFormContact', 'http://fireform/list/26/contactUsFF', options); 46 | } 47 | 48 | //might need this at some point to edit tmpls 49 | // $rootScope.initHighlighting= function(loggedin, loggedout){ 50 | // // debugger 51 | // // setTimeout(function(){hljs.initHighlighting()},1000)//when in doubt setTimeout!! hljs.initHighlighting messes up {{angular-tmpl-var}} so we set timeout to make sure it happens after and in window scope 52 | // } 53 | 54 | $rootScope.getTmplUrlforVal= function(loggedin, loggedout, bool){ 55 | return bool ? loggedin : loggedout; 56 | } 57 | 58 | $rootScope.timeoutSet= function(variable, value, duration){ 59 | var that=this; 60 | $timeout(function(){ 61 | that[variable]=value; 62 | }, duration); 63 | //return $rootScope.auth.user ? loggedin : loggedout; 64 | } 65 | 66 | $rootScope.setVarInScope=function(key, val){ 67 | var scope=this; 68 | var scopefind=scope; 69 | for (var i = arguments.length - 1; i >= 2; i--) { 70 | if (scopefind[arguments[i]]) 71 | scopefind=scopefind[arguments[i]]; 72 | }; 73 | scopefind[key]=val; 74 | } 75 | 76 | 77 | 78 | $rootScope.scrollTop=function(x, y){ 79 | x=x?x:0, 80 | y=y?y:0, 81 | window.scrollTo(x,y) 82 | } 83 | 84 | 85 | $rootScope.JSON2CSV=JSON2CSV; 86 | 87 | }]); 88 | 89 | 90 | function JSON2CSV(objArray, label, quotes) { 91 | var objArray=_.map(objArray, function(val){ 92 | return _.omit(val, '$$hashKey'); 93 | }), 94 | array = typeof objArray != 'object' ? JSON.parse(objArray) : objArray, 95 | str = '', 96 | line = ''; 97 | 98 | 99 | 100 | 101 | // quotes = quotes==="true"?true:false, 102 | // label = label==="true"?true:false; 103 | if (label) { 104 | var head = array[0]; 105 | if (quotes) { 106 | for (var index in array[0]) { 107 | var value = index.value + ""; 108 | line += '"' + value.replace(/"/g, '""') + '",'; 109 | } 110 | } else { 111 | for (var index in array[0]) { 112 | line += index + ','; 113 | } 114 | } 115 | 116 | line = line.slice(0, -1); 117 | str += line + '\r\n'; 118 | } 119 | 120 | for (var i = 0; i < array.length; i++) { 121 | var line = '', val; 122 | 123 | for (var index in array[i]) { 124 | if (array[i][index].type==="checkbox") 125 | val = array[i][index].checked; 126 | else 127 | val = array[i][index].value; 128 | 129 | if (quotes) { 130 | var value = val + ""; 131 | line += '"' + value.replace(/"/g, '""') + '",'; 132 | } else { 133 | line += val + ','; 134 | } 135 | } 136 | 137 | line = line.slice(0, -1); 138 | str += line + '\r\n'; 139 | } 140 | window.open("data:text/csv;charset=utf-8," + escape(str)) 141 | } 142 | -------------------------------------------------------------------------------- /js/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Declare app level module which depends on filters, and services 4 | angular.module('myApp.config', []) 5 | 6 | // version of this seed app is compatible with angularFire 0.6 7 | // see tags for other versions: https://github.com/firebase/angularFire-seed/tags 8 | .constant('version', '0.6') 9 | 10 | // where to redirect users if they need to authenticate (see module.routeSecurity) 11 | .constant('loginRedirectPath', '/login') 12 | 13 | .constant('signupRedirectPath', '/signup') 14 | 15 | 16 | // your Firebase URL goes here 17 | .constant('FBURL', 'https://fireform.firebaseio.com') 18 | 19 | //you can use this one to try out a demo of the seed 20 | // .constant('FBURL', 'https://angularfire-seed.firebaseio.com'); 21 | 22 | 23 | /********************* 24 | * !!FOR E2E TESTING!! 25 | * 26 | * Must enable email/password logins and manually create 27 | * the test user before the e2e tests will pass 28 | * 29 | * user: test@test.com 30 | * pass: test123 31 | */ -------------------------------------------------------------------------------- /js/controllers-ck.js: -------------------------------------------------------------------------------- 1 | "use strict";angular.module("myApp.controllers",[]).controller("PublicHomeCtrl",["$rootScope","$scope","syncData",function(e,t,n){n("syncedValue").$bind(t,"syncedValue")}]).controller("docController",["$rootScope","$scope","syncData",function(e,t,n){n("syncedValue").$bind(t,"syncedValue")}]).controller("AppHomeCtrl",["$scope","loginService","syncData",function(e,t,n){e.Lists=n("users/"+e.auth.user.uid+"/lists",10);n("syncedValue").$bind(e,"syncedValue")}]).controller("LoginCtrl",["$scope","loginService","$location",function(e,t,n){e.email=null;e.pass=null;e.confirm=null;e.createMode=!1;e.login=function(n){e.err=null;e.email?e.pass?t.login(e.email,e.pass,function(t,r){e.err=t?t+"":null;t||n&&n(r)}):e.err="Please enter a password":e.err="Please enter an email address"}}]).controller("SignupCtrl",["$scope","loginService","$location","syncData",function(e,t,n,r){function i(){e.email?e.pass?e.formName?e.formName.search(" ")!==-1&&(e.err="Form names can not contain spaces."):e.err="Please enter a name for your first form":e.err="Please enter a password":e.err="Please enter an email address";return!e.err}e.email=null;e.pass=null;e.confirm=null;e.createMode=!1;e.createAccount=function(){e.err=null;i()&&t.createAccount(e.email,e.pass,function(i,s){i?e.err=i?i+"":null:e.login(function(){t.createProfile(s.uid,s.email);var i=r("users/"+s.uid+"/lists/"+e.formName);i.$set({id:e.formName,_created:String(new Date),welcomMessage:!0});n.path("/list/"+s.id+"/"+e.formName)})})};e.login=function(n){e.err=null;e.email?e.pass?t.login(e.email,e.pass,function(t,r){e.err=t?t+"":null;t||n&&n(r)}):e.err="Please enter a password":e.err="Please enter an email address"}}]).controller("AccountCtrl",["$scope","loginService","syncData","$location",function(e,t,n,r){function i(){return{email:e.auth.user.email,oldpass:e.oldpass,newpass:e.newpass,confirm:e.confirm,callback:function(t){if(t)e.err=t;else{e.oldpass=null;e.newpass=null;e.confirm=null;e.msg="Password updated!"}}}}n(["users",e.auth.user.uid]).$bind(e,"user");e.newList=null;e.Lists=n("users/"+e.auth.user.uid+"/lists",10);e.oldpass=null;e.newpass=null;e.confirm=null;e.reset=function(){e.err=null;e.msg=null};e.updatePassword=function(){e.reset();t.changePassword(i())}}]).controller("AddCtrl",["$scope","loginService","syncData","$location",function(e,t,n,r){n(["users",e.auth.user.uid]).$bind(e,"user");e.newList=null;e.addList=function(){if(e.newList){e.err=null;var t=n("users/"+e.auth.user.uid+"/lists/"+e.newList),i=e.auth.user.uid.replace("simplelogin:","");if(t.$getIndex().length){e.err="A form repositry with this name already exists!";return!1}if(e.newList.search(" ")!==-1){e.err="Form names can not contain spaces.";return!1}t.$set({id:e.newList,_created:String(new Date)});r.path("/list/"+i+"/"+e.newList);e.newList=null}}}]).controller("ListCtrl",["$scope","loginService","syncData","$location",function(e,t,n,r){n(["users",e.auth.user.uid]).$bind(e,"user");e.$location=r;e.Lists=n("users/"+e.auth.user.uid+"/lists",10)}]).controller("listViewCtrl",["$rootScope","$scope","loginService","syncData","$location","$timeout",function(e,t,n,r,i,s){r(["users",t.auth.user.uid]).$bind(t,"user");var o=t.auth.user.uid.replace("simplelogin:",""),u=i.$$path.replace("/list/"+o+"/","");t.ListView=r("users/"+t.auth.user.uid+"/lists/"+u);t.emailConfirmation=r("users/"+t.auth.user.uid+"/lists/"+u+"/emailConfirmation");t.emailNotification=r("users/"+t.auth.user.uid+"/lists/"+u+"/emailNotification");t.welcomMessage=r("users/"+t.auth.user.uid+"/lists/"+u+"/welcomMessage");t.setEmailNotification=function(){var e=t.emailNotification.$value?!1:!0;t.emailNotification.$set(e)};t.setEmailConfirmation=function(){var e=t.emailConfirmation.$value?!1:!0;t.emailConfirmation.$set(e)};t.removeWelcome=function(){t.welcomMessage.$set(!1)};t.$location=i;t.pageLoaded=!1;t.pageLoadedFade=!1;t.ListView.$on("loaded",function(e){t.pageLoadedFade=!0;s(function(){t.pageLoaded=!0},300)});t.addWelcomeRemoveMe=function(){t.welcomMessage.$set(!0);t.welcomAlert=""};t.deleteList=function(){t.ListView.$remove();i.path("/list")}}]).controller("listViewExampleCtrl",["$rootScope","$scope","loginService","syncData","$location","$timeout",function(e,t,n,r,i,s){t.ListView=r("example");t.$location=i;t.pageLoaded=!1;t.pageLoadedFade=!1;t.ListView.$on("loaded",function(e){t.pageLoadedFade=!0;s(function(){t.pageLoaded=!0},300)})}]); -------------------------------------------------------------------------------- /js/controllers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Controllers */ 4 | 5 | angular.module('myApp.controllers', []) 6 | 7 | .controller('PublicHomeCtrl', ['$rootScope', '$scope', 'syncData', 8 | function($rootScope, $scope, syncData) { 9 | syncData('syncedValue').$bind($scope, 'syncedValue'); 10 | } 11 | ]) 12 | 13 | .controller('docController', ['$rootScope', '$scope', 'syncData', 14 | function($rootScope, $scope, syncData) { 15 | syncData('syncedValue').$bind($scope, 'syncedValue'); 16 | } 17 | ]) 18 | 19 | 20 | .controller('AppHomeCtrl', ['$scope', 'loginService', 'syncData', 21 | function($scope, loginService, syncData) { 22 | //alert($scope.username); 23 | // constrain number of messages by limit into syncData 24 | // add the array into $scope.messages 25 | $scope.Lists = syncData('users/' + $scope.auth.user.uid + '/lists', 10); 26 | syncData('syncedValue').$bind($scope, 'syncedValue'); 27 | } 28 | ]) 29 | 30 | .controller('LoginCtrl', ['$scope', 'loginService', '$location', 31 | function($scope, loginService, $location) { 32 | $scope.email = null; 33 | $scope.pass = null; 34 | $scope.confirm = null; 35 | $scope.createMode = false; 36 | 37 | $scope.login = function(cb) { 38 | $scope.err = null; 39 | if (!$scope.email) { 40 | $scope.err = 'Please enter an email address'; 41 | } else if (!$scope.pass) { 42 | $scope.err = 'Please enter a password'; 43 | } else { 44 | loginService.login($scope.email, $scope.pass, function(err, user) { 45 | $scope.err = err ? err + '' : null; 46 | if (!err) { 47 | cb && cb(user); 48 | } 49 | }); 50 | } 51 | } 52 | } 53 | ]) 54 | 55 | .controller('SignupCtrl', ['$scope', 'loginService', '$location', 'syncData', 56 | function($scope, loginService, $location, syncData) { 57 | $scope.email = null; 58 | $scope.pass = null; 59 | $scope.confirm = null; 60 | $scope.createMode = false; 61 | 62 | $scope.createAccount = function() { 63 | $scope.err = null; 64 | if (assertValidLoginAttempt()) { 65 | loginService.createAccount($scope.email, $scope.pass, function(err, user) { 66 | if (err) { 67 | $scope.err = err ? err + '' : null; 68 | } else { 69 | // must be logged in before I can write to my profile 70 | $scope.login(function() { 71 | loginService.createProfile(user.uid, user.email); 72 | 73 | var newList = syncData('users/' + user.uid + '/lists/' + $scope.formName); 74 | 75 | //Create the form by setting the object in firebase 76 | newList.$set({ 77 | id: $scope.formName, 78 | _created: String(new Date()), 79 | welcomMessage: true 80 | }); 81 | $location.path('/list/'+user.id+"/"+$scope.formName); 82 | }); 83 | } 84 | }); 85 | } 86 | }; 87 | 88 | $scope.login = function(cb) { 89 | $scope.err = null; 90 | if (!$scope.email) { 91 | $scope.err = 'Please enter an email address'; 92 | } else if (!$scope.pass) { 93 | $scope.err = 'Please enter a password'; 94 | } else { 95 | loginService.login($scope.email, $scope.pass, function(err, user) { 96 | $scope.err = err ? err + '' : null; 97 | if (!err) { 98 | cb && cb(user); 99 | } 100 | }); 101 | } 102 | } 103 | 104 | function assertValidLoginAttempt() { 105 | if (!$scope.email) { 106 | $scope.err = 'Please enter an email address'; 107 | } else if (!$scope.pass) { 108 | $scope.err = 'Please enter a password'; 109 | } else if (!$scope.formName) { 110 | $scope.err = 'Please enter a name for your first form'; 111 | }else if ($scope.formName.search(" ")!==-1) { 112 | $scope.err = 'Form names can not contain spaces.'; 113 | } 114 | return !$scope.err; 115 | } 116 | } 117 | ]) 118 | 119 | .controller('AccountCtrl', ['$scope', 'loginService', 'syncData', '$location', 120 | function($scope, loginService, syncData, $location) { 121 | syncData(['users', $scope.auth.user.uid]).$bind($scope, 'user'); 122 | 123 | $scope.newList = null; 124 | 125 | // constrain number of messages by limit into syncData 126 | // add the array into $scope.messages 127 | $scope.Lists = syncData('users/' + $scope.auth.user.uid + '/lists', 10); 128 | 129 | // // add new messages to the list 130 | // $scope.addList = function() { 131 | // if ($scope.newList) { 132 | // $scope.Lists.$add({ 133 | // text: $scope.newList 134 | // }); 135 | // $scope.newList = null; 136 | // } 137 | // }; 138 | 139 | $scope.oldpass = null; 140 | $scope.newpass = null; 141 | $scope.confirm = null; 142 | 143 | $scope.reset = function() { 144 | $scope.err = null; 145 | $scope.msg = null; 146 | }; 147 | 148 | $scope.updatePassword = function() { 149 | $scope.reset(); 150 | loginService.changePassword(buildPwdParms()); 151 | }; 152 | 153 | function buildPwdParms() { 154 | return { 155 | email: $scope.auth.user.email, 156 | oldpass: $scope.oldpass, 157 | newpass: $scope.newpass, 158 | confirm: $scope.confirm, 159 | callback: function(err) { 160 | if (err) { 161 | $scope.err = err; 162 | } else { 163 | $scope.oldpass = null; 164 | $scope.newpass = null; 165 | $scope.confirm = null; 166 | $scope.msg = 'Password updated!'; 167 | } 168 | } 169 | } 170 | } 171 | 172 | } 173 | ]) 174 | 175 | .controller('AddCtrl', ['$scope', 'loginService', 'syncData', '$location', 176 | function($scope, loginService, syncData, $location) { 177 | syncData(['users', $scope.auth.user.uid]).$bind($scope, 'user'); 178 | 179 | $scope.newList = null; 180 | 181 | 182 | $scope.addList = function() { 183 | if ($scope.newList) { 184 | $scope.err = null; 185 | var newList = syncData('users/' + $scope.auth.user.uid + '/lists/' + $scope.newList); 186 | var uid= $scope.auth.user.uid.replace("simplelogin:",""); 187 | //set new list. It will autosync with FB 188 | if (newList.$getIndex().length) { 189 | $scope.err = 'A form repositry with this name already exists!'; 190 | return false; 191 | } 192 | //Check for spaces 193 | if ($scope.newList.search(" ")!==-1) { 194 | $scope.err = 'Form names can not contain spaces.'; 195 | return false; 196 | } 197 | //Create the form by setting the object in firebase 198 | newList.$set({ 199 | id: $scope.newList, 200 | _created: String(new Date()), 201 | // welcomMessage: true; 202 | 203 | }); 204 | //redirect to the new form 205 | $location.path( "/list/"+uid+"/"+$scope.newList ); 206 | $scope.newList = null; 207 | } 208 | }; 209 | 210 | 211 | } 212 | ]) 213 | 214 | 215 | .controller('ListCtrl', ['$scope', 'loginService', 'syncData', '$location', 216 | function($scope, loginService, syncData, $location) { 217 | syncData(['users', $scope.auth.user.uid]).$bind($scope, 'user'); 218 | $scope.$location = $location; 219 | $scope.Lists = syncData('users/' + $scope.auth.user.uid + '/lists', 10); 220 | } 221 | ]) 222 | 223 | .controller('listViewCtrl', ['$rootScope','$scope', 'loginService', 'syncData', '$location', '$timeout', 224 | function($rootScope, $scope, loginService, syncData, $location, $timeout) { 225 | syncData(['users', $scope.auth.user.uid]).$bind($scope, 'user'); 226 | var uid= $scope.auth.user.uid.replace("simplelogin:","") 227 | ,loc = $location.$$path.replace('/list/'+uid+'/', '') 228 | $scope.ListView = syncData('users/' + $scope.auth.user.uid + '/lists/' + loc); 229 | 230 | 231 | //$scope.emailAuth = syncData('users/' + $scope.auth.user.uid 232 | 233 | $scope.emailConfirmation = syncData('users/' + $scope.auth.user.uid + '/lists/' + loc+'/emailConfirmation'); 234 | $scope.emailNotification = syncData('users/' + $scope.auth.user.uid + '/lists/' + loc+'/emailNotification'); 235 | $scope.welcomMessage = syncData('users/' + $scope.auth.user.uid + '/lists/' + loc+'/welcomMessage'); 236 | 237 | 238 | $scope.setEmailNotification = function() { 239 | var val = $scope.emailNotification.$value?false:true; 240 | $scope.emailNotification.$set(val); 241 | 242 | } 243 | $scope.setEmailConfirmation = function() { 244 | var val = $scope.emailConfirmation.$value?false:true; 245 | $scope.emailConfirmation.$set(val); 246 | } 247 | 248 | $scope.removeWelcome = function() { 249 | $scope.welcomMessage.$set(false); 250 | } 251 | 252 | $scope.$location = $location; 253 | 254 | //This tells us when the page loads*/ 255 | $scope.pageLoaded = false; 256 | $scope.pageLoadedFade = false; 257 | 258 | $scope.ListView.$on('loaded',function(data){ 259 | $scope.pageLoadedFade = true; 260 | $timeout(function(){ 261 | $scope.pageLoaded = true; 262 | },300); 263 | 264 | }); 265 | 266 | //remove me 267 | $scope.addWelcomeRemoveMe = function() { 268 | $scope.welcomMessage.$set(true); 269 | 270 | 271 | $scope.welcomAlert=''; 272 | } 273 | 274 | //delete this form. by calling remove on the firebase object 275 | $scope.deleteList = function() { 276 | $scope.ListView.$remove(); 277 | $location.path( "/list"); 278 | } 279 | } 280 | ]) 281 | .controller('listViewExampleCtrl', ['$rootScope','$scope', 'loginService', 'syncData', '$location', '$timeout', 282 | function($rootScope, $scope, loginService, syncData, $location, $timeout) { 283 | $scope.ListView = syncData('example'); 284 | $scope.$location = $location; 285 | 286 | 287 | 288 | //This tells us when the page loads*/ 289 | $scope.pageLoaded = false; 290 | $scope.pageLoadedFade = false; 291 | $scope.ListView.$on('loaded',function(data){ 292 | $scope.pageLoadedFade = true; 293 | $timeout(function(){ 294 | $scope.pageLoaded = true; 295 | },300); 296 | }); 297 | } 298 | ]); -------------------------------------------------------------------------------- /js/directives.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Directives */ 4 | 5 | angular.module("myApp.directives", []) 6 | 7 | // .directive("tmplAuthInclude", function(waitForAuth, $rootScope) { 8 | // return { 9 | // restrict: 'E', 10 | // templateUrl: function(el, attr){ 11 | // waitForAuth.then(function() { 12 | // return $rootScope.auth.user? attr.loggedinSrc : attr.loggedoutSrc; 13 | // }) 14 | // } 15 | // }; 16 | // }) 17 | .directive("bodyClassInject", function($rootScope) { 18 | return { 19 | restrict: 'A', 20 | link: function(scope, el, attr){ 21 | $rootScope.bodyClass=attr.bodyClassInject 22 | } 23 | }; 24 | }) 25 | .directive("bodyClassClick", function($rootScope) { 26 | return { 27 | restrict: 'A', 28 | link: function(scope, el, attr){ 29 | $rootScope.bodyClass=scope.bodyClass; 30 | } 31 | }; 32 | }) 33 | .directive("tabletop", function($rootScope) { 34 | return { 35 | restrict: 'A', 36 | link: function(scope, el, attr){ 37 | //Row Header 38 | 39 | var rowsHeader = scope.$parent.rowsHeader, 40 | rows=scope.$parent.rows, 41 | valueF=scope.valueF; 42 | scope.$parent.predicate={col:"_time",value:'value', reverse: true} , //This sets the default sort to most recent 43 | valueF._time.value=new Date(scope.valueF._time.value); //Make our a date a time object 44 | 45 | var keys = _.union(//union (join with no duplicates) the new keys with the building list 46 | _.pluck(rowsHeader,'fireformName'), 47 | _.keys(valueF) 48 | ); 49 | 50 | scope.$parent.rowsHeader= _.map(keys, function(key){ 51 | var map 52 | if (valueF[key]) 53 | map=valueF[key]; 54 | else 55 | map= _.findWhere(rowsHeader, {fireformName:key}); 56 | 57 | if (map===undefined) return {};///saftey for malformed data; 58 | map.fireformName=key 59 | return map; 60 | }); 61 | //Rows 62 | 63 | scope.$parent.rows.push(valueF) //Make our a date a time object 64 | //Kill the element. Crude but effective. 65 | el.remove(); 66 | } 67 | }; 68 | }) 69 | 70 | .directive("highlight", function($rootScope) { 71 | return { 72 | restrict: 'A', 73 | link: function(scope, el, attr){ 74 | //debugger 75 | var m=el.html() 76 | .replace("{{location}}",scope.$location.absUrl()) 77 | .replace("{{email}}",scope.auth.user.email) 78 | .replace("{{options}}",''); 79 | 80 | el.html(m) 81 | hljs.highlightBlock(el[0]) 82 | } 83 | }; 84 | }) 85 | 86 | .directive("count", function($rootScope) { 87 | return { 88 | restrict: 'A', 89 | link: function(scope, el, attr){ 90 | var varfind=scope, args; 91 | args = attr.count.split('.') 92 | for (var i = 0; i < args.length; i++) { 93 | if (varfind[args[i]]) 94 | varfind=varfind[args[i]] 95 | else{ 96 | varfind=null; 97 | break 98 | }; 99 | }; 100 | if (varfind) 101 | scope.count=_.keys(varfind).length; 102 | else 103 | scope.count=0; 104 | } 105 | }; 106 | }) 107 | .directive("mostRecent", function($rootScope) { 108 | return { 109 | restrict: 'A', 110 | link: function(scope, el, attr){ 111 | var varfind=scope, args, keys, lastK; 112 | args = attr.mostRecent.split('.') 113 | for (var i = 0; i < args.length; i++) { 114 | if (varfind[args[i]]) 115 | varfind=varfind[args[i]]; 116 | else{ 117 | varfind=null; 118 | break 119 | } 120 | } 121 | if (varfind) 122 | keys=_.keys(varfind), 123 | lastK=keys[keys.length-1], 124 | scope.mostRecent=new Date(varfind[lastK]._time.value); 125 | else 126 | scope.mostRecent="" 127 | } 128 | }; 129 | }) 130 | .directive('includeReplace', function () { 131 | return { 132 | require: 'ngInclude', 133 | restrict: 'A', /* optional */ 134 | link: function (scope, el, attrs) { 135 | el.replaceWith(el.children()); 136 | } 137 | }; 138 | }); 139 | -------------------------------------------------------------------------------- /js/filters.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* Filters */ 4 | 5 | angular.module('myApp.filters', []) 6 | .filter('interpolate', ['version', function(version) { 7 | return function(text) { 8 | return String(text).replace(/\%VERSION\%/mg, version); 9 | } 10 | }]) 11 | 12 | .filter('reverse', function() { 13 | function toArray(list) { 14 | var k, out = []; 15 | if( list ) { 16 | if( angular.isArray(list) ) { 17 | out = list; 18 | } 19 | else if( typeof(list) === 'object' ) { 20 | for (k in list) { 21 | if (list.hasOwnProperty(k)) { out.push(list[k]); } 22 | } 23 | } 24 | } 25 | return out; 26 | } 27 | return function(items) { 28 | return toArray(items).slice().reverse(); 29 | }; 30 | }) 31 | .filter('orderObjectBy', function() { 32 | return function(items, col, feild, reverse) { 33 | var filtered = []; 34 | angular.forEach(items, function(item) { 35 | filtered.push(item); 36 | }); 37 | filtered.sort(function (a, b) { 38 | var aVal = a[col] && a[col][feild]? a[col][feild]:""; 39 | var bVal = b[col] && b[col][feild]? b[col][feild]:""; 40 | return (aVal > bVal); 41 | }); 42 | if(reverse) filtered.reverse(); 43 | return filtered; 44 | }; 45 | }); 46 | -------------------------------------------------------------------------------- /js/module.routeSecurity.js: -------------------------------------------------------------------------------- 1 | (function(angular) { 2 | angular.module('routeSecurity', []) 3 | .run(['$injector', '$location', '$rootScope', 'loginRedirectPath', 'signupRedirectPath', function($injector, $location, $rootScope, loginRedirectPath, signupRedirectPath) { 4 | if( $injector.has('$route') ) { 5 | new RouteSecurityManager($location, $rootScope, $injector.get('$route'), loginRedirectPath, signupRedirectPath); 6 | } 7 | }]); 8 | 9 | function RouteSecurityManager($location, $rootScope, $route, loginPath, signupPath) { 10 | this._route = $route; 11 | this._location = $location; 12 | this._rootScope = $rootScope; 13 | this._loginPath = loginPath; 14 | this._signupPath = signupPath; 15 | this._redirectTo = null; 16 | this._authenticated = !!($rootScope.auth && $rootScope.auth.user); 17 | this._init(); 18 | } 19 | 20 | RouteSecurityManager.prototype = { 21 | _init: function() { 22 | var self = this; 23 | this._checkCurrent(); 24 | 25 | // Set up a handler for all future route changes, so we can check 26 | // if authentication is required. 27 | self._rootScope.$on("$routeChangeStart", function(e, next) { 28 | self._authRequiredRedirect(next, self._loginPath, self._signupPath); 29 | }); 30 | 31 | self._rootScope.$on('$firebaseSimpleLogin:login', angular.bind(this, this._login)); 32 | self._rootScope.$on('$firebaseSimpleLogin:logout', angular.bind(this, this._logout)); 33 | self._rootScope.$on('$firebaseSimpleLogin:error', angular.bind(this, this._error)); 34 | }, 35 | 36 | _checkCurrent: function() { 37 | // Check if the current page requires authentication. 38 | if (this._route.current) { 39 | this._authRequiredRedirect(this._route.current, this._loginPath); 40 | } 41 | }, 42 | 43 | _login: function() { 44 | this._authenticated = true; 45 | if( this._redirectTo ) { 46 | this._redirect(this._redirectTo); 47 | this._redirectTo = null; 48 | } 49 | else if( this._location.path() === this._loginPath ) { 50 | this._location.replace(); 51 | this._location.path('/list'); 52 | } 53 | }, 54 | 55 | _logout: function() { 56 | this._authenticated = false; 57 | this._checkCurrent(); 58 | }, 59 | 60 | _error: function() { 61 | if( !this._rootScope.auth || !this._rootScope.auth.user ) { 62 | this._authenticated = false; 63 | } 64 | this._checkCurrent(); 65 | }, 66 | 67 | _redirect: function(path) { 68 | this._location.replace(); 69 | this._location.path(path); 70 | }, 71 | 72 | // A function to check whether the current path requires authentication, 73 | // and if so, whether a redirect to a login page is needed. 74 | _authRequiredRedirect: function(route, path) { 75 | if (route.authRequired && !this._authenticated){ 76 | if (route.pathTo === undefined) { 77 | this._redirectTo = this._location.path(); 78 | } else { 79 | this._redirectTo = route.pathTo === path ? "/" : route.pathTo; 80 | } 81 | this._redirect(path); 82 | } 83 | else if( this._authenticated && (this._location.path() === this._loginPath || this._location.path() === this._signupPath) ) { 84 | this._redirect('/list'); 85 | } 86 | } 87 | 88 | }; 89 | })(angular); 90 | -------------------------------------------------------------------------------- /js/module.waitForAuth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This module monitors angularFire's authentication and performs actions based on authentication state. 3 | * 4 | * See usage examples here: https://gist.github.com/katowulf/7328023 5 | */ 6 | angular.module('waitForAuth', []) 7 | 8 | /** 9 | * A service that returns a promise object, which is resolved once $firebaseSimpleLogin 10 | * is initialized (i.e. it returns login, logout, or error) 11 | */ 12 | .service('waitForAuth', function($rootScope, $q, $timeout) { 13 | var def = $q.defer(), 14 | subs = []; 15 | subs.push($rootScope.$on('$firebaseSimpleLogin:login', fn)); 16 | subs.push($rootScope.$on('$firebaseSimpleLogin:logout', fn)); 17 | subs.push($rootScope.$on('$firebaseSimpleLogin:error', fn)); 18 | 19 | function fn(err) { 20 | if ($rootScope.auth) { 21 | $rootScope.auth.error = err instanceof Error ? err.toString() : null; 22 | } 23 | for (var i = 0; i < subs.length; i++) { 24 | subs[i](); 25 | } 26 | $timeout(function() { 27 | // force $scope.$apply to be re-run after login resolves 28 | def.resolve(); 29 | }); 30 | } 31 | return def.promise; 32 | }) 33 | 34 | /** 35 | * A directive that hides the element from view until waitForAuth resolves 36 | */ 37 | .directive('ngCloakAuth', function(waitForAuth) { 38 | return { 39 | restrict: 'A', 40 | compile: function(el) { 41 | el.addClass('hide'); 42 | waitForAuth.then(function() { 43 | el.removeClass('hide'); 44 | }) 45 | } 46 | } 47 | }) 48 | 49 | /** 50 | * A directive that shows elements only when the given authentication state is in effect 51 | */ 52 | .directive('ngShowAuth', function($rootScope) { 53 | var loginState; 54 | $rootScope.$on("$firebaseSimpleLogin:login", function() { 55 | loginState = 'login' 56 | }); 57 | $rootScope.$on("$firebaseSimpleLogin:logout", function() { 58 | loginState = 'logout' 59 | }); 60 | $rootScope.$on("$firebaseSimpleLogin:error", function() { 61 | loginState = 'error' 62 | }); 63 | 64 | function inList(needle, list) { 65 | var res = false; 66 | angular.forEach(list, function(x) { 67 | if (x === needle) { 68 | res = true; 69 | return true; 70 | } 71 | return false; 72 | }); 73 | return res; 74 | } 75 | 76 | function assertValidState(state) { 77 | if (!state) { 78 | throw new Error('ng-show-auth directive must be login, logout, or error (you may use a comma-separated list)'); 79 | } 80 | var states = (state || '').split(','); 81 | angular.forEach(states, function(s) { 82 | if (!inList(s, ['login', 'logout', 'error'])) { 83 | throw new Error('Invalid state "' + s + '" for ng-show-auth directive, must be one of login, logout, or error'); 84 | } 85 | }); 86 | return true; 87 | } 88 | return { 89 | restrict: 'A', 90 | compile: function(el, attr) { 91 | assertValidState(attr.ngShowAuth); 92 | var expState = (attr.ngShowAuth || '').split(','); 93 | 94 | function fn(newState) { 95 | loginState = newState; 96 | var hide = !inList(newState, expState); 97 | el.toggleClass('hide', hide); 98 | } 99 | fn(loginState); 100 | $rootScope.$on("$firebaseSimpleLogin:login", function() { 101 | fn('login') 102 | }); 103 | $rootScope.$on("$firebaseSimpleLogin:logout", function() { 104 | fn('logout') 105 | }); 106 | $rootScope.$on("$firebaseSimpleLogin:error", function() { 107 | fn('error') 108 | }); 109 | } 110 | } 111 | }); -------------------------------------------------------------------------------- /js/routes.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | angular.module('myApp.routes', ['ngRoute']) 4 | 5 | // configure views; the authRequired parameter is used for specifying pages 6 | // which should only be available while logged in 7 | .config(['$routeProvider', '$locationProvider', 8 | function($routeProvider, $locationProvider) { 9 | $locationProvider.html5Mode(true); 10 | $routeProvider.when('/', { 11 | templateUrl: '/partials/public-home.html', 12 | controller: 'PublicHomeCtrl' 13 | }); 14 | 15 | $routeProvider.when('/docs', { 16 | templateUrl: '/partials/docs.html', 17 | controller: 'docController' 18 | }); 19 | 20 | // $routeProvider.when('/chat', { 21 | // templateUrl: 'partials/chat.html', 22 | // controller: 'ChatCtrl' 23 | // }); 24 | 25 | $routeProvider.when('/account', { 26 | authRequired: true, // must authenticate before viewing this page 27 | templateUrl: '/partials/account.html', 28 | controller: 'AccountCtrl' 29 | }); 30 | 31 | $routeProvider.when('/list/:uid/:id', { 32 | authRequired: true, // must authenticate before viewing this page 33 | templateUrl: '/partials/list-view.html', 34 | controller: 'listViewCtrl' 35 | }); 36 | 37 | $routeProvider.when('/publicexample', { 38 | templateUrl: '/partials/list-view.html', 39 | controller: 'listViewExampleCtrl' 40 | }); 41 | 42 | $routeProvider.when('/list', { 43 | authRequired: true, // must authenticate before viewing this page 44 | templateUrl: 'partials/lists.html', 45 | controller: 'ListCtrl' 46 | }); 47 | 48 | $routeProvider.when('/add', { 49 | authRequired: true, // must authenticate before viewing this page 50 | templateUrl: 'partials/add.html', 51 | controller: 'AddCtrl' 52 | }); 53 | 54 | 55 | $routeProvider.when('/login', { 56 | templateUrl: '/partials/login.html', 57 | controller: 'LoginCtrl' 58 | }); 59 | 60 | $routeProvider.when('/signup', { 61 | templateUrl: '/partials/signup.html', 62 | controller: 'SignupCtrl' 63 | }); 64 | 65 | $routeProvider.otherwise({ 66 | redirectTo: '/' 67 | }); 68 | } 69 | ]); -------------------------------------------------------------------------------- /js/service.firebase.js: -------------------------------------------------------------------------------- 1 | 2 | angular.module('myApp.service.firebase', ['firebase']) 3 | 4 | // a simple utility to create references to Firebase paths 5 | .factory('firebaseRef', ['Firebase', 'FBURL', function(Firebase, FBURL) { 6 | /** 7 | * @function 8 | * @name firebaseRef 9 | * @param {String|Array...} path 10 | * @return a Firebase instance 11 | */ 12 | return function(path) { 13 | return new Firebase(pathRef([FBURL].concat(Array.prototype.slice.call(arguments)))); 14 | } 15 | }]) 16 | 17 | // a simple utility to create $firebase objects from angularFire 18 | .service('syncData', ['$firebase', 'firebaseRef', function($firebase, firebaseRef) { 19 | /** 20 | * @function 21 | * @name syncData 22 | * @param {String|Array...} path 23 | * @param {int} [limit] 24 | * @return a Firebase instance 25 | */ 26 | return function(path, limit) { 27 | var ref = firebaseRef(path); 28 | limit && (ref = ref.limit(limit)); 29 | return $firebase(ref); 30 | } 31 | }]); 32 | 33 | function pathRef(args) { 34 | for(var i=0; i < args.length; i++) { 35 | if( typeof(args[i]) === 'object' ) { 36 | args[i] = pathRef(args[i]); 37 | } 38 | } 39 | return args.join('/'); 40 | } -------------------------------------------------------------------------------- /js/service.login.js: -------------------------------------------------------------------------------- 1 | 2 | angular.module('myApp.service.login', ['firebase', 'myApp.service.firebase']) 3 | 4 | .factory('loginService', ['$rootScope', '$firebaseSimpleLogin', 'firebaseRef', 'profileCreator', '$timeout', 5 | function($rootScope, $firebaseSimpleLogin, firebaseRef, profileCreator, $timeout) { 6 | var auth = null; 7 | return { 8 | init: function() { 9 | return auth = $firebaseSimpleLogin(firebaseRef()); 10 | }, 11 | 12 | /** 13 | * @param {string} email 14 | * @param {string} pass 15 | * @param {Function} [callback] 16 | * @returns {*} 17 | */ 18 | login: function(email, pass, callback) { 19 | assertAuth(); 20 | auth.$login('password', { 21 | email: email, 22 | password: pass, 23 | rememberMe: true 24 | }).then(function(user) { 25 | if( callback ) { 26 | //todo-bug https://github.com/firebase/angularFire/issues/199 27 | $timeout(function() { 28 | callback(null, user); 29 | }); 30 | } 31 | }, callback); 32 | }, 33 | 34 | logout: function() { 35 | assertAuth(); 36 | auth.$logout(); 37 | }, 38 | 39 | changePassword: function(opts) { 40 | assertAuth(); 41 | var cb = opts.callback || function() {}; 42 | if( !opts.oldpass || !opts.newpass ) { 43 | $timeout(function(){ cb('Please enter a password'); }); 44 | } 45 | else if( opts.newpass !== opts.confirm ) { 46 | $timeout(function() { cb('Passwords do not match'); }); 47 | } 48 | else { 49 | auth.$changePassword(opts.email, opts.oldpass, opts.newpass).then(function() { cb && cb(null) }, cb); 50 | } 51 | }, 52 | 53 | createAccount: function(email, pass, callback) { 54 | assertAuth(); 55 | auth.$createUser(email, pass).then(function(user) { callback && callback(null, user) }, callback); 56 | }, 57 | 58 | createProfile: profileCreator 59 | }; 60 | 61 | function assertAuth() { 62 | if( auth === null ) { throw new Error('Must call loginService.init() before using its methods'); } 63 | } 64 | }]) 65 | 66 | .factory('profileCreator', ['firebaseRef', '$timeout', function(firebaseRef, $timeout) { 67 | return function(id, email, callback) { 68 | firebaseRef('users/'+id).set({email: email, name: firstPartOfEmail(email)}, function(err) { 69 | //err && console.error(err); 70 | if( callback ) { 71 | $timeout(function() { 72 | callback(err); 73 | }) 74 | } 75 | }); 76 | 77 | function firstPartOfEmail(email) { 78 | return ucfirst(email.substr(0, email.indexOf('@'))||''); 79 | } 80 | 81 | function ucfirst (str) { 82 | // credits: http://kevin.vanzonneveld.net 83 | str += ''; 84 | var f = str.charAt(0).toUpperCase(); 85 | return f + str.substr(1); 86 | } 87 | } 88 | }]); 89 | -------------------------------------------------------------------------------- /js/services.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /* Services */ 5 | 6 | angular.module('myApp.services', ['myApp.service.login', 'myApp.service.firebase']) 7 | 8 | // put your services here! 9 | // .service('serviceName', ['dependency', function(dependency) {}]); 10 | 11 | })(); 12 | 13 | -------------------------------------------------------------------------------- /js/vendor/underscore-min.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.5.2 2 | // http://underscorejs.org 3 | // (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 4 | // Underscore may be freely distributed under the MIT license. 5 | (function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,h=e.reduce,v=e.reduceRight,g=e.filter,d=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,w=Object.keys,_=i.bind,j=function(n){return n instanceof j?n:this instanceof j?(this._wrapped=n,void 0):new j(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=j),exports._=j):n._=j,j.VERSION="1.5.2";var A=j.each=j.forEach=function(n,t,e){if(null!=n)if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a=j.keys(n),u=0,i=a.length;i>u;u++)if(t.call(e,n[a[u]],a[u],n)===r)return};j.map=j.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e.push(t.call(r,n,u,i))}),e)};var E="Reduce of empty array with no initial value";j.reduce=j.foldl=j.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=j.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(E);return r},j.reduceRight=j.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=j.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=j.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(E);return r},j.find=j.detect=function(n,t,r){var e;return O(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},j.filter=j.select=function(n,t,r){var e=[];return null==n?e:g&&n.filter===g?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&e.push(n)}),e)},j.reject=function(n,t,r){return j.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},j.every=j.all=function(n,t,e){t||(t=j.identity);var u=!0;return null==n?u:d&&n.every===d?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var O=j.some=j.any=function(n,t,e){t||(t=j.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};j.contains=j.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:O(n,function(n){return n===t})},j.invoke=function(n,t){var r=o.call(arguments,2),e=j.isFunction(t);return j.map(n,function(n){return(e?t:n[t]).apply(n,r)})},j.pluck=function(n,t){return j.map(n,function(n){return n[t]})},j.where=function(n,t,r){return j.isEmpty(t)?r?void 0:[]:j[r?"find":"filter"](n,function(n){for(var r in t)if(t[r]!==n[r])return!1;return!0})},j.findWhere=function(n,t){return j.where(n,t,!0)},j.max=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.max.apply(Math,n);if(!t&&j.isEmpty(n))return-1/0;var e={computed:-1/0,value:-1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;a>e.computed&&(e={value:n,computed:a})}),e.value},j.min=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.min.apply(Math,n);if(!t&&j.isEmpty(n))return 1/0;var e={computed:1/0,value:1/0};return A(n,function(n,u,i){var a=t?t.call(r,n,u,i):n;ae||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var F=function(n){return function(t,r,e){var u={},i=null==r?j.identity:k(r);return A(t,function(r,a){var o=i.call(e,r,a,t);n(u,o,r)}),u}};j.groupBy=F(function(n,t,r){(j.has(n,t)?n[t]:n[t]=[]).push(r)}),j.indexBy=F(function(n,t,r){n[t]=r}),j.countBy=F(function(n,t){j.has(n,t)?n[t]++:n[t]=1}),j.sortedIndex=function(n,t,r,e){r=null==r?j.identity:k(r);for(var u=r.call(e,t),i=0,a=n.length;a>i;){var o=i+a>>>1;r.call(e,n[o])=0})})},j.difference=function(n){var t=c.apply(e,o.call(arguments,1));return j.filter(n,function(n){return!j.contains(t,n)})},j.zip=function(){for(var n=j.max(j.pluck(arguments,"length").concat(0)),t=new Array(n),r=0;n>r;r++)t[r]=j.pluck(arguments,""+r);return t},j.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},j.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=j.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},j.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},j.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=new Array(e);e>u;)i[u++]=n,n+=r;return i};var R=function(){};j.bind=function(n,t){var r,e;if(_&&n.bind===_)return _.apply(n,o.call(arguments,1));if(!j.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));R.prototype=n.prototype;var u=new R;R.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},j.partial=function(n){var t=o.call(arguments,1);return function(){return n.apply(this,t.concat(o.call(arguments)))}},j.bindAll=function(n){var t=o.call(arguments,1);if(0===t.length)throw new Error("bindAll must be passed function names");return A(t,function(t){n[t]=j.bind(n[t],n)}),n},j.memoize=function(n,t){var r={};return t||(t=j.identity),function(){var e=t.apply(this,arguments);return j.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},j.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},j.defer=function(n){return j.delay.apply(j,[n,1].concat(o.call(arguments,1)))},j.throttle=function(n,t,r){var e,u,i,a=null,o=0;r||(r={});var c=function(){o=r.leading===!1?0:new Date,a=null,i=n.apply(e,u)};return function(){var l=new Date;o||r.leading!==!1||(o=l);var f=t-(l-o);return e=this,u=arguments,0>=f?(clearTimeout(a),a=null,o=l,i=n.apply(e,u)):a||r.trailing===!1||(a=setTimeout(c,f)),i}},j.debounce=function(n,t,r){var e,u,i,a,o;return function(){i=this,u=arguments,a=new Date;var c=function(){var l=new Date-a;t>l?e=setTimeout(c,t-l):(e=null,r||(o=n.apply(i,u)))},l=r&&!e;return e||(e=setTimeout(c,t)),l&&(o=n.apply(i,u)),o}},j.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},j.wrap=function(n,t){return function(){var r=[n];return a.apply(r,arguments),t.apply(this,r)}},j.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},j.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},j.keys=w||function(n){if(n!==Object(n))throw new TypeError("Invalid object");var t=[];for(var r in n)j.has(n,r)&&t.push(r);return t},j.values=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},j.pairs=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},j.invert=function(n){for(var t={},r=j.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},j.functions=j.methods=function(n){var t=[];for(var r in n)j.isFunction(n[r])&&t.push(r);return t.sort()},j.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},j.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},j.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)j.contains(r,u)||(t[u]=n[u]);return t},j.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]===void 0&&(n[r]=t[r])}),n},j.clone=function(n){return j.isObject(n)?j.isArray(n)?n.slice():j.extend({},n):n},j.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof j&&(n=n._wrapped),t instanceof j&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==String(t);case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;var a=n.constructor,o=t.constructor;if(a!==o&&!(j.isFunction(a)&&a instanceof a&&j.isFunction(o)&&o instanceof o))return!1;r.push(n),e.push(t);var c=0,f=!0;if("[object Array]"==u){if(c=n.length,f=c==t.length)for(;c--&&(f=S(n[c],t[c],r,e)););}else{for(var s in n)if(j.has(n,s)&&(c++,!(f=j.has(t,s)&&S(n[s],t[s],r,e))))break;if(f){for(s in t)if(j.has(t,s)&&!c--)break;f=!c}}return r.pop(),e.pop(),f};j.isEqual=function(n,t){return S(n,t,[],[])},j.isEmpty=function(n){if(null==n)return!0;if(j.isArray(n)||j.isString(n))return 0===n.length;for(var t in n)if(j.has(n,t))return!1;return!0},j.isElement=function(n){return!(!n||1!==n.nodeType)},j.isArray=x||function(n){return"[object Array]"==l.call(n)},j.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){j["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),j.isArguments(arguments)||(j.isArguments=function(n){return!(!n||!j.has(n,"callee"))}),"function"!=typeof/./&&(j.isFunction=function(n){return"function"==typeof n}),j.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},j.isNaN=function(n){return j.isNumber(n)&&n!=+n},j.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},j.isNull=function(n){return null===n},j.isUndefined=function(n){return n===void 0},j.has=function(n,t){return f.call(n,t)},j.noConflict=function(){return n._=t,this},j.identity=function(n){return n},j.times=function(n,t,r){for(var e=Array(Math.max(0,n)),u=0;n>u;u++)e[u]=t.call(r,u);return e},j.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))};var I={escape:{"&":"&","<":"<",">":">",'"':""","'":"'"}};I.unescape=j.invert(I.escape);var T={escape:new RegExp("["+j.keys(I.escape).join("")+"]","g"),unescape:new RegExp("("+j.keys(I.unescape).join("|")+")","g")};j.each(["escape","unescape"],function(n){j[n]=function(t){return null==t?"":(""+t).replace(T[n],function(t){return I[n][t]})}}),j.result=function(n,t){if(null==n)return void 0;var r=n[t];return j.isFunction(r)?r.call(n):r},j.mixin=function(n){A(j.functions(n),function(t){var r=j[t]=n[t];j.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),z.call(this,r.apply(j,n))}})};var N=0;j.uniqueId=function(n){var t=++N+"";return n?n+t:t},j.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\t|\u2028|\u2029/g;j.template=function(n,t,r){var e;r=j.defaults({},r,j.templateSettings);var u=new RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(D,function(n){return"\\"+B[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=new Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,j);var c=function(n){return e.call(this,n,j)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},j.chain=function(n){return j(n).chain()};var z=function(n){return this._chain?j(n).chain():n};j.mixin(j),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];j.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],z.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];j.prototype[n]=function(){return z.call(this,t.apply(this._wrapped,arguments))}}),j.extend(j.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this); 6 | //# sourceMappingURL=underscore-min.map -------------------------------------------------------------------------------- /list/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Fireform 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Fireform", 3 | "author": "Jeff Jenkins & Christine Ricks", 4 | "version": "0.0.1", 5 | "private": true, 6 | "scripts": { 7 | "start": "node app.js" 8 | }, 9 | "dependencies": { 10 | "express": "3.4.8", 11 | "jade": "*", 12 | "stylus": "*", 13 | "nib": "*" 14 | } 15 | } -------------------------------------------------------------------------------- /package.jsoncop.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Fireform", 3 | "author": "Jeff Jenkins & Christine Ricks", 4 | "version": "0.0.1", 5 | "private": true, 6 | "scripts": { 7 | "start": "sudo nodemon index.js" 8 | }, 9 | "dependencies": { 10 | "express": "3.4.8", 11 | "connect": "*", 12 | "jade": "*", 13 | "stylus": "*", 14 | "nib": "*", 15 | "watchr": "*" 16 | } 17 | } -------------------------------------------------------------------------------- /partials/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SFDevLabs/fireform/9b00ce76cbc0f7a5ceb56629bfb85b0eb10b7862/partials/.gitkeep -------------------------------------------------------------------------------- /partials/account.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |

Account

5 | 6 |

Change Email

7 | 8 | 17 | 18 |

19 | 20 | 21 |

22 |

23 |

{{err}}

24 |

{{msg}}

25 | 26 | 27 |
28 | 29 |

Change Password

30 | 31 |
    32 | 33 |
  • 34 | 35 | 36 |
  • 37 | 38 |
  • 39 | 40 | 41 |
  • 42 | 43 |
  • 44 | 45 | 46 |
  • 47 | 48 |
49 | 50 |

51 | 52 |

{{err}}

53 |

{{msg}}

54 | 55 |
56 | 57 |
58 |
59 |
60 | 61 | 62 | -------------------------------------------------------------------------------- /partials/add.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 |
6 |

New Fireform

7 |

{{err}}

8 |
    9 |
  • 10 | 11 |
  • 12 |
13 |

14 |
15 | 16 |
17 | 18 |
19 |
-------------------------------------------------------------------------------- /partials/app-header.html: -------------------------------------------------------------------------------- 1 |
2 | 29 |
30 | -------------------------------------------------------------------------------- /partials/docs.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |

Docs

5 | 12 | 129 |
130 |
131 |
132 | 133 | 134 | 135 | 136 | 137 | 141 | -------------------------------------------------------------------------------- /partials/footer.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | 4 | Released by SF Dev Labs under the MIT license 5 |

6 | 7 | 8 |
9 |

View on GitHub

10 | 11 |

Contact/Feedback

12 |
13 | 14 | 15 | 42 | 43 |
44 | -------------------------------------------------------------------------------- /partials/list-view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
Loading data
4 | 5 |
6 | 7 | 8 |
9 |
10 |

{{ListView.id}}

11 | 12 |
13 | 14 | 15 | 16 | 17 |
18 | 19 |

‹ Back to all forms

20 | 21 |
22 |

Form Options

23 |

Download CSV

24 |

Get Code

25 |
26 | 27 |
28 | 29 |
30 |
31 | 32 |

Email Options

33 | 34 |
    35 |
  • 36 | 37 | 38 |

    When checked, this will send an email confirmation to the form submitter with the data they submitted.

    39 |
  • 40 |
  • 41 | 42 | 43 |

    When checked, this sends a notification of each form submission to your email address.

    44 |
  • 45 |
46 |
47 | 48 |
49 |
51 |
52 |
54 |
55 |
57 |
58 |
60 |
61 |
62 |
64 |
65 |
67 |
68 |
70 |
71 |
73 |
74 |

75 | 76 |

77 |
78 | 79 | 80 |
81 |

No data submitted yet.

82 |
83 |
84 | 85 | 86 | 87 |
88 | 89 | 90 | 91 | 167 | 168 | 169 | 174 | 175 |
92 | 93 | 94 | 95 | 96 | Timestamp 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | {{key.fireformName}} 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | {{key.fireformName}} 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | {{key.fireformName}} 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | # 132 | {{key.fireformName}} 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | {{key.fireformName}} 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | {{key.fireformName}} 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | {{key.fireformName}} 160 | 161 | 162 | 163 | 164 | 165 | 166 |
170 | {{item[ffkey].value | date:'EEEE h:mma, MM/dd/yy'}} 171 | {{item[ffkey].checked}} 172 | {{item[ffkey].value}} 173 |
176 | 177 |
178 | 179 |
180 |

‹ Back to all forms

181 |
182 | 183 |
184 | 185 | 186 | 187 | 224 | 225 | 277 | 278 | -------------------------------------------------------------------------------- /partials/lists.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 23 | -------------------------------------------------------------------------------- /partials/login.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |

Log into Fireform

7 |

{{err}}

8 |
    9 |
  • 10 | 11 |
  • 12 |
  • 13 | 14 |
  • 15 |
  • 16 | 17 |
  • 18 |
19 |

20 |
21 | 22 |
23 |
24 |
25 | 26 | -------------------------------------------------------------------------------- /partials/public-header.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /partials/public-home.html: -------------------------------------------------------------------------------- 1 |

Login

2 | 3 |
4 | 5 |
6 |

Fireform

7 |
8 |

Hook your website <form> into a hosted private backend.

9 |
    10 |
  • 11 | Generate a Fireform code 12 |

    Generate code for your form

    13 |
  • 14 |
  • 15 | Paste the Fireform code into your HTML 16 |

    Paste the code into your HTML

    17 |
  • 18 |
  • 19 | Collect data with Fireform's backend 20 |

    Collect and see your data

    21 |
  • 22 |
23 |

Create a Form

24 |
25 |
26 | 27 |
28 |

See an example.

29 | 43 |

Or read our documentation.

44 |
45 | 46 |
47 |

Powered by

48 |
    49 |
  • 50 | 51 |
  • 52 |
  • 53 | 54 |
  • 55 |
56 |
57 | 58 |
59 |
60 | -------------------------------------------------------------------------------- /partials/signup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |

Sign Up

6 |

{{err}}

7 |
    8 |
  • 9 | 10 | 11 |
  • 12 |
  • 13 | 14 | 15 |
  • 16 |
  • 17 | 18 | 19 |
  • 20 |
21 |

22 | 23 |

24 |
25 |
26 |
27 |
28 | -------------------------------------------------------------------------------- /partials/snippet/boiler-confirmation.html: -------------------------------------------------------------------------------- 1 |

Boilerplate Form

2 | 25 | 26 |

27 |   <!doctype html>
28 |     <body>
29 |       <form id="MySelector">
30 |         <input type="text" name="givenname" placeholder="Name">
31 |         <input type="email" name="inputexample" placeholder="Email">
32 |         <input type="submit">
33 |       </form>
34 | 
35 |       <script  src="http://src.fireform.org/fireform.min.js"></script>
36 |       <script >
37 |           /* See more options at http://fireform.org/docs */
38 |           options={
39 |             emailConfirmationName:"inputexample",  //The email input we will send a confirmation to 
40 |             emailConfirmationFrom:"{{auth.user.email}}",   //Email appears as sent by this address.
41 |             emailConfirmationSubject:"Thanks for Signing Up",
42 |             emailConfirmationBodyHTML:"<p>You have been signed up!</p>",
43 |             emailConfirmationBodyText:"You have been signed up!"
44 |           }
45 |           new Fireform('#MySelector', '{{$location.absUrl()}}', options)
46 |       </script>
47 |   </body>
48 |  
-------------------------------------------------------------------------------- /partials/snippet/boiler-notification-confirmation.html: -------------------------------------------------------------------------------- 1 |

Boilerplate Form

2 | 26 |

27 |   <!doctype html>
28 |     <body>
29 |       <form id="MySelector">
30 |         <input type="text" name="givenname" placeholder="Name">
31 |         <input type="email" name="inputexample" placeholder="Email">
32 |         <input type="submit">
33 |       </form>
34 | 
35 |       <script type="application/javascript" src="http://src.fireform.org/fireform.min.js"></script>
36 |       <script type="application/javascript">
37 |           /* See more options at http://fireform.org/docs */
38 |           options={
39 |             emailNotification:"{{auth.user.email}}",
40 |             emailConfirmationName:"inputexample",  //The email input we will send a confirmation to 
41 |             emailConfirmationFrom:"{{auth.user.email}}",  //Email appears as sent by this address.
42 |             emailConfirmationSubject:"Thanks for Signing Up",
43 |             emailConfirmationBodyHTML:"<p>You have been signed up!</p>",
44 |             emailConfirmationBodyText:"You have been signed up!"
45 |           }
46 |           new Fireform('#MySelector', '{{$location.absUrl()}}', options)
47 |       </script>
48 |   </body>
49 |  
-------------------------------------------------------------------------------- /partials/snippet/boiler-notification.html: -------------------------------------------------------------------------------- 1 |

Boilerplate Form

2 | 21 | 22 |

23 |   <!doctype html>
24 |     <body>
25 |       <form id="MySelector">
26 |         <input type="text" name="givenname" placeholder="Name">
27 |         <input type="text" name="message" placeholder="Message">
28 |         <input type="submit">
29 |       </form>
30 | 
31 |       <script  src="http://src.fireform.org/fireform.min.js"></script>
32 |       <script>
33 |           /* See more options at http://fireform.org/docs */
34 |           options={
35 |             emailNotification:"{{auth.user.email}}",
36 |           }
37 |           new Fireform('#MySelector', '{{$location.absUrl()}}', options)
38 |       </script>
39 |   </body>
40 |  
-------------------------------------------------------------------------------- /partials/snippet/boiler.html: -------------------------------------------------------------------------------- 1 |

Boilerplate Form

2 | 3 | 24 | 25 | 26 |

27 |   <!doctype html>
28 |     <body>
29 |       <form id="MySelector">
30 |         <input type="text" name="givenname" placeholder="Name">
31 |         <input type="text" name="message" placeholder="Message">
32 |         <input type="submit">
33 |       </form>
34 | 
35 |       <script  src="http://src.fireform.org/fireform.min.js"></script>
36 |       <script >
37 |         /* See more options at http://fireform.org/docs */
38 |         var options = {
39 |           simpleValidation:false,
40 |           formValidationClass:"custom-submit-success",
41 |           callback:function(err, val){ console.log(err,"callback") }
42 |           }
43 |         new Fireform('#MySelector', '{{$location.absUrl()}}', options)
44 |       </script>
45 |   </body>
46 |  
-------------------------------------------------------------------------------- /partials/snippet/default-confirmation.html: -------------------------------------------------------------------------------- 1 |

JavaScript Snippet

2 | 3 |

Copy this snippet and paste it into your HTML underneath your <form>.

4 |
 
 5 |       <script  src="http://src.fireform.org/fireform.min.js"></script>
 6 |       <script>
 7 |           options={
 8 |                         emailConfirmationName:"InputName",  //The email input we will send a confirmation to 
 9 |                         emailConfirmationFrom:"{{auth.user.email}}", //Email appears as sent by this address.
10 |                         emailConfirmationSubject:"Thanks for Signing Up",
11 |                         emailConfirmationBodyHTML:"<p>You have been signed up!</p>",
12 |                         emailConfirmationBodyText:"You have been signed up!"
13 |           }
14 |           new Fireform('#MySelector', '{{$location.absUrl()}}', options);
15 |       </script>
16 |      
17 | -------------------------------------------------------------------------------- /partials/snippet/default-notification-confirmation.html: -------------------------------------------------------------------------------- 1 |

JavaScript Snippet

2 | 3 |

Copy this snippet and paste it into your HTML underneath your <form>.

4 | 20 |
 
21 |       <script  src="http://src.fireform.org/fireform.min.js"></script>
22 |       <script>
23 |           options={
24 |             emailNotification:"{{auth.user.email}}",
25 |             emailConfirmationName:"InputName",  //The form input we get the email from.
26 |             emailConfirmationFrom:"{{auth.user.email}}",  //Email appears as sent by this address.
27 |             emailConfirmationSubject:"Thanks for signing up",
28 |             emailConfirmationBodyHTML:"<p>You have been signed up!</p>",
29 |             emailConfirmationBodyText:"You have been signed up!"
30 |           }
31 |           new Fireform('#MySelector', '{{$location.absUrl()}}', options);
32 |       </script>
33 |      
-------------------------------------------------------------------------------- /partials/snippet/default-notification.html: -------------------------------------------------------------------------------- 1 |

JavaScript Snippet

2 | 3 |

Copy this snippet and paste it into your HTML underneath your <form>.

4 | 13 | 14 | 15 | 16 |
 
17 |       <script  src="http://src.fireform.org/fireform.min.js"></script>
18 |       <script >
19 |         options={
20 |           emailNotification:"{{auth.user.email}}",
21 |         }
22 |         new Fireform('#MySelector', '{{$location.absUrl()}}', options);
23 |       </script>
24 |      
-------------------------------------------------------------------------------- /partials/snippet/default.html: -------------------------------------------------------------------------------- 1 |

JavaScript Snippet

2 | 3 | 12 | 13 | 14 |
 
15 |       <script  src="http://src.fireform.org/fireform.min.js"></script>
16 |       <script>
17 |         new Fireform('#MySelector', '{{$location.absUrl()}}');
18 |       </script>
19 |      
-------------------------------------------------------------------------------- /partials/snippetBoil.html: -------------------------------------------------------------------------------- 1 |

Boilerplate Form

2 |

 3 |   <!doctype html>
 4 |     <body>
 5 |       <form id="MySelector">
 6 |         <input type="text" name="givenname" placeholder="Name">
 7 |         <input type="text" name="message" placeholder="Message">
 8 |         <input type="submit">
 9 |       </form>
10 | 
11 |       <script type="application/javascript" src="http://fireform.org/fireform.js"></script>
12 |       <script type="application/javascript">
13 |           /* See more options at http://fireform.org/docs */
14 |            {{email}}
15 |            var options = {
16 |               simpleValidation:false,
17 |               successClass: "custom-submit-success",
18 |               callback:function(err, val){console.log(err,'callback'){} 
19 |             }
20 |           new Fireform('#MySelector', '{{location}}', options)
21 |       </script>
22 |   </body>
23 |  
--------------------------------------------------------------------------------