├── .bowerrc ├── .editorconfig ├── .gitignore ├── .jshintrc ├── .travis.yml ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── api-stub ├── README.md └── routes.js ├── app ├── adapters │ ├── application.js │ ├── content-type.js │ ├── control-type-group.js │ ├── control-type.js │ └── redirect.js ├── app.js ├── components │ ├── .gitkeep │ ├── audio-player.js │ ├── audio-upload.js │ ├── auto-complete.js │ ├── colored-element.js │ ├── date-time.js │ ├── disqus-embed.js │ ├── download-backup.js │ ├── embedly-control.js │ ├── file-upload.js │ ├── gallery-upload.js │ ├── geolocation-control.js │ ├── grid-control.js │ ├── image-upload.js │ ├── markdown-editor.js │ ├── pretty-color.js │ ├── redactor-rte.js │ ├── select-file.js │ ├── tabular-data.js │ ├── template-less.js │ ├── webhook-blog.js │ └── webhook-modal.js ├── controllers │ ├── .gitkeep │ ├── confirm-email.js │ ├── create-user.js │ ├── form.js │ ├── login.js │ ├── password-reset.js │ ├── reindex.js │ ├── resend-email.js │ ├── theme.js │ ├── wh.js │ ├── wh │ │ ├── content │ │ │ ├── all-types.js │ │ │ ├── start.js │ │ │ └── type │ │ │ │ ├── edit.js │ │ │ │ ├── index.js │ │ │ │ ├── json.js │ │ │ │ └── new.js │ │ ├── index.js │ │ ├── search-global-results.js │ │ └── settings │ │ │ ├── data.js │ │ │ ├── domain.js │ │ │ ├── general.js │ │ │ ├── password-change.js │ │ │ ├── team.js │ │ │ └── urls.js │ └── wordpress.js ├── helpers │ ├── .gitkeep │ ├── format-number.js │ ├── format-time.js │ ├── resize-image.js │ ├── reverse-word.js │ ├── to-markdown.js │ ├── translate-control.js │ └── truncate-string.js ├── index.html ├── initializers │ ├── analytics.js │ ├── environment.js │ ├── session.js │ ├── team.js │ └── tracker.js ├── models │ ├── .gitkeep │ ├── content-type.js │ ├── control-type-group.js │ ├── control-type.js │ ├── control.js │ ├── data.js │ ├── group.js │ ├── item.js │ ├── redirect.js │ ├── settings.js │ └── user.js ├── router.js ├── routes │ ├── .gitkeep │ ├── application.js │ ├── component_test.js │ ├── confirm-email.js │ ├── create-user.js │ ├── form.js │ ├── helper_test.js │ ├── import.js │ ├── index.js │ ├── login.js │ ├── password-change.js │ ├── reindex.js │ ├── resend-email.js │ ├── start.js │ ├── theme.js │ ├── wh.js │ ├── wh │ │ ├── content │ │ │ ├── all-types.js │ │ │ ├── start.js │ │ │ ├── type.js │ │ │ └── type │ │ │ │ ├── edit.js │ │ │ │ ├── index.js │ │ │ │ ├── json.js │ │ │ │ └── new.js │ │ ├── index.js │ │ ├── search-global-results.js │ │ └── settings │ │ │ ├── data.js │ │ │ ├── domain.js │ │ │ ├── general.js │ │ │ ├── team.js │ │ │ └── urls.js │ └── wordpress.js ├── serializers │ ├── application.js │ ├── control-type-group.js │ ├── control-type.js │ ├── control.js │ ├── data.js │ ├── item.js │ └── settings.js ├── styles │ ├── .gitkeep │ ├── _app_codemirror.sass │ ├── _app_content_list.sass │ ├── _app_font_icons.sass │ ├── _app_fonts.sass │ ├── _app_form_builder.sass │ ├── _app_form_controls.sass │ ├── _app_form_gallery.sass │ ├── _app_form_markdown.sass │ ├── _app_grid.sass │ ├── _app_groups.sass │ ├── _app_layout.sass │ ├── _app_mixin.sass │ ├── _app_variables.sass │ └── app.sass ├── templates │ ├── .gitkeep │ ├── application.hbs │ ├── component-test.hbs │ ├── components │ │ ├── .gitkeep │ │ ├── audio-player.hbs │ │ ├── audio-upload.hbs │ │ ├── auto-complete.hbs │ │ ├── colored-element.hbs │ │ ├── date-time.hbs │ │ ├── download-backup.hbs │ │ ├── embedly-control.hbs │ │ ├── file-upload.hbs │ │ ├── formbuilder-widget.hbs │ │ ├── gallery-upload.hbs │ │ ├── geolocation-control.hbs │ │ ├── grid-control.hbs │ │ ├── image-upload.hbs │ │ ├── markdown-editor.hbs │ │ ├── pretty-color.hbs │ │ ├── redactor-rte.hbs │ │ ├── tabular-data.hbs │ │ ├── webhook-blog.hbs │ │ └── webhook-modal.hbs │ ├── confirm-email.hbs │ ├── create-user.hbs │ ├── error.hbs │ ├── expired.hbs │ ├── form.hbs │ ├── form │ │ ├── _changedcontrols.hbs │ │ ├── _initialscaffolding.hbs │ │ └── _nav.hbs │ ├── grid-row.hbs │ ├── helper-test.hbs │ ├── import.hbs │ ├── index.hbs │ ├── loading.hbs │ ├── login.hbs │ ├── password-reset.hbs │ ├── reindex.hbs │ ├── resend-email.hbs │ ├── start.hbs │ ├── theme.hbs │ ├── wh.hbs │ ├── wh │ │ ├── _nav.hbs │ │ ├── content │ │ │ ├── all-types.hbs │ │ │ ├── start.hbs │ │ │ └── type │ │ │ │ ├── edit.hbs │ │ │ │ ├── index.hbs │ │ │ │ ├── json.hbs │ │ │ │ └── loading.hbs │ │ ├── index.hbs │ │ ├── search-global-results.hbs │ │ └── settings │ │ │ ├── billing.hbs │ │ │ ├── data.hbs │ │ │ ├── domain.hbs │ │ │ ├── general.hbs │ │ │ ├── group-permissions.hbs │ │ │ ├── password-change.hbs │ │ │ ├── team.hbs │ │ │ ├── urls-rule.hbs │ │ │ └── urls.hbs │ ├── widgets │ │ ├── _address.hbs │ │ ├── _audio.hbs │ │ ├── _boolean.hbs │ │ ├── _checkbox.hbs │ │ ├── _color.hbs │ │ ├── _datetime.hbs │ │ ├── _email.hbs │ │ ├── _embedly.hbs │ │ ├── _file.hbs │ │ ├── _gallery.hbs │ │ ├── _geolocation.hbs │ │ ├── _grid.hbs │ │ ├── _image.hbs │ │ ├── _instruction.hbs │ │ ├── _layout.hbs │ │ ├── _markdown.hbs │ │ ├── _name.hbs │ │ ├── _number.hbs │ │ ├── _phone.hbs │ │ ├── _radio.hbs │ │ ├── _rating.hbs │ │ ├── _relation.hbs │ │ ├── _select.hbs │ │ ├── _tabular.hbs │ │ ├── _tag.hbs │ │ ├── _textarea.hbs │ │ ├── _textfield.hbs │ │ ├── _url.hbs │ │ ├── _wysiwyg.hbs │ │ ├── common │ │ │ ├── _help.hbs │ │ │ ├── _imagemodal.hbs │ │ │ ├── _label.hbs │ │ │ └── _minmaxchars.hbs │ │ ├── info │ │ │ ├── _address.hbs │ │ │ ├── _audio.hbs │ │ │ ├── _boolean.hbs │ │ │ ├── _checkbox.hbs │ │ │ ├── _color.hbs │ │ │ ├── _datetime.hbs │ │ │ ├── _email.hbs │ │ │ ├── _embedly.hbs │ │ │ ├── _file.hbs │ │ │ ├── _gallery.hbs │ │ │ ├── _geolocation.hbs │ │ │ ├── _grid.hbs │ │ │ ├── _image.hbs │ │ │ ├── _instruction.hbs │ │ │ ├── _layout.hbs │ │ │ ├── _markdown.hbs │ │ │ ├── _name.hbs │ │ │ ├── _number.hbs │ │ │ ├── _phone.hbs │ │ │ ├── _radio.hbs │ │ │ ├── _rating.hbs │ │ │ ├── _relation.hbs │ │ │ ├── _select.hbs │ │ │ ├── _tabular.hbs │ │ │ ├── _tag.hbs │ │ │ ├── _textarea.hbs │ │ │ ├── _textfield.hbs │ │ │ ├── _url.hbs │ │ │ └── _wysiwyg.hbs │ │ ├── relation │ │ │ ├── few.hbs │ │ │ └── many.hbs │ │ └── value │ │ │ ├── _address.hbs │ │ │ ├── _audio.hbs │ │ │ ├── _boolean.hbs │ │ │ ├── _checkbox.hbs │ │ │ ├── _color.hbs │ │ │ ├── _datetime.hbs │ │ │ ├── _email.hbs │ │ │ ├── _embedly.hbs │ │ │ ├── _file.hbs │ │ │ ├── _gallery.hbs │ │ │ ├── _geolocation.hbs │ │ │ ├── _grid.hbs │ │ │ ├── _image.hbs │ │ │ ├── _instruction.hbs │ │ │ ├── _layout.hbs │ │ │ ├── _markdown.hbs │ │ │ ├── _name.hbs │ │ │ ├── _number.hbs │ │ │ ├── _phone.hbs │ │ │ ├── _radio.hbs │ │ │ ├── _rating.hbs │ │ │ ├── _relation.hbs │ │ │ ├── _select.hbs │ │ │ ├── _tabular.hbs │ │ │ ├── _tag.hbs │ │ │ ├── _textarea.hbs │ │ │ ├── _textfield.hbs │ │ │ ├── _url.hbs │ │ │ ├── _wysiwyg.hbs │ │ │ └── relation-item.hbs │ └── wordpress.hbs ├── transforms │ └── json.js ├── utils │ ├── .gitkeep │ ├── ajax.js │ ├── controls.js │ ├── downcode.js │ ├── meta-options.js │ ├── search-index.js │ ├── slugger.js │ ├── uuid.js │ └── validators.js └── views │ ├── .gitkeep │ ├── animate-collection.js │ ├── auto-complete.js │ ├── autocomplete-results.js │ ├── checkbox-control.js │ ├── draggable.js │ ├── error.js │ ├── fluidbox.js │ ├── form.js │ ├── formbuilder-grid.js │ ├── formbuilder-widget-grid.js │ ├── formbuilder-widget.js │ ├── formbuilder.js │ ├── gallery.js │ ├── grid-rows.js │ ├── grid-widget.js │ ├── group-panel.js │ ├── group-permissions.js │ ├── item-cell.js │ ├── item-form.js │ ├── item-row.js │ ├── radio-button.js │ ├── relation-value.js │ ├── relation-values.js │ ├── select-control.js │ ├── sortable-redirect-rules.js │ ├── sortable.js │ ├── star-rating.js │ ├── switch.js │ ├── wh.js │ └── widget.js ├── bower.json ├── config ├── environment.js └── environments │ ├── development.js │ ├── production.js │ └── test.js ├── libs └── cloudStorage.js ├── package.json ├── public ├── assets │ ├── .gitkeep │ ├── fonts │ │ ├── bitter-bold.woff │ │ ├── bitter.woff │ │ ├── hook.eot │ │ ├── hook.svg │ │ ├── hook.ttf │ │ ├── hook.woff │ │ └── selection.json │ ├── html │ │ └── analytics.html │ ├── images │ │ ├── Webhook_Graphics.png │ │ ├── dragon.png │ │ ├── favicon.png │ │ ├── robot.png │ │ └── wizard.png │ └── javascript │ │ ├── async.js │ │ ├── codemirror-compressed.js │ │ ├── codemirror.css │ │ ├── highlight.pack.js │ │ ├── layout.js │ │ ├── marked.js │ │ ├── shortcode.js │ │ ├── tracker.js │ │ ├── uslug.js │ │ ├── wxml-converter.js │ │ └── wxml-importer.js ├── crossdomain.xml ├── humans.txt ├── robots.txt └── testem.js ├── tasks ├── .jshintrc ├── custom-options │ └── .gitkeep ├── deploy.js ├── express-server.js ├── helpers.js ├── locking.js ├── options │ ├── autoprefixer.js │ ├── clean.js │ ├── coffee.js │ ├── compass.js │ ├── concat_sourcemap.js │ ├── concurrent.js │ ├── copy.js │ ├── emberTemplates.js │ ├── emberscript.js │ ├── emblem.js │ ├── exec.js │ ├── express-server.js │ ├── fancySprites.js │ ├── htmlmin.js │ ├── imagemin.js │ ├── jshint.js │ ├── less.js │ ├── preprocess.js │ ├── rev.js │ ├── sass.js │ ├── stylus.js │ ├── testem.js │ ├── transpile.js │ ├── uglify.js │ ├── usemin.js │ ├── useminPrepare.js │ ├── validate-imports.js │ └── watch.js └── push-production.js ├── testem.json └── tests ├── .jshintrc ├── acceptance └── index-test.js ├── ember-shim.js ├── helpers ├── resolver.js └── start-app.js ├── qunit-shim.js ├── test-helper.js ├── test-loader.js └── unit ├── .gitkeep └── routes └── application-test.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "vendor" 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | /vendor 10 | 11 | # misc 12 | /.sass-cache 13 | /connect.lock 14 | /libpeerconnection.log 15 | .DS_Store 16 | Thumbs.db 17 | /coverage/* 18 | npm-debug.log 19 | 20 | .cloudstorage.key 21 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "location", 6 | "setTimeout", 7 | "Ember", 8 | "Em", 9 | "DS", 10 | "$", 11 | "EmberFire", 12 | "Firebase", 13 | "FirebaseSimpleLogin", 14 | "moment", 15 | "marked", 16 | "Webhook", 17 | "XMLHttpRequest", 18 | "ga" 19 | ], 20 | "node" : false, 21 | "browser" : false, 22 | "boss" : true, 23 | "curly": false, 24 | "debug": false, 25 | "devel": false, 26 | "eqeqeq": true, 27 | "evil": true, 28 | "forin": false, 29 | "immed": false, 30 | "laxbreak": false, 31 | "newcap": true, 32 | "noarg": true, 33 | "noempty": false, 34 | "nonew": false, 35 | "nomen": false, 36 | "onevar": false, 37 | "plusplus": false, 38 | "regexp": false, 39 | "undef": true, 40 | "sub": true, 41 | "strict": false, 42 | "white": false, 43 | "eqnull": true, 44 | "esnext": true 45 | } 46 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10 4 | before_script: 5 | - npm install -g grunt-cli 6 | - npm install -g bower 7 | - bower install 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Getting Involved 2 | 3 | Help us help you. 4 | 5 | ## Issues 6 | 7 | Let us know about CMS bugs or feature requests in this repository's [issue tracker](https://github.com/webhook/webhook-cms/issues). 8 | 9 | ### Reporting Bugs 10 | 11 | 1. Search for similar bugs in the tracker to avoid duplicates. 12 | - Provide steps to reproduce the problem. The more detailed the better. 13 | 14 | ### Request Features 15 | 16 | 1. Search for similar feature requests in the tracker to avoid duplicates. 17 | - Provide an explanation of why you think this feature is needed and how your use case. 18 | 19 | ## Pull Requests 20 | 21 | 1. Follow the syntax and code style already present in the project (2 spaces, no extraneous whitespace, etc.). 22 | - Fork the repo. 23 | - Commit changes. 24 | - Push changes and submit a pull request. 25 | - We'll take a look provide feedback and/or merge it in a timely manner. 26 | 27 | # Questions 28 | 29 | Find us (enemykite, LtSquigs, and gpbmike) on IRC Freenode channel **#webhook**. We frequent our [Webhook forums](http://forums.webhook.com/). [@webhookcms](https://twitter.com/webhookcms) on Twitter and email (support@webhook.com) are also viable options. 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Webhook Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /api-stub/routes.js: -------------------------------------------------------------------------------- 1 | module.exports = function(server) { 2 | 3 | // Create an API namespace, so that the root does not 4 | // have to be repeated for each end point. 5 | server.namespace('/api', function() { 6 | 7 | // Return fixture data for '/api/posts/:id' 8 | server.get('/posts/:id', function(req, res) { 9 | var post = { 10 | "post": { 11 | "id": 1, 12 | "title": "Rails is omakase", 13 | "comments": ["1", "2"], 14 | "user" : "dhh" 15 | }, 16 | 17 | "comments": [{ 18 | "id": "1", 19 | "body": "Rails is unagi" 20 | }, { 21 | "id": "2", 22 | "body": "Omakase O_o" 23 | }] 24 | }; 25 | 26 | res.send(post); 27 | }); 28 | 29 | }); 30 | 31 | }; -------------------------------------------------------------------------------- /app/adapters/control-type-group.js: -------------------------------------------------------------------------------- 1 | export default DS.FixtureAdapter.extend(); 2 | -------------------------------------------------------------------------------- /app/adapters/control-type.js: -------------------------------------------------------------------------------- 1 | export default DS.FixtureAdapter.extend(); 2 | -------------------------------------------------------------------------------- /app/adapters/redirect.js: -------------------------------------------------------------------------------- 1 | import ApplicationAdapter from 'appkit/adapters/application'; 2 | 3 | export default ApplicationAdapter.extend({ 4 | firebase: window.ENV.firebase.child('settings') 5 | }); 6 | -------------------------------------------------------------------------------- /app/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webhook/webhook-cms/8b58110801bc0d3a749bb230bb9983ee5b77ffcd/app/components/.gitkeep -------------------------------------------------------------------------------- /app/components/audio-player.js: -------------------------------------------------------------------------------- 1 | export default Ember.Component.extend({ 2 | tagName: 'audio', 3 | didInsertElement: function () { 4 | 5 | var audioComponent = this; 6 | 7 | this.$().prop('controls', true); 8 | this.$().on('loadedmetadata', function () { 9 | audioComponent.sendAction('load', this); 10 | }); 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /app/components/audio-upload.js: -------------------------------------------------------------------------------- 1 | import FileUploadComponent from 'appkit/components/file-upload'; 2 | 3 | export default FileUploadComponent.extend({ 4 | selectAccept : 'audio/*', 5 | defaultClasses: 'icon-music', 6 | successMsg : ' Audio upload complete.', 7 | 8 | actions: { 9 | audioLoaded: function (audio) { 10 | this.set('control.value.duration', audio.duration); 11 | } 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /app/components/colored-element.js: -------------------------------------------------------------------------------- 1 | export default Ember.Component.extend({ 2 | tagName: 'span', 3 | classNames: 'wh-color-value', 4 | attributeBindings: ['customColor:style'], 5 | customColor: function () { 6 | return 'background-color:' + this.get('color'); 7 | }.property('color') 8 | }); 9 | -------------------------------------------------------------------------------- /app/components/date-time.js: -------------------------------------------------------------------------------- 1 | export default Ember.Component.extend({ 2 | 3 | tagName: 'span', 4 | 5 | date: function () { 6 | 7 | if (this.get('value') && moment(this.get('value')).isValid()) { 8 | return moment(this.get('value')).format('YYYY-MM-DD'); 9 | } else { 10 | return null; 11 | } 12 | 13 | }.property('value'), 14 | 15 | time: function () { 16 | 17 | if (!this.get('value') || !moment(this.get('value')).isValid()) { 18 | return null; 19 | } 20 | 21 | if (moment(this.get('value')).minutes() + moment(this.get('value')).hours() === 0) { 22 | return null; 23 | } 24 | 25 | return moment(this.get('value')).format('HH:mm'); 26 | }.property('value'), 27 | 28 | datetimeChanged: function () { 29 | 30 | var date = this.get('date'); 31 | var time = this.get('time'); 32 | 33 | if (!date && !time) { 34 | this.set('value', null); 35 | return; 36 | } 37 | 38 | if (this.get('meta.hideDate')) { 39 | if (!time) { 40 | this.set('value', null); 41 | return; 42 | } 43 | if (!date) { 44 | date = moment().format('YYYY-MM-DD'); 45 | } 46 | } 47 | 48 | var value = time ? (date + ' ' + time) : date; 49 | 50 | if (moment(value).isValid()) { 51 | this.set('value', moment(value).format()); 52 | } else { 53 | this.set('value', null); 54 | } 55 | 56 | }.observes('date', 'time') 57 | }); 58 | -------------------------------------------------------------------------------- /app/components/disqus-embed.js: -------------------------------------------------------------------------------- 1 | export default Ember.Component.extend({ 2 | elementId: 'disqus_thread', 3 | 4 | didInsertElement: function () { 5 | 6 | window.disqus_shortname = this.get('shortname'); 7 | window.disqus_identifier = this.get('identifier'); 8 | 9 | var src = 'http://' + window.disqus_shortname + '.disqus.com/embed.js'; 10 | 11 | if (!Ember.$('script[src="' + src + '"]').length) { 12 | var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true; 13 | dsq.src = src; 14 | (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq); 15 | } else { 16 | window.DISQUS.reset({ 17 | reload: true 18 | }); 19 | } 20 | } 21 | }); 22 | -------------------------------------------------------------------------------- /app/components/download-backup.js: -------------------------------------------------------------------------------- 1 | export default Ember.Component.extend({ 2 | tagName: 'button', 3 | classNames: ['btn'], 4 | 5 | text: 'Download Backup', 6 | 7 | click: function () { 8 | var fileName = this.get('buildEnvironment.siteDisplayName') + '-' + moment().format() + '.json'; 9 | window.ENV.firebase.once('value', function (snapshot) { 10 | var data = snapshot.val(); 11 | 12 | var dataWhiteList = { 13 | contentType: data.contentType, 14 | data: data.data, 15 | settings: data.settings 16 | }; 17 | 18 | var blob = new window.Blob([JSON.stringify(dataWhiteList, null, 2)], { type: "text/plain;charset=utf-8" }); 19 | window.saveAs(blob, fileName); 20 | }); 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /app/components/embedly-control.js: -------------------------------------------------------------------------------- 1 | export default Ember.Component.extend({ 2 | 3 | showPreview: true, 4 | showCode : false, 5 | 6 | hasValue: function () { 7 | var value = this.get('control.value'); 8 | return !Ember.isEmpty(value) && typeof value === 'object' && Object.keys(value).length; 9 | }.property('control.value'), 10 | 11 | dataString: function () { 12 | return JSON.stringify(this.get('control.value'), null, 2); 13 | }.property('control.value'), 14 | 15 | isVisual: function () { 16 | return ['video', 'rich', 'photo'].indexOf(this.get('control.value.type')) >= 0; 17 | }.property('control.value.type'), 18 | 19 | previewValue: function () { 20 | 21 | var preview = ''; 22 | 23 | switch (this.get('control.value.type')) { 24 | case 'video': 25 | case 'rich': 26 | preview = this.get('control.value.html'); 27 | break; 28 | case 'photo': 29 | preview = ''; 30 | break; 31 | default: 32 | if (this.get('control.value.title')) { 33 | preview = this.get('control.value.title') + ' (' + this.get('control.value.original_url') + ')'; 34 | } 35 | break; 36 | } 37 | 38 | return preview; 39 | 40 | }.property('control.value'), 41 | 42 | actions: { 43 | getEmbed: function () { 44 | if (!this.get('url') || this.get('isFetching')) { 45 | return; 46 | } 47 | 48 | this.set('isFetching', true); 49 | this.set('control.value', {}); 50 | 51 | var embedlyControl = this; 52 | 53 | $.embedly.oembed(this.get('url'), { 54 | key: window.ENV.embedlyKey, 55 | query: this.get('control.meta.options') 56 | }).progress(function (data) { 57 | embedlyControl.set('isFetching', false); 58 | embedlyControl.set('control.value', data); 59 | }); 60 | }, 61 | 62 | togglePreview: function () { 63 | this.toggleProperty('showCode'); 64 | this.toggleProperty('showPreview'); 65 | }, 66 | 67 | clearValue: function () { 68 | this.set('control.value', {}); 69 | } 70 | } 71 | }); 72 | -------------------------------------------------------------------------------- /app/components/image-upload.js: -------------------------------------------------------------------------------- 1 | /* global Image */ 2 | 3 | import FileUploadComponent from 'appkit/components/file-upload'; 4 | 5 | export default FileUploadComponent.extend({ 6 | selectAccept : 'image/*', 7 | defaultClasses: 'icon-picture', 8 | successMsg : ' Image upload complete.', 9 | tempUrl : null, 10 | 11 | postParams: { 12 | resize_url: true 13 | }, 14 | 15 | hasPreview: function () { 16 | return !!(this.get('control.value.resize_url') || this.get('tempUrl')); 17 | }.property('control.value.resize_url', 'tempUrl'), 18 | 19 | // Show preview of file 20 | beforeUpload: function (file) { 21 | this._super.apply(this, arguments); 22 | this.set('tempUrl', typeof file === 'string' ? file : (window.URL || window.webkitURL).createObjectURL(file)); 23 | }, 24 | 25 | // Add image meta data 26 | doneUpload: function (file, response) { 27 | this._super.apply(this, arguments); 28 | 29 | this.set('control.value.resize_url', response.resize_url); 30 | this.set('tempUrl', null); 31 | 32 | var imageComponent = this; 33 | 34 | // Load image to get dimensions 35 | var image = new Image(); 36 | 37 | image.onload = function() { 38 | imageComponent.set('control.value.width', this.width); 39 | imageComponent.set('control.value.height', this.height); 40 | }; 41 | 42 | image.src = response.url; 43 | }, 44 | 45 | failUpload: function () { 46 | this.$('.wy-form-upload-image').remove(); 47 | } 48 | }); 49 | -------------------------------------------------------------------------------- /app/components/pretty-color.js: -------------------------------------------------------------------------------- 1 | export default Ember.Component.extend({ 2 | classNames: ['pretty-color'], 3 | attributeBindings: ['style'], 4 | style: function(){ 5 | return 'color: ' + this.get('name') + ';'; 6 | }.property('name') 7 | }); 8 | -------------------------------------------------------------------------------- /app/components/select-file.js: -------------------------------------------------------------------------------- 1 | // Simple component for selecting files 2 | // usage: {{#select-file action='handleFile'}}Button Text{{/select-file}} 3 | export default Ember.Component.extend({ 4 | tagName: 'button', 5 | classNames: 'btn btn-neutral', 6 | 7 | accept: '*', 8 | multiple: false, 9 | 10 | didInsertElement: function () { 11 | 12 | var component = this; 13 | 14 | this.$().selectFile({ 15 | accept : this.get('accept'), 16 | multiple: this.get('multiple') 17 | }).on('selectedFile', function (event, file) { 18 | component.sendAction('action', file); 19 | }); 20 | } 21 | 22 | }); 23 | -------------------------------------------------------------------------------- /app/components/tabular-data.js: -------------------------------------------------------------------------------- 1 | export default Ember.Component.extend({ 2 | actions: { 3 | addTabularRow: function (control) { 4 | var emptyRow = Ember.A([]); 5 | control.get('meta.options').forEach(function () { 6 | emptyRow.pushObject(Ember.Object.create()); 7 | }); 8 | control.get('value').pushObject(emptyRow); 9 | }, 10 | 11 | removeTabularRow: function (row, control) { 12 | control.get('value').removeObject(row); 13 | } 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /app/components/template-less.js: -------------------------------------------------------------------------------- 1 | export default Ember.Component.extend({ 2 | classNames: ['look-ma-no-template'], 3 | tagName: ['span'] 4 | }); 5 | -------------------------------------------------------------------------------- /app/components/webhook-blog.js: -------------------------------------------------------------------------------- 1 | export default Ember.Component.extend({ 2 | 3 | posts: Ember.A([]), 4 | 5 | willInsertElement: function () { 6 | 7 | var component = this; 8 | 9 | $.ajax({ 10 | url: 'http://www.webhook.com/blog-json/', 11 | dataType: 'jsonp', 12 | jsonpCallback: 'callback' 13 | }).success(function (data) { 14 | 15 | data.forEach(function (post) { 16 | component.get('posts').addObject(post); 17 | }); 18 | }); 19 | 20 | } 21 | }); 22 | -------------------------------------------------------------------------------- /app/components/webhook-modal.js: -------------------------------------------------------------------------------- 1 | export default Ember.Component.extend({ 2 | classNames: ['wy-modal'], 3 | 4 | show: false, 5 | canClose: true, 6 | 7 | showChange: function () { 8 | this.$().toggle(); 9 | this.$mask.css('z-index', parseInt(this.$().css('z-index'), 10) - 1); 10 | this.$mask.toggleClass('on'); 11 | }.observes('show'), 12 | 13 | willInsertElement: function () { 14 | this.$mask = Ember.$('
').css('left', 0); 15 | }, 16 | 17 | didInsertElement: function () { 18 | this.$mask.insertBefore(this.$()); 19 | this.$().hide().css({ 20 | top: '20%', 21 | marginTop: 0 22 | }); 23 | }, 24 | 25 | willDestroyElement: function () { 26 | this.$mask.remove(); 27 | }, 28 | 29 | actions: { 30 | confirm: function (data) { 31 | this.sendAction('confirm', data); 32 | }, 33 | cancel: function () { 34 | this.sendAction('cancel'); 35 | }, 36 | close: function () { 37 | this.set('show', false); 38 | } 39 | } 40 | }); 41 | -------------------------------------------------------------------------------- /app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webhook/webhook-cms/8b58110801bc0d3a749bb230bb9983ee5b77ffcd/app/controllers/.gitkeep -------------------------------------------------------------------------------- /app/controllers/confirm-email.js: -------------------------------------------------------------------------------- 1 | export default Ember.ObjectController.extend({ 2 | isSending: true, 3 | success : false, 4 | error : null, 5 | }); 6 | -------------------------------------------------------------------------------- /app/controllers/login.js: -------------------------------------------------------------------------------- 1 | export default Ember.Controller.extend({ 2 | email : null, 3 | password : null, 4 | isLoading: false, 5 | 6 | userChanged: function () { 7 | Ember.Logger.log('LoginController::userChanged'); 8 | this.set('isLoading', false); 9 | if (this.get('session.transition')) { 10 | this.get('session.transition').retry(); 11 | } else { 12 | this.transitionToRoute('index'); 13 | } 14 | }.observes('session.user'), 15 | 16 | errorChanged: function () { 17 | this.set('isLoading', false); 18 | }.observes('session.error'), 19 | 20 | supportedLanguages: function () { 21 | var languages = Ember.A([]); 22 | Ember.$.each(Ember.ENV.I18N_CODE_MAP, function (code, language) { 23 | languages.push({ code: code, language: language }); 24 | }); 25 | return languages; 26 | }.property(), 27 | 28 | actions: { 29 | loginUser: function () { 30 | if (this.get('isLoading')) { 31 | return; 32 | } 33 | 34 | if (this.get('email') === '') { 35 | this.get('session').set('error', { 36 | code: 'Invalid Login', 37 | message: 'Please enter an email address.' 38 | }); 39 | return; 40 | } 41 | 42 | if (this.get('password') === '') { 43 | this.get('session').set('error', { 44 | code: 'Invalid Login', 45 | message: 'Please enter a password.' 46 | }); 47 | return; 48 | } 49 | 50 | this.get('session').set('error', null); 51 | this.set('isLoading', true); 52 | 53 | this.get('session.auth').login('password', { 54 | email : this.get('email'), 55 | password : this.get('password'), 56 | rememberMe: true 57 | }); 58 | } 59 | } 60 | }); 61 | -------------------------------------------------------------------------------- /app/controllers/password-reset.js: -------------------------------------------------------------------------------- 1 | export default Ember.ObjectController.extend({ 2 | email: null, 3 | isSending: false, 4 | success: false, 5 | error: null, 6 | 7 | actions: { 8 | resetPassword: function () { 9 | 10 | if (Ember.isEmpty(this.get('email'))) { 11 | return; 12 | } 13 | 14 | this.set('isSending', true); 15 | this.set('success', false); 16 | this.set('error', null); 17 | 18 | this.get('session.auth').sendPasswordResetEmail(this.get('email'), function () { 19 | this.set('success', true); 20 | this.set('isSending', false); 21 | }.bind(this)); 22 | 23 | } 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /app/controllers/reindex.js: -------------------------------------------------------------------------------- 1 | export default Ember.ArrayController.extend({ 2 | sortProperties: ['name'], 3 | 4 | isIndexing: function () { 5 | return !this.get('model').isEvery('indexingClass', 'complete'); 6 | }.property('model.@each.isIndexing'), 7 | 8 | handleBeforeUnload: function () { 9 | return 'We are not done reindexing your content. If you leave now, content will be missing from the search index.'; 10 | }, 11 | 12 | watchForUnload: function () { 13 | if (this.get('isIndexing')) { 14 | Ember.$(window).one('beforeunload', this.handleBeforeUnload); 15 | } else { 16 | Ember.$(window).off('beforeunload', this.handleBeforeUnload); 17 | } 18 | }.observes('isIndexing') 19 | 20 | }); 21 | -------------------------------------------------------------------------------- /app/controllers/resend-email.js: -------------------------------------------------------------------------------- 1 | export default Ember.ObjectController.extend({ 2 | email : null, 3 | isSending: false, 4 | success : false, 5 | error : null, 6 | 7 | actions: { 8 | resendEmail: function () { 9 | 10 | this.setProperties({ 11 | success: false, 12 | error: null 13 | }); 14 | 15 | this.set('isSending', true); 16 | 17 | function uniqueId() { 18 | return Date.now() + 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 19 | var r = Math.random()*16|0, v = c === 'x' ? r : (r&0x3|0x8); 20 | return v.toString(16); 21 | }); 22 | } 23 | 24 | var escapedEmail = this.get('email').toLowerCase().replace(/\./g, ',1'); 25 | var root = window.ENV.firebaseRoot.child('management/commands/verification/' + escapedEmail); 26 | 27 | root.set({ userid: this.get('email').toLowerCase(), siteref: window.location.host, id: uniqueId() }, function(err) { 28 | 29 | if(err) { 30 | this.set('error', err); 31 | return; 32 | } else { 33 | this.set('success', { message: 'Verification e-mail resent' }); 34 | } 35 | 36 | this.set('isSending', false); 37 | }.bind(this)); 38 | } 39 | } 40 | }); 41 | -------------------------------------------------------------------------------- /app/controllers/wh/content/type/json.js: -------------------------------------------------------------------------------- 1 | export default Ember.Controller.extend({ 2 | error: null, 3 | saving: false, 4 | 5 | actions: { 6 | save: function (itemJSON) { 7 | this.set('error', null); 8 | this.set('saving', true); 9 | 10 | var itemData; 11 | 12 | try { 13 | itemData = JSON.parse(itemJSON); 14 | } catch (error) { 15 | return this.set('error', error); 16 | } 17 | 18 | this.get('model').set('itemData', itemData); 19 | this.get('model').save().then(function (item) { 20 | this.set('saving', false); 21 | this.send('notify', 'info', item.get('itemData.name') + ' saved!', { icon: 'ok-sign' }); 22 | this.transitionToRoute('wh.content.type', item.get('constructor.typeKey')); 23 | }.bind(this)); 24 | 25 | } 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /app/controllers/wh/content/type/new.js: -------------------------------------------------------------------------------- 1 | import EditController from 'appkit/controllers/wh/content/type/edit'; 2 | 3 | export default EditController.extend(); 4 | -------------------------------------------------------------------------------- /app/controllers/wh/search-global-results.js: -------------------------------------------------------------------------------- 1 | export default Ember.Controller.extend({ 2 | needs: ["wh"], 3 | searchResults: Ember.computed.alias("controllers.wh.searchResults"), 4 | isLoading: Ember.computed.alias("controllers.wh.searchLoading"), 5 | debouncedQuery: Ember.computed.alias("controllers.wh.debouncedQuery") 6 | }); 7 | -------------------------------------------------------------------------------- /app/controllers/wh/settings/general.js: -------------------------------------------------------------------------------- 1 | export default Ember.ObjectController.extend({ 2 | actions: { 3 | saveSettings: function () { 4 | var controller = this; 5 | controller.get('model').save().then(function () { 6 | controller.send('notify', 'success', 'Settings saved!'); 7 | controller.send('buildSignal'); 8 | }); 9 | } 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /app/controllers/wordpress.js: -------------------------------------------------------------------------------- 1 | /*global WXMLConverter,WXMLImporter*/ 2 | import downcode from 'appkit/utils/downcode'; 3 | import SearchIndex from 'appkit/utils/search-index'; 4 | 5 | export default Ember.Controller.extend({ 6 | 7 | needs: ['application'], 8 | 9 | wxmlDoneClass: 'pending', 10 | wxmlStatus: null, 11 | 12 | isComplete: false, 13 | 14 | convertXml: function () { 15 | 16 | var file = this.get('controllers.application.wordpressXML'); 17 | 18 | var controller = this; 19 | var reader = new window.FileReader(); 20 | 21 | reader.onload = function(e) { 22 | var data = reader.result; 23 | 24 | WXMLConverter.onConverterUpdated = function(updateEvent) { 25 | controller.set('wxmlStatus.messages', true); 26 | controller.set('wxmlStatus.' + updateEvent.event, updateEvent); 27 | }; 28 | 29 | WXMLImporter.onImporterUpdated = function(updateEvent) { 30 | controller.set('wxmlStatus.messages', true); 31 | controller.set('wxmlStatus.' + updateEvent.event, updateEvent); 32 | }; 33 | 34 | WXMLConverter.convert(data, function(parsedData) { 35 | WXMLImporter.import(parsedData, downcode, window.ENV.firebase, controller.get('session.site.name'), controller.get('session.site.token'), function() { 36 | controller.set('wxmlStatus.search', { running: true, class: 'active'}); 37 | SearchIndex.reindex().then(function () { 38 | controller.set('wxmlStatus.search', { running: false, class: 'complete'}); 39 | controller.set('wxmlDoneClass', 'complete'); 40 | controller.set('isComplete', true); 41 | }, function (error) { 42 | controller.set('wxmlStatus.search', { running: false, class: 'danger'}); 43 | controller.set('wxmlDoneClass', 'complete'); 44 | controller.set('isComplete', false); 45 | }); 46 | }); 47 | }); 48 | }; 49 | 50 | if (Ember.isEmpty(file)) { 51 | this.transitionToRoute('wh'); 52 | } else { 53 | reader.readAsText(file); 54 | } 55 | 56 | } 57 | }); 58 | -------------------------------------------------------------------------------- /app/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webhook/webhook-cms/8b58110801bc0d3a749bb230bb9983ee5b77ffcd/app/helpers/.gitkeep -------------------------------------------------------------------------------- /app/helpers/format-number.js: -------------------------------------------------------------------------------- 1 | // see http://numeraljs.com/ for formats 2 | 3 | /* global numeral */ 4 | export default Ember.Handlebars.makeBoundHelper(function(number, options) { 5 | if (!number) { 6 | return ""; 7 | } 8 | if (options.hash.format) { 9 | return numeral(number).format(options.hash.format); 10 | } else { 11 | return numeral(number).format(); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /app/helpers/format-time.js: -------------------------------------------------------------------------------- 1 | export default Ember.Handlebars.makeBoundHelper(function(datetime, options) { 2 | if (!datetime) { 3 | return ""; 4 | } 5 | return moment(datetime).format(options.hash.format || 'LLLL'); 6 | }); 7 | -------------------------------------------------------------------------------- /app/helpers/resize-image.js: -------------------------------------------------------------------------------- 1 | export default Ember.Handlebars.makeBoundHelper(function(src, options) { 2 | 3 | var params = []; 4 | ['width', 'height', 'grow'].forEach(function (key) { 5 | if (options.hash[key]) { 6 | params.push(key + '=' + options.hash[key]); 7 | } 8 | }); 9 | 10 | var imageSource = ''; 11 | var safeImageSource = ''; 12 | 13 | // New image format 14 | if (typeof src === 'object' && src.resize_url) { 15 | imageSource = src.resize_url; 16 | 17 | if (src.resize_url.indexOf('http://static-cdn.jtvnw.net') === 0) { 18 | var parts = src.resize_url.split('.'), 19 | ext = parts.length > 1 ? ('.' + parts.pop()) : '', 20 | dim = options.hash.size || 100; 21 | 22 | imageSource = parts.join('.') + '-' + dim + 'x' + dim; 23 | 24 | if (options.hash.crop) { 25 | imageSource += '-c'; 26 | } else { 27 | imageSource += '-a'; 28 | } 29 | 30 | imageSource += ext; 31 | } else { 32 | imageSource = imageSource + '=s' + (options.hash.size || 100); 33 | 34 | if (options.hash.crop) { 35 | imageSource = imageSource + '-c'; 36 | } 37 | } 38 | 39 | safeImageSource = Ember.Handlebars.Utils.escapeExpression(imageSource); 40 | 41 | return new Ember.Handlebars.SafeString(''); 42 | 43 | // Old image format 44 | } else if (typeof src === 'string') { 45 | 46 | // Relative url 47 | if (src.indexOf('http://') === -1) { 48 | src = 'http://' + window.ENV.siteDNS + src; 49 | } 50 | 51 | params.push('url=' + encodeURIComponent(src)); 52 | params.push('key=' + window.ENV.embedlyKey); 53 | 54 | imageSource = window.ENV.displayUrl + (options.hash.crop ? 'crop' : 'resize') + '?' + params.join('&'); 55 | safeImageSource = Ember.Handlebars.Utils.escapeExpression(imageSource); 56 | 57 | return new Ember.Handlebars.SafeString(''); 58 | } else { 59 | return ''; 60 | } 61 | 62 | }); 63 | -------------------------------------------------------------------------------- /app/helpers/reverse-word.js: -------------------------------------------------------------------------------- 1 | // Please note that Handlebars helpers will only be found automatically by the 2 | // resolver if their name contains a dash (reverse-word, translate-text, etc.) 3 | // For more details: http://stefanpenner.github.io/ember-app-kit/guides/using-modules.html 4 | 5 | export default Ember.Handlebars.makeBoundHelper(function(word) { 6 | return word.split('').reverse().join(''); 7 | }); 8 | 9 | -------------------------------------------------------------------------------- /app/helpers/to-markdown.js: -------------------------------------------------------------------------------- 1 | export default Ember.Handlebars.makeBoundHelper(function(string) { 2 | if (typeof string === 'string') { 3 | return new Ember.Handlebars.SafeString(marked(string)); 4 | } else { 5 | return ''; 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /app/helpers/translate-control.js: -------------------------------------------------------------------------------- 1 | // This is essentially a hack until you can pass variables to the {{t}} helper. 2 | // See https://github.com/jamesarosen/ember-i18n/issues/131 for updates 3 | export default Ember.Handlebars.makeBoundHelper(function(name, group) { 4 | if (!name) { 5 | return ''; 6 | } 7 | 8 | return Ember.I18n.translations['form'][group === true ? 'group' : 'widget'][name.toLowerCase()]; 9 | }); 10 | -------------------------------------------------------------------------------- /app/helpers/truncate-string.js: -------------------------------------------------------------------------------- 1 | export default Ember.Handlebars.makeBoundHelper(function(str,len) { 2 | if (!str || !len) { return str; } 3 | 4 | // strip the html 5 | str = Ember.$('').html(str).text(); 6 | 7 | if (str.length > len && str.length > 0) { 8 | var new_str = str + " "; 9 | new_str = str.substr(0, len); 10 | new_str = str.substr(0, new_str.lastIndexOf(" ")); 11 | new_str = (new_str.length > 0) ? new_str : str.substr(0, len); 12 | 13 | return new Ember.Handlebars.SafeString(new_str + '...'); 14 | } 15 | return str; 16 | }); 17 | -------------------------------------------------------------------------------- /app/initializers/analytics.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'analytics', 3 | 4 | initialize: function() { 5 | /** 6 | * Creates a temporary global ga object and loads analytics.js. 7 | * Paramenters o, a, and m are all used internally. They could have been declared using 'var', 8 | * instead they are declared as parameters to save 4 bytes ('var '). 9 | * 10 | * @param {Window} i The global context object. 11 | * @param {Document} s The DOM document object. 12 | * @param {string} o Must be 'script'. 13 | * @param {string} g URL of the analytics.js script. Inherits protocol from page. 14 | * @param {string} r Global name of analytics object. Defaults to 'ga'. 15 | * @param {DOMElement?} a Async script tag. 16 | * @param {DOMElement?} m First script tag in document. 17 | */ 18 | (function(i, s, o, g, r, a, m){ 19 | i['GoogleAnalyticsObject'] = r; // Acts as a pointer to support renaming. 20 | 21 | // Creates an initial ga() function. The queued commands will be executed once analytics.js loads. 22 | i[r] = i[r] || function() { 23 | (i[r].q = i[r].q || []).push(arguments); 24 | }; 25 | 26 | // Sets the time (as an integer) this tag was executed. Used for timing hits. 27 | i[r].l = 1 * new Date(); 28 | 29 | // Insert the script tag asynchronously. Inserts above current tag to prevent blocking in 30 | // addition to using the async attribute. 31 | a = s.createElement(o); 32 | m = s.getElementsByTagName(o)[0]; 33 | a.async = 1; 34 | a.src = g; 35 | m.parentNode.insertBefore(a, m); 36 | })(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga'); 37 | ga('create', 'UA-47335625-2', {'cookieDomain': 'none'}); 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /app/initializers/session.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'session', 3 | 4 | initialize: function (container, application) { 5 | 6 | var session = Ember.Object.create({ 7 | auth: null, 8 | user: null, 9 | site: Ember.Object.create({ 10 | name: Ember.$('meta[name="siteName"]').attr('content'), 11 | token: null 12 | }) 13 | }); 14 | 15 | // Add `session` to all the things 16 | application.register('session:current', session, { instantiate: false }); 17 | Ember.A(['model', 'controller', 'view', 'route', 'component']).forEach(function (component) { 18 | application.inject(component, 'session', 'session:current'); 19 | }); 20 | 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /app/initializers/team.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'team', 3 | 4 | initialize: function (container, application) { 5 | 6 | var team = Ember.Object.create(); 7 | 8 | // Add `session` to all the things 9 | application.register('team:current', team, { instantiate: false }); 10 | Ember.A(['model', 'controller', 'view', 'route', 'component']).forEach(function (component) { 11 | application.inject(component, 'team', 'team:current'); 12 | }); 13 | 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webhook/webhook-cms/8b58110801bc0d3a749bb230bb9983ee5b77ffcd/app/models/.gitkeep -------------------------------------------------------------------------------- /app/models/control-type-group.js: -------------------------------------------------------------------------------- 1 | var ControlTypeGroup = DS.Model.extend({ 2 | name: DS.attr('string'), 3 | controlTypes: DS.hasMany('control-type', { async: true }) 4 | }); 5 | 6 | var fixtures = []; 7 | 8 | var controlTypeGroupId = 0, 9 | controlTypeId = 0; 10 | 11 | $.each(window.ENV.controlTypeGroups, function (index, group) { 12 | controlTypeGroupId++; 13 | fixtures.push({ 14 | id: controlTypeGroupId, 15 | name: group.name, 16 | controlTypes: $.map(group.controlTypes, function (control, index) { 17 | return control.widget; 18 | }) 19 | }); 20 | }); 21 | 22 | ControlTypeGroup.reopenClass({ 23 | FIXTURES: fixtures 24 | }); 25 | 26 | export default ControlTypeGroup; 27 | -------------------------------------------------------------------------------- /app/models/control-type.js: -------------------------------------------------------------------------------- 1 | var ControlType = DS.Model.extend({ 2 | name : DS.attr('string'), 3 | group : DS.belongsTo('control-type-group'), 4 | iconClass: DS.attr('string'), 5 | widget : DS.attr('string', { defaultValue: 'textfield' }), 6 | 7 | // The following are used as defaults for new controls of this type 8 | label : DS.attr('string'), 9 | placeholder: DS.attr('string'), 10 | help : DS.attr('string'), 11 | 12 | valueType : DS.attr('string', { defaultValue: 'string' }), 13 | 14 | controlPartialPath: function () { 15 | return 'widgets/' + this.get('widget'); 16 | }.property('widget'), 17 | infoPartialPath: function () { 18 | return 'widgets/info/' + this.get('widget'); 19 | }.property('widget'), 20 | valuePartialPath: function () { 21 | return 'widgets/value/' + this.get('widget'); 22 | }.property('widget') 23 | }); 24 | 25 | var fixtures = []; 26 | 27 | var controlTypeGroupId = 0, 28 | controlTypeId = 0; 29 | 30 | $.each(window.ENV.controlTypeGroups, function (index, group) { 31 | controlTypeGroupId++; 32 | $.each(group.controlTypes, function (index, control) { 33 | fixtures.push($.extend({ 34 | id: control.widget, 35 | group: controlTypeGroupId 36 | }, control)); 37 | }); 38 | }); 39 | 40 | ControlType.reopenClass({ 41 | FIXTURES: fixtures 42 | }); 43 | 44 | export default ControlType; 45 | -------------------------------------------------------------------------------- /app/models/data.js: -------------------------------------------------------------------------------- 1 | // This is the "One Off" model 2 | // it is named `data` because that will put it in `dev/data/{id}` 3 | 4 | import Item from 'appkit/models/item'; 5 | 6 | export default Item.extend(); 7 | -------------------------------------------------------------------------------- /app/models/group.js: -------------------------------------------------------------------------------- 1 | export default Ember.Object.extend({ 2 | key: null, 3 | name: null, 4 | permissions: null, 5 | users: Ember.A([]), 6 | isOpen: false, 7 | isEditingName: false, 8 | isSaved: false, 9 | saveChanged: function () { 10 | if (!this.get('isSaving')) { 11 | var group = this; 12 | group.set('isSaved', true); 13 | Ember.run.later(function () { 14 | group.set('isSaved', false); 15 | }, 500); 16 | } 17 | }.observes('isSaving'), 18 | isSaving: function () { 19 | return !!this.get('saveQueue'); 20 | }.property('saveQueue'), 21 | saveQueue: 0 22 | }); 23 | -------------------------------------------------------------------------------- /app/models/item.js: -------------------------------------------------------------------------------- 1 | import SearchIndex from 'appkit/utils/search-index'; 2 | 3 | export default DS.Model.extend({ 4 | itemData: DS.attr('json'), 5 | 6 | updateSearchIndex: function () { 7 | SearchIndex.indexItem(this); 8 | }.on('didUpdate', 'didCreate') 9 | }); 10 | -------------------------------------------------------------------------------- /app/models/redirect.js: -------------------------------------------------------------------------------- 1 | export default DS.Model.extend({ 2 | pattern: DS.attr('string'), 3 | destination: DS.attr('string'), 4 | 5 | isValid: function () { 6 | 7 | try { 8 | new RegExp(this.get('pattern')); 9 | } catch (error) { 10 | return false; 11 | } 12 | 13 | return true; 14 | 15 | }.property('pattern') 16 | }); 17 | -------------------------------------------------------------------------------- /app/models/settings.js: -------------------------------------------------------------------------------- 1 | export default DS.Model.extend({ 2 | siteName : DS.attr('string'), 3 | siteUrl : DS.attr('string'), 4 | siteDescription: DS.attr('string'), 5 | siteKeywords : DS.attr('string'), 6 | analyticsId : DS.attr('string'), 7 | siteTwitter : DS.attr('string'), 8 | siteFacebook : DS.attr('string'), 9 | siteMessage : DS.attr('string'), 10 | siteMessageType: DS.attr('string') 11 | }); 12 | -------------------------------------------------------------------------------- /app/models/user.js: -------------------------------------------------------------------------------- 1 | export default Ember.Object.extend({ 2 | key: null, 3 | email: null, 4 | owner: false, 5 | user: false, 6 | potential: false, 7 | isUser: function () { 8 | return this.get('owner') || this.get('user') || this.get('potential'); 9 | }.property('owner', 'user', 'potential') 10 | }); 11 | -------------------------------------------------------------------------------- /app/router.js: -------------------------------------------------------------------------------- 1 | /*globals ga*/ 2 | // ensure we don't share routes between all Router instances 3 | var Router = Ember.Router.extend({ 4 | location: 'hash' 5 | }); 6 | 7 | Router.map(function() { 8 | 9 | this.route('login'); 10 | this.route('password-reset'); 11 | this.route('create-user'); 12 | this.route('confirm-email'); 13 | this.route('resend-email'); 14 | this.route('start'); 15 | this.route('theme'); 16 | this.route('wordpress'); 17 | this.route('reindex'); 18 | this.route('import'); 19 | 20 | this.route('expired'); 21 | 22 | this.route('form', { path: '/form/:id' }); 23 | 24 | this.resource('wh', function () { 25 | this.resource('wh.settings', { path: '/settings/' }, function () { 26 | this.route('billing'); 27 | this.route('data'); 28 | this.route('domain'); 29 | this.route('general'); 30 | this.route('team'); 31 | this.route('password-change'); 32 | this.route('urls'); 33 | }); 34 | 35 | this.route('search-global-results'); 36 | this.resource('wh.content', { path: '/content/' }, function () { 37 | this.route('all-types'); 38 | this.route('start'); 39 | 40 | this.resource('wh.content.type', { path: '/:type_id' }, function () { 41 | this.route('index', { path: '/' }); 42 | this.route('edit', { path: '/:item_id' }); 43 | this.route('json', { path: '/:item_id/json' }); 44 | this.route('new'); 45 | }); 46 | 47 | }); 48 | }); 49 | 50 | this.route('component-test'); 51 | this.route('helper-test'); 52 | 53 | }); 54 | 55 | Router.reopen({ 56 | notifyAnalytics: function() { 57 | var dim1 = Ember.$('meta[name="siteName"]').attr('content'); 58 | Ember.Logger.log('Sending pageview to analytics.', dim1); 59 | 60 | ga('set', 'dimension1', dim1); 61 | ga('send', 'pageview'); 62 | }.on('didTransition') 63 | }); 64 | 65 | export default Router; 66 | -------------------------------------------------------------------------------- /app/routes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webhook/webhook-cms/8b58110801bc0d3a749bb230bb9983ee5b77ffcd/app/routes/.gitkeep -------------------------------------------------------------------------------- /app/routes/component_test.js: -------------------------------------------------------------------------------- 1 | export default Ember.Route.extend({ 2 | model: function() { 3 | return ['purple', 'green', 'orange']; 4 | } 5 | }); 6 | -------------------------------------------------------------------------------- /app/routes/confirm-email.js: -------------------------------------------------------------------------------- 1 | export default Ember.Route.extend({ 2 | setupController: function (controller) { 3 | 4 | function getURLParameter(name) { 5 | return decodeURI( 6 | ((new RegExp(name + '=' + '(.+?)(&|$)')).exec(location.hash)||[,''])[1] 7 | ); 8 | } 9 | 10 | var username = getURLParameter('username'); 11 | var key = getURLParameter('key'); 12 | 13 | if(!username || !key) { 14 | controller.setProperties({ 15 | isSending: false, 16 | error: { 17 | code: 'NOPE', 18 | message: 'NEED TO SET KEY AND USERNAME' 19 | } 20 | }); 21 | } else { 22 | var root = window.ENV.firebaseRoot.child('management/users/' + username + '/verification'); 23 | 24 | root.child('verified').once('value', function(snapshot) { 25 | var val = snapshot.val(); 26 | 27 | if(val) { 28 | controller.setProperties({ 29 | isSending: false, 30 | success: true 31 | }); 32 | 33 | setTimeout(function() { 34 | this.transitionTo('login'); 35 | }.bind(this), 1000); 36 | } else { 37 | root.set({ verification_key_match: key, verified: true }, function(err) { 38 | if(err) { 39 | controller.setProperties({ 40 | isSending: false, 41 | error: { 42 | code: 'NOPE', 43 | message: 'Invalid key or username' 44 | } 45 | }); 46 | } else { 47 | controller.setProperties({ 48 | isSending: false, 49 | success: true 50 | }); 51 | 52 | setTimeout(function() { 53 | this.transitionTo('login'); 54 | }.bind(this), 1000); 55 | } 56 | }.bind(this)); 57 | } 58 | 59 | }.bind(this)); 60 | } 61 | } 62 | }); 63 | -------------------------------------------------------------------------------- /app/routes/create-user.js: -------------------------------------------------------------------------------- 1 | export default Ember.Route.extend({ 2 | setupController: function (controller) { 3 | 4 | function getURLParameter(name) { 5 | return decodeURI( 6 | ((new RegExp(name + '=' + '(.+?)(&|$)')).exec(location.hash)||[,''])[1] 7 | ); 8 | } 9 | 10 | var username = getURLParameter('username') || null; 11 | var key = getURLParameter('key') || null; 12 | 13 | controller.setProperties({ 14 | email : username.toLowerCase(), 15 | verification_key: key, 16 | password : "", 17 | password2: "", 18 | isSending: false, 19 | success : false, 20 | error : null 21 | }); 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /app/routes/helper_test.js: -------------------------------------------------------------------------------- 1 | export default Ember.Route.extend({ 2 | model: function() { 3 | return { 4 | name: "rebmE" 5 | }; 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /app/routes/import.js: -------------------------------------------------------------------------------- 1 | import SearchIndex from 'appkit/utils/search-index'; 2 | 3 | export default Ember.Route.extend({ 4 | 5 | controllerName: 'reindex', 6 | 7 | beforeModel: function () { 8 | 9 | var data = this.controllerFor('wh.settings.data').get('dataBackup'); 10 | 11 | if (Ember.isEmpty(data)) { 12 | this.transitionTo('wh.settings.data'); 13 | } else { 14 | this.set('data', data); 15 | } 16 | 17 | var removeContentTypes = this.modelFor('wh').map(function (contentType) { 18 | return contentType.destroyRecord(); 19 | }); 20 | 21 | var removeSettings = this.store.find('settings').then(function (settings) { 22 | return settings.map(function (setting) { 23 | return setting.destroyRecord(); 24 | }); 25 | }); 26 | 27 | var removeRef = function (ref) { 28 | new Ember.RSVP.Promise(function (resolve, reject) { 29 | window.ENV.firebase.child(ref).remove(function (error) { 30 | if (error) { 31 | reject(error); 32 | } else { 33 | resolve(); 34 | } 35 | }); 36 | }); 37 | }; 38 | 39 | return Ember.RSVP.all([ 40 | removeContentTypes, 41 | removeRef('settings'), 42 | removeRef('data') 43 | ]); 44 | 45 | }, 46 | 47 | model: function () { 48 | return this.modelFor('wh'); 49 | }, 50 | 51 | afterModel: function (model) { 52 | 53 | var data = this.get('data'); 54 | 55 | return new Ember.RSVP.Promise(function (resolve, reject) { 56 | window.ENV.firebase.update(data, function (error) { 57 | if (error) { 58 | reject(error); 59 | } else { 60 | resolve(); 61 | } 62 | }); 63 | }).then(function () { 64 | SearchIndex.indexSite(); 65 | }); 66 | }, 67 | 68 | actions: { 69 | willTransition: function (transition) { 70 | 71 | if (this.controller.get('isIndexing')) { 72 | Ember.Logger.log('Indexing in progress, aborting transition'); 73 | transition.abort(); 74 | window.history.forward(); 75 | } else { 76 | Ember.Logger.log('Indexing complete, continue with transition.'); 77 | return true; 78 | } 79 | 80 | } 81 | } 82 | }); 83 | -------------------------------------------------------------------------------- /app/routes/index.js: -------------------------------------------------------------------------------- 1 | export default Ember.Route.extend({ 2 | beforeModel: function () { 3 | 4 | this._super.apply(this, arguments); 5 | 6 | if (Ember.isEmpty(this.get('session.user'))) { 7 | return; 8 | } 9 | 10 | var route = this; 11 | 12 | this.store.find('content-type').then(function (types) { 13 | if (types.get('length')) { 14 | route.transitionTo('wh'); 15 | } else { 16 | route.transitionTo('start'); 17 | } 18 | }); 19 | 20 | } 21 | }); 22 | -------------------------------------------------------------------------------- /app/routes/login.js: -------------------------------------------------------------------------------- 1 | export default Ember.Route.extend({ 2 | setupController: function (controller) { 3 | controller.setProperties({ 4 | email: null, 5 | password: null 6 | }); 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /app/routes/password-change.js: -------------------------------------------------------------------------------- 1 | export default Ember.Route.extend({ 2 | setupController: function (controller) { 3 | controller.reset(); 4 | } 5 | }); 6 | -------------------------------------------------------------------------------- /app/routes/reindex.js: -------------------------------------------------------------------------------- 1 | import SearchIndex from 'appkit/utils/search-index'; 2 | 3 | export default Ember.Route.extend({ 4 | model: function () { 5 | return this.store.find('content-type'); 6 | }, 7 | 8 | afterModel: function () { 9 | SearchIndex.indexSite(); 10 | }, 11 | 12 | actions: { 13 | willTransition: function (transition) { 14 | 15 | if (this.controller.get('isIndexing')) { 16 | Ember.Logger.log('Indexing in progress, aborting transition'); 17 | transition.abort(); 18 | window.history.forward(); 19 | } else { 20 | Ember.Logger.log('Indexing complete, continue with transition.'); 21 | return true; 22 | } 23 | 24 | } 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /app/routes/resend-email.js: -------------------------------------------------------------------------------- 1 | export default Ember.Route.extend({ 2 | setupController: function (controller) { 3 | controller.setProperties({ 4 | email : null, 5 | isSending: false, 6 | success : false, 7 | error : null 8 | }); 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /app/routes/start.js: -------------------------------------------------------------------------------- 1 | export default Ember.Route.extend({ 2 | beforeModel: function () { 3 | if (!this.get('buildEnvironment.local')) { 4 | this.transitionTo('wh'); 5 | } 6 | this._super.apply(this, arguments); 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /app/routes/theme.js: -------------------------------------------------------------------------------- 1 | export default Ember.Route.extend({ 2 | beforeModel: function () { 3 | if (!this.get('buildEnvironment.local')) { 4 | this.transitionTo('wh'); 5 | } 6 | this._super.apply(this, arguments); 7 | }, 8 | setupController: function (controller) { 9 | controller.setProperties({ 10 | themes: window.ENV.themes, 11 | isSending: false, 12 | success : false, 13 | error : null 14 | }); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /app/routes/wh.js: -------------------------------------------------------------------------------- 1 | export default Ember.Route.extend({ 2 | model: function () { 3 | return this.store.find('content-type'); 4 | } 5 | }); 6 | -------------------------------------------------------------------------------- /app/routes/wh/content/all-types.js: -------------------------------------------------------------------------------- 1 | export default Ember.Route.extend({ 2 | beforeModel: function () { 3 | if (!this.modelFor('wh').get('length')) { 4 | this.transitionTo('wh.content.start'); 5 | } 6 | }, 7 | model: function () { 8 | return this.modelFor('wh'); 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /app/routes/wh/content/start.js: -------------------------------------------------------------------------------- 1 | export default Ember.Route.extend({ 2 | model: function () { 3 | return this.modelFor('wh'); 4 | } 5 | }); 6 | -------------------------------------------------------------------------------- /app/routes/wh/content/type/json.js: -------------------------------------------------------------------------------- 1 | export default Ember.Route.extend({ 2 | model: function (params) { 3 | return this.store.find(this.modelFor('wh.content.type').get('itemModelName'), params.item_id); 4 | }, 5 | 6 | setupController: function (controller, model) { 7 | controller.set('itemJSON', JSON.stringify(model.get('itemData'), null, 2)); 8 | controller.set('saving', false); 9 | controller.set('error', null); 10 | this._super.apply(this, arguments); 11 | }, 12 | 13 | actions: { 14 | cancel: function () { 15 | this.transitionTo('wh.content.type', this.modelFor('wh.content.type').get('itemModelName')); 16 | } 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /app/routes/wh/content/type/new.js: -------------------------------------------------------------------------------- 1 | import EditRoute from 'appkit/routes/wh/content/type/edit'; 2 | 3 | export default EditRoute.extend({ 4 | templateName: 'wh/content/type/edit' 5 | }); 6 | -------------------------------------------------------------------------------- /app/routes/wh/index.js: -------------------------------------------------------------------------------- 1 | export default Ember.Route.extend({ 2 | 3 | defaultMessage: function () { 4 | var msg = ''; 5 | 6 | msg += '#' + Em.I18n.t('wh.index.welcome.title') + "\n\n"; 7 | msg += Em.I18n.t('wh.index.welcome.description') + "\n\n"; 8 | 9 | msg += '* [%@](%@)'.fmt(Em.I18n.t('wh.index.welcome.docs'), "http://www.webhook.com/docs/") + "\n"; 10 | msg += '* [%@](%@)'.fmt(Em.I18n.t('wh.index.welcome.support'), "http://www.webhook.com/help/") + "\n"; 11 | msg += '* [%@](%@)'.fmt(Em.I18n.t('wh.index.welcome.issues'), "https://github.com/webhook/webhook/issues?state=open") + "\n"; 12 | msg += '* [%@](%@)'.fmt(Em.I18n.t('wh.index.welcome.forums'), "http://forums.webhook.com/") + "\n"; 13 | 14 | return msg; 15 | }.property(), 16 | 17 | beforeModel: function () { 18 | var route = this; 19 | 20 | return this.store.find('settings', 'general').then(function (settings) { 21 | if (Ember.isEmpty(settings.get('siteMessage'))) { 22 | settings.set('siteMessage', route.get('defaultMessage')); 23 | } 24 | route.set('settings', settings); 25 | }, function (error) { 26 | var settings = route.store.getById('settings', 'general'); 27 | settings.loadedData(); 28 | if (route.get('session.isOwner')) { 29 | return settings.save().then(function () { 30 | settings.set('siteMessage', route.get('defaultMessage')); 31 | route.set('settings', settings); 32 | }); 33 | } 34 | }); 35 | 36 | }, 37 | 38 | setupController: function (controller) { 39 | 40 | this._super.apply(this, arguments); 41 | 42 | controller.set('contentTypes', this.modelFor('wh')); 43 | controller.set('settings', this.get('settings')); 44 | controller.set('defaultMessage', this.get('defaultMessage')); 45 | }, 46 | 47 | actions: { 48 | willTransition: function (transition) { 49 | this.get('settings').rollback(); 50 | } 51 | } 52 | }); 53 | -------------------------------------------------------------------------------- /app/routes/wh/search-global-results.js: -------------------------------------------------------------------------------- 1 | export default Ember.Route.extend({ 2 | model: function () { 3 | return Ember.A([]); 4 | } 5 | }); 6 | -------------------------------------------------------------------------------- /app/routes/wh/settings/data.js: -------------------------------------------------------------------------------- 1 | export default Ember.Route.extend({ 2 | model: function () { 3 | 4 | var siteName = this.get('session.site.name'); 5 | var siteToken = this.get('session.site.token'); 6 | 7 | return new Ember.RSVP.Promise(function (resolve, reject) { 8 | var backupsRef = window.ENV.firebaseRoot.child('management/backups'); 9 | backupsRef.once('value', function (snapshot) { 10 | 11 | var backups = Ember.$.map(snapshot.val() || [], function (timestamp) { 12 | return { 13 | fileName: siteName + '-' + moment(timestamp).format() + '.json', 14 | url: window.ENV.uploadUrl + 'backup-snapshot/?site=' + siteName + '&token=' + siteToken + '×tamp=' + timestamp, 15 | timestamp: timestamp 16 | }; 17 | }); 18 | 19 | Ember.run(null, resolve, backups.reverse()); 20 | }); 21 | }); 22 | }, 23 | 24 | setupController: function (controller) { 25 | controller.set('deleteOption', 'data'); 26 | controller.set('wordpressFile', null); 27 | 28 | // controller.set('dataBackup', null); 29 | controller.set('dataError', null); 30 | 31 | controller.set('downloadLink', window.ENV.uploadUrl + 'download/?site=' +this.get('session.site.name') + '&token=' + this.get('session.site.token')); 32 | controller.set('downloadFileName', this.get('session.site.name')); 33 | 34 | window.ENV.firebaseRoot.child('management/sites').child(this.get('session.site.name')).child('api-key').once('value', function(snap) { 35 | var val = snap.val(); 36 | 37 | if(!val) { 38 | //Generate and set 39 | var newKey = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {var r = Math.random()*16|0,v=c==='x'?r:r&0x3|0x8;return v.toString(16);}); 40 | 41 | window.ENV.firebaseRoot.child('management/sites').child(this.get('session.site.name')).child('api-key').set(newKey, function(err) { 42 | controller.set('apiKey', newKey); 43 | }.bind(this)); 44 | } else { 45 | controller.set('apiKey', val); 46 | } 47 | }.bind(this)); 48 | 49 | return this._super.apply(this, arguments); 50 | }, 51 | 52 | actions: { 53 | reindex: function () { 54 | this.transitionTo('reindex'); 55 | } 56 | } 57 | }); 58 | -------------------------------------------------------------------------------- /app/routes/wh/settings/domain.js: -------------------------------------------------------------------------------- 1 | export default Ember.Route.extend({ 2 | 3 | setupController: function (controller) { 4 | 5 | var siteName = this.get('session.site.name'); 6 | 7 | controller.setProperties({ 8 | domain : "", 9 | isSending: false, 10 | success : false, 11 | errors : Ember.A([]), 12 | }); 13 | 14 | window.ENV.firebaseRoot.child("management/sites/" + siteName + "/dns").once('value', function(snapshot) { 15 | controller.set('domain', snapshot.val() || ''); 16 | }); 17 | 18 | this._super.apply(this, arguments); 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /app/routes/wh/settings/general.js: -------------------------------------------------------------------------------- 1 | // import Settings from 'appkit/models/settings'; 2 | 3 | export default Ember.Route.extend({ 4 | model: function () { 5 | var route = this; 6 | return this.store.find('settings', 'general').then(null, function () { 7 | var settings = route.store.getById('settings', 'general'); 8 | settings.loadedData(); 9 | return settings; 10 | }); 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /app/routes/wh/settings/team.js: -------------------------------------------------------------------------------- 1 | export default Ember.Route.extend({ 2 | 3 | beforeModel: function () { 4 | 5 | this._super.apply(this, arguments); 6 | 7 | // Check to see if site has been deployed 8 | if (!this.get('session.serverMessages.length')) { 9 | 10 | var route = this; 11 | var siteName = this.get('session.site.name'); 12 | 13 | return new Ember.RSVP.Promise(function (resolve, reject) { 14 | 15 | window.ENV.firebaseRoot.child('management/sites/' + siteName + '/messages').limitToLast(10).once('value', function (snapshot) { 16 | 17 | snapshot.forEach(function (childSnapshot) { 18 | var message = Ember.$.extend({}, childSnapshot.val(), { id: childSnapshot.key() }); 19 | if (typeof message.status !== 'undefined' && message.status === 0) { 20 | route.set('session.isDeployed', true); 21 | } 22 | }); 23 | 24 | resolve(); 25 | 26 | }); 27 | 28 | }); 29 | 30 | } 31 | 32 | }, 33 | 34 | model: function () { 35 | return this.get('team.users'); 36 | }, 37 | 38 | setupController: function (controller) { 39 | controller.set('groups', this.get('team.groups')); 40 | controller.set('contentTypes', this.modelFor('wh')); 41 | controller.set('multiContentTypes', this.modelFor('wh').rejectBy('oneOff')); 42 | controller.set('singleContentTypes', this.modelFor('wh').filterBy('oneOff')); 43 | 44 | this._super.apply(this, arguments); 45 | } 46 | }); 47 | -------------------------------------------------------------------------------- /app/routes/wh/settings/urls.js: -------------------------------------------------------------------------------- 1 | export default Ember.Route.extend({ 2 | model: function () { 3 | return this.store.find('redirect').then(function (redirects) { 4 | redirects.forEach(function (redirect, index) { 5 | redirect.set('priority', index); 6 | }); 7 | return Ember.RSVP.Promise.resolve(redirects); 8 | }); 9 | }, 10 | setupController: function (controller) { 11 | 12 | var siteName = this.get('session.site.name'); 13 | 14 | window.ENV.firebaseRoot.child("management/sites/" + siteName + "/dns").once('value', function(snapshot) { 15 | controller.set('domain', snapshot.val()); 16 | }); 17 | 18 | this._super.apply(this, arguments); 19 | 20 | } 21 | }); 22 | -------------------------------------------------------------------------------- /app/routes/wordpress.js: -------------------------------------------------------------------------------- 1 | export default Ember.Route.extend({ 2 | setupController: function (controller) { 3 | 4 | controller.set('wxmlDoneClass', 'pending'); 5 | controller.set('wxmlStatus', { 6 | messages: false, 7 | parsingXML: { running: false, class: 'pending' }, 8 | siteInfo: { running: false, class: 'pending' }, 9 | tags: { running: false, class: 'pending' }, 10 | authors: { running: false, class: 'pending' }, 11 | images: { running: false, class: 'pending' }, 12 | posts: { running: false, class: 'pending' }, 13 | pages: { running: false, class: 'pending' }, 14 | firebase: { running: false, class: 'pending' }, 15 | search: { running: false, class: 'pending' }, 16 | }); 17 | 18 | controller.set('isComplete', false); 19 | 20 | controller.convertXml(); 21 | 22 | return this._super.apply(this, arguments); 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /app/serializers/application.js: -------------------------------------------------------------------------------- 1 | export default DS.FirebaseSerializer.extend({ 2 | 3 | // fix legacy webhook relationships for emberfire 4 | // example: { "controls": { "control_id": true } } 5 | normalize: function (type, payload) { 6 | 7 | type.eachRelationship(function (key, relationship) { 8 | if (relationship.options.embedded && Ember.isArray(payload[key])) { 9 | var payloadKeyObject = {}; 10 | payload[key].forEach(function (embed, index) { 11 | payloadKeyObject[window.ENV.firebase.push().key()] = embed; 12 | }); 13 | payload[key] = payloadKeyObject; 14 | } 15 | }); 16 | 17 | return this._super.apply(this, [type, payload]); 18 | }, 19 | 20 | // firebase throws a fit with Ember's prototype extensions. 21 | // this returns the object to a native object 22 | serialize: function () { 23 | var jsonDirty = this._super.apply(this, arguments); 24 | var jsonClean = JSON.parse(JSON.stringify(jsonDirty)); 25 | return jsonClean; 26 | } 27 | 28 | }); 29 | -------------------------------------------------------------------------------- /app/serializers/control-type-group.js: -------------------------------------------------------------------------------- 1 | export default DS.JSONSerializer.extend(); 2 | -------------------------------------------------------------------------------- /app/serializers/control-type.js: -------------------------------------------------------------------------------- 1 | export default DS.JSONSerializer.extend(); 2 | -------------------------------------------------------------------------------- /app/serializers/control.js: -------------------------------------------------------------------------------- 1 | import ApplicationSerializer from 'appkit/serializers/application'; 2 | 3 | export default ApplicationSerializer.extend({ 4 | serialize: function (record) { 5 | 6 | var serializedSubControls = []; 7 | 8 | record.get('controls').forEach(function (control) { 9 | serializedSubControls.push(control.serialize()); 10 | }); 11 | 12 | var json = this._super.apply(this, arguments); 13 | 14 | json.controls = serializedSubControls; 15 | 16 | return json; 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /app/serializers/data.js: -------------------------------------------------------------------------------- 1 | export default DS.JSONSerializer.extend({ 2 | normalize: function (type, hash) { 3 | var newHash; 4 | 5 | // Was using Ember.isArray, but user had `length` as a field name. 6 | if (Ember.$.isArray(hash)) { 7 | newHash = Ember.$.map(hash, this._normalizeSingle); 8 | } else { 9 | newHash = this._normalizeSingle(hash); 10 | } 11 | 12 | return this._super(type, newHash); 13 | }, 14 | serialize: function (record, options) { 15 | return JSON.parse(JSON.stringify(record.get('itemData'))); 16 | }, 17 | _normalizeSingle: function (hash) { 18 | var newHash = { itemData: {} }; 19 | 20 | Ember.$.each(hash, function(key, value) { 21 | if (key === 'id') { 22 | newHash[key] = value; 23 | } else { 24 | newHash.itemData[key] = value; 25 | } 26 | }); 27 | 28 | return newHash; 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /app/serializers/item.js: -------------------------------------------------------------------------------- 1 | import DataSerializer from 'appkit/serializers/data'; 2 | 3 | export default DataSerializer.extend(); 4 | -------------------------------------------------------------------------------- /app/serializers/settings.js: -------------------------------------------------------------------------------- 1 | export default DS.JSONSerializer.extend({ 2 | keyForAttribute: function(attr) { 3 | return Ember.String.underscore(attr); 4 | }, 5 | normalize: function (type, payload) { 6 | 7 | var normalizedPayload = {}; 8 | 9 | Ember.$.each(payload, function(key, value) { 10 | normalizedPayload[Ember.String.camelize(key)] = value; 11 | }); 12 | 13 | return this._super(type, normalizedPayload); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /app/styles/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webhook/webhook-cms/8b58110801bc0d3a749bb230bb9983ee5b77ffcd/app/styles/.gitkeep -------------------------------------------------------------------------------- /app/styles/_app_fonts.sass: -------------------------------------------------------------------------------- 1 | @font-face 2 | font-family: 'Bitter' 3 | src: url('#{$font-path}/bitter.woff') 4 | font-weight: normal 5 | font-style: normal 6 | 7 | @font-face 8 | font-family: 'Bitter' 9 | src: url('#{$font-path}/bitter-bold.woff') 10 | font-weight: bold 11 | font-style: normal 12 | -------------------------------------------------------------------------------- /app/styles/_app_mixin.sass: -------------------------------------------------------------------------------- 1 | // Mixin to build the WH logo, variable is defined in app_variables.sass 2 | 3 | =wh-logo($logo-size: 45px, $logo-color: $link-color) 4 | background-image: $wh-logo 5 | width: $logo-size 6 | height: $logo-size 7 | background-color: $logo-color 8 | background-size: $logo-size * .6 $logo-size * .48 9 | background-position: center center 10 | background-repeat: no-repeat 11 | margin: auto 12 | border-radius: 100% 13 | display: block 14 | vertical-align: middle 15 | 16 | -------------------------------------------------------------------------------- /app/templates/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webhook/webhook-cms/8b58110801bc0d3a749bb230bb9983ee5b77ffcd/app/templates/.gitkeep -------------------------------------------------------------------------------- /app/templates/application.hbs: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 |
11 |

Restart your runserver, then refresh

12 |

Or just click here when you've restarted

13 |

14 | Your local runserver died most likely because of a template error in your frontend code. Unfortunately, this 15 | means that saves you make in the current tab will not regenerate the site until you refresh this page AFTER 16 | you've restarted your runserver. It's a shitty bug, and we're fixing it. For now, bear with us. 17 |

18 |
19 | 20 |
21 | 22 | {{outlet}} 23 | -------------------------------------------------------------------------------- /app/templates/component-test.hbs: -------------------------------------------------------------------------------- 1 | {{#each}} 2 | {{pretty-color name=this}} 3 | {{/each}} 4 | -------------------------------------------------------------------------------- /app/templates/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webhook/webhook-cms/8b58110801bc0d3a749bb230bb9983ee5b77ffcd/app/templates/components/.gitkeep -------------------------------------------------------------------------------- /app/templates/components/audio-player.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/templates/components/audio-upload.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 | {{#if control.value.url}} 4 | {{ control.value.url }} 5 | {{#unless control.disabled}}{{/unless}} 6 | {{audio-player src=control.value.url load="audioLoaded"}} 7 | {{/if}} 8 | 9 | {{#unless control.disabled}} 10 |
11 |
12 |
13 | 14 |
15 |
16 | 17 |
18 |
19 |
20 | 21 | 22 |
23 | 24 |
25 | {{t 'form.control.file.paste'}} 26 | 27 | 28 |
29 | {{/unless}} 30 | 31 |
32 | -------------------------------------------------------------------------------- /app/templates/components/auto-complete.hbs: -------------------------------------------------------------------------------- 1 | {{#if isFew}} 2 | {{view 'sortable' 3 | content=currentSelection 4 | sortArray=control.value 5 | itemTemplate='widgets/relation/few' 6 | tagName='span' 7 | classNames='current-selection' 8 | itemTagName='span' 9 | itemClassNameBindings=':wy-tag content.justAdded:active' 10 | disabled=control.disabled 11 | }} 12 | {{/if}} 13 | 14 | {{#if isMany}} 15 | {{view 'sortable' 16 | content=currentSelection 17 | sortArray=control.value 18 | itemTemplate='widgets/relation/many' 19 | itemClassNameBindings='content.justAdded:active' 20 | disabled=control.disabled 21 | }} 22 | {{/if}} 23 | 24 | {{#if showAutocomplete}} 25 |
26 | 27 | 28 | {{view "auto-complete" placeholder="Add item" filter=control.meta.contentTypeId value=autocompleteValue}} 29 | 30 | {{#if isLoading}} 31 | 32 | {{/if}} 33 | 34 | {{#collection "autocomplete-results" content=results}} 35 | {{{name}}} 36 | {{/collection}} 37 | 38 |
39 | {{/if}} 40 | -------------------------------------------------------------------------------- /app/templates/components/colored-element.hbs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webhook/webhook-cms/8b58110801bc0d3a749bb230bb9983ee5b77ffcd/app/templates/components/colored-element.hbs -------------------------------------------------------------------------------- /app/templates/components/date-time.hbs: -------------------------------------------------------------------------------- 1 | {{#unless meta.hideDate}} 2 | {{input type="date" value=date placeholder="mm/dd/yyyy" disabled=disabled}} 3 | {{/unless}} 4 | 5 | {{#unless meta.hideTime}} 6 | {{input type="time" value=time placeholder="hh:mm:ss" disabled=disabled}} 7 | {{/unless}} 8 | -------------------------------------------------------------------------------- /app/templates/components/download-backup.hbs: -------------------------------------------------------------------------------- 1 | {{#if text}}{{text}}{{else}}{{yield}}{{/if}} 2 | -------------------------------------------------------------------------------- /app/templates/components/embedly-control.hbs: -------------------------------------------------------------------------------- 1 | {{#unless control.disabled}} 2 | {{input type="url" value=url disabled=isFetching action="getEmbed"}} 3 | 4 | 5 | {{/unless}} 6 | 7 | {{#if hasValue}} 8 | 9 | {{#if showPreview}} 10 | 11 | {{/if}} 12 | 13 | {{#if showCode}} 14 | 15 | {{/if}} 16 | 17 | {{#unless control.disabled}}{{/unless}} 18 | 19 |
20 | {{#if showPreview}} 21 | {{#if isVisual}} 22 |
23 |
{{{previewValue}}}
24 |
25 | {{else}} 26 |
{{{previewValue}}}
27 | {{/if}} 28 | {{/if}} 29 | 30 | {{#if showCode}} 31 |
{{dataString}}
32 | {{/if}} 33 |
34 | 35 | {{/if}} 36 | -------------------------------------------------------------------------------- /app/templates/components/file-upload.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 | {{#if control.value.url}} 4 | {{ control.value.url }} 5 | {{#unless control.disabled}}{{/unless}} 6 | {{/if}} 7 | 8 | {{#unless control.disabled}} 9 | 10 |
11 |
12 |
13 | 14 |
15 |
16 | 17 |
18 |
19 |
20 | 21 | 22 |
23 | 24 |
25 | {{t 'form.control.file.paste'}} 26 | 27 | 28 |
29 | 30 | {{/unless}} 31 | 32 |
33 | -------------------------------------------------------------------------------- /app/templates/components/formbuilder-widget.hbs: -------------------------------------------------------------------------------- 1 | {{#if view.useDefaultLayout}} 2 | 3 | {{partial 'widgets/common/label'}} 4 |
5 | 6 | {{!-- Grid widget help is above the control. --}} 7 | {{#if view.isGridWidget}} 8 | {{partial 'widgets/common/help'}} 9 | {{/if}} 10 | 11 | {{partial view.content.controlType.controlPartialPath}} 12 | 13 | {{!-- All other widgets have the help below. --}} 14 | {{#unless view.isGridWidget}} 15 | {{partial 'widgets/common/help'}} 16 | {{/unless}} 17 | 18 | {{#if view.content.widgetErrors}} 19 |
    20 | {{#each view.content.widgetErrors}} 21 |
  • {{this}}
  • 22 | {{/each}} 23 |
24 | {{/if}} 25 | 26 |
27 | 28 | {{else}} 29 | 30 |
31 | {{partial view.content.controlType.controlPartialPath}} 32 |
33 | 34 | {{/if}} 35 | 36 | {{!-- we only set `doEdit` on the formbuilder --}} 37 |
38 | -------------------------------------------------------------------------------- /app/templates/components/geolocation-control.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{input action="parseInput" value=coordsString placeholder="Enter lat, lng coordinates or Google Maps URL" disabled=control.disabled}} 3 |
4 | 5 |
6 | -------------------------------------------------------------------------------- /app/templates/components/grid-control.hbs: -------------------------------------------------------------------------------- 1 | {{#if control.isInFormbuilder}} 2 | 3 |
4 |
5 | 6 | 0 7 | {{t 'form.control.grid.valueName'}} 8 |
9 |
10 | {{#unless control.controls}} 11 |
12 |

{{t 'form.control.grid.emptyTitle'}}

13 |

{{t 'form.control.grid.emptyDesc'}}

14 |
15 | {{/unless}} 16 | {{view "formbuilder-grid" model=control controller=formController}} 17 |
18 |
19 | 20 | {{else}} 21 | 22 | {{view "grid-rows" gridControl=control}} 23 | 24 | {{#if control.isPlaceholder}} 25 |
26 | {{/if}} 27 | 28 | {{/if}} 29 | -------------------------------------------------------------------------------- /app/templates/components/image-upload.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 | 6 | {{#if hasPreview}} 7 |
8 | {{#if control.value.resize_url}} 9 | {{#view 'fluidbox' image=control.value}} 10 | {{resize-image control.value size="150"}} 11 | {{/view}} 12 | {{else}} 13 | 14 | {{/if}} 15 |
16 | {{/if}} 17 | 18 | {{#unless control.disabled}} 19 |
20 | 21 |
22 |
23 | 24 |
25 |
26 | {{/unless}} 27 | 28 |
29 | 30 | {{#unless control.disabled}} 31 | 32 | 33 | {{#if control.value.url}} 34 | 35 | {{/if}} 36 | {{/unless}} 37 |
38 | 39 | {{#unless control.disabled}} 40 |
41 | {{t 'form.control.file.paste'}} 42 | 43 | 44 |
45 | {{/unless}} 46 | 47 |
48 | -------------------------------------------------------------------------------- /app/templates/components/markdown-editor.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{t 'form.control.markdown.syntax'}} 4 | 5 | 6 | 7 | 8 |
9 | 10 |
11 | {{textarea placeholder=placeholder class="wh-form-markdown" value=value disabled=disabled}} 12 |
13 |
14 | 15 |
16 | 17 | {{webhook-modal 18 | show=showImageModal 19 | modalTemplate="widgets/common/imagemodal" 20 | session=session 21 | confirm="handleUpload" 22 | fakeControl=fakeImageControl 23 | }} 24 | -------------------------------------------------------------------------------- /app/templates/components/pretty-color.hbs: -------------------------------------------------------------------------------- 1 | Pretty Color: {{name}} 2 | -------------------------------------------------------------------------------- /app/templates/components/redactor-rte.hbs: -------------------------------------------------------------------------------- 1 | {{textarea id=id placeholder=placeholder disabled=disabled}} 2 | 3 | {{webhook-modal 4 | show=showImageModal 5 | modalTemplate="widgets/common/imagemodal" 6 | session=session 7 | confirm="handleUpload" 8 | fakeControl=fakeImageControl 9 | }} 10 | -------------------------------------------------------------------------------- /app/templates/components/tabular-data.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{#each option in control.meta.options}} 5 | 6 | {{/each}} 7 | {{#unless control.disabled}}{{/unless}} 8 | 9 | 10 | 11 | {{#each row in control.value}} 12 | 13 | {{#each data in row}} 14 | 15 | {{/each}} 16 | {{#unless control.disabled}} 18 | {{/each}} 19 | 20 |
{{option.value}}
{{input value=data.value disabled=control.disabled}}{{/unless}} 17 |
21 | 22 | {{#unless control.disabled}}{{/unless}} 23 | -------------------------------------------------------------------------------- /app/templates/components/webhook-blog.hbs: -------------------------------------------------------------------------------- 1 |
2 |

{{t 'wh.index.updates'}}

3 | 14 |
15 | -------------------------------------------------------------------------------- /app/templates/components/webhook-modal.hbs: -------------------------------------------------------------------------------- 1 | {{#if canClose}}{{/if}} 2 | {{partial modalTemplate}} 3 | -------------------------------------------------------------------------------- /app/templates/confirm-email.hbs: -------------------------------------------------------------------------------- 1 | 29 | -------------------------------------------------------------------------------- /app/templates/create-user.hbs: -------------------------------------------------------------------------------- 1 | 42 | -------------------------------------------------------------------------------- /app/templates/error.hbs: -------------------------------------------------------------------------------- 1 |

Sorry, Something went wrong

2 | {{message}} 3 |
4 | {{stack}}
5 | 
6 | -------------------------------------------------------------------------------- /app/templates/expired.hbs: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /app/templates/form/_changedcontrols.hbs: -------------------------------------------------------------------------------- 1 |
2 | Are you sure you want to do this? 3 |
4 |
5 |

6 | Looks like you're making changes to existing data. This is normally safe, but we suggest downloading a backup before making this change. Also, don't forget to replicate these changes in your templates as well. 7 |

8 | {{download-backup classNames="btn btn-small btn-neutral icon icon-download" text=" Download a quick backup first"}} 9 |
10 |
11 | 12 | 13 |
14 | -------------------------------------------------------------------------------- /app/templates/form/_initialscaffolding.hbs: -------------------------------------------------------------------------------- 1 |
2 | Templates created 3 |
4 |
5 |

6 | {{#if contentType.oneOff}} 7 | We automatically created a starter template for you. You can find it at 8 | /{{buildEnvironment.siteDisplayName}}/pages/{{contentType.id}}.html. 9 | It will teach you the basics of pulling content into your site from the CMS. 10 | {{else}} 11 | We automatically created starter templates in your 12 | /{{buildEnvironment.siteDisplayName}}/templates/{{contentType.id}}/ 13 | folder. They will teach you the basics of pulling content into your 14 | site from the CMS. 15 | {{/if}} 16 |

17 |
18 | 19 |
20 | 21 |
22 | -------------------------------------------------------------------------------- /app/templates/grid-row.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 | {{view.rowIndex}} 4 | 5 | {{#view 'item-cell' context=view.firstControl tagName='span'}} 6 | {{partial controlType.valuePartialPath}} 7 | {{/view}} 8 | 9 | 10 | 11 |
12 | 13 | {{#if view.isActive}} 14 |
15 | {{#each rowControl in view.parentView.gridControl.controls}} 16 | {{view 'grid-widget' 17 | rowControl=rowControl 18 | contentType=view.parentView.context.contentType 19 | values=view.content 20 | gridControl=view.parentView.gridControl 21 | }} 22 | {{/each}} 23 |
24 | 25 | 26 |
27 |
28 | {{/if}} 29 | -------------------------------------------------------------------------------- /app/templates/helper-test.hbs: -------------------------------------------------------------------------------- 1 |

My name is {{reverse-word name}}.

2 | -------------------------------------------------------------------------------- /app/templates/import.hbs: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /app/templates/index.hbs: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /app/templates/loading.hbs: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /app/templates/login.hbs: -------------------------------------------------------------------------------- 1 | 32 | 33 | 40 | 41 |

{{t "login.addLanguage" }}.

42 | 43 | -------------------------------------------------------------------------------- /app/templates/password-reset.hbs: -------------------------------------------------------------------------------- 1 | 31 | -------------------------------------------------------------------------------- /app/templates/reindex.hbs: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /app/templates/resend-email.hbs: -------------------------------------------------------------------------------- 1 | 33 | -------------------------------------------------------------------------------- /app/templates/start.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 |

{{t 'start.title'}}

4 | 5 |
6 |
7 | {{#link-to 'wh.content.all-types' class="btn btn-neutral icon icon-leaf"}} {{t 'start.empty.action'}}{{/link-to}} 8 |
9 |
10 | {{t 'start.empty.instruction'}} 11 |
12 |
13 |
14 | {{#link-to 'theme' class="btn btn-neutral icon icon-rocket"}} {{t 'start.theme.action'}}{{/link-to}} 15 |
16 |
17 | {{t 'start.theme.instruction'}} 18 |
19 |
20 |
21 | {{#select-file action='importWordpressFile' accept='application/xml' class="icon icon-wordpress"}} {{t 'start.wordpress.action'}}{{/select-file}} 22 |
23 |
24 | {{t 'start.wordpress.instruction'}} 25 |
26 |
27 | -------------------------------------------------------------------------------- /app/templates/wh.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 | {{partial "wh/nav"}} 4 | 5 |
6 | 7 | 8 | 12 | 13 | {{#if buildEnvironment.local}} 14 |
{{t 'wh.localNotice'}}
15 | {{/if}} 16 | 17 | 18 |
19 | {{outlet}} 20 |
21 | 22 |
23 | 24 |
25 | -------------------------------------------------------------------------------- /app/templates/wh/content/all-types.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{#link-to 'wh.content.start' class="btn icon icon-plus-sign btn-preview btn-small btn-success"}} {{t 'wh.content.allTypes.addType'}}{{/link-to}} 3 |

{{t 'wh.content.allTypes.title'}}

4 |
5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {{#each}} 19 | {{#link-to 'form' id tagName='tr'}} 20 | 21 | 22 | 23 | 27 | {{/link-to}} 28 | {{/each}} 29 |
{{t 'wh.content.allTypes.contentType'}}{{t 'wh.content.allTypes.id'}}{{t 'wh.content.allTypes.formStyle'}}{{t 'wh.content.allTypes.actions'}}
{{name}}{{id}}{{#if oneOff}}{{t 'wh.content.allTypes.single'}}{{else}}{{t 'wh.content.allTypes.multiple'}}{{/if}} 24 | 25 | 26 |
30 | 31 |
32 | -------------------------------------------------------------------------------- /app/templates/wh/content/start.hbs: -------------------------------------------------------------------------------- 1 |
2 |

{{t 'wh.content.start.title'}}

3 |
4 | 5 |
6 | {{input value=newTypeName placeholderTranslation="wh.content.start.namePlaceholder" class="wy-input-large full-width"}} 7 |
8 |
9 | {{#if isDuplicate}} 10 | {{t 'wh.content.start.duplicate'}} 11 | {{/if}} 12 | {{#if isIdTooLong}} 13 | {{t 'wh.content.start.long' lengthBinding="newTypeId.length"}} 14 | {{/if}} 15 |

{{t 'wh.content.start.instruction'}}

16 | {{#if newTypeName}} 17 |

{{t 'wh.content.start.multiple.title' newTypeNameBinding="newTypeName"}}

18 |

{{t 'wh.content.start.multiple.instruction'}}

19 |
20 |
21 | 25 | 29 |
30 |
31 |
32 | 33 | {{#link-to 'wh.content.start' class="btn btn-small btn-link icon icon-remove-sign"}} {{t 'wh.content.start.cancel'}}{{/link-to}} 34 |
35 | {{/if}} 36 |
37 | -------------------------------------------------------------------------------- /app/templates/wh/content/type/json.hbs: -------------------------------------------------------------------------------- 1 |
2 |

3 | Editing {{content.itemData.name}} JSON 4 |

5 |
6 | 7 |
8 | 9 |
10 | {{#if error}} 11 |
12 |
{{error.name}}
13 |

{{error.message}}

14 |

Try linting your JSON at jsonlint.com or another linting service.

15 |
16 | {{else}} 17 |
18 |
Warning
19 |

This is an advanced feature only available to site owners to directly manipulate data. Consult Webhook Documentation and proceed with caution.

20 |
    21 |
  • If you would like to remove a value, set it to null.
  • 22 |
  • There are no data validations or safeguards when saving.
  • 23 |
  • Reverse relationships are not created when saving.
  • 24 |
25 |
26 | {{/if}} 27 |
28 | 29 |
30 | 31 |
{{textarea value=itemJSON rows=30}}
32 |
33 | 34 |
35 |
36 | 37 | 38 |
39 |
40 | 41 |
42 | -------------------------------------------------------------------------------- /app/templates/wh/content/type/loading.hbs: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /app/templates/wh/index.hbs: -------------------------------------------------------------------------------- 1 | {{#if noTypesLive}} 2 |
3 |
{{t 'wh.index.welcome.noTypesTitle'}}
4 |

{{t 'wh.index.welcome.noTypesMessage'}}

5 |
6 | {{else}} 7 | 8 | {{#unless isEditingMessage}} 9 | 10 |
{{to-markdown settings.siteMessage}}
11 | {{#if session.isOwner}} 12 | 13 | {{/if}} 14 | 15 | {{else}} 16 |
17 | {{markdown-editor value=settings.siteMessage session=session}} 18 |
19 | 20 | 21 | 22 | 23 | {{/unless}} 24 | 25 |
26 | 27 | {{#if buildEnvironment.local}} 28 | {{webhook-blog}} 29 | {{/if}} 30 | 31 | {{#if session.serverMessages}} 32 |
33 |

{{t 'wh.index.history'}}

34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | {{#each session.serverMessages}} 45 | 46 | 47 | 48 | 49 | 50 | {{/each}} 51 | 52 |
TypeMessageDate
{{code}}{{message}}{{format-time timestamp}}
53 | 54 | {{#if moreServerMessages}}{{t 'wh.index.moreHistory'}}{{/if}} 55 |
56 | {{/if}} 57 | {{/if}} 58 | -------------------------------------------------------------------------------- /app/templates/wh/search-global-results.hbs: -------------------------------------------------------------------------------- 1 |
2 |

Search results

3 |
4 | 5 | 6 |
7 | {{#if isLoading}} 8 |
9 | {{else}} 10 | 11 | {{#if debouncedQuery}} 12 | {{#if searchResults}} 13 |

Search results for {{debouncedQuery}}.

14 | {{else}} 15 |

No results for {{debouncedQuery}}.

16 | {{/if}} 17 | {{else}} 18 |

Please enter a search query.

19 | {{/if}} 20 | 21 | {{#if searchResults}} 22 |
    23 | {{#each searchResults}} 24 |
  • 25 | {{#link-to 'wh.content.type.edit' type id }} 26 | {{{ name }}} 27 | {{ type }} 28 | {{/link-to}} 29 |
    30 | {{#each highlights}} 31 |
    {{ key }}
    32 |
    {{{ highlight }}}
    33 | {{/each}} 34 |
    35 |
  • 36 | {{/each}} 37 |
38 | {{/if}} 39 | 40 | {{/if}} 41 |
42 | -------------------------------------------------------------------------------- /app/templates/wh/settings/billing.hbs: -------------------------------------------------------------------------------- 1 |
2 |

{{t 'wh.settings.billing.title'}}

3 |
4 |
5 | {{#if session.billing.isTrial }} 6 |

7 | {{#if session.billing.endTrialIsLastDay}} 8 | {{t 'wh.settings.billing.trial.lastDay'}} 9 | {{else}} 10 | {{t 'wh.settings.billing.trial.days' countBinding="session.billing.endTrialDays"}} 11 | {{/if}} 12 |

13 | {{t 'wh.settings.billing.trial.instruction'}} 14 | {{t 'wh.settings.billing.trial.action'}} 15 | {{/if}} 16 | {{#if session.billing.isPaid }} 17 |

{{t 'wh.settings.billing.paid.title'}}

18 | {{t 'wh.settings.billing.paid.instruction'}} 19 | {{t 'wh.settings.billing.paid.action'}} 20 | {{/if}} 21 |
22 | -------------------------------------------------------------------------------- /app/templates/wh/settings/group-permissions.hbs: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /app/templates/wh/settings/urls-rule.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{input value=view.content.pattern placeholder="^/blog/([^/]+)/" classNameBindings="view.content.isValid::invalid" }} 4 | {{#unless view.content.isValid}} 5 |
Invalid regular expression 6 | {{/unless}} 7 | 8 | {{input value=view.content.destination placeholder="/articles/$1/" }} 9 | 10 | -------------------------------------------------------------------------------- /app/templates/wh/settings/urls.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{t 'wh.settings.urls.title'}} 3 |
4 | 5 |
6 |
7 |
{{t 'wh.settings.urls.infoTitle'}}
8 |

9 | {{t 'wh.settings.urls.infoMessage'}} 10 |

11 |
    12 |
  • {{t 'wh.settings.urls.infoRuleOne'}}
  • 13 |
  • {{t 'wh.settings.urls.infoRuleTwo'}}
  • 14 |
15 |
16 | 17 | {{#unless domain}} 18 |
19 |
{{t 'wh.settings.urls.noDomainTitle'}}
20 |

{{t 'wh.settings.urls.noDomainMessage'}}

21 |
22 | {{/unless}} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | {{view 'sortable-redirect-rules' content=arrangedContent}} 34 |
{{t 'wh.settings.urls.tableRedirect'}}{{t 'wh.settings.urls.tableDestination'}}
35 | 36 | 37 | 38 |
39 | 40 | 41 | 42 |
43 | -------------------------------------------------------------------------------- /app/templates/widgets/_address.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{input placeholder="Street address 1" class="street1" value=view.content.value.street1 disabled=view.content.disabled}} 3 | 4 | 5 | {{input placeholder="address 2" class="street2" value=view.content.value.street2 disabled=view.content.disabled}} 6 | 7 | 8 | {{input placeholder="City" class="city" value=view.content.value.city disabled=view.content.disabled}} 9 | 10 | 11 | {{input placeholder="State / province / region" class="state" value=view.content.value.state disabled=view.content.disabled}} 12 | 13 | 14 | {{input placeholder="Postal / zip code" class="postal" value=view.content.value.zip disabled=view.content.disabled}} 15 | 16 | 17 | {{input placeholder="Country" class="country" value=view.content.value.country disabled=view.content.disabled}} 18 | 19 | -------------------------------------------------------------------------------- /app/templates/widgets/_audio.hbs: -------------------------------------------------------------------------------- 1 | {{audio-upload control=view.content session=controller.session}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/_boolean.hbs: -------------------------------------------------------------------------------- 1 | {{#view 'switch' control=view.content}} 2 | 3 | {{#if view.control.value}} 4 | {{#if view.control.meta.trueLabel}} 5 | {{view.control.meta.trueLabel}} 6 | {{else}} 7 | true 8 | {{/if}} 9 | {{else}} 10 | {{#if view.control.meta.falseLabel}} 11 | {{view.control.meta.falseLabel}} 12 | {{else}} 13 | false 14 | {{/if}} 15 | {{/if}} 16 | 17 | {{/view}} 18 | -------------------------------------------------------------------------------- /app/templates/widgets/_checkbox.hbs: -------------------------------------------------------------------------------- 1 | {{#each option in view.content.meta.options}} 2 | 11 | {{/each}} 12 | -------------------------------------------------------------------------------- /app/templates/widgets/_color.hbs: -------------------------------------------------------------------------------- 1 | {{input type="color" value=view.content.value disabled=view.content.disabled}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/_datetime.hbs: -------------------------------------------------------------------------------- 1 | {{date-time 2 | value=view.content.value 3 | meta=view.content.meta 4 | disabled=view.content.disabled 5 | }} 6 | 7 | {{#unless view.content.meta.hideDate}} 8 | {{#unless view.content.meta.hideTime}} 9 | {{format-time view.content.value}} 10 | {{/unless}} 11 | {{/unless}} 12 | -------------------------------------------------------------------------------- /app/templates/widgets/_email.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{input type="email" placeholder=view.content.placeholder value=view.content.value disabled=view.content.disabled}} 3 | 4 | -------------------------------------------------------------------------------- /app/templates/widgets/_embedly.hbs: -------------------------------------------------------------------------------- 1 | {{embedly-control control=view.content}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/_file.hbs: -------------------------------------------------------------------------------- 1 | {{file-upload control=view.content session=controller.session}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/_gallery.hbs: -------------------------------------------------------------------------------- 1 | {{gallery-upload control=view.content session=controller.session}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/_geolocation.hbs: -------------------------------------------------------------------------------- 1 | {{geolocation-control control=view.content}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/_grid.hbs: -------------------------------------------------------------------------------- 1 | {{grid-control control=view.content store=store formController=controller contentType=type}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/_image.hbs: -------------------------------------------------------------------------------- 1 | {{image-upload control=view.content session=controller.session}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/_instruction.hbs: -------------------------------------------------------------------------------- 1 |

{{#if view.content.label}}{{view.content.label}}{{else}}{{view.content.controlType.name}}{{/if}}

2 | 3 | {{#if view.content.widgetErrors}} 4 | {{#each view.content.widgetErrors}} 5 |

{{this}}

6 | {{/each}} 7 | {{/if}} 8 | 9 | {{to-markdown view.content.help}} 10 | -------------------------------------------------------------------------------- /app/templates/widgets/_layout.hbs: -------------------------------------------------------------------------------- 1 | {{view "select-control" 2 | content=view.content.meta.options 3 | optionLabelPath="content.label" 4 | optionValuePath="content.value" 5 | defaultValue=view.content.meta.defaultValue 6 | value=view.content.value 7 | disabled=view.content.disabled 8 | }} 9 | -------------------------------------------------------------------------------- /app/templates/widgets/_markdown.hbs: -------------------------------------------------------------------------------- 1 | {{markdown-editor placeholder=view.content.placeholder value=view.content.value session=controller.session disabled=view.content.disabled}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/_name.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{input placeholder="First" value=view.content.value.first disabled=view.content.disabled}} 3 | 4 | 5 | {{input placeholder="Last" value=view.content.value.last disabled=view.content.disabled}} 6 | 7 | -------------------------------------------------------------------------------- /app/templates/widgets/_number.hbs: -------------------------------------------------------------------------------- 1 | {{input type="number" placeholder=view.content.placeholder value=view.content.value step="any" name=view.content.name disabled=view.content.disabled}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/_phone.hbs: -------------------------------------------------------------------------------- 1 | {{input type="tel" placeholder="x-(xxx)-xxx-xxxx" value=view.content.value disabled=view.content.disabled}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/_radio.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{#each option in view.content.meta.options}} 3 | 13 | {{/each}} 14 | -------------------------------------------------------------------------------- /app/templates/widgets/_rating.hbs: -------------------------------------------------------------------------------- 1 | {{view 'star-rating' control=view.content}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/_relation.hbs: -------------------------------------------------------------------------------- 1 | {{auto-complete control=view.content store=store}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/_select.hbs: -------------------------------------------------------------------------------- 1 | {{view "select-control" 2 | content=view.content.meta.options 3 | optionLabelPath="content.value" 4 | optionValuePath="content.value" 5 | defaultValue=view.content.meta.defaultValue 6 | value=view.content.value 7 | disabled=view.content.disabled 8 | }} 9 | -------------------------------------------------------------------------------- /app/templates/widgets/_tabular.hbs: -------------------------------------------------------------------------------- 1 | {{tabular-data control=view.content}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/_tag.hbs: -------------------------------------------------------------------------------- 1 |
2 | Dave Snider 3 | Ian Kelly 4 | Mike Horn 5 |
6 | 7 |
8 |
    9 |
  • 10 | Dave snider 11 |
  • 12 |
  • 13 | Ian Kelly 14 |
  • 15 |
  • 16 | Mike Horn 17 |
  • 18 |
19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /app/templates/widgets/_textarea.hbs: -------------------------------------------------------------------------------- 1 | {{textarea placeholder=view.content.placeholder value=view.content.value disabled=view.content.disabled}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/_textfield.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{input placeholder=view.content.placeholder value=view.content.value disabled=view.content.disabled}} 3 | 4 | -------------------------------------------------------------------------------- /app/templates/widgets/_url.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{input type="url" placeholder=view.content.placeholder value=view.content.value disabled=view.content.disabled}} 3 | 4 | -------------------------------------------------------------------------------- /app/templates/widgets/_wysiwyg.hbs: -------------------------------------------------------------------------------- 1 | {{redactor-rte placeholder=view.content.placeholder value=view.content.value options=view.content.meta session=controller.session disabled=view.content.disabled}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/common/_help.hbs: -------------------------------------------------------------------------------- 1 | {{#if view.content.help}}{{to-markdown view.content.help}}{{/if}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/common/_imagemodal.hbs: -------------------------------------------------------------------------------- 1 |
2 | Upload Image 3 |
4 |
5 | {{image-upload 6 | session=session 7 | onDoneUpload="confirm" 8 | control=fakeControl 9 | }} 10 |
11 | -------------------------------------------------------------------------------- /app/templates/widgets/common/_label.hbs: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /app/templates/widgets/common/_minmaxchars.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 | {{input type="number" value=editingControl.meta.min}} 4 |
5 |
6 | 7 | {{input type="number" value=editingControl.meta.max}} 8 |
9 | -------------------------------------------------------------------------------- /app/templates/widgets/info/_address.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/templates/widgets/info/_audio.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/templates/widgets/info/_boolean.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 | 7 | 15 | 16 | 24 | 25 |
26 |
27 | -------------------------------------------------------------------------------- /app/templates/widgets/info/_checkbox.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 | {{#each option in editingControl.meta.indexedOptions}} 7 | 13 | {{/each}} 14 |
15 |
16 | -------------------------------------------------------------------------------- /app/templates/widgets/info/_color.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/templates/widgets/info/_datetime.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 | 9 | 13 |
14 |
15 | -------------------------------------------------------------------------------- /app/templates/widgets/info/_email.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/templates/widgets/info/_embedly.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/templates/widgets/info/_file.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/templates/widgets/info/_gallery.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/templates/widgets/info/_geolocation.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/templates/widgets/info/_grid.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/templates/widgets/info/_image.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/templates/widgets/info/_instruction.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/templates/widgets/info/_layout.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |

What are page layouts?Page layouts allow the user to choose a specific HTML layout to use for each {{model.name}} object in the CMS. These HTML files need to be included in the /{{ model.id }}/layouts/ directory of the content object. By default, if no layout is selected, the /{{model.id}}/individual.html file will be used as normal.

5 | 6 |
7 | {{#each option in editingControl.meta.indexedOptions}} 8 | 19 | {{/each}} 20 |
21 |
22 | -------------------------------------------------------------------------------- /app/templates/widgets/info/_markdown.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | {{partial "widgets/common/minmaxchars"}} 5 |
6 | -------------------------------------------------------------------------------- /app/templates/widgets/info/_name.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/templates/widgets/info/_number.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 | 6 | {{input type="number" valueBinding="editingControl.meta.min"}} 7 |
8 |
9 | 10 | {{input type="number" valueBinding="editingControl.meta.max"}} 11 |
12 |
13 | -------------------------------------------------------------------------------- /app/templates/widgets/info/_phone.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/templates/widgets/info/_radio.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 | {{#each option in editingControl.meta.indexedOptions}} 7 | 17 | {{/each}} 18 |
19 |
20 | -------------------------------------------------------------------------------- /app/templates/widgets/info/_rating.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 | 7 | {{input type="number" valueBinding="editingControl.meta.min"}} 8 |
9 | 10 |
11 | 12 | {{input type="number" valueBinding="editingControl.meta.max"}} 13 |
14 | 15 |
16 | 17 | {{input type="number" valueBinding="editingControl.meta.step"}} 18 |
19 | 20 |
21 | -------------------------------------------------------------------------------- /app/templates/widgets/info/_relation.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 | 6 |
7 | {{view "select" 8 | content=contentTypes 9 | optionValuePath="content.id" 10 | optionLabelPath="content.name" 11 | value=editingControl.meta.contentTypeId 12 | prompt="Select a relation" 13 | }} 14 |
15 |
16 | 20 |
21 |
22 | 26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /app/templates/widgets/info/_select.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 | {{#each option in editingControl.meta.indexedOptions}} 7 | 17 | {{/each}} 18 |
19 |
20 | -------------------------------------------------------------------------------- /app/templates/widgets/info/_tabular.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 | {{#each option in editingControl.meta.indexedOptions}} 7 | 12 | {{/each}} 13 |
14 |
15 | -------------------------------------------------------------------------------- /app/templates/widgets/info/_tag.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/templates/widgets/info/_textarea.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | {{partial "widgets/common/minmaxchars"}} 5 |
6 | -------------------------------------------------------------------------------- /app/templates/widgets/info/_textfield.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | {{partial "widgets/common/minmaxchars"}} 5 |
6 | -------------------------------------------------------------------------------- /app/templates/widgets/info/_url.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/templates/widgets/info/_wysiwyg.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 | 7 | {{input type="number" valueBinding="editingControl.meta.min"}} 8 |
9 |
10 | 11 | {{input type="number" valueBinding="editingControl.meta.max"}} 12 |
13 | 14 |
15 | 16 | 20 | 24 | 28 | 32 | 36 | 40 |
41 | 42 |
43 | -------------------------------------------------------------------------------- /app/templates/widgets/relation/few.hbs: -------------------------------------------------------------------------------- 1 | {{view.content.itemData.name}}{{#unless view.context.control.disabled}}{{/unless}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/relation/many.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{view.content.itemData.name}} 3 | {{#unless view.context.control.disabled}} 4 | 5 | {{/unless}} 6 | 7 | -------------------------------------------------------------------------------- /app/templates/widgets/value/_address.hbs: -------------------------------------------------------------------------------- 1 | {{value.street1}} 2 | {{value.street2}} 3 | {{value.city}} 4 | {{value.state}} 5 | {{value.postal}} 6 | {{value.country}} 7 | -------------------------------------------------------------------------------- /app/templates/widgets/value/_audio.hbs: -------------------------------------------------------------------------------- 1 | {{#if value.url}} 2 | 3 | {{/if}} 4 | -------------------------------------------------------------------------------- /app/templates/widgets/value/_boolean.hbs: -------------------------------------------------------------------------------- 1 | {{value}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/value/_checkbox.hbs: -------------------------------------------------------------------------------- 1 | {{#each value}} 2 | {{#if this.value}}{{this.label}}{{/if}} 3 | {{/each}} 4 | -------------------------------------------------------------------------------- /app/templates/widgets/value/_color.hbs: -------------------------------------------------------------------------------- 1 | {{colored-element color=value}} {{value}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/value/_datetime.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{format-time value format="L LT"}} 3 | 4 | -------------------------------------------------------------------------------- /app/templates/widgets/value/_email.hbs: -------------------------------------------------------------------------------- 1 | {{value}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/value/_embedly.hbs: -------------------------------------------------------------------------------- 1 | {{#if value.title.length}} 2 | {{value.title}} 3 | {{/if}} 4 | -------------------------------------------------------------------------------- /app/templates/widgets/value/_file.hbs: -------------------------------------------------------------------------------- 1 | {{#if value.url}} 2 | 3 | {{/if}} 4 | 5 | -------------------------------------------------------------------------------- /app/templates/widgets/value/_gallery.hbs: -------------------------------------------------------------------------------- 1 | {{#if value.length}} 2 | {{resize-image value.[0] size="30"}} 3 | {{ value.length }} images 4 | {{/if}} 5 | -------------------------------------------------------------------------------- /app/templates/widgets/value/_geolocation.hbs: -------------------------------------------------------------------------------- 1 | {{#if value.latitude}}{{value.latitude}}{{/if}}{{#if value.longitude}}, {{value.longitude}}{{/if}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/value/_grid.hbs: -------------------------------------------------------------------------------- 1 | {{#if value.length}} 2 | {{ value.length }} items 3 | {{/if}} 4 | -------------------------------------------------------------------------------- /app/templates/widgets/value/_image.hbs: -------------------------------------------------------------------------------- 1 | {{resize-image value size="30"}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/value/_instruction.hbs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webhook/webhook-cms/8b58110801bc0d3a749bb230bb9983ee5b77ffcd/app/templates/widgets/value/_instruction.hbs -------------------------------------------------------------------------------- /app/templates/widgets/value/_layout.hbs: -------------------------------------------------------------------------------- 1 | {{value}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/value/_markdown.hbs: -------------------------------------------------------------------------------- 1 | {{truncate-string value 100}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/value/_name.hbs: -------------------------------------------------------------------------------- 1 | {{value.first}} {{value.last}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/value/_number.hbs: -------------------------------------------------------------------------------- 1 | {{value}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/value/_phone.hbs: -------------------------------------------------------------------------------- 1 | {{value}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/value/_radio.hbs: -------------------------------------------------------------------------------- 1 | {{value}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/value/_rating.hbs: -------------------------------------------------------------------------------- 1 | {{value}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/value/_relation.hbs: -------------------------------------------------------------------------------- 1 | {{#if control.meta.isSingle}} 2 | {{view 'relation-value' context=value}} 3 | {{else}} 4 | {{#view 'relation-values' relationKeys=value}} 5 | {{each view.slicedKeys itemView='relation-value'}} 6 | {{#if view.more}} 7 | and {{view.more}} more 8 | {{/if}} 9 | {{/view}} 10 | {{/if}} 11 | -------------------------------------------------------------------------------- /app/templates/widgets/value/_select.hbs: -------------------------------------------------------------------------------- 1 | {{value}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/value/_tabular.hbs: -------------------------------------------------------------------------------- 1 | tabular data 2 | -------------------------------------------------------------------------------- /app/templates/widgets/value/_tag.hbs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webhook/webhook-cms/8b58110801bc0d3a749bb230bb9983ee5b77ffcd/app/templates/widgets/value/_tag.hbs -------------------------------------------------------------------------------- /app/templates/widgets/value/_textarea.hbs: -------------------------------------------------------------------------------- 1 | {{truncate-string value 100}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/value/_textfield.hbs: -------------------------------------------------------------------------------- 1 | {{value}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/value/_url.hbs: -------------------------------------------------------------------------------- 1 | {{#if value.length}} 2 | {{truncate-string value 30}} 3 | {{/if}} 4 | -------------------------------------------------------------------------------- /app/templates/widgets/value/_wysiwyg.hbs: -------------------------------------------------------------------------------- 1 | {{truncate-string value 100}} 2 | -------------------------------------------------------------------------------- /app/templates/widgets/value/relation-item.hbs: -------------------------------------------------------------------------------- 1 | {{#link-to 'wh.content.type.edit' view.contentType.id view.relatedItem.id}}{{view.relatedItem.itemData.name}}{{/link-to}}{{view.comma}} 2 | -------------------------------------------------------------------------------- /app/transforms/json.js: -------------------------------------------------------------------------------- 1 | export default DS.Transform.extend({ 2 | serialize: function (value) { 3 | return value; 4 | }, 5 | deserialize: function (value) { 6 | return Ember.Object.create(Ember.isBlank(value) ? {} : value); 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /app/utils/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webhook/webhook-cms/8b58110801bc0d3a749bb230bb9983ee5b77ffcd/app/utils/.gitkeep -------------------------------------------------------------------------------- /app/utils/ajax.js: -------------------------------------------------------------------------------- 1 | /* global ic */ 2 | export default function ajax(){ 3 | return ic.ajax.apply(null, arguments); 4 | } 5 | -------------------------------------------------------------------------------- /app/utils/meta-options.js: -------------------------------------------------------------------------------- 1 | export default Ember.Object.extend({ 2 | indexedOptions: function () { 3 | return this.get('options').map(function(option, index) { 4 | return { option: option, index: index }; 5 | }); 6 | }.property('options.@each'), 7 | isOneOption: function () { 8 | return this.get('options.length') === 1; 9 | }.property('options.length') 10 | }); 11 | -------------------------------------------------------------------------------- /app/utils/slugger.js: -------------------------------------------------------------------------------- 1 | /*global uslug*/ 2 | export default function slugger (item, type, customUrls) { 3 | var tmpSlug = ''; 4 | tmpSlug = uslug(item.name).toLowerCase(); 5 | 6 | if(customUrls && customUrls.individualUrl) { 7 | tmpSlug = parseCustomUrl(customUrls.individualUrl, item, type) + '/' + tmpSlug; 8 | } 9 | 10 | if(customUrls && customUrls.listUrl) { 11 | tmpSlug = customUrls.listUrl + '/' + tmpSlug; 12 | } else { 13 | tmpSlug = type + '/' + tmpSlug; 14 | } 15 | 16 | return tmpSlug; 17 | } 18 | 19 | function parseCustomUrl (url, object, type) { 20 | var publishDate = object.publish_date ? object.publish_date : object; 21 | 22 | publishDate = moment(publishDate); 23 | 24 | function replacer(match, timeIdent, offset, string){ 25 | if(timeIdent === 'Y') { 26 | return publishDate.format('YYYY').toLowerCase(); 27 | } else if (timeIdent === 'y') { 28 | return publishDate.format('YY').toLowerCase(); 29 | } else if (timeIdent === 'm') { 30 | return publishDate.format('MM').toLowerCase(); 31 | } else if (timeIdent === 'n') { 32 | return publishDate.format('M').toLowerCase(); 33 | } else if (timeIdent === 'F') { 34 | return publishDate.format('MMMM').toLowerCase(); 35 | } else if (timeIdent === 'M') { 36 | return publishDate.format('MMM').toLowerCase(); 37 | } else if (timeIdent === 'd') { 38 | return publishDate.format('DD').toLowerCase(); 39 | } else if (timeIdent === 'j') { 40 | return publishDate.format('D').toLowerCase(); 41 | } else if (timeIdent === 'T') { 42 | return type.toLowerCase(); 43 | } else { 44 | return match; 45 | } 46 | } 47 | 48 | url = url.replace(/#(\w)/g, replacer); 49 | 50 | return url; 51 | } -------------------------------------------------------------------------------- /app/utils/uuid.js: -------------------------------------------------------------------------------- 1 | function s4() { 2 | return Math.floor((1 + Math.random()) * 0x10000) 3 | .toString(16) 4 | .substring(1); 5 | } 6 | 7 | export default function guid() { 8 | return s4() + s4() + '-' + s4() + '-' + s4() + '-' + 9 | s4() + '-' + s4() + s4() + s4(); 10 | } 11 | -------------------------------------------------------------------------------- /app/views/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webhook/webhook-cms/8b58110801bc0d3a749bb230bb9983ee5b77ffcd/app/views/.gitkeep -------------------------------------------------------------------------------- /app/views/animate-collection.js: -------------------------------------------------------------------------------- 1 | export default Ember.CollectionView.extend({ 2 | tagName : "ul", 3 | initialsAdded : 0, 4 | animationLength: 5000, 5 | 6 | willInsertElement: function () { 7 | this.set('initialLength', this.get('content.length')); 8 | }, 9 | 10 | itemViewClass: Ember.View.extend({ 11 | classNames: ['wy-animate-add'], 12 | didInsertElement: function () { 13 | var collectionView = this.get('parentView'); 14 | 15 | if (collectionView.get('initialsAdded') === collectionView.get('initialLength')) { 16 | this.$().addClass('wy-just-added'); 17 | this.set('addedTimeout', Ember.run.later(this, function () { 18 | this.$().removeClass('wy-just-added'); 19 | }, collectionView.get('animationLength'))); 20 | } else { 21 | collectionView.incrementProperty('initialsAdded'); 22 | } 23 | 24 | this.get('context').on('didUpdate', function () { 25 | this.$().addClass('wy-just-updated'); 26 | this.set('updatedTimeout', Ember.run.later(this, function () { 27 | this.$().removeClass('wy-just-updated'); 28 | }, collectionView.get('animationLength'))); 29 | }.bind(this)); 30 | 31 | }, 32 | willDestroyElement: function () { 33 | this.get('context').off('didUpdate'); 34 | Ember.run.cancel(this.get('addedTimeout')); 35 | Ember.run.cancel(this.get('updatedTimeout')); 36 | } 37 | }) 38 | }); 39 | -------------------------------------------------------------------------------- /app/views/autocomplete-results.js: -------------------------------------------------------------------------------- 1 | export default Ember.CollectionView.extend({ 2 | tagName: 'ul', 3 | classNames: ['wy-autocomplete-dropdown'], 4 | 5 | didInsertElement: function () { 6 | this.$().hide(); 7 | }, 8 | 9 | contentChanged: function () { 10 | 11 | if (this.get('content.length')) { 12 | this.get('content.firstObject').set('isSelected', true); 13 | this.$().show(); 14 | } else { 15 | this.$().hide(); 16 | } 17 | 18 | }.observes('content.@each'), 19 | 20 | itemViewClass: Ember.View.extend({ 21 | classNameBindings: [ 22 | 'context.isSelected:on', 23 | 'context.createStub:add' 24 | ], 25 | 26 | mouseEnter: function () { 27 | this.get('parentView.content').setEach('isSelected', false); 28 | this.set('context.isSelected', true); 29 | }, 30 | 31 | click: function () { 32 | this.get('parentView.controller').send('addToSelection', this.get('context')); 33 | } 34 | 35 | }) 36 | }); 37 | -------------------------------------------------------------------------------- /app/views/checkbox-control.js: -------------------------------------------------------------------------------- 1 | export default Ember.Checkbox.extend({ 2 | 3 | stateChanged: function () { 4 | this.set('option.value', this.get('checked')); 5 | this.set('control.value', this.get('control.meta.options').map(function (option) { 6 | return { label: option.label, value: option.value }; 7 | }).toArray()); 8 | }.observes('checked'), 9 | 10 | defaultChanged: function () { 11 | this.set('checked', this.get('option.defaultValue')); 12 | }.observes('option.defaultValue'), 13 | 14 | willInsertElement: function () { 15 | if (this.get('option.value') === undefined) { 16 | this.set('checked', this.getWithDefault('option.defaultValue', false)); 17 | } 18 | } 19 | 20 | }); 21 | -------------------------------------------------------------------------------- /app/views/draggable.js: -------------------------------------------------------------------------------- 1 | export default Ember.View.extend({ 2 | 3 | didInsertElement: function () { 4 | // real dumb way of making sure the content types are there 5 | this.$().one('mouseenter', this.makeDraggable.bind(this)); 6 | }, 7 | 8 | makeDraggable: function () { 9 | 10 | var view = this; 11 | 12 | var sortable; 13 | 14 | // layout and grid control types are not allowed in grid 15 | switch (view.get('controlType.id')) { 16 | case 'layout': 17 | case 'grid': 18 | sortable = Ember.$('form > fieldset > .ui-sortable'); 19 | break; 20 | default: 21 | sortable = Ember.$('form .ui-sortable'); 22 | break; 23 | } 24 | 25 | this.$().draggable({ 26 | helper: function (event) { 27 | 28 | var helper = $('
'), 29 | icon = $('').appendTo(helper); 30 | 31 | helper.width(Ember.$('.wh-content-edit').width()); 32 | 33 | helper.data('id', view.get('controlType.id')); 34 | 35 | icon.addClass($(this).find('a').attr('class')).text($(this).text()); 36 | 37 | return helper; 38 | 39 | }, 40 | appendTo: 'body', 41 | connectToSortable: sortable, 42 | revert: true, 43 | revertDuration: 0 44 | }); 45 | } 46 | 47 | }); 48 | -------------------------------------------------------------------------------- /app/views/error.js: -------------------------------------------------------------------------------- 1 | export default Ember.View.extend({ 2 | didInsertElement: function () { 3 | if(window.Raygun) { 4 | window.Raygun.send(this.controller.model); 5 | } 6 | }, 7 | }); 8 | -------------------------------------------------------------------------------- /app/views/fluidbox.js: -------------------------------------------------------------------------------- 1 | export default Ember.View.extend({ 2 | tagName: 'a', 3 | attributeBindings: ['href'], 4 | href: function () { 5 | var dim = Math.max($(window).height(), $(window).width()), 6 | url = this.get('image.resize_url'); 7 | if (url.indexOf('http://static-cdn.jtvnw.net') === 0) { 8 | var parts = url.split('.'), 9 | ext = parts.length > 1 ? ('.' + parts.pop()) : ''; 10 | 11 | return parts.join('.') + '-' + dim + 'x' + dim + '-a' + ext; 12 | } else { 13 | return url + '=s' + dim; 14 | } 15 | }.property(), 16 | didInsertElement: function () { 17 | this.$().fluidbox({ 18 | stackIndex: 301 19 | }); 20 | } 21 | }); 22 | -------------------------------------------------------------------------------- /app/views/form.js: -------------------------------------------------------------------------------- 1 | export default Ember.View.extend({ 2 | affixNav: function () { 3 | // we have to wait until the template updates. 4 | Ember.run.later(function () { 5 | this.$('[data-spy=affix]').affix(); 6 | }.bind(this), 10); 7 | }, 8 | didInsertElement: function () { 9 | this.get('controller').addObserver('isEditing', this, this.affixNav); 10 | this.affixNav(); 11 | }, 12 | willDestroyElement: function () { 13 | this.get('controller').removeObserver('isEditing', this, this.affixNav); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /app/views/formbuilder-grid.js: -------------------------------------------------------------------------------- 1 | import FormbuilderView from 'appkit/views/formbuilder'; 2 | import FormbuilderWidgetGridView from 'appkit/views/formbuilder-widget-grid'; 3 | 4 | export default FormbuilderView.extend({ 5 | sortableItemsSelector: '> li', 6 | 7 | itemViewClass: FormbuilderWidgetGridView 8 | }); 9 | -------------------------------------------------------------------------------- /app/views/formbuilder-widget-grid.js: -------------------------------------------------------------------------------- 1 | import FormbuilderWidgetView from 'appkit/views/formbuilder-widget'; 2 | 3 | export default FormbuilderWidgetView.extend({ 4 | templateVar: function () { 5 | return '{{ item.%@[0].%@ }}'.fmt(this.get('parentView.model.name'), this.get('content.name')); 6 | }.property('parentView.model.name', 'content.name') 7 | }); 8 | -------------------------------------------------------------------------------- /app/views/formbuilder.js: -------------------------------------------------------------------------------- 1 | import FormbuilderWidgetView from 'appkit/views/formbuilder-widget'; 2 | 3 | export default Ember.CollectionView.extend({ 4 | tagName : "ol", 5 | initialControlsAdded: 0, 6 | animationLength : 500, 7 | 8 | content: function () { 9 | return this.get('model.controls'); 10 | }.property(), 11 | 12 | willInsertElement: function () { 13 | this.set('initialControlsLength', this.get('content.length')); 14 | }, 15 | 16 | didInsertElement : function () { 17 | this.makeSortable(); 18 | }, 19 | 20 | sortableItemsSelector: '> li:not(.wy-control-group-hidden, .wy-control-name-name)', 21 | 22 | makeSortable: function () { 23 | 24 | var view = this; 25 | var controller = this.get('controller'); 26 | 27 | var originalIndex; 28 | 29 | this.$().sortable({ 30 | items : this.get('sortableItemsSelector'), 31 | placeholder: 'wh-form-control-placeholder', 32 | helper : 'clone', 33 | axis : 'y', 34 | 35 | start: function (event, ui) { 36 | 37 | ui.helper.find('.wy-control-group-edit').removeClass('wy-control-group-edit'); 38 | ui.helper.addClass('wh-control-group-dragged'); 39 | ui.helper.find('.wy-tooltip').remove(); 40 | 41 | originalIndex = ui.item.parent().children().index(ui.item); 42 | 43 | }, 44 | update: function (event, ui) { 45 | 46 | var newIndex = ui.item.parent().children().index(ui.item); 47 | 48 | if (ui.item.hasClass('ui-draggable-dragging')) { 49 | 50 | var type = ui.item.data('id'); 51 | 52 | $(this).sortable('cancel'); 53 | ui.item.remove(); 54 | 55 | controller.addControlAtIndex(view.get('model'), type, newIndex); 56 | 57 | } else { 58 | 59 | $(this).sortable('cancel'); 60 | 61 | controller.updateOrder(view.get('model'), originalIndex, newIndex); 62 | 63 | } 64 | 65 | } 66 | }); 67 | 68 | }, 69 | 70 | itemViewClass: FormbuilderWidgetView 71 | }); 72 | -------------------------------------------------------------------------------- /app/views/gallery.js: -------------------------------------------------------------------------------- 1 | export default Ember.CollectionView.extend({ 2 | tagName : "ol", 3 | classNames: ["wy-form-gallery"], 4 | 5 | didInsertElement : function () { 6 | if (!this.get('disabled')) { 7 | this.makeSortable(); 8 | } else { 9 | this.get('content').setEach('disabled', true); 10 | } 11 | }, 12 | 13 | makeSortable: function () { 14 | 15 | var originalindex, self = this; 16 | 17 | this.$().sortable({ 18 | items : "> li", 19 | placeholder: 'wh-form-gallery-placeholder', 20 | scroll: false, 21 | // helper : 'clone', 22 | 23 | start: function (event, ui) { 24 | 25 | ui.helper.find('.wy-control-group-edit').removeClass('wy-control-group-edit'); 26 | ui.helper.find('.wy-tooltip').remove(); 27 | 28 | originalindex = ui.item.parent().children('li').index(ui.item); 29 | 30 | }, 31 | update: function (event, ui) { 32 | 33 | var items = this.get('content'), 34 | images = this.get('images'); 35 | 36 | var newindex = ui.item.parent().children(':not(script)').index(ui.item); 37 | 38 | self.$().sortable('cancel'); 39 | 40 | var image = images.objectAt(originalindex); 41 | images.removeAt(originalindex); 42 | images.insertAt(newindex, image); 43 | 44 | var item = items.objectAt(originalindex); 45 | items.removeAt(originalindex); 46 | items.insertAt(newindex, item); 47 | 48 | }.bind(this) 49 | }); 50 | 51 | }, 52 | 53 | itemViewClass: Ember.View.extend({ 54 | tagName: 'li', 55 | classNameBindings: [ 56 | 'context.progress:loading', 57 | 'context.image.caption:captioned' 58 | ] 59 | }) 60 | }); 61 | -------------------------------------------------------------------------------- /app/views/grid-widget.js: -------------------------------------------------------------------------------- 1 | import WidgetView from 'appkit/views/widget'; 2 | 3 | export default WidgetView.extend({ 4 | 5 | didInsertElement: function () { 6 | // override default so controls named `name` aren't hidden 7 | }, 8 | 9 | content: function () { 10 | 11 | var control = this.get('rowControl'); 12 | var store = control.get('store'); 13 | 14 | // fighting with Ember to copy control 15 | var controlData = control.serialize(); 16 | delete controlData.controls; 17 | controlData.controlType = control.get('controlType'); 18 | 19 | var clone = store.createRecord('control', controlData); 20 | 21 | clone.set('value', this.get('values').get(control.get('name'))); 22 | 23 | return clone; 24 | 25 | }.property(), 26 | 27 | valueChanged: function () { 28 | this.get('values').set(this.get('rowControl.name'), this.get('content.value')); 29 | }.observes('content.value'), 30 | 31 | templateVar: function () { 32 | 33 | var index = this.get('gridControl.value').indexOf(this.get('values')); 34 | 35 | return '{{ item.%@[%@].%@ }}'.fmt(this.get('gridControl.name'), index, this.get('content.name')); 36 | 37 | }.property('content.name', 'gridControl.name', 'gridControl.value.length') 38 | 39 | }); 40 | -------------------------------------------------------------------------------- /app/views/group-panel.js: -------------------------------------------------------------------------------- 1 | export default Ember.View.extend({ 2 | tagName: 'li', 3 | classNameBindings: ['group.isOpen:active'], 4 | 5 | opened: function () { 6 | if (this.get('group.isOpen')) { 7 | $('html, body').animate({ 8 | scrollTop: this.$().offset().top 9 | }, 1000); 10 | } 11 | }.observes('group.isOpen') 12 | }); 13 | -------------------------------------------------------------------------------- /app/views/group-permissions.js: -------------------------------------------------------------------------------- 1 | export default Ember.CollectionView.extend({ 2 | itemViewClass: Ember.View.extend({ 3 | 4 | templateName: 'wh/settings/group-permissions', 5 | 6 | permission: null, 7 | 8 | setPermission: function () { 9 | this.set('permission', this.get('parentView.group.permissions').get(this.get('content.id'))); 10 | }, 11 | 12 | willInsertElement: function () { 13 | this.get('parentView.group').addObserver('permissions.' + this.get('content.id'), this.setPermission.bind(this)); 14 | this.setPermission(); 15 | }, 16 | 17 | willDestroyElement: function () { 18 | this.get('parentView.group').removeObserver('permissions.' + this.get('content.id'), this.setPermission.bind(this)); 19 | }, 20 | 21 | canView: function () { 22 | return ['view', 'draft', 'publish', 'delete'].contains(this.get('permission')); 23 | }.property('permission'), 24 | 25 | canDraft: function () { 26 | return ['draft', 'publish', 'delete'].contains(this.get('permission')); 27 | }.property('permission'), 28 | 29 | canPublish: function () { 30 | return ['publish', 'delete'].contains(this.get('permission')); 31 | }.property('permission'), 32 | 33 | canDelete: function () { 34 | return this.get('permission') === 'delete'; 35 | }.property('permission') 36 | 37 | }) 38 | }); 39 | -------------------------------------------------------------------------------- /app/views/item-cell.js: -------------------------------------------------------------------------------- 1 | export default Ember.View.extend({ 2 | tagName: 'td', 3 | classNameBindings: ['controlClass'], 4 | controlClass: function () { 5 | return 'wh-item-row-' + this.get('context.controlType.widget'); 6 | }.property() 7 | }); 8 | -------------------------------------------------------------------------------- /app/views/item-form.js: -------------------------------------------------------------------------------- 1 | export default Ember.View.extend({ 2 | tagName: 'form', 3 | classNames: ['wy-form-stacked'], 4 | 5 | submit: function (event) { 6 | event.preventDefault(); 7 | this.$().triggerHandler('submit'); 8 | this.get('controller').saveItem(); 9 | }, 10 | 11 | didInsertElement: function () { 12 | this.$('.wy-dropdown-menu').on('click', function () { 13 | $(this).hide(); 14 | }); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /app/views/item-row.js: -------------------------------------------------------------------------------- 1 | export default Ember.View.extend({ 2 | tagName: 'tr', 3 | 4 | click: function () { 5 | var controller = this.get('controller'); 6 | var modelName = controller.get('itemModelName'); 7 | var modelId = this.get('content.id'); 8 | 9 | controller.transitionToRoute('wh.content.type.edit', modelName, modelId); 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /app/views/radio-button.js: -------------------------------------------------------------------------------- 1 | export default Ember.View.extend({ 2 | tagName: "input", 3 | type : "radio", 4 | 5 | attributeBindings: [ 6 | "name", 7 | "type", 8 | "value", 9 | "checked:checked:", 10 | "disabled:disabled" 11 | ], 12 | 13 | click: function () { 14 | this.set('selection', this.get('value')); 15 | }, 16 | 17 | checked: function () { 18 | return this.get('value') === this.get('selection'); 19 | }.property('selection'), 20 | 21 | defaultChanged: function () { 22 | this.set('selection', this.get('defaultValue')); 23 | }.observes('defaultValue'), 24 | 25 | valueChanged: function () { 26 | this.set('defaultValue', this.get('value')); 27 | }.observes('value'), 28 | 29 | willInsertElement: function () { 30 | if (!this.get('selection') && this.get('defaultValue')) { 31 | this.set('selection', this.get('defaultValue')); 32 | } 33 | } 34 | }); 35 | -------------------------------------------------------------------------------- /app/views/relation-value.js: -------------------------------------------------------------------------------- 1 | export default Ember.View.extend({ 2 | 3 | tagName: 'span', 4 | 5 | templateName: 'widgets/value/relation-item', 6 | 7 | willInsertElement: function () { 8 | 9 | var view = this; 10 | var store = this.get('controller').store; 11 | 12 | var context = this.get('context'); 13 | 14 | if (Ember.isEmpty(context)) { 15 | return; 16 | } 17 | 18 | var keyParts = context.split(' '); 19 | var contentTypeId = keyParts[0]; 20 | var itemId = keyParts[1]; 21 | 22 | return store.find('contentType', contentTypeId).then(function (contentType) { 23 | view.set('contentType', contentType); 24 | view.set('relatedItem', store.find(contentType.get('itemModelName'), itemId)); 25 | }); 26 | 27 | }, 28 | 29 | // values are displayed in a list, comma is true if this is not the last item or if there are more items 30 | comma: function () { 31 | if (Ember.isEmpty(this.get('_parentView._parentView.relationKeys'))) { 32 | return; 33 | } 34 | var isLast = this.get('_parentView._parentView.relationKeys.lastObject') === this.get('context'); 35 | return Ember.isEmpty(this.get('_parentView.more')) && isLast ? null : ','; 36 | }.property('relatedItem.id', 'contentType.id', '_parentView.content.@each', '_parentView.more') 37 | 38 | }); 39 | -------------------------------------------------------------------------------- /app/views/relation-values.js: -------------------------------------------------------------------------------- 1 | export default Ember.View.extend({ 2 | 3 | limit: 3, 4 | 5 | slicedKeys: function () { 6 | return this.getWithDefault('relationKeys', []).slice(0, this.get('limit')); 7 | }.property('relationKeys.@each'), 8 | 9 | more: function () { 10 | var delta = this.get('relationKeys.length') - this.get('limit'); 11 | return delta > 0 ? delta : null; 12 | }.property('limit', 'relationKeys.@each') 13 | 14 | }); 15 | -------------------------------------------------------------------------------- /app/views/select-control.js: -------------------------------------------------------------------------------- 1 | export default Ember.Select.extend({ 2 | 3 | // the default only changes when you're on the form builder 4 | // visually reflect change 5 | defaultChanged: function () { 6 | this.set('value', this.get('defaultValue')); 7 | }.observes('defaultValue'), 8 | 9 | willInsertElement: function () { 10 | 11 | // if we don't have a starting value, use the default 12 | // value is null in formbuilder, undefined in new item for some reason. 13 | if (this.get('defaultValue') && (this.get('value') === null || this.get('value') === undefined)) { 14 | this.set('value', this.get('defaultValue')); 15 | } 16 | 17 | // if not starting value and no default value, just use the first item 18 | else if (this.get('defaultValue') === null || this.get('defaultValue') === undefined) { 19 | this.set('value', this.get('content.firstObject.value')); 20 | } 21 | } 22 | 23 | }); 24 | -------------------------------------------------------------------------------- /app/views/sortable-redirect-rules.js: -------------------------------------------------------------------------------- 1 | export default Ember.CollectionView.extend({ 2 | tagName: 'tbody', 3 | 4 | didInsertElement: function () { 5 | 6 | var originalIndex; 7 | var controller = this.get('controller'); 8 | 9 | this.$().sortable({ 10 | helper: 'clone', 11 | axis: 'y', 12 | start: function (event, ui) { 13 | originalIndex = ui.item.parent().children('tr').index(ui.item); 14 | }, 15 | update: function (event, ui) { 16 | 17 | var newIndex = ui.item.parent().children('tr').index(ui.item); 18 | 19 | $(this).sortable('cancel'); 20 | 21 | controller.moveRule(originalIndex, newIndex); 22 | 23 | } 24 | }); 25 | 26 | }, 27 | 28 | itemViewClass: Ember.View.extend({ 29 | templateName: 'wh/settings/urls-rule', 30 | 31 | classNameBindings: [ 32 | 'content.isValid::invalid' 33 | ] 34 | }) 35 | }); 36 | -------------------------------------------------------------------------------- /app/views/sortable.js: -------------------------------------------------------------------------------- 1 | export default Ember.CollectionView.extend({ 2 | tagName : "ol", 3 | 4 | didInsertElement : function () { 5 | if (!this.get('disabled')) { 6 | this.makeSortable(); 7 | } 8 | }, 9 | 10 | makeSortable: function () { 11 | 12 | var content = this.get('sortArray') || this.get('content'); 13 | 14 | var originalIndex; 15 | 16 | this.$().sortable({ 17 | helper: 'clone', 18 | 19 | start: function (event, ui) { 20 | originalIndex = ui.item.parent().children().index(ui.item); 21 | }, 22 | stop: function (event, ui) { 23 | 24 | var newIndex = ui.item.parent().children().index(ui.item); 25 | 26 | $(this).sortable('cancel'); 27 | 28 | var movingItem = content.objectAt(originalIndex); 29 | content.removeAt(originalIndex); 30 | content.insertAt(newIndex, movingItem); 31 | 32 | } 33 | }); 34 | 35 | }, 36 | 37 | createChildView: function(viewClass, attrs) { 38 | 39 | viewClass = Ember.View.extend(); 40 | 41 | if (Ember.isEmpty(this.get('itemTemplate'))) { 42 | viewClass.reopen({ 43 | template: Ember.Handlebars.compile("{{view.content}}") 44 | }); 45 | } else { 46 | viewClass.reopen({ 47 | templateName: this.get('itemTemplate') 48 | }); 49 | } 50 | 51 | if (!Ember.isEmpty(this.get('itemTagName'))) { 52 | viewClass.reopen({ 53 | tagName: this.get('itemTagName') 54 | }); 55 | } 56 | 57 | if (!Ember.isEmpty(this.get('itemClassNameBindings'))) { 58 | viewClass.reopen({ 59 | classNameBindings: this.get('itemClassNameBindings').split(' ') 60 | }); 61 | } else if (!Ember.isEmpty(this.get('itemClassNames'))) { 62 | viewClass.reopen({ 63 | classNames: this.get('itemClassNames') 64 | }); 65 | } 66 | 67 | return this._super(viewClass, attrs); 68 | } 69 | }); 70 | -------------------------------------------------------------------------------- /app/views/switch.js: -------------------------------------------------------------------------------- 1 | export default Ember.View.extend({ 2 | 3 | tagName: 'div', 4 | 5 | classNameBindings: [ 6 | ':wy-switch', 7 | 'control.value:active', 8 | 'control.disabled:disabled' 9 | ], 10 | 11 | click: function () { 12 | 13 | if (this.get('control.disabled')) { 14 | return; 15 | } 16 | 17 | this.toggleProperty('control.value'); 18 | 19 | return false; 20 | }, 21 | 22 | // The control value will not be set in the formbuilder 23 | willInsertElement: function () { 24 | if (Ember.isEmpty(this.get('control.value'))) { 25 | this.set('control.value', this.get('control.meta.defaultValue')); 26 | } 27 | }, 28 | 29 | // In the formbuilder we want to show the default value change 30 | defaultSet: function () { 31 | this.set('control.value', this.get('control.meta.defaultValue')); 32 | }.observes('control.meta.defaultValue') 33 | 34 | }); 35 | -------------------------------------------------------------------------------- /app/views/wh.js: -------------------------------------------------------------------------------- 1 | export default Ember.View.extend({ 2 | didInsertElement: function () { 3 | this.$('[data-spy=affix]').affix(); 4 | } 5 | }); 6 | -------------------------------------------------------------------------------- /app/views/widget.js: -------------------------------------------------------------------------------- 1 | export default Ember.View.extend({ 2 | 3 | templateName: 'components/formbuilder-widget', 4 | 5 | classNames: ['wy-control-group'], 6 | 7 | classNameBindings: [ 8 | 'content.required:wy-control-group-required', 9 | 'content.hidden:wy-control-group-hidden', 10 | 'content.widgetIsValid::wy-control-group-error', 11 | 'controlClass', 12 | 'content.disabled:wy-control-group-disabled', 13 | 'content.isPlaceholder:wy-control-group-placeholder' 14 | ], 15 | 16 | controlClass: function () { 17 | return 'wy-control-group-' + this.get('content.controlType.widget'); 18 | }.property(), 19 | 20 | didInsertElement: function () { 21 | if (this.get('content.name') === 'name') { 22 | this.$().hide(); 23 | } 24 | }, 25 | 26 | templateVar: function () { 27 | return '{{ item.%@ }}'.fmt(this.get('content.name')); 28 | }.property('content.name'), 29 | 30 | setIsWidget: function () { 31 | var widgetVar = 'is_%@_widget'.fmt(this.get('content.controlType.widget')).camelize(); 32 | this.set(widgetVar, true); 33 | }.on('init'), 34 | 35 | useDefaultLayout: function () { 36 | 37 | var widget = this.get('content.controlType.widget'); 38 | 39 | if (widget === 'instruction') { 40 | return false; 41 | } 42 | 43 | return true; 44 | 45 | }.property() 46 | 47 | }); 48 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-app-kit", 3 | "dependencies": { 4 | "jquery": "1.11.x", 5 | "jquery-ui": "1.11.x", 6 | "handlebars": "1.3.x", 7 | "ember": "1.8.1", 8 | "ember-data": "1.0.0-beta.11", 9 | "ember-resolver": "~0.1.5", 10 | "ic-ajax": "~0.3.0", 11 | "loader": "stefanpenner/loader.js#1.0.0", 12 | "ember-cli-shims": "stefanpenner/ember-cli-shims#0.0.2", 13 | "ember-load-initializers": "stefanpenner/ember-load-initializers#0.0.2", 14 | "webhook-js": "*", 15 | "webhook-redactor": "webhook/webhook-redactor", 16 | "firebase": "2.0.x", 17 | "firebase-simple-login": "1.6.x", 18 | "embedly-jquery": "~3.1.1", 19 | "FileSaver.js": "eligrey/FileSaver.js", 20 | "moment": "2.8.x", 21 | "emberfire": "1.3.x", 22 | "fluidbox": "1.4.2", 23 | "webhook-cms-i18n": "webhook/webhook-cms-i18n#2.0.x", 24 | "leaflet": "~0.7.3", 25 | "numeral": "~1.5.3", 26 | "raygun4js": "~1.15.0" 27 | }, 28 | "devDependencies": { 29 | "qunit": "~1.15.0", 30 | "ember-qunit": "~0.1.8", 31 | "wyrm": "~1.0.x", 32 | "ember-testing-httpRespond": "~0.1.1" 33 | }, 34 | "resolutions": { 35 | "firebase": "2.0.x", 36 | "ember": "1.8.1", 37 | "ember-data": "1.0.0-beta.11", 38 | "handlebars": "1.3.x" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /config/environments/development.js: -------------------------------------------------------------------------------- 1 | // Put your development configuration here. 2 | // 3 | // This is useful when using a separate API 4 | // endpoint in development than in production. 5 | // 6 | // window.ENV.public_key = '123456' 7 | 8 | window.ENV.isDevelopment = true; 9 | window.ENV.isProduction = false; 10 | -------------------------------------------------------------------------------- /config/environments/production.js: -------------------------------------------------------------------------------- 1 | // Put your production configuration here. 2 | // 3 | // This is useful when using a separate API 4 | // endpoint in development than in production. 5 | // 6 | // window.ENV.public_key = '123456' 7 | 8 | window.ENV.isDevelopment = false; 9 | window.ENV.isProduction = true; 10 | -------------------------------------------------------------------------------- /config/environments/test.js: -------------------------------------------------------------------------------- 1 | // Put your test configuration here. 2 | // 3 | // This is useful when using a separate API 4 | // endpoint in test than in production. 5 | // 6 | // window.ENV.public_key = '123456' 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app-kit", 3 | "namespace": "appkit", 4 | "APIMethod": "stub", 5 | "proxyURL": "http://localhost:8000", 6 | "version": "0.0.0", 7 | "private": true, 8 | "directories": { 9 | "doc": "doc", 10 | "test": "test" 11 | }, 12 | "scripts": { 13 | "start": "grunt server", 14 | "build": "grunt build:debug", 15 | "test": "grunt test:ci", 16 | "postinstall": "bower install" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git://github.com/stefanpenner/ember-app-kit.git" 21 | }, 22 | "author": "Webhook", 23 | "license": "MIT", 24 | "devDependencies": { 25 | "express": "~3.4.8", 26 | "lockfile": "~>0.4.2", 27 | "bower": "~1.3.12", 28 | "grunt": "~0.4.2", 29 | "grunt-cli": "~0.1.9", 30 | "load-grunt-config": "~0.7.0", 31 | "grunt-contrib-watch": "~0.5.3", 32 | "grunt-contrib-copy": "~0.4.1", 33 | "grunt-contrib-concat": "~0.5.0", 34 | "grunt-contrib-clean": "~0.5.0", 35 | "grunt-contrib-jshint": "~0.8.0", 36 | "grunt-contrib-cssmin": "~0.6.2", 37 | "grunt-preprocess": "~3.0.1", 38 | "grunt-es6-module-transpiler": "~0.6.0", 39 | "grunt-concat-sourcemap": "~0.4.0", 40 | "grunt-concurrent": "~0.4.3", 41 | "grunt-rev": "~0.1.0", 42 | "grunt-ember-templates": "~0.4.23", 43 | "ember-template-compiler": "~1.8.0", 44 | "handlebars": "~1", 45 | "grunt-contrib-testem": "~0.5.16", 46 | "express-namespace": "~0.1.1", 47 | "loom-generators-ember-appkit": "~1.0.5", 48 | "originate": "~0.1.5", 49 | "loom": "~3.1.2", 50 | "connect-livereload": "~0.3.1", 51 | "grunt-contrib-sass": "~0.7.2", 52 | "grunt-es6-import-validate": "0.0.6", 53 | "grunt-contrib-uglify": "~0.7.0", 54 | "grunt-usemin": "~3.0.0" 55 | }, 56 | "dependencies": { 57 | "gapitoken": "~0.1.0", 58 | "request": "~2.33.0", 59 | "mime": "~1.2.11", 60 | "wrench": "~1.5.6", 61 | "async": "~0.2.10", 62 | "grunt-exec": "~0.4.5", 63 | "proxy-middleware": "~0.5.0", 64 | "connect-header": "0.0.5" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /public/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webhook/webhook-cms/8b58110801bc0d3a749bb230bb9983ee5b77ffcd/public/assets/.gitkeep -------------------------------------------------------------------------------- /public/assets/fonts/bitter-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webhook/webhook-cms/8b58110801bc0d3a749bb230bb9983ee5b77ffcd/public/assets/fonts/bitter-bold.woff -------------------------------------------------------------------------------- /public/assets/fonts/bitter.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webhook/webhook-cms/8b58110801bc0d3a749bb230bb9983ee5b77ffcd/public/assets/fonts/bitter.woff -------------------------------------------------------------------------------- /public/assets/fonts/hook.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webhook/webhook-cms/8b58110801bc0d3a749bb230bb9983ee5b77ffcd/public/assets/fonts/hook.eot -------------------------------------------------------------------------------- /public/assets/fonts/hook.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webhook/webhook-cms/8b58110801bc0d3a749bb230bb9983ee5b77ffcd/public/assets/fonts/hook.ttf -------------------------------------------------------------------------------- /public/assets/fonts/hook.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webhook/webhook-cms/8b58110801bc0d3a749bb230bb9983ee5b77ffcd/public/assets/fonts/hook.woff -------------------------------------------------------------------------------- /public/assets/images/Webhook_Graphics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webhook/webhook-cms/8b58110801bc0d3a749bb230bb9983ee5b77ffcd/public/assets/images/Webhook_Graphics.png -------------------------------------------------------------------------------- /public/assets/images/dragon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webhook/webhook-cms/8b58110801bc0d3a749bb230bb9983ee5b77ffcd/public/assets/images/dragon.png -------------------------------------------------------------------------------- /public/assets/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webhook/webhook-cms/8b58110801bc0d3a749bb230bb9983ee5b77ffcd/public/assets/images/favicon.png -------------------------------------------------------------------------------- /public/assets/images/robot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webhook/webhook-cms/8b58110801bc0d3a749bb230bb9983ee5b77ffcd/public/assets/images/robot.png -------------------------------------------------------------------------------- /public/assets/images/wizard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webhook/webhook-cms/8b58110801bc0d3a749bb230bb9983ee5b77ffcd/public/assets/images/wizard.png -------------------------------------------------------------------------------- /public/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /public/humans.txt: -------------------------------------------------------------------------------- 1 | # humanstxt.org/ 2 | # The humans responsible & technology colophon 3 | 4 | # TEAM 5 | 6 | -- -- 7 | 8 | # THANKS 9 | 10 | 11 | 12 | # TECHNOLOGY COLOPHON 13 | 14 | HTML5, CSS3 15 | Ember.js 16 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org/ 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /public/testem.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is dummy file that exists for the sole purpose 3 | * of allowing tests to run directly in the browser as 4 | * well as by Testem. 5 | * 6 | * Testem is configured to run tests directly against 7 | * the test build of index.html, which requires a 8 | * snippet to load the testem.js file: 9 | * 10 | * This has to go after the qunit framework and app 11 | * tests are loaded. 12 | * 13 | * Testem internally supplies this file. However, if you 14 | * run the tests directly in the browser (localhost:8000/tests), 15 | * this file does not exist. 16 | * 17 | * Hence the purpose of this fake file. This file is served 18 | * directly from the express server to satisify the script load. 19 | */ 20 | -------------------------------------------------------------------------------- /tasks/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "location", 6 | "setTimeout", 7 | "Ember", 8 | "Em", 9 | "$", 10 | "QUnit", 11 | "define", 12 | "console", 13 | "require", 14 | "requirejs", 15 | "equal", 16 | "notEqual", 17 | "notStrictEqual", 18 | "test", 19 | "asyncTest", 20 | "testBoth", 21 | "testWithDefault", 22 | "raises", 23 | "throws", 24 | "deepEqual", 25 | "start", 26 | "stop", 27 | "ok", 28 | "strictEqual", 29 | "module", 30 | "process", 31 | "expect", 32 | "visit", 33 | "exists", 34 | "fillIn", 35 | "click", 36 | "find", 37 | "isolatedContainer", 38 | "startApp", 39 | "__dirname" 40 | ], 41 | "node" : false, 42 | "browser" : false, 43 | "boss" : true, 44 | "curly": false, 45 | "debug": false, 46 | "devel": false, 47 | "eqeqeq": true, 48 | "evil": true, 49 | "forin": false, 50 | "immed": false, 51 | "laxbreak": false, 52 | "newcap": true, 53 | "noarg": true, 54 | "noempty": false, 55 | "nonew": false, 56 | "nomen": false, 57 | "onevar": false, 58 | "plusplus": false, 59 | "regexp": false, 60 | "undef": true, 61 | "sub": true, 62 | "strict": false, 63 | "white": false, 64 | "eqnull": true, 65 | "esnext": true 66 | } 67 | -------------------------------------------------------------------------------- /tasks/custom-options/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webhook/webhook-cms/8b58110801bc0d3a749bb230bb9983ee5b77ffcd/tasks/custom-options/.gitkeep -------------------------------------------------------------------------------- /tasks/deploy.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.registerTask('deploy', function() { 3 | grunt.task.run('exec:bower_update'); 4 | grunt.task.run('dist'); 5 | grunt.task.run('push-prod'); 6 | }); 7 | 8 | }; 9 | -------------------------------------------------------------------------------- /tasks/helpers.js: -------------------------------------------------------------------------------- 1 | var grunt = require('grunt'), 2 | _ = grunt.util._, 3 | Helpers = {}; 4 | 5 | // List of package requisits for tasks 6 | // Notated in conjunctive normal form (CNF) 7 | // e.g. ['a', ['b', 'alternative-to-b']] 8 | var taskRequirements = { 9 | coffee: ['grunt-contrib-coffee'], 10 | compass: ['grunt-contrib-compass'], 11 | sass: [['grunt-sass', 'grunt-contrib-sass']], 12 | less: ['grunt-contrib-less'], 13 | stylus: ['grunt-contrib-stylus'], 14 | emberTemplates: ['grunt-ember-templates'], 15 | emblem: ['grunt-emblem'], 16 | emberscript: ['grunt-ember-script'], 17 | imagemin: ['grunt-contrib-imagemin'], 18 | htmlmin: ['grunt-contrib-htmlmin'], 19 | fancySprites: ['grunt-fancy-sprites'], 20 | autoprefixer: ['grunt-autoprefixer'], 21 | rev: ['grunt-rev'], 22 | 'validate-imports': ['grunt-es6-import-validate'], 23 | yuidoc: ['grunt-contrib-yuidoc'] 24 | }; 25 | 26 | // Task fallbacks 27 | // e.g. 'a': ['fallback-a-step-1', 'fallback-a-step-2'] 28 | var taskFallbacks = { 29 | 'imagemin': 'copy:imageminFallback' 30 | }; 31 | 32 | 33 | Helpers.filterAvailableTasks = function(tasks){ 34 | tasks = tasks.map(function(taskName) { 35 | // Maps to task name or fallback if task is unavailable 36 | 37 | var baseName = taskName.split(':')[0]; // e.g. 'coffee' for 'coffee:compile' 38 | var reqs = taskRequirements[baseName]; 39 | var isAvailable = Helpers.isPackageAvailable(reqs); 40 | return isAvailable ? taskName : taskFallbacks[taskName]; 41 | }); 42 | 43 | return _.flatten(_.compact(tasks)); // Remove undefined's and flatten it 44 | }; 45 | 46 | Helpers.isPackageAvailable = function(pkgNames) { 47 | if (!pkgNames) return true; // packages are assumed to exist 48 | 49 | if (!_.isArray(pkgNames)) { pkgNames = [pkgNames]; } 50 | 51 | return _.every(pkgNames, function(pkgNames) { 52 | if (!_.isArray(pkgNames)) { pkgNames = [pkgNames]; } 53 | 54 | return _.any(pkgNames, function(pkgName) { 55 | return !!Helpers.pkg.devDependencies[pkgName]; 56 | }); 57 | }); 58 | }; 59 | 60 | module.exports = Helpers; 61 | -------------------------------------------------------------------------------- /tasks/locking.js: -------------------------------------------------------------------------------- 1 | var lockFile = require('lockfile'); 2 | 3 | module.exports = function(grunt) { 4 | grunt.registerTask('lock', 'Set semaphore for connect server to wait on.', function() { 5 | grunt.file.mkdir('tmp'); 6 | lockFile.lockSync('tmp/connect.lock'); 7 | grunt.log.writeln("Locked - Development server won't answer incoming requests until App Kit is done updating."); 8 | }); 9 | 10 | grunt.registerTask('unlock', 'Release semaphore that connect server waits on.', function() { 11 | lockFile.unlockSync('tmp/connect.lock'); 12 | grunt.log.writeln("Unlocked - Development server now handles requests."); 13 | }); 14 | }; 15 | -------------------------------------------------------------------------------- /tasks/options/autoprefixer.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | app: { 3 | src: 'tmp/result/assets/**/*.css' 4 | } 5 | }; -------------------------------------------------------------------------------- /tasks/options/clean.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'debug': ['tmp'], 3 | 'dist': ['tmp', 'dist'], 4 | 'tmp' : ['.tmp'] 5 | }; 6 | -------------------------------------------------------------------------------- /tasks/options/coffee.js: -------------------------------------------------------------------------------- 1 | // CoffeeScript compilation. This must be enabled by modification 2 | // of Gruntfile.js. 3 | // 4 | // The `bare` option is used since this file will be transpiled 5 | // anyway. In CoffeeScript files, you need to escape out for 6 | // some ES6 features like import and export. For example: 7 | // 8 | // `import User from 'appkit/models/user'` 9 | // 10 | // Post = Em.Object.extend 11 | // init: (userId) -> 12 | // @set 'user', User.findById(userId) 13 | // 14 | // `export default Post` 15 | // 16 | 17 | module.exports = { 18 | "test": { 19 | options: { 20 | bare: true 21 | }, 22 | files: [{ 23 | expand: true, 24 | cwd: 'tests/', 25 | src: '**/*.coffee', 26 | dest: 'tmp/javascript/tests', 27 | ext: '.js' 28 | }] 29 | }, 30 | "app": { 31 | options: { 32 | bare: true 33 | }, 34 | files: [{ 35 | expand: true, 36 | cwd: 'app/', 37 | src: '**/*.coffee', 38 | dest: 'tmp/javascript/app', 39 | ext: '.js' 40 | }] 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /tasks/options/compass.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | sassDir: "app/styles", 4 | cssDir: "tmp/result/assets", 5 | generatedImagesDir: "tmp/result/assets/images/generated", 6 | imagesDir: "public/assets/images", 7 | javascriptsDir: "app", 8 | fontsDir: "public/assets/fonts", 9 | importPath: ['vendor/bourbon/app/assets/stylesheets','vendor/neat/app/assets/stylesheets', 'vendor/wyrm/sass', 'vendor/font-awesome/scss'], 10 | httpImagesPath: "/assets/images", 11 | httpGeneratedImagesPath: "/assets/images/generated", 12 | httpFontsPath: "/assets/fonts", 13 | relativeAssets: false, 14 | debugInfo: true 15 | }, 16 | compile: {} 17 | }; 18 | -------------------------------------------------------------------------------- /tasks/options/concat_sourcemap.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | app: { 3 | src: ['tmp/transpiled/app/**/*.js'], 4 | dest: 'tmp/result/assets/app.js', 5 | options: { 6 | sourcesContent: true 7 | }, 8 | }, 9 | 10 | config: { 11 | src: ['tmp/result/config/**/*.js'], 12 | dest: 'tmp/result/assets/config.js', 13 | options: { 14 | sourcesContent: true 15 | }, 16 | }, 17 | 18 | test: { 19 | src: 'tmp/transpiled/tests/**/*.js', 20 | dest: 'tmp/result/tests/tests.js', 21 | options: { 22 | sourcesContent: true 23 | } 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /tasks/options/concurrent.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // Remaining configuration done in Gruntfile.js 3 | options: { 4 | logConcurrentOutput: true 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /tasks/options/emberTemplates.js: -------------------------------------------------------------------------------- 1 | var grunt = require('grunt'); 2 | 3 | module.exports = { 4 | options: { 5 | templateBasePath: /app\//, 6 | templateFileExtensions: /\.(hbs|hjs|handlebars)/, 7 | templateRegistration: function(name, template) { 8 | return grunt.config.process("define('<%= package.namespace %>/") + name + "', ['exports'], function(__exports__){ __exports__['default'] = " + template + "; });"; 9 | } 10 | }, 11 | debug: { 12 | options: { 13 | precompile: false 14 | }, 15 | src: "app/templates/**/*.{hbs,hjs,handlebars}", 16 | dest: "tmp/result/assets/templates.js" 17 | }, 18 | dist: { 19 | src: "<%= emberTemplates.debug.src %>", 20 | dest: "<%= emberTemplates.debug.dest %>" 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /tasks/options/emberscript.js: -------------------------------------------------------------------------------- 1 | // EmberScript compilation. This must be enabled by modification 2 | // of Gruntfile.js. 3 | // 4 | // The `bare` option is used since this file will be transpiled 5 | // anyway. In EmberScript files, you need to escape out for 6 | // some ES6 features like import and export. For example: 7 | // 8 | // `import User from 'appkit/models/user'` 9 | // 10 | // class Post 11 | // init: (userId) -> 12 | // @user = User.findById(userId) 13 | // 14 | // `export default Post` 15 | // 16 | 17 | module.exports = { 18 | "test": { 19 | options: { 20 | bare: true 21 | }, 22 | files: [{ 23 | expand: true, 24 | cwd: 'tests/', 25 | src: '**/*.em', 26 | dest: 'tmp/javascript/tests', 27 | ext: '.js' 28 | }] 29 | }, 30 | "app": { 31 | options: { 32 | bare: true 33 | }, 34 | files: [{ 35 | expand: true, 36 | cwd: 'app/', 37 | src: '**/*.em', 38 | dest: 'tmp/javascript/app', 39 | ext: '.js' 40 | }] 41 | } 42 | }; -------------------------------------------------------------------------------- /tasks/options/emblem.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | compile: { 3 | files: { 4 | "tmp/result/assets/templates.js": ['app/templates/**/*.{emblem,hbs,hjs,handlebars}'] 5 | }, 6 | options: { 7 | root: 'app/templates/', 8 | dependencies: { 9 | jquery: 'vendor/jquery/jquery.js', 10 | ember: 'vendor/ember/ember.js', 11 | handlebars: 'vendor/handlebars/handlebars.js', 12 | emblem: 'vendor/emblem.js/emblem.js' 13 | } 14 | } 15 | } 16 | }; -------------------------------------------------------------------------------- /tasks/options/exec.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bower_update: 'bower update' 3 | }; 4 | -------------------------------------------------------------------------------- /tasks/options/express-server.js: -------------------------------------------------------------------------------- 1 | var grunt = require('grunt'); 2 | 3 | module.exports = { 4 | options: { 5 | APIMethod: "<%= package.APIMethod %>", // stub or proxy 6 | proxyURL: "<%= package.proxyURL %>", // URL to the API server, if using APIMethod: 'proxy' 7 | proxyPath: "<%= package.proxyPath %>" // path for the API endpoints, default is '/api', if using APIMethod: 'proxy' 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /tasks/options/fancySprites.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = { 4 | create: { 5 | destStyles: 'tmp/sprites', 6 | destSpriteSheets: 'tmp/result/assets/sprites', 7 | files: [{ 8 | src: ['app/sprites/**/*.{png,jpg,jpeg}', '!app/sprites/**/*@2x.{png,jpg,jpeg}'], 9 | spriteSheetName: '1x', 10 | spriteName: function(name) { 11 | return path.basename(name, path.extname(name)); 12 | } 13 | }, { 14 | src: 'app/sprites/**/*@2x.{png,jpg,jpeg}', 15 | spriteSheetName: '2x', 16 | spriteName: function(name) { 17 | return path.basename(name, path.extname(name)).replace(/@2x/, ''); 18 | } 19 | } 20 | ] 21 | } 22 | }; -------------------------------------------------------------------------------- /tasks/options/htmlmin.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | dist: { 3 | options: { 4 | removeComments: true, 5 | collapseWhitespace: true 6 | }, 7 | files: [{ 8 | src: 'dist/index.html', 9 | dest: 'dist/index.html' 10 | }] 11 | } 12 | }; -------------------------------------------------------------------------------- /tasks/options/imagemin.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | dist: { 3 | files: [{ 4 | expand: true, 5 | cwd: 'tmp/result', 6 | src: '**/*.{png,gif,jpg,jpeg}', 7 | dest: 'dist/' 8 | }] 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /tasks/options/jshint.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | app: { 3 | src: [ 4 | 'app/**/*.js' 5 | ], 6 | options: { jshintrc: '.jshintrc' } 7 | }, 8 | 9 | tooling: { 10 | src: [ 11 | 'Gruntfile.js', 12 | 'tasks/**/*.js' 13 | ], 14 | options: { jshintrc: 'tasks/.jshintrc' } 15 | }, 16 | 17 | tests: { 18 | src: [ 19 | 'tests/**/*.js', 20 | ], 21 | options: { jshintrc: 'tests/.jshintrc' } 22 | }, 23 | 24 | options: { 25 | force: true 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /tasks/options/less.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | compile: { 3 | files: [{ 4 | expand: true, 5 | cwd: 'app/styles', 6 | src: ['**/*.less', '!**/_*.less'], 7 | dest: 'tmp/result/assets/', 8 | ext: '.css' 9 | }] 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /tasks/options/preprocess.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | indexHTMLDebugApp: { 3 | src : 'app/index.html', dest : 'tmp/result/index.html', 4 | options: { context: { dist: false, tests: false } } 5 | }, 6 | indexHTMLDebugTests: { 7 | src : 'app/index.html', dest : 'tmp/result/tests/index.html', 8 | options: { context: { dist: false, tests: true } } 9 | }, 10 | indexHTMLDistApp: { 11 | src : 'app/index.html', dest : 'tmp/result/index.html', 12 | options: { context: { dist: true, tests: false } } 13 | }, 14 | indexHTMLDistTests: { 15 | src : 'app/index.html', dest : 'tmp/result/tests/index.html', 16 | options: { context: { dist: true, tests: true } } 17 | } 18 | }; -------------------------------------------------------------------------------- /tasks/options/rev.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | dist: { 3 | files: { 4 | src: [ 5 | 'dist/assets/config.min.js', 6 | 'dist/assets/app.min.js', 7 | 'dist/assets/vendor.min.js', 8 | 'dist/assets/app.min.css' 9 | ] 10 | } 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /tasks/options/sass.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | compile: { 3 | options: { 4 | style: 'expanded', 5 | loadPath: ['vendor/bourbon/dist', 'vendor/neat/app/assets/stylesheets', 'vendor/wyrm/sass', 'vendor/font-awesome/scss'] 6 | }, 7 | files: [{ 8 | expand: true, 9 | cwd: 'app/styles', 10 | src: ['**/*.{scss,sass}', '!**/_*.{scss,sass}'], 11 | dest: 'tmp/result/assets/', 12 | ext: '.css' 13 | }] 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /tasks/options/stylus.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | compile: { 3 | files: [{ 4 | expand: true, 5 | cwd: 'app/styles', 6 | src: ['**/*.styl', '!**/_*.styl'], 7 | dest: 'tmp/result/assets/', 8 | ext: '.css' 9 | }] 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /tasks/options/transpile.js: -------------------------------------------------------------------------------- 1 | var grunt = require('grunt'); 2 | 3 | module.exports = { 4 | "tests": { 5 | type: 'amd', 6 | moduleName: function(path) { 7 | return grunt.config.process('<%= package.namespace %>/tests/') + path; 8 | }, 9 | files: [{ 10 | expand: true, 11 | cwd: 'tmp/javascript/tests/', 12 | src: '**/*.js', 13 | dest: 'tmp/transpiled/tests/' 14 | }] 15 | }, 16 | "app": { 17 | type: 'amd', 18 | moduleName: function(path) { 19 | return grunt.config.process('<%= package.namespace %>/') + path; 20 | }, 21 | files: [{ 22 | expand: true, 23 | cwd: 'tmp/javascript/app/', 24 | src: '**/*.js', 25 | dest: 'tmp/transpiled/app/' 26 | }] 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /tasks/options/uglify.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | sourceMap: true 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /tasks/options/usemin.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | html: ['dist/index.html'], 3 | }; 4 | -------------------------------------------------------------------------------- /tasks/options/useminPrepare.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | html: 'tmp/result/index.html', 3 | options: { 4 | dest: 'dist/', 5 | flow: { 6 | steps: { 7 | js: ['concat', 'uglifyjs'], 8 | css: ['concat', 'cssmin'] 9 | }, 10 | post: { 11 | js: [{ 12 | name: 'concat', 13 | createConfig: function(context, block) { 14 | context.options.generated.files.forEach(function(file) { 15 | if(file.dest.indexOf('.min.js') !== -1) { 16 | file.dest = file.dest.replace('.min.js', '.js'); 17 | } 18 | }); 19 | } 20 | }, { 21 | name: 'uglify', 22 | createConfig: function(context, block) { 23 | context.options.generated.files.forEach(function(file) { 24 | if(file.src.indexOf('.min.js') !== -1) { 25 | file.src = file.src.replace('.min.js', '.js'); 26 | } else if (Array.isArray(file.src)) { 27 | var newArray = []; 28 | file.src.forEach(function(realsrc) { 29 | newArray.push(realsrc.replace('.min.js', '.js')); 30 | }); 31 | file.src = newArray; 32 | } 33 | }); 34 | } 35 | }] 36 | } 37 | } 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /tasks/options/validate-imports.js: -------------------------------------------------------------------------------- 1 | var grunt = require('grunt'); 2 | 3 | module.exports = { 4 | options: { 5 | whitelist: { 6 | 'ember/resolver': ['default'], 7 | 'ember/load-initializers': ['default'], 8 | 'ember-qunit': ['moduleForComponent', 'moduleForModel', 'moduleFor', 'test', 'default'], 9 | 'ic-ajax': ['default', 'request', 'raw', 'defineFixture', 'lookupFixture'], 10 | } 11 | }, 12 | 13 | app: { 14 | options: { 15 | moduleName: function (name) { 16 | return grunt.config.process('<%= package.namespace %>/') + name; 17 | } 18 | }, 19 | files: [{ 20 | expand: true, 21 | cwd: 'app', 22 | src: ['**/*.js'] 23 | }] 24 | }, 25 | 26 | tests: { 27 | options: { 28 | moduleName: function (name) { 29 | // Trim of the leading app/ from app modules 30 | if (name.slice(0, 4) === 'app/') { 31 | name = name.slice(4); 32 | } 33 | return grunt.config.process('<%= package.namespace %>/') + name; 34 | } 35 | }, 36 | // Test files reference app files so we have to make sure we pull in both sets 37 | files: [{ 38 | expand: true, 39 | cwd: '.', 40 | src: ['tests/**/*.js'] 41 | }, { 42 | expand: true, 43 | cwd: '.', 44 | src: ['app/**/*.js'] 45 | }] 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /tasks/options/watch.js: -------------------------------------------------------------------------------- 1 | var Helpers = require('../helpers'), 2 | filterAvailable = Helpers.filterAvailableTasks, 3 | LIVERELOAD_PORT = 35729, 4 | liveReloadPort = (parseInt(process.env.PORT || 8000, 10) - 8000) + LIVERELOAD_PORT; 5 | 6 | var docs = '{app}/**/*.{js,coffee,em}', 7 | scripts = '{app,tests,config}/**/*.{js,coffee,em}', 8 | templates = 'app/templates/**/*.{hbs,handlebars,hjs,emblem}', 9 | sprites = 'app/sprites/**/*.{png,jpg,jpeg}', 10 | styles = ['app/styles/**/*.{css,sass,scss,less,styl}','vendor/wyrm/**/*.{css,sass,scss,less,styl}'], 11 | indexHTML = 'app/index.html', 12 | other = '{app,tests,public}/**/*', 13 | bowerFile = 'bower.json', 14 | npmFile = 'package.json'; 15 | 16 | module.exports = { 17 | scripts: { 18 | files: [scripts], 19 | tasks: ['lock', 'buildScripts', 'unlock'] 20 | }, 21 | templates: { 22 | files: [templates], 23 | tasks: ['lock', 'buildTemplates:debug', 'unlock'] 24 | }, 25 | sprites: { 26 | files: [sprites], 27 | tasks: filterAvailable(['lock', 'fancySprites:create', 'unlock']) 28 | }, 29 | styles: { 30 | files: [styles], 31 | tasks: ['lock', 'buildStyles', 'unlock'] 32 | }, 33 | indexHTML: { 34 | files: [indexHTML], 35 | tasks: ['lock', 'buildIndexHTML:debug', 'unlock'] 36 | }, 37 | docs: { 38 | files: [docs], 39 | tasks: ['lock', 'buildDocs', 'unlock'] 40 | }, 41 | other: { 42 | files: [other, '!'+scripts, '!'+templates, '!'+styles[0], '!'+styles[1], '!'+indexHTML, bowerFile, npmFile], 43 | tasks: ['lock', 'build:debug', 'unlock'] 44 | }, 45 | 46 | options: { 47 | // No need to debounce 48 | debounceDelay: 0, 49 | // When we don't have inotify 50 | interval: 100, 51 | livereload: liveReloadPort 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /testem.json: -------------------------------------------------------------------------------- 1 | { 2 | "framework": "qunit", 3 | "test_page": "tmp/result/tests/index.html", 4 | "routes": { 5 | "/tests/tests.js": "tmp/result/tests/tests.js", 6 | "/assets/app.js": "tmp/result/assets/app.js", 7 | "/assets/templates.js": "tmp/result/assets/templates.js", 8 | "/assets/app.css": "tmp/result/assets/app.css" 9 | }, 10 | "src_files": [ 11 | "tmp/result/**/*.js" 12 | ], 13 | "launch_in_dev": ["PhantomJS", "Chrome"] 14 | } 15 | -------------------------------------------------------------------------------- /tests/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "location", 6 | "setTimeout", 7 | "Ember", 8 | "Em", 9 | "$", 10 | "QUnit", 11 | "define", 12 | "console", 13 | "require", 14 | "requirejs", 15 | "equal", 16 | "notEqual", 17 | "notStrictEqual", 18 | "test", 19 | "asyncTest", 20 | "testBoth", 21 | "testWithDefault", 22 | "raises", 23 | "throws", 24 | "deepEqual", 25 | "start", 26 | "stop", 27 | "ok", 28 | "strictEqual", 29 | "module", 30 | "process", 31 | "expect", 32 | "visit", 33 | "exists", 34 | "fillIn", 35 | "click", 36 | "keyEvent", 37 | "find", 38 | "wait", 39 | "DS", 40 | "keyEvent", 41 | "isolatedContainer", 42 | "startApp", 43 | "andThen" 44 | ], 45 | "node" : false, 46 | "browser" : false, 47 | "boss" : true, 48 | "curly": false, 49 | "debug": false, 50 | "devel": false, 51 | "eqeqeq": true, 52 | "evil": true, 53 | "forin": false, 54 | "immed": false, 55 | "laxbreak": false, 56 | "newcap": true, 57 | "noarg": true, 58 | "noempty": false, 59 | "nonew": false, 60 | "nomen": false, 61 | "onevar": false, 62 | "plusplus": false, 63 | "regexp": false, 64 | "undef": true, 65 | "sub": true, 66 | "strict": false, 67 | "white": false, 68 | "eqnull": true, 69 | "esnext": true 70 | } 71 | -------------------------------------------------------------------------------- /tests/acceptance/index-test.js: -------------------------------------------------------------------------------- 1 | var App; 2 | 3 | module('Acceptances - Index', { 4 | setup: function(){ 5 | App = startApp(); 6 | }, 7 | teardown: function() { 8 | Ember.run(App, 'destroy'); 9 | } 10 | }); 11 | 12 | asyncTest('index renders', function(){ 13 | expect(1); 14 | 15 | visit('/').then(function(){ 16 | var loginForm = find('.wh-login'); 17 | equal(loginForm.length, 0); 18 | start(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /tests/ember-shim.js: -------------------------------------------------------------------------------- 1 | define('ember', [ ], function(){ 2 | return Ember; 3 | }); 4 | -------------------------------------------------------------------------------- /tests/helpers/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember/resolver'; 2 | 3 | var resolver = Resolver.create(); 4 | 5 | resolver.namespace = { 6 | modulePrefix: 'appkit' 7 | }; 8 | 9 | export default resolver; 10 | -------------------------------------------------------------------------------- /tests/helpers/start-app.js: -------------------------------------------------------------------------------- 1 | import Application from 'appkit/app'; 2 | import Router from 'appkit/router'; 3 | 4 | function startApp(attrs) { 5 | var App; 6 | 7 | var attributes = Ember.merge({ 8 | // useful Test defaults 9 | rootElement: '#ember-testing', 10 | LOG_ACTIVE_GENERATION:false, 11 | LOG_VIEW_LOOKUPS: false 12 | }, attrs); // but you can override; 13 | 14 | Router.reopen({ 15 | location: 'none' 16 | }); 17 | 18 | Ember.run(function(){ 19 | App = Application.create(attributes); 20 | App.setupForTesting(); 21 | App.injectTestHelpers(); 22 | }); 23 | 24 | App.reset(); // this shouldn't be needed, i want to be able to "start an app at a specific URL" 25 | 26 | return App; 27 | } 28 | 29 | export default startApp; 30 | -------------------------------------------------------------------------------- /tests/qunit-shim.js: -------------------------------------------------------------------------------- 1 | define('qunit', [ ], function(){ 2 | return QUnit; 3 | }); 4 | -------------------------------------------------------------------------------- /tests/test-helper.js: -------------------------------------------------------------------------------- 1 | document.write('
'); 2 | 3 | Ember.testing = true; 4 | 5 | var resolver = require('appkit/tests/helpers/resolver')['default']; 6 | require('ember-qunit').setResolver(resolver); 7 | 8 | 9 | window.startApp = require('appkit/tests/helpers/start-app')['default']; 10 | window.isolatedContainer = require('ember-qunit/isolated-container')['default']; 11 | 12 | function exists(selector) { 13 | return !!find(selector).length; 14 | } 15 | 16 | function getAssertionMessage(actual, expected, message) { 17 | return message || QUnit.jsDump.parse(expected) + " expected but was " + QUnit.jsDump.parse(actual); 18 | } 19 | 20 | function equal(actual, expected, message) { 21 | message = getAssertionMessage(actual, expected, message); 22 | QUnit.equal.call(this, actual, expected, message); 23 | } 24 | 25 | function strictEqual(actual, expected, message) { 26 | message = getAssertionMessage(actual, expected, message); 27 | QUnit.strictEqual.call(this, actual, expected, message); 28 | } 29 | 30 | window.exists = exists; 31 | window.equal = equal; 32 | window.strictEqual = strictEqual; 33 | -------------------------------------------------------------------------------- /tests/test-loader.js: -------------------------------------------------------------------------------- 1 | // TODO: load based on params 2 | Ember.keys(requirejs._eak_seen).filter(function(key) { 3 | return (/\-test/).test(key); 4 | }).forEach(function(moduleName) { 5 | require(moduleName, null, null, true); 6 | }); 7 | -------------------------------------------------------------------------------- /tests/unit/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webhook/webhook-cms/8b58110801bc0d3a749bb230bb9983ee5b77ffcd/tests/unit/.gitkeep -------------------------------------------------------------------------------- /tests/unit/routes/application-test.js: -------------------------------------------------------------------------------- 1 | import { test, moduleFor } from 'ember-qunit'; 2 | 3 | import Application from 'appkit/routes/application'; 4 | 5 | moduleFor('route:application', "Unit - ApplicationRoute"); 6 | 7 | test("it exists", function(){ 8 | ok(this.subject() instanceof Application); 9 | }); 10 | --------------------------------------------------------------------------------