Merge branch 'master' into ember

Conflicts:
	.gitignore
	Gruntfile.js
	bower.json
	core/client/assets/lib/showdown/extensions/ghostdown.js
	core/client/assets/vendor/showdown/extensions/ghostdown.js
	core/client/router.js
	core/clientold/assets/vendor/chart.min.js
	core/clientold/assets/vendor/codemirror/addon/mode/overlay.js
	core/clientold/assets/vendor/codemirror/codemirror.js
	core/clientold/assets/vendor/codemirror/mode/gfm/gfm.js
	core/clientold/assets/vendor/codemirror/mode/gfm/index.html
	core/clientold/assets/vendor/codemirror/mode/gfm/test.js
	core/clientold/assets/vendor/codemirror/mode/markdown/index.html
	core/clientold/assets/vendor/codemirror/mode/markdown/markdown.js
	core/clientold/assets/vendor/codemirror/mode/markdown/test.js
	core/clientold/assets/vendor/countable.js
	core/clientold/assets/vendor/fastclick.js
	core/clientold/assets/vendor/icheck/jquery.icheck.min.js
	core/clientold/assets/vendor/jquery.hammer.min.js
	core/clientold/assets/vendor/nprogress.js
	core/clientold/assets/vendor/packery.pkgd.min.js
	core/clientold/assets/vendor/showdown/extensions/ghostdown.js
	core/clientold/assets/vendor/showdown/showdown.js
	core/clientold/assets/vendor/validator-client.js
	core/test/unit/client_ghostdown_spec.js
	core/test/unit/client_showdown_int_spec.js
This commit is contained in:
Hannah Wolfe 2014-03-05 23:10:44 +00:00
commit 31f41822e0
127 changed files with 1978 additions and 72496 deletions

View File

@ -1,3 +0,0 @@
{
"directory": "core/shared/vendor"
}

14
.gitignore vendored
View File

@ -13,6 +13,7 @@ results
npm-debug.log
node_modules
bower_components
.idea/*
*.iml
@ -31,14 +32,23 @@ projectFilesBackup
.dist
.tmp
/core/clientold/tpl/hbs-tpl.js
/core/clientold/assets/css
.sass-cache/
/core/clientold/tpl/hbs-tpl.js
/core/clientold/assets/vendor
/core/clientold/assets/css
/core/clientold/assets/sass/config.rb
/core/clientold/assets/sass/layouts/config.rb
/core/clientold/assets/sass/modules/config.rb
/core/clientold/assets/sass/modules/bourbon
/core/clientold/assets/sass/modules/bourbon/*
/core/client/assets/vendor
/core/client/assets/css
/core/client/assets/sass/config.rb
/core/client/assets/sass/layouts/config.rb
/core/client/assets/sass/modules/config.rb
/core/client/assets/sass/modules/bourbon
/core/client/assets/sass/modules/bourbon/*
/core/server/data/export/exported*
/docs
/_site

View File

@ -26,4 +26,5 @@ CONTRIBUTING.md
SECURITY.md
.travis.yml
Gemfile*
*.html
*.html
bower_components/**

View File

@ -1,11 +1,11 @@
# Contributing to Ghost
So you're interested in giving us a hand? That's awesome! We've put together some brief guidelines that should help
So you're interested in giving us a hand? That's awesome! We've put together some brief guidelines that should help
you get started quickly and easily.
There are lots and lots of ways to get involved, this document covers:
* [raising issues](#raising-issues)
* [raising issues](#raising-issues)
* [bug reports](#bugs)
* [feature requests](#features)
* [change requests](#changes)
@ -19,14 +19,14 @@ There are lots and lots of ways to get involved, this document covers:
<a name="raising-issues"></a>
## Reporting An Issue
If you're about to raise an issue because think you've found a problem with Ghost, or you'd like to make a request
If you're about to raise an issue because think you've found a problem with Ghost, or you'd like to make a request
for a new feature in the codebase, or any other reason… please read this first.
The GitHub issue tracker is the preferred channel for [bug reports](#bugs),
[feature requests](#features), [change requests](#changes) and [submitting pull
requests](#pull-requests), but please respect the following restrictions:
* Please **search for existing issues**. Help us keep duplicate issues to a minimum by checking to see if someone
* Please **search for existing issues**. Help us keep duplicate issues to a minimum by checking to see if someone
has already reported your problem or requested your idea.
* Please **do not** use the issue tracker for personal support requests (use
@ -51,13 +51,13 @@ Guidelines for bug reports:
3. **Isolate the problem** &mdash; ideally create a [reduced test
case](http://css-tricks.com/6263-reduced-test-cases/) and a live example.
4. **Include a screencast if relevant** - Is your issue about a design or front end feature or bug? The most
helpful thing in the world is if we can *see* what you're talking about.
4. **Include a screencast if relevant** - Is your issue about a design or front end feature or bug? The most
helpful thing in the world is if we can *see* what you're talking about.
Use [LICEcap](http://www.cockos.com/licecap/) to quickly and easily record a short screencast (24fps) and save it as an animated gif! Embed it directly into your GitHub issue. Kapow.
5. Use the Bug Report template below or [click this link](https://github.com/TryGhost/Ghost/issues/new?title=Bug%3A&body=%23%23%23%20Issue%20Summary%0A%0A%23%23%23%20Steps%20to%20Reproduce%0A%0A1.%20This%20is%20the%20first%20step%0A%0AThis%20is%20a%20bug%20because...%0A%0A%23%23%23%20Technical%20details%0A%0A*%20Ghost%20Version%3A%20master%20-%20latest%20commit%3A%20%20INSERT%20COMMIT%20REF%0A*%20Client%20OS%3A%20%0A*%20Server%20OS%3A%20%0A*%20Node%20Version%3A%20%0A*%20Browser%3A) to start creating a bug report with the template automatically.
A good bug report shouldn't leave others needing to chase you up for more information. Be sure to include the
A good bug report shouldn't leave others needing to chase you up for more information. Be sure to include the
details of your environment.
Here is a [real example](https://github.com/TryGhost/Ghost/issues/413)
@ -80,7 +80,7 @@ suitable, include the steps required to reproduce the bug.
Any other information you want to share that is relevant to the issue being
reported. Especially, why do you consider this to be a bug? What do you expect to happen instead?
### Technical details:
### Technical details:
* Ghost Version: master (latest commit: 590ba48988b51b9c5e8d99afbb84c997436d7f21)
* Client OS: Mac OS X 10.8.4
@ -95,38 +95,38 @@ reported. Especially, why do you consider this to be a bug? What do you expect t
Feature requests are welcome. Before you submit one be sure to have:
1. Read the [Roadmap](https://github.com/TryGhost/Ghost/wiki/Roadmap) and
[Planned Features](https://github.com/TryGhost/Ghost/wiki/Planned-Features) listing, **use the GitHub search** and
[Planned Features](https://github.com/TryGhost/Ghost/wiki/Planned-Features) listing, **use the GitHub search** and
check the feature hasn't already been requested.
2. Take a moment to think about whether your idea fits with the scope and aims of the project, or if it might
2. Take a moment to think about whether your idea fits with the scope and aims of the project, or if it might
better fit being an app/plugin.
3. Remember, it's up to *you* to make a strong case to convince the project's leaders of the merits of this
feature. Please provide as much detail and context as possible, this means explaining the use case and why it is
likely to be common.
3. Remember, it's up to *you* to make a strong case to convince the project's leaders of the merits of this
feature. Please provide as much detail and context as possible, this means explaining the use case and why it is
likely to be common.
4. Clearly indicate whether this is a feature request for Ghost admin, or for themes or apps.
<a name="changes"></a>
### Change Requests
Change requests cover both architectural and functional changes to how Ghost works. If you have an idea for a
Change requests cover both architectural and functional changes to how Ghost works. If you have an idea for a
new or different dependency, a refactor, or an improvement to a feature, etc - please be sure to:
1. **Use the GitHub search** and check someone else didn't get there first
2. Take a moment to think about the best way to make a case for, and explain what you're thinking. Are you sure
2. Take a moment to think about the best way to make a case for, and explain what you're thinking. Are you sure
this shouldn't really be a [bug report](#bug-reports) or a [feature request](#feature-requests)? Is it really one
idea or is it many? What's the context? What problem are you solving? Why is what you are suggesting better than
what's already there? Does it fit with the Roadmap?
what's already there? Does it fit with the Roadmap?
<a name="pull-requests"></a>
### Submitting Pull Requests
Pull requests are awesome. If you're looking to raise a PR for something which doesn't have an open issue, please think carefully about [raising an issue](#raising-issues) which your PR can close, especially if you're fixing a bug. This makes it more likely that there will be enough information available for your PR to be properly tested and merged. To make sure your PR is accepted as quickly as possible, you should be sure to have read
Pull requests are awesome. If you're looking to raise a PR for something which doesn't have an open issue, please think carefully about [raising an issue](#raising-issues) which your PR can close, especially if you're fixing a bug. This makes it more likely that there will be enough information available for your PR to be properly tested and merged. To make sure your PR is accepted as quickly as possible, you should be sure to have read
all the guidelines on:
* [code standards](https://github.com/TryGhost/Ghost/wiki/Code-standards)
* [commit messages](https://github.com/TryGhost/Ghost/wiki/Git-workflow#commit-messages)
* [cleaning-up history](https://github.com/TryGhost/Ghost/wiki/Git-workflow#clean-up-history)
* [cleaning-up history](https://github.com/TryGhost/Ghost/wiki/Git-workflow#wiki-clean-up-history)
* [not breaking the build](https://github.com/TryGhost/Ghost/wiki/Git-workflow#check-it-passes-the-tests)
##### Need Help?
@ -138,29 +138,29 @@ If you're not completely clear on how to submit / update / *do* Pull Requests, p
<a name="testing"></a>
### Testing and Quality Assurance
Never underestimate just how useful quality assurance is. If you're looking to get involved with the code base and
Never underestimate just how useful quality assurance is. If you're looking to get involved with the code base and
don't know where to start, checking out and testing a pull request is one of the most useful things you could do.
If you want to get involved with testing Ghost, there is a set of
If you want to get involved with testing Ghost, there is a set of
[QA Documentation](https://github.com/TryGhost/Ghost/wiki/QA-Documentation) on the wiki.
Essentially though, [check out the latest master](#core), take it for a spin, and if you find anything odd, please
Essentially though, [check out the latest master](#core), take it for a spin, and if you find anything odd, please
follow the [bug report guidelines](#bug-reports) and let us know!
#### Checking out a Pull Request
These are some [excellent instructions](https://gist.github.com/piscisaureus/3342247) on configuring your GitHub
repository to allow you to checkout pull requests in the same way as branches:
These are some [excellent instructions](https://gist.github.com/piscisaureus/3342247) on configuring your GitHub
repository to allow you to checkout pull requests in the same way as branches:
<https://gist.github.com/piscisaureus/3342247>.
<a name="documentation"></a>
### Documentation
Ghost's main documentation can be found at [docs.ghost.org](http://docs.ghost.org).
Ghost's main documentation can be found at [docs.ghost.org](http://docs.ghost.org).
The documentation is generated using jekyll, all of the docs are on the gh-pages branch on the GitHub repository.
You can clone the repo, checkout the gh-pages branch, and submit pull requests following
The documentation is generated using jekyll, all of the docs are on the gh-pages branch on the GitHub repository.
You can clone the repo, checkout the gh-pages branch, and submit pull requests following
the [pull-request](#pull-requests) guidelines.
@ -174,7 +174,7 @@ Full documentation on contributing translations can be found at <http://docs.gho
<a name="core"></a>
## Working on Ghost Core
**Note:** It is recommended that you use the [Ghost-Vagrant](https://github.com/TryGhost/Ghost-Vagrant) setup for
**Note:** It is recommended that you use the [Ghost-Vagrant](https://github.com/TryGhost/Ghost-Vagrant) setup for
developing Ghost.
**Pre-requisites:**
@ -215,25 +215,25 @@ Addresses for development:
### Updating with the latest changes
Pulling down the latest changes from master will often require more than just a pull, you may also need to do one
Pulling down the latest changes from master will often require more than just a pull, you may also need to do one
or more of the following:
* `npm install` - fetch any new dependencies
* `git submodule update` - fetch the latest changes to Casper (the default theme)
* `grunt` - will recompile handlebars templates and sass for the admin (as long as you have previously
* `grunt` - will recompile handlebars templates and sass for the admin (as long as you have previously
run `grunt init` to install bourbon)
* delete content/data/*.db - delete the database and allow Ghost to recreate the fixtures
### Key Branches & Tags
- **[master](https://github.com/TryGhost/Ghost)** is the bleeding edge development branch. All work on the next
- **[master](https://github.com/TryGhost/Ghost)** is the bleeding edge development branch. All work on the next
release is here.
- **[gh-pages](http://tryghost.github.io/Ghost)** is The Ghost Guide documentation for Getting Started with Ghost.
### Compiling CSS & JavaScript
A SASS compiler is required to work with the CSS in this project. You can either do this by running `grunt` from
the command line - or by using a 3rd party app. We recommend [CodeKit](http://incident57.com/codekit/) (Paid/Mac)
A SASS compiler is required to work with the CSS in this project. You can either do this by running `grunt` from
the command line - or by using a 3rd party app. We recommend [CodeKit](http://incident57.com/codekit/) (Paid/Mac)
& [Scout](http://mhs.github.io/scout-app/) (Free/Mac/PC).
You will need to have Ruby installed, as well as having run `gem install sass && gem install bourbon`.
@ -241,19 +241,19 @@ You will need to have Ruby installed, as well as having run `gem install sass &&
Ghost uses Grunt heavily to automate useful tasks such as building assets, testing, live reloading/watching etc etc
[Grunt Toolkit docs](https://github.com/TryGhost/Ghost/wiki/Grunt-Toolkit) are a worthwhile read for any would-be
[Grunt Toolkit docs](https://github.com/TryGhost/Ghost/wiki/Grunt-Toolkit) are a worthwhile read for any would-be
contributor.
## Troubleshooting / FAQ
### I get "ERROR: Failed to lookup view "index"
Sounds like you don't have our default theme - Casper, your content/themes/casper folder is probably empty.
When cloning from GitHub be sure to use SSH and to run `git submodule update --init`.
Sounds like you don't have our default theme - Casper, your content/themes/casper folder is probably empty.
When cloning from GitHub be sure to use SSH and to run `git submodule update --init`.
### I get "Syntax error: File to import not found or unreadable: bourbon/_bourbon."
Sounds like you don't have the Ruby gem "bourbon" installed. Make sure you have Ruby, and then
Sounds like you don't have the Ruby gem "bourbon" installed. Make sure you have Ruby, and then
run `gem install bourbon`, and `grunt init`.
### Ghost doesn't do anything - I get a blank screen
@ -262,31 +262,31 @@ Sounds like you probably didn't run the right grunt command for building assets
### SQLite3 doesn't install properly during npm install
Ghost depends upon SQLite3, which requires a native binary. These are provided for most major platforms, but if you
are using a more obscure *nix flavor you may need to follow
Ghost depends upon SQLite3, which requires a native binary. These are provided for most major platforms, but if you
are using a more obscure *nix flavor you may need to follow
the [node-sqlite3 binary instructions](https://github.com/developmentseed/node-sqlite3/wiki/Binaries).
## Contributor License Agreement
By contributing your code to Ghost you grant the Ghost Foundation a non-exclusive, irrevocable, worldwide,
royalty-free, sublicenseable, transferable license under all of Your relevant intellectual property rights
(including copyright, patent, and any other rights), to use, copy, prepare derivative works of, distribute and
publicly perform and display the Contributions on any licensing terms, including without limitation:
(a) open source licenses like the MIT license; and (b) binary, proprietary, or commercial licenses. Except for the
By contributing your code to Ghost you grant the Ghost Foundation a non-exclusive, irrevocable, worldwide,
royalty-free, sublicenseable, transferable license under all of Your relevant intellectual property rights
(including copyright, patent, and any other rights), to use, copy, prepare derivative works of, distribute and
publicly perform and display the Contributions on any licensing terms, including without limitation:
(a) open source licenses like the MIT license; and (b) binary, proprietary, or commercial licenses. Except for the
licenses granted herein, You reserve all right, title, and interest in and to the Contribution.
You confirm that you are able to grant us these rights. You represent that You are legally entitled to grant the
above license. If Your employer has rights to intellectual property that You create, You represent that You have
received permission to make the Contributions on behalf of that employer, or that Your employer has waived such
You confirm that you are able to grant us these rights. You represent that You are legally entitled to grant the
above license. If Your employer has rights to intellectual property that You create, You represent that You have
received permission to make the Contributions on behalf of that employer, or that Your employer has waived such
rights for the Contributions.
You represent that the Contributions are Your original works of authorship, and to Your knowledge, no other person
claims, or has the right to claim, any right in any invention or patent related to the Contributions. You also
represent that You are not legally obligated, whether by entering into an agreement or otherwise, in any way that
You represent that the Contributions are Your original works of authorship, and to Your knowledge, no other person
claims, or has the right to claim, any right in any invention or patent related to the Contributions. You also
represent that You are not legally obligated, whether by entering into an agreement or otherwise, in any way that
conflicts with the terms of this license.
The Ghost Foundation acknowledges that, except as explicitly described in this Agreement, any Contribution which
you provide is on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED,
INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS
The Ghost Foundation acknowledges that, except as explicitly described in this Agreement, any Contribution which
you provide is on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED,
INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS
FOR A PARTICULAR PURPOSE.

View File

@ -124,21 +124,39 @@ var path = require('path'),
}
},
// ### Config for grunt-jslint
// JSLint all the things!
jslint: {
// ### Config for grunt-contrib-jshint
// JSHint all the things!
jshint: {
server: {
directives: {
options: {
// node environment
node: true,
// browser environment
browser: false,
// allow dangling underscores in var names
nomen: true,
// allow to do statements
todo: true,
nomen: false,
// don't require use strict pragma
sloppy: true
strict: false,
sub: true,
eqeqeq: true,
laxbreak: true,
bitwise: true,
curly: true,
forin: true,
immed: true,
latedef: true,
newcap: true,
noarg: true,
noempty: true,
nonew: true,
plusplus: true,
regexp: true,
undef: true,
unused: true,
trailing: true,
indent: 4,
onevar: true,
white: true
},
files: {
src: [
@ -149,46 +167,89 @@ var path = require('path'),
}
},
client: {
directives: {
options: {
"predef": {
"document": true,
"window": true,
"location": true,
"setTimeout": true,
"Ember": true,
"Em": true,
"DS": true,
"$": true
},
// node environment
node: false,
// browser environment
browser: true,
// allow dangling underscores in var names
nomen: true,
// allow to do statements
todo: true
nomen: false,
bitwise: true,
curly: true,
eqeqeq: true,
forin: true,
immed: true,
latedef: true,
newcap: true,
noarg: true,
noempty: true,
nonew: true,
plusplus: true,
regexp: true,
undef: true,
unused: true,
trailing: true,
indent: 4,
esnext: true,
onevar: true,
white: true
},
files: {
src: 'core/client/**/*.js'
},
exclude: [
'core/client/templates/**/*.js'
]
src: [
'core/client/**/*.js',
// Ignore files
'!core/client/assets/vendor/**/*.js',
'!core/client/tpl/**/*.js'
]
}
},
shared: {
directives: {
options: {
// node environment
node: true,
// browser environment
browser: false,
// allow dangling underscores in var names
nomen: true,
// allow to do statements
todo: true,
// allow unused parameters
unparam: true,
// don't require use strict pragma
sloppy: true
strict: false,
// allow dangling underscores in var names
nomen: false,
bitwise: true,
curly: true,
eqeqeq: true,
forin: true,
immed: true,
latedef: true,
newcap: true,
noarg: true,
noempty: true,
nonew: true,
plusplus: true,
regexp: true,
undef: true,
unused: true,
trailing: true,
indent: 4,
onevar: true,
white: true
},
files: {
src: [
'core/shared/**/*.js'
'core/shared/**/*.js',
// Ignore files
'!core/shared/vendor/**/*.js',
'!core/shared/lib/**/*.js'
]
},
exclude: [
'core/shared/vendor/**/*.js'
]
}
}
},
@ -237,7 +298,10 @@ var path = require('path'),
},
integration: {
src: ['core/test/integration/**/model*_spec.js']
src: [
'core/test/integration/**/model*_spec.js',
'core/test/integration/**/api*_spec.js'
]
},
api: {
@ -278,6 +342,12 @@ var path = require('path'),
bourbon: {
command: 'bourbon install --path <%= paths.adminOldAssets %>/sass/modules/'
},
bower: {
command: path.resolve(__dirname + '/node_modules/.bin/bower install'),
options: {
stdout: true
}
},
// generate coverage report
coverage: {
command: function () {
@ -391,8 +461,29 @@ var path = require('path'),
// ### Config for grunt-contrib-copy
// Prepare files for builds / releases
copy: {
dev: {
files: [{
cwd: 'bower_components/jquery/dist/',
src: 'jquery.js',
dest: 'core/built/public/',
expand: true
}]
},
prod: {
files: [{
cwd: 'bower_components/jquery/dist/',
src: 'jquery.js',
dest: 'core/built/public/',
expand: true
}]
},
release: {
files: [{
cwd: 'bower_components/jquery/dist/',
src: 'jquery.js',
dest: 'core/built/public/',
expand: true
}, {
expand: true,
src: buildGlob,
dest: '<%= paths.releaseBuild %>/'
@ -419,33 +510,34 @@ var path = require('path'),
dev: {
files: {
'core/built/scripts/vendor.js': [
'core/shared/vendor/jquery/jquery.js',
'core/shared/vendor/jquery/jquery-ui-1.10.3.custom.min.js',
'bower_components/jquery/dist/jquery.js',
'bower_components/jquery-ui/ui/jquery-ui.js',
'core/clientold/assets/lib/jquery-utils.js',
'core/clientold/assets/lib/uploader.js',
'core/shared/vendor/lodash.underscore.js',
'core/shared/vendor/backbone/backbone.js',
'core/shared/vendor/handlebars/handlebars-runtime.js',
'core/shared/vendor/moment.js',
'core/shared/vendor/jquery/jquery.ui.widget.js',
'core/shared/vendor/jquery/jquery.iframe-transport.js',
'core/shared/vendor/jquery/jquery.fileupload.js',
'bower_components/lodash/dist/lodash.underscore.js',
'bower_components/backbone/backbone.js',
'bower_components/handlebars.js/dist/handlebars.runtime.js',
'bower_components/moment/moment.js',
'bower_components/jquery-file-upload/js/jquery.fileupload.js',
'bower_components/codemirror/lib/codemirror.js',
'bower_components/codemirror/addon/mode/overlay.js',
'bower_components/codemirror/mode/markdown/markdown.js',
'bower_components/codemirror/mode/gfm/gfm.js',
'bower_components/showdown/src/showdown.js',
'bower_components/validator-js/validator.js',
'core/clientold/assets/vendor/codemirror/codemirror.js',
'core/clientold/assets/vendor/codemirror/addon/mode/overlay.js',
'core/clientold/assets/vendor/codemirror/mode/markdown/markdown.js',
'core/clientold/assets/vendor/codemirror/mode/gfm/gfm.js',
'core/clientold/assets/vendor/showdown/showdown.js',
'core/clientold/assets/vendor/showdown/extensions/ghostdown.js',
'core/shared/vendor/showdown/extensions/github.js',
'core/clientold/assets/lib/showdown/extensions/ghostdown.js',
'core/shared/lib/showdown/extensions/typography.js',
'core/shared/lib/showdown/extensions/github.js',
// ToDo: Remove or replace
'core/clientold/assets/vendor/shortcuts.js',
'core/clientold/assets/vendor/validator-client.js',
'core/clientold/assets/vendor/countable.js',
'core/clientold/assets/vendor/to-title-case.js',
'core/clientold/assets/vendor/packery.pkgd.min.js',
'core/clientold/assets/vendor/fastclick.js',
'core/clientold/assets/vendor/nprogress.js'
'bower_components/Countable/Countable.js',
'bower_components/fastclick/lib/fastclick.js',
'bower_components/nprogress/nprogress.js'
],
'core/built/scripts/helpers.js': [
@ -474,44 +566,45 @@ var path = require('path'),
'dev-ember': {
files: {
'core/built/scripts/vendor-ember.js': [
'core/shared/vendor/loader.js',
'core/shared/vendor/jquery/jquery.js',
'core/shared/vendor/handlebars/handlebars.js',
'core/shared/vendor/ember/ember.js',
'core/shared/vendor/ember-resolver/dist/ember-resolver.js'
'core/client/assets/vendor/loader.js',
'bower_components/jquery/dist/jquery.js',
'bower_components/handlebars.js/dist/handlebars.js',
'bower_components/ember/ember.js',
'bower_components/ember-resolver/dist/ember-resolver.js'
]
}
},
prod: {
files: {
'core/built/scripts/ghost.js': [
'core/shared/vendor/jquery/jquery.js',
'core/shared/vendor/jquery/jquery-ui-1.10.3.custom.min.js',
'bower_components/jquery/dist/jquery.js',
'bower_components/jquery-ui/ui/jquery-ui.js',
'core/clientold/assets/lib/jquery-utils.js',
'core/clientold/assets/lib/uploader.js',
'core/shared/vendor/lodash.underscore.js',
'core/shared/vendor/backbone/backbone.js',
'core/shared/vendor/handlebars/handlebars-runtime.js',
'core/shared/vendor/moment.js',
'core/shared/vendor/jquery/jquery.ui.widget.js',
'core/shared/vendor/jquery/jquery.iframe-transport.js',
'core/shared/vendor/jquery/jquery.fileupload.js',
'bower_components/lodash/dist/lodash.underscore.js',
'bower_components/backbone/backbone.js',
'bower_components/handlebars.js/dist/handlebars.runtime.js',
'bower_components/moment/moment.js',
'bower_components/jquery-file-upload/js/jquery.fileupload.js',
'bower_components/codemirror/lib/codemirror.js',
'bower_components/codemirror/addon/mode/overlay.js',
'bower_components/codemirror/mode/markdown/markdown.js',
'bower_components/codemirror/mode/gfm/gfm.js',
'bower_components/showdown/src/showdown.js',
'bower_components/validator-js/validator.js',
'core/clientold/assets/vendor/codemirror/codemirror.js',
'core/clientold/assets/vendor/codemirror/addon/mode/overlay.js',
'core/clientold/assets/vendor/codemirror/mode/markdown/markdown.js',
'core/clientold/assets/vendor/codemirror/mode/gfm/gfm.js',
'core/clientold/assets/vendor/showdown/showdown.js',
'core/clientold/assets/vendor/showdown/extensions/ghostdown.js',
'core/shared/vendor/showdown/extensions/github.js',
'core/clientold/assets/lib/showdown/extensions/ghostdown.js',
'core/shared/lib/showdown/extensions/typography.js',
'core/shared/lib/showdown/extensions/github.js',
// ToDo: Remove or replace
'core/clientold/assets/vendor/shortcuts.js',
'core/clientold/assets/vendor/validator-client.js',
'core/clientold/assets/vendor/countable.js',
'core/clientold/assets/vendor/to-title-case.js',
'core/clientold/assets/vendor/packery.pkgd.min.js',
'core/clientold/assets/vendor/fastclick.js',
'core/clientold/assets/vendor/nprogress.js',
'bower_components/Countable/Countable.js',
'bower_components/fastclick/lib/fastclick.js',
'bower_components/nprogress/nprogress.js',
'core/clientold/init.js',
@ -582,7 +675,7 @@ var path = require('path'),
stdio: 'inherit'
}
}, function (error, result, code) {
/*jslint unparam:true*/
/*jshint unused:false*/
if (error) {
grunt.fail.fatal(result.stdout);
}
@ -711,7 +804,7 @@ var path = require('path'),
data.replace(
commitRegex,
function (wholeCommit, hash, author, email, date, message) {
/*jslint unparam:true*/
/*jshint unused:false*/
// The author name and commit message may have trailing space.
author = author.trim();
@ -822,7 +915,7 @@ var path = require('path'),
when.reduce(tags,
function (prev, tag, idx) {
/*jslint unparam:true*/
/*jshint unused:false*/
return when.promise(function (resolve) {
processTag(tag, function (releaseData) {
resolve(prev + '\n' + releaseData);
@ -846,6 +939,7 @@ var path = require('path'),
' - Zip files in release-folder to dist-folder/#{version} directory',
[
'shell:bourbon',
'shell:bower',
'sass:compress',
'handlebars',
'concat',
@ -862,6 +956,7 @@ var path = require('path'),
'handlebars',
'concat',
'emberBuild',
'copy:dev',
'express:dev',
'watch'
]);
@ -881,13 +976,13 @@ var path = require('path'),
grunt.registerTask('test-integration', 'Run integration tests (mocha + db access)', ['clean:test', 'setTestEnv', 'loadConfig', 'mochacli:integration']);
grunt.registerTask('test-functional', 'Run functional interface tests (CasperJS)', ['clean:test', 'setTestEnv', 'loadConfig', 'express:test', 'spawn-casperjs', 'express:test:stop']);
grunt.registerTask('test-functional', 'Run functional interface tests (CasperJS)', ['clean:test', 'setTestEnv', 'loadConfig', 'copy:dev', 'express:test', 'spawn-casperjs', 'express:test:stop']);
grunt.registerTask('test-api', 'Run functional api tests (mocha)', ['clean:test', 'setTestEnv', 'loadConfig', 'express:test', 'mochacli:api', 'express:test:stop']);
grunt.registerTask('test-routes', 'Run functional route tests (mocha)', ['clean:test', 'setTestEnv', 'loadConfig', 'express:test', 'mochacli:routes', 'express:test:stop']);
grunt.registerTask('validate', 'Run tests and lint code', ['jslint', 'test-routes', 'test-unit', 'test-api', 'test-integration', 'test-functional']);
grunt.registerTask('validate', 'Run tests and lint code', ['jshint', 'test-routes', 'test-unit', 'test-api', 'test-integration', 'test-functional']);
// ### Coverage report for Unit and Integration Tests
@ -902,16 +997,16 @@ var path = require('path'),
// ### Tools for building assets
grunt.registerTask('init', 'Prepare the project for development', ['shell:bundle', 'shell:bourbon', 'default']);
grunt.registerTask('init', 'Prepare the project for development', ['shell:bundle', 'shell:bourbon', 'shell:bower', 'default']);
// Before running in production mode
grunt.registerTask('prod', 'Build CSS, JS & templates for production', ['sass:compress', 'handlebars', 'concat', 'uglify']);
grunt.registerTask('prod', 'Build CSS, JS & templates for production', ['sass:compress', 'handlebars', 'concat', 'uglify', 'copy:prod']);
// All tasks related to building the Ember client code
grunt.registerTask('emberBuild', 'Build Ember JS & templates for development', ['emberTemplates:dev', 'transpile', 'concat_sourcemap']);
// When you just say 'grunt'
grunt.registerTask('default', 'Build CSS, JS & templates for development', ['update_submodules', 'sass:compress', 'handlebars', 'concat', 'emberBuild']);
grunt.registerTask('default', 'Build CSS, JS & templates for development', ['update_submodules', 'sass:compress', 'handlebars', 'concat', 'copy:dev', 'emberBuild']);
};
module.exports = configureGrunt;
module.exports = configureGrunt;

View File

@ -1,11 +1,25 @@
{
"name": "ghost",
"dependencies": {
"handlebars": "~1.1.2",
"ember": "~1.4.0",
"ember-resolver": "git://github.com/stefanpenner/ember-jj-abrams-resolver.git#9805033c178e7f857f801359664adb599444b430"
},
"resolutions": {
"ember": "~1.4.0"
}
}
"name": "ghost",
"dependencies": {
"backbone": "1.0.0",
"codemirror": "3.15.0",
"Countable": "2.0.2",
"ember": "~1.4.0",
"ember-resolver": "git://github.com/stefanpenner/ember-jj-abrams-resolver.git#9805033c178e7f857f801359664adb599444b430",
"fastclick": "1.0.0",
"handlebars": "~1.1.2",
"iCheck": "1.0.1",
"jquery": "1.11.0",
"jquery-file-upload": "9.5.6",
"jquery-hammerjs": "1.0.1",
"jquery-ui": "1.10.4",
"lodash": "2.4.1",
"moment": "2.4.0",
"nprogress": "0.1.2",
"showdown": "0.3.1",
"validator-js": "3.4.0"
},
"resolutions": {
"ember": "~1.4.0"
}
}

5
core/bootstrap.js vendored
View File

@ -7,7 +7,6 @@
var fs = require('fs'),
url = require('url'),
when = require('when'),
path = require('path'),
errors = require('./server/errorHandling'),
config = require('./server/config'),
@ -36,14 +35,14 @@ function writeConfigFile() {
// Copy config.example.js => config.js
read = fs.createReadStream(configExample);
read.on('error', function (err) {
/*jslint unparam:true*/
/*jshint unused:false*/
return errors.logError(new Error('Could not open config.example.js for read.'), appRoot, 'Please check your deployment for config.js or config.example.js.');
});
read.on('end', written.resolve);
write = fs.createWriteStream(configFile);
write.on('error', function (err) {
/*jslint unparam:true*/
/*jshint unused:false*/
return errors.logError(new Error('Could not open config.js for write.'), appRoot, 'Please check your deployment for config.js or config.example.js.');
});

View File

@ -1,5 +1,5 @@
export default Ember.Component.extend({
time: function() {
return new Date();
}.property()
time: function () {
return new Date();
}.property()
});

View File

@ -1,3 +1,3 @@
export default Ember.Controller.extend({
message: 'its a new beginning.'
message: 'its a new beginning.'
});

View File

@ -5,7 +5,6 @@ var Router = Ember.Router.extend();
Router.map(function () {
'use strict';
});
export default Router;

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,7 @@
* @returns {boolean}
*/
$.expr[":"].containsExact = function (obj, index, meta, stack) {
/*jslint unparam:true*/
/*jshint unused:false*/
return (obj.textContent || obj.innerText || $(obj).text() || "") === meta[3];
};

View File

@ -1,3 +1,4 @@
/* jshint node:true, browser:true */
(function () {
var ghostdown = function () {
return [
@ -41,5 +42,7 @@
window.Showdown.extensions.ghostdown = ghostdown;
}
// Server-side export
if (typeof module !== 'undefined') module.exports = ghostdown;
if (typeof module !== 'undefined') {
module.exports = ghostdown;
}
}());

View File

@ -1,4 +1,4 @@
/*global jQuery, Ghost, document, Image, window */
/*global jQuery, Ghost */
(function ($) {
"use strict";
@ -68,7 +68,7 @@
'X-CSRF-Token': $("meta[name='csrf-param']").attr('content')
},
add: function (e, data) {
/*jslint unparam:true*/
/*jshint unused:false*/
$('.js-button-accept').prop('disabled', true);
$dropzone.find('.js-fileupload').removeClass('right');
$dropzone.find('.js-url').remove();
@ -86,7 +86,7 @@
},
dropZone: settings.fileStorage ? $dropzone : null,
progressall: function (e, data) {
/*jslint unparam:true*/
/*jshint unused:false*/
var progress = parseInt(data.loaded / data.total * 100, 10);
if (!settings.editor) {$progress.find('div.js-progress').css({"position": "absolute", "top": "40px"}); }
if (settings.progressbar) {
@ -95,7 +95,7 @@
}
},
fail: function (e, data) {
/*jslint unparam:true*/
/*jshint unused:false*/
$('.js-button-accept').prop('disabled', false);
$dropzone.trigger("uploadfailure", [data.result]);
$dropzone.find('.js-upload-progress-bar').addClass('fail');
@ -115,7 +115,7 @@
});
},
done: function (e, data) {
/*jslint unparam:true*/
/*jshint unused:false*/
self.complete(data.result);
}
});

View File

@ -162,7 +162,7 @@
.services a { @include icon($i-services) }
.users a { @include icon($i-users) }
.appearance a { @include icon($i-appearance) }
.plugins a { @include icon($i-plugins) }
.apps a { @include icon($i-plugins) }
}//.settings-menu

View File

@ -1,39 +0,0 @@
var Chart=function(s){function v(a,c,b){a=A((a-c.graphMin)/(c.steps*c.stepValue),1,0);return b*c.steps*a}function x(a,c,b,e){function h(){g+=f;var k=a.animation?A(d(g),null,0):1;e.clearRect(0,0,q,u);a.scaleOverlay?(b(k),c()):(c(),b(k));if(1>=g)D(h);else if("function"==typeof a.onAnimationComplete)a.onAnimationComplete()}var f=a.animation?1/A(a.animationSteps,Number.MAX_VALUE,1):1,d=B[a.animationEasing],g=a.animation?0:1;"function"!==typeof c&&(c=function(){});D(h)}function C(a,c,b,e,h,f){var d;a=
Math.floor(Math.log(e-h)/Math.LN10);h=Math.floor(h/(1*Math.pow(10,a)))*Math.pow(10,a);e=Math.ceil(e/(1*Math.pow(10,a)))*Math.pow(10,a)-h;a=Math.pow(10,a);for(d=Math.round(e/a);d<b||d>c;)a=d<b?a/2:2*a,d=Math.round(e/a);c=[];z(f,c,d,h,a);return{steps:d,stepValue:a,graphMin:h,labels:c}}function z(a,c,b,e,h){if(a)for(var f=1;f<b+1;f++)c.push(E(a,{value:(e+h*f).toFixed(0!=h%1?h.toString().split(".")[1].length:0)}))}function A(a,c,b){return!isNaN(parseFloat(c))&&isFinite(c)&&a>c?c:!isNaN(parseFloat(b))&&
isFinite(b)&&a<b?b:a}function y(a,c){var b={},e;for(e in a)b[e]=a[e];for(e in c)b[e]=c[e];return b}function E(a,c){var b=!/\W/.test(a)?F[a]=F[a]||E(document.getElementById(a).innerHTML):new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+a.replace(/[\r\t\n]/g," ").split("<%").join("\t").replace(/((^|%>)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split("\t").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');");return c?
b(c):b}var r=this,B={linear:function(a){return a},easeInQuad:function(a){return a*a},easeOutQuad:function(a){return-1*a*(a-2)},easeInOutQuad:function(a){return 1>(a/=0.5)?0.5*a*a:-0.5*(--a*(a-2)-1)},easeInCubic:function(a){return a*a*a},easeOutCubic:function(a){return 1*((a=a/1-1)*a*a+1)},easeInOutCubic:function(a){return 1>(a/=0.5)?0.5*a*a*a:0.5*((a-=2)*a*a+2)},easeInQuart:function(a){return a*a*a*a},easeOutQuart:function(a){return-1*((a=a/1-1)*a*a*a-1)},easeInOutQuart:function(a){return 1>(a/=0.5)?
0.5*a*a*a*a:-0.5*((a-=2)*a*a*a-2)},easeInQuint:function(a){return 1*(a/=1)*a*a*a*a},easeOutQuint:function(a){return 1*((a=a/1-1)*a*a*a*a+1)},easeInOutQuint:function(a){return 1>(a/=0.5)?0.5*a*a*a*a*a:0.5*((a-=2)*a*a*a*a+2)},easeInSine:function(a){return-1*Math.cos(a/1*(Math.PI/2))+1},easeOutSine:function(a){return 1*Math.sin(a/1*(Math.PI/2))},easeInOutSine:function(a){return-0.5*(Math.cos(Math.PI*a/1)-1)},easeInExpo:function(a){return 0==a?1:1*Math.pow(2,10*(a/1-1))},easeOutExpo:function(a){return 1==
a?1:1*(-Math.pow(2,-10*a/1)+1)},easeInOutExpo:function(a){return 0==a?0:1==a?1:1>(a/=0.5)?0.5*Math.pow(2,10*(a-1)):0.5*(-Math.pow(2,-10*--a)+2)},easeInCirc:function(a){return 1<=a?a:-1*(Math.sqrt(1-(a/=1)*a)-1)},easeOutCirc:function(a){return 1*Math.sqrt(1-(a=a/1-1)*a)},easeInOutCirc:function(a){return 1>(a/=0.5)?-0.5*(Math.sqrt(1-a*a)-1):0.5*(Math.sqrt(1-(a-=2)*a)+1)},easeInElastic:function(a){var c=1.70158,b=0,e=1;if(0==a)return 0;if(1==(a/=1))return 1;b||(b=0.3);e<Math.abs(1)?(e=1,c=b/4):c=b/(2*
Math.PI)*Math.asin(1/e);return-(e*Math.pow(2,10*(a-=1))*Math.sin((1*a-c)*2*Math.PI/b))},easeOutElastic:function(a){var c=1.70158,b=0,e=1;if(0==a)return 0;if(1==(a/=1))return 1;b||(b=0.3);e<Math.abs(1)?(e=1,c=b/4):c=b/(2*Math.PI)*Math.asin(1/e);return e*Math.pow(2,-10*a)*Math.sin((1*a-c)*2*Math.PI/b)+1},easeInOutElastic:function(a){var c=1.70158,b=0,e=1;if(0==a)return 0;if(2==(a/=0.5))return 1;b||(b=1*0.3*1.5);e<Math.abs(1)?(e=1,c=b/4):c=b/(2*Math.PI)*Math.asin(1/e);return 1>a?-0.5*e*Math.pow(2,10*
(a-=1))*Math.sin((1*a-c)*2*Math.PI/b):0.5*e*Math.pow(2,-10*(a-=1))*Math.sin((1*a-c)*2*Math.PI/b)+1},easeInBack:function(a){return 1*(a/=1)*a*(2.70158*a-1.70158)},easeOutBack:function(a){return 1*((a=a/1-1)*a*(2.70158*a+1.70158)+1)},easeInOutBack:function(a){var c=1.70158;return 1>(a/=0.5)?0.5*a*a*(((c*=1.525)+1)*a-c):0.5*((a-=2)*a*(((c*=1.525)+1)*a+c)+2)},easeInBounce:function(a){return 1-B.easeOutBounce(1-a)},easeOutBounce:function(a){return(a/=1)<1/2.75?1*7.5625*a*a:a<2/2.75?1*(7.5625*(a-=1.5/2.75)*
a+0.75):a<2.5/2.75?1*(7.5625*(a-=2.25/2.75)*a+0.9375):1*(7.5625*(a-=2.625/2.75)*a+0.984375)},easeInOutBounce:function(a){return 0.5>a?0.5*B.easeInBounce(2*a):0.5*B.easeOutBounce(2*a-1)+0.5}},q=s.canvas.width,u=s.canvas.height;window.devicePixelRatio&&(s.canvas.style.width=q+"px",s.canvas.style.height=u+"px",s.canvas.height=u*window.devicePixelRatio,s.canvas.width=q*window.devicePixelRatio,s.scale(window.devicePixelRatio,window.devicePixelRatio));this.PolarArea=function(a,c){r.PolarArea.defaults={scaleOverlay:!0,
scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleShowLine:!0,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowLabelBackdrop:!0,scaleBackdropColor:"rgba(255,255,255,0.75)",scaleBackdropPaddingY:2,scaleBackdropPaddingX:2,segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,animation:!0,animationSteps:100,animationEasing:"easeOutBounce",
animateRotate:!0,animateScale:!1,onAnimationComplete:null};var b=c?y(r.PolarArea.defaults,c):r.PolarArea.defaults;return new G(a,b,s)};this.Radar=function(a,c){r.Radar.defaults={scaleOverlay:!1,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleShowLine:!0,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!1,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowLabelBackdrop:!0,scaleBackdropColor:"rgba(255,255,255,0.75)",
scaleBackdropPaddingY:2,scaleBackdropPaddingX:2,angleShowLineOut:!0,angleLineColor:"rgba(0,0,0,.1)",angleLineWidth:1,pointLabelFontFamily:"'Arial'",pointLabelFontStyle:"normal",pointLabelFontSize:12,pointLabelFontColor:"#666",pointDot:!0,pointDotRadius:3,pointDotStrokeWidth:1,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,animation:!0,animationSteps:60,animationEasing:"easeOutQuart",onAnimationComplete:null};var b=c?y(r.Radar.defaults,c):r.Radar.defaults;return new H(a,b,s)};this.Pie=function(a,
c){r.Pie.defaults={segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,animation:!0,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,onAnimationComplete:null};var b=c?y(r.Pie.defaults,c):r.Pie.defaults;return new I(a,b,s)};this.Doughnut=function(a,c){r.Doughnut.defaults={segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,percentageInnerCutout:50,animation:!0,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,
onAnimationComplete:null};var b=c?y(r.Doughnut.defaults,c):r.Doughnut.defaults;return new J(a,b,s)};this.Line=function(a,c){r.Line.defaults={scaleOverlay:!1,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,bezierCurve:!0,
pointDot:!0,pointDotRadius:4,pointDotStrokeWidth:2,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,animation:!0,animationSteps:60,animationEasing:"easeOutQuart",onAnimationComplete:null};var b=c?y(r.Line.defaults,c):r.Line.defaults;return new K(a,b,s)};this.Bar=function(a,c){r.Bar.defaults={scaleOverlay:!1,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleFontFamily:"'Arial'",
scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,barShowStroke:!0,barStrokeWidth:2,barValueSpacing:5,barDatasetSpacing:1,animation:!0,animationSteps:60,animationEasing:"easeOutQuart",onAnimationComplete:null};var b=c?y(r.Bar.defaults,c):r.Bar.defaults;return new L(a,b,s)};var G=function(a,c,b){var e,h,f,d,g,k,j,l,m;g=Math.min.apply(Math,[q,u])/2;g-=Math.max.apply(Math,[0.5*c.scaleFontSize,0.5*c.scaleLineWidth]);
d=2*c.scaleFontSize;c.scaleShowLabelBackdrop&&(d+=2*c.scaleBackdropPaddingY,g-=1.5*c.scaleBackdropPaddingY);l=g;d=d?d:5;e=Number.MIN_VALUE;h=Number.MAX_VALUE;for(f=0;f<a.length;f++)a[f].value>e&&(e=a[f].value),a[f].value<h&&(h=a[f].value);f=Math.floor(l/(0.66*d));d=Math.floor(0.5*(l/d));m=c.scaleShowLabels?c.scaleLabel:null;c.scaleOverride?(j={steps:c.scaleSteps,stepValue:c.scaleStepWidth,graphMin:c.scaleStartValue,labels:[]},z(m,j.labels,j.steps,c.scaleStartValue,c.scaleStepWidth)):j=C(l,f,d,e,h,
m);k=g/j.steps;x(c,function(){for(var a=0;a<j.steps;a++)if(c.scaleShowLine&&(b.beginPath(),b.arc(q/2,u/2,k*(a+1),0,2*Math.PI,!0),b.strokeStyle=c.scaleLineColor,b.lineWidth=c.scaleLineWidth,b.stroke()),c.scaleShowLabels){b.textAlign="center";b.font=c.scaleFontStyle+" "+c.scaleFontSize+"px "+c.scaleFontFamily;var e=j.labels[a];if(c.scaleShowLabelBackdrop){var d=b.measureText(e).width;b.fillStyle=c.scaleBackdropColor;b.beginPath();b.rect(Math.round(q/2-d/2-c.scaleBackdropPaddingX),Math.round(u/2-k*(a+
1)-0.5*c.scaleFontSize-c.scaleBackdropPaddingY),Math.round(d+2*c.scaleBackdropPaddingX),Math.round(c.scaleFontSize+2*c.scaleBackdropPaddingY));b.fill()}b.textBaseline="middle";b.fillStyle=c.scaleFontColor;b.fillText(e,q/2,u/2-k*(a+1))}},function(e){var d=-Math.PI/2,g=2*Math.PI/a.length,f=1,h=1;c.animation&&(c.animateScale&&(f=e),c.animateRotate&&(h=e));for(e=0;e<a.length;e++)b.beginPath(),b.arc(q/2,u/2,f*v(a[e].value,j,k),d,d+h*g,!1),b.lineTo(q/2,u/2),b.closePath(),b.fillStyle=a[e].color,b.fill(),
c.segmentShowStroke&&(b.strokeStyle=c.segmentStrokeColor,b.lineWidth=c.segmentStrokeWidth,b.stroke()),d+=h*g},b)},H=function(a,c,b){var e,h,f,d,g,k,j,l,m;a.labels||(a.labels=[]);g=Math.min.apply(Math,[q,u])/2;d=2*c.scaleFontSize;for(e=l=0;e<a.labels.length;e++)b.font=c.pointLabelFontStyle+" "+c.pointLabelFontSize+"px "+c.pointLabelFontFamily,h=b.measureText(a.labels[e]).width,h>l&&(l=h);g-=Math.max.apply(Math,[l,1.5*(c.pointLabelFontSize/2)]);g-=c.pointLabelFontSize;l=g=A(g,null,0);d=d?d:5;e=Number.MIN_VALUE;
h=Number.MAX_VALUE;for(f=0;f<a.datasets.length;f++)for(m=0;m<a.datasets[f].data.length;m++)a.datasets[f].data[m]>e&&(e=a.datasets[f].data[m]),a.datasets[f].data[m]<h&&(h=a.datasets[f].data[m]);f=Math.floor(l/(0.66*d));d=Math.floor(0.5*(l/d));m=c.scaleShowLabels?c.scaleLabel:null;c.scaleOverride?(j={steps:c.scaleSteps,stepValue:c.scaleStepWidth,graphMin:c.scaleStartValue,labels:[]},z(m,j.labels,j.steps,c.scaleStartValue,c.scaleStepWidth)):j=C(l,f,d,e,h,m);k=g/j.steps;x(c,function(){var e=2*Math.PI/
a.datasets[0].data.length;b.save();b.translate(q/2,u/2);if(c.angleShowLineOut){b.strokeStyle=c.angleLineColor;b.lineWidth=c.angleLineWidth;for(var d=0;d<a.datasets[0].data.length;d++)b.rotate(e),b.beginPath(),b.moveTo(0,0),b.lineTo(0,-g),b.stroke()}for(d=0;d<j.steps;d++){b.beginPath();if(c.scaleShowLine){b.strokeStyle=c.scaleLineColor;b.lineWidth=c.scaleLineWidth;b.moveTo(0,-k*(d+1));for(var f=0;f<a.datasets[0].data.length;f++)b.rotate(e),b.lineTo(0,-k*(d+1));b.closePath();b.stroke()}c.scaleShowLabels&&
(b.textAlign="center",b.font=c.scaleFontStyle+" "+c.scaleFontSize+"px "+c.scaleFontFamily,b.textBaseline="middle",c.scaleShowLabelBackdrop&&(f=b.measureText(j.labels[d]).width,b.fillStyle=c.scaleBackdropColor,b.beginPath(),b.rect(Math.round(-f/2-c.scaleBackdropPaddingX),Math.round(-k*(d+1)-0.5*c.scaleFontSize-c.scaleBackdropPaddingY),Math.round(f+2*c.scaleBackdropPaddingX),Math.round(c.scaleFontSize+2*c.scaleBackdropPaddingY)),b.fill()),b.fillStyle=c.scaleFontColor,b.fillText(j.labels[d],0,-k*(d+
1)))}for(d=0;d<a.labels.length;d++){b.font=c.pointLabelFontStyle+" "+c.pointLabelFontSize+"px "+c.pointLabelFontFamily;b.fillStyle=c.pointLabelFontColor;var f=Math.sin(e*d)*(g+c.pointLabelFontSize),h=Math.cos(e*d)*(g+c.pointLabelFontSize);b.textAlign=e*d==Math.PI||0==e*d?"center":e*d>Math.PI?"right":"left";b.textBaseline="middle";b.fillText(a.labels[d],f,-h)}b.restore()},function(d){var e=2*Math.PI/a.datasets[0].data.length;b.save();b.translate(q/2,u/2);for(var g=0;g<a.datasets.length;g++){b.beginPath();
b.moveTo(0,d*-1*v(a.datasets[g].data[0],j,k));for(var f=1;f<a.datasets[g].data.length;f++)b.rotate(e),b.lineTo(0,d*-1*v(a.datasets[g].data[f],j,k));b.closePath();b.fillStyle=a.datasets[g].fillColor;b.strokeStyle=a.datasets[g].strokeColor;b.lineWidth=c.datasetStrokeWidth;b.fill();b.stroke();if(c.pointDot){b.fillStyle=a.datasets[g].pointColor;b.strokeStyle=a.datasets[g].pointStrokeColor;b.lineWidth=c.pointDotStrokeWidth;for(f=0;f<a.datasets[g].data.length;f++)b.rotate(e),b.beginPath(),b.arc(0,d*-1*
v(a.datasets[g].data[f],j,k),c.pointDotRadius,2*Math.PI,!1),b.fill(),b.stroke()}b.rotate(e)}b.restore()},b)},I=function(a,c,b){for(var e=0,h=Math.min.apply(Math,[u/2,q/2])-5,f=0;f<a.length;f++)e+=a[f].value;x(c,null,function(d){var g=-Math.PI/2,f=1,j=1;c.animation&&(c.animateScale&&(f=d),c.animateRotate&&(j=d));for(d=0;d<a.length;d++){var l=j*a[d].value/e*2*Math.PI;b.beginPath();b.arc(q/2,u/2,f*h,g,g+l);b.lineTo(q/2,u/2);b.closePath();b.fillStyle=a[d].color;b.fill();c.segmentShowStroke&&(b.lineWidth=
c.segmentStrokeWidth,b.strokeStyle=c.segmentStrokeColor,b.stroke());g+=l}},b)},J=function(a,c,b){for(var e=0,h=Math.min.apply(Math,[u/2,q/2])-5,f=h*(c.percentageInnerCutout/100),d=0;d<a.length;d++)e+=a[d].value;x(c,null,function(d){var k=-Math.PI/2,j=1,l=1;c.animation&&(c.animateScale&&(j=d),c.animateRotate&&(l=d));for(d=0;d<a.length;d++){var m=l*a[d].value/e*2*Math.PI;b.beginPath();b.arc(q/2,u/2,j*h,k,k+m,!1);b.arc(q/2,u/2,j*f,k+m,k,!0);b.closePath();b.fillStyle=a[d].color;b.fill();c.segmentShowStroke&&
(b.lineWidth=c.segmentStrokeWidth,b.strokeStyle=c.segmentStrokeColor,b.stroke());k+=m}},b)},K=function(a,c,b){var e,h,f,d,g,k,j,l,m,t,r,n,p,s=0;g=u;b.font=c.scaleFontStyle+" "+c.scaleFontSize+"px "+c.scaleFontFamily;t=1;for(d=0;d<a.labels.length;d++)e=b.measureText(a.labels[d]).width,t=e>t?e:t;q/a.labels.length<t?(s=45,q/a.labels.length<Math.cos(s)*t?(s=90,g-=t):g-=Math.sin(s)*t):g-=c.scaleFontSize;d=c.scaleFontSize;g=g-5-d;e=Number.MIN_VALUE;h=Number.MAX_VALUE;for(f=0;f<a.datasets.length;f++)for(l=
0;l<a.datasets[f].data.length;l++)a.datasets[f].data[l]>e&&(e=a.datasets[f].data[l]),a.datasets[f].data[l]<h&&(h=a.datasets[f].data[l]);f=Math.floor(g/(0.66*d));d=Math.floor(0.5*(g/d));l=c.scaleShowLabels?c.scaleLabel:"";c.scaleOverride?(j={steps:c.scaleSteps,stepValue:c.scaleStepWidth,graphMin:c.scaleStartValue,labels:[]},z(l,j.labels,j.steps,c.scaleStartValue,c.scaleStepWidth)):j=C(g,f,d,e,h,l);k=Math.floor(g/j.steps);d=1;if(c.scaleShowLabels){b.font=c.scaleFontStyle+" "+c.scaleFontSize+"px "+c.scaleFontFamily;
for(e=0;e<j.labels.length;e++)h=b.measureText(j.labels[e]).width,d=h>d?h:d;d+=10}r=q-d-t;m=Math.floor(r/(a.labels.length-1));n=q-t/2-r;p=g+c.scaleFontSize/2;x(c,function(){b.lineWidth=c.scaleLineWidth;b.strokeStyle=c.scaleLineColor;b.beginPath();b.moveTo(q-t/2+5,p);b.lineTo(q-t/2-r-5,p);b.stroke();0<s?(b.save(),b.textAlign="right"):b.textAlign="center";b.fillStyle=c.scaleFontColor;for(var d=0;d<a.labels.length;d++)b.save(),0<s?(b.translate(n+d*m,p+c.scaleFontSize),b.rotate(-(s*(Math.PI/180))),b.fillText(a.labels[d],
0,0),b.restore()):b.fillText(a.labels[d],n+d*m,p+c.scaleFontSize+3),b.beginPath(),b.moveTo(n+d*m,p+3),c.scaleShowGridLines&&0<d?(b.lineWidth=c.scaleGridLineWidth,b.strokeStyle=c.scaleGridLineColor,b.lineTo(n+d*m,5)):b.lineTo(n+d*m,p+3),b.stroke();b.lineWidth=c.scaleLineWidth;b.strokeStyle=c.scaleLineColor;b.beginPath();b.moveTo(n,p+5);b.lineTo(n,5);b.stroke();b.textAlign="right";b.textBaseline="middle";for(d=0;d<j.steps;d++)b.beginPath(),b.moveTo(n-3,p-(d+1)*k),c.scaleShowGridLines?(b.lineWidth=c.scaleGridLineWidth,
b.strokeStyle=c.scaleGridLineColor,b.lineTo(n+r+5,p-(d+1)*k)):b.lineTo(n-0.5,p-(d+1)*k),b.stroke(),c.scaleShowLabels&&b.fillText(j.labels[d],n-8,p-(d+1)*k)},function(d){function e(b,c){return p-d*v(a.datasets[b].data[c],j,k)}for(var f=0;f<a.datasets.length;f++){b.strokeStyle=a.datasets[f].strokeColor;b.lineWidth=c.datasetStrokeWidth;b.beginPath();b.moveTo(n,p-d*v(a.datasets[f].data[0],j,k));for(var g=1;g<a.datasets[f].data.length;g++)c.bezierCurve?b.bezierCurveTo(n+m*(g-0.5),e(f,g-1),n+m*(g-0.5),
e(f,g),n+m*g,e(f,g)):b.lineTo(n+m*g,e(f,g));b.stroke();c.datasetFill?(b.lineTo(n+m*(a.datasets[f].data.length-1),p),b.lineTo(n,p),b.closePath(),b.fillStyle=a.datasets[f].fillColor,b.fill()):b.closePath();if(c.pointDot){b.fillStyle=a.datasets[f].pointColor;b.strokeStyle=a.datasets[f].pointStrokeColor;b.lineWidth=c.pointDotStrokeWidth;for(g=0;g<a.datasets[f].data.length;g++)b.beginPath(),b.arc(n+m*g,p-d*v(a.datasets[f].data[g],j,k),c.pointDotRadius,0,2*Math.PI,!0),b.fill(),b.stroke()}}},b)},L=function(a,
c,b){var e,h,f,d,g,k,j,l,m,t,r,n,p,s,w=0;g=u;b.font=c.scaleFontStyle+" "+c.scaleFontSize+"px "+c.scaleFontFamily;t=1;for(d=0;d<a.labels.length;d++)e=b.measureText(a.labels[d]).width,t=e>t?e:t;q/a.labels.length<t?(w=45,q/a.labels.length<Math.cos(w)*t?(w=90,g-=t):g-=Math.sin(w)*t):g-=c.scaleFontSize;d=c.scaleFontSize;g=g-5-d;e=Number.MIN_VALUE;h=Number.MAX_VALUE;for(f=0;f<a.datasets.length;f++)for(l=0;l<a.datasets[f].data.length;l++)a.datasets[f].data[l]>e&&(e=a.datasets[f].data[l]),a.datasets[f].data[l]<
h&&(h=a.datasets[f].data[l]);f=Math.floor(g/(0.66*d));d=Math.floor(0.5*(g/d));l=c.scaleShowLabels?c.scaleLabel:"";c.scaleOverride?(j={steps:c.scaleSteps,stepValue:c.scaleStepWidth,graphMin:c.scaleStartValue,labels:[]},z(l,j.labels,j.steps,c.scaleStartValue,c.scaleStepWidth)):j=C(g,f,d,e,h,l);k=Math.floor(g/j.steps);d=1;if(c.scaleShowLabels){b.font=c.scaleFontStyle+" "+c.scaleFontSize+"px "+c.scaleFontFamily;for(e=0;e<j.labels.length;e++)h=b.measureText(j.labels[e]).width,d=h>d?h:d;d+=10}r=q-d-t;m=
Math.floor(r/a.labels.length);s=(m-2*c.scaleGridLineWidth-2*c.barValueSpacing-(c.barDatasetSpacing*a.datasets.length-1)-(c.barStrokeWidth/2*a.datasets.length-1))/a.datasets.length;n=q-t/2-r;p=g+c.scaleFontSize/2;x(c,function(){b.lineWidth=c.scaleLineWidth;b.strokeStyle=c.scaleLineColor;b.beginPath();b.moveTo(q-t/2+5,p);b.lineTo(q-t/2-r-5,p);b.stroke();0<w?(b.save(),b.textAlign="right"):b.textAlign="center";b.fillStyle=c.scaleFontColor;for(var d=0;d<a.labels.length;d++)b.save(),0<w?(b.translate(n+
d*m,p+c.scaleFontSize),b.rotate(-(w*(Math.PI/180))),b.fillText(a.labels[d],0,0),b.restore()):b.fillText(a.labels[d],n+d*m+m/2,p+c.scaleFontSize+3),b.beginPath(),b.moveTo(n+(d+1)*m,p+3),b.lineWidth=c.scaleGridLineWidth,b.strokeStyle=c.scaleGridLineColor,b.lineTo(n+(d+1)*m,5),b.stroke();b.lineWidth=c.scaleLineWidth;b.strokeStyle=c.scaleLineColor;b.beginPath();b.moveTo(n,p+5);b.lineTo(n,5);b.stroke();b.textAlign="right";b.textBaseline="middle";for(d=0;d<j.steps;d++)b.beginPath(),b.moveTo(n-3,p-(d+1)*
k),c.scaleShowGridLines?(b.lineWidth=c.scaleGridLineWidth,b.strokeStyle=c.scaleGridLineColor,b.lineTo(n+r+5,p-(d+1)*k)):b.lineTo(n-0.5,p-(d+1)*k),b.stroke(),c.scaleShowLabels&&b.fillText(j.labels[d],n-8,p-(d+1)*k)},function(d){b.lineWidth=c.barStrokeWidth;for(var e=0;e<a.datasets.length;e++){b.fillStyle=a.datasets[e].fillColor;b.strokeStyle=a.datasets[e].strokeColor;for(var f=0;f<a.datasets[e].data.length;f++){var g=n+c.barValueSpacing+m*f+s*e+c.barDatasetSpacing*e+c.barStrokeWidth*e;b.beginPath();
b.moveTo(g,p);b.lineTo(g,p-d*v(a.datasets[e].data[f],j,k)+c.barStrokeWidth/2);b.lineTo(g+s,p-d*v(a.datasets[e].data[f],j,k)+c.barStrokeWidth/2);b.lineTo(g+s,p);c.barShowStroke&&b.stroke();b.closePath();b.fill()}}},b)},D=window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(a){window.setTimeout(a,1E3/60)},F={}};

View File

@ -1,59 +0,0 @@
// Utility function that allows modes to be combined. The mode given
// as the base argument takes care of most of the normal mode
// functionality, but a second (typically simple) mode is used, which
// can override the style of text. Both modes get to parse all of the
// text, but when both assign a non-null style to a piece of code, the
// overlay wins, unless the combine argument was true, in which case
// the styles are combined.
// overlayParser is the old, deprecated name
CodeMirror.overlayMode = CodeMirror.overlayParser = function(base, overlay, combine) {
return {
startState: function() {
return {
base: CodeMirror.startState(base),
overlay: CodeMirror.startState(overlay),
basePos: 0, baseCur: null,
overlayPos: 0, overlayCur: null
};
},
copyState: function(state) {
return {
base: CodeMirror.copyState(base, state.base),
overlay: CodeMirror.copyState(overlay, state.overlay),
basePos: state.basePos, baseCur: null,
overlayPos: state.overlayPos, overlayCur: null
};
},
token: function(stream, state) {
if (stream.start == state.basePos) {
state.baseCur = base.token(stream, state.base);
state.basePos = stream.pos;
}
if (stream.start == state.overlayPos) {
stream.pos = stream.start;
state.overlayCur = overlay.token(stream, state.overlay);
state.overlayPos = stream.pos;
}
stream.pos = Math.min(state.basePos, state.overlayPos);
if (stream.eol()) state.basePos = state.overlayPos = 0;
if (state.overlayCur == null) return state.baseCur;
if (state.baseCur != null && combine) return state.baseCur + " " + state.overlayCur;
else return state.overlayCur;
},
indent: base.indent && function(state, textAfter) {
return base.indent(state.base, textAfter);
},
electricChars: base.electricChars,
innerMode: function(state) { return {state: state.base, mode: base}; },
blankLine: function(state) {
if (base.blankLine) base.blankLine(state.base);
if (overlay.blankLine) overlay.blankLine(state.overlay);
}
};
};

File diff suppressed because it is too large Load Diff

View File

@ -1,96 +0,0 @@
CodeMirror.defineMode("gfm", function(config) {
var codeDepth = 0;
function blankLine(state) {
state.code = false;
return null;
}
var gfmOverlay = {
startState: function() {
return {
code: false,
codeBlock: false,
ateSpace: false
};
},
copyState: function(s) {
return {
code: s.code,
codeBlock: s.codeBlock,
ateSpace: s.ateSpace
};
},
token: function(stream, state) {
// Hack to prevent formatting override inside code blocks (block and inline)
if (state.codeBlock) {
if (stream.match(/^```/)) {
state.codeBlock = false;
return null;
}
stream.skipToEnd();
return null;
}
if (stream.sol()) {
state.code = false;
}
if (stream.sol() && stream.match(/^```/)) {
stream.skipToEnd();
state.codeBlock = true;
return null;
}
// If this block is changed, it may need to be updated in Markdown mode
if (stream.peek() === '`') {
stream.next();
var before = stream.pos;
stream.eatWhile('`');
var difference = 1 + stream.pos - before;
if (!state.code) {
codeDepth = difference;
state.code = true;
} else {
if (difference === codeDepth) { // Must be exact
state.code = false;
}
}
return null;
} else if (state.code) {
stream.next();
return null;
}
// Check if space. If so, links can be formatted later on
if (stream.eatSpace()) {
state.ateSpace = true;
return null;
}
if (stream.sol() || state.ateSpace) {
state.ateSpace = false;
if(stream.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+@)?(?:[a-f0-9]{7,40}\b)/)) {
// User/Project@SHA
// User@SHA
// SHA
return "link";
} else if (stream.match(/^(?:[a-zA-Z0-9\-_]+\/)?(?:[a-zA-Z0-9\-_]+)?#[0-9]+\b/)) {
// User/Project#Num
// User#Num
// #Num
return "link";
}
}
if (stream.match(/^((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\([^\s()<>]*\))+(?:\([^\s()<>]*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/i)) {
// URLs
// Taken from http://daringfireball.net/2010/07/improved_regex_for_matching_urls
// And then (issue #1160) simplified to make it not crash the Chrome Regexp engine
return "link";
}
stream.next();
return null;
},
blankLine: blankLine
};
CodeMirror.defineMIME("gfmBase", {
name: "markdown",
underscoresBreakWords: false,
taskLists: true,
fencedCodeBlocks: true
});
return CodeMirror.overlayMode(CodeMirror.getMode(config, "gfmBase"), gfmOverlay);
}, "markdown");

View File

@ -1,74 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>CodeMirror: GFM mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="../../addon/mode/overlay.js"></script>
<script src="../xml/xml.js"></script>
<script src="../markdown/markdown.js"></script>
<script src="gfm.js"></script>
<!-- Code block highlighting modes -->
<script src="../javascript/javascript.js"></script>
<script src="../css/css.js"></script>
<script src="../htmlmixed/htmlmixed.js"></script>
<script src="../clike/clike.js"></script>
<style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
<link rel="stylesheet" href="../../doc/docs.css">
</head>
<body>
<h1>CodeMirror: GFM mode</h1>
<form><textarea id="code" name="code">
GitHub Flavored Markdown
========================
Everything from markdown plus GFM features:
## URL autolinking
Underscores_are_allowed_between_words.
## Fenced code blocks (and syntax highlighting)
```javascript
for (var i = 0; i &lt; items.length; i++) {
console.log(items[i], i); // log them
}
```
## Task Lists
- [ ] Incomplete task list item
- [x] **Completed** task list item
## A bit of GitHub spice
* SHA: be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2
* User@SHA ref: mojombo@be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2
* User/Project@SHA: mojombo/god@be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2
* \#Num: #1
* User/#Num: mojombo#1
* User/Project#Num: mojombo/god#1
See http://github.github.com/github-flavored-markdown/.
</textarea></form>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
mode: 'gfm',
lineNumbers: true,
theme: "default"
});
</script>
<p>Optionally depends on other modes for properly highlighted code blocks.</p>
<p><strong>Parsing/Highlighting Tests:</strong> <a href="../../test/index.html#gfm_*">normal</a>, <a href="../../test/index.html#verbose,gfm_*">verbose</a>.</p>
</body>
</html>

View File

@ -1,112 +0,0 @@
(function() {
var mode = CodeMirror.getMode({tabSize: 4}, "gfm");
function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); }
MT("emInWordAsterisk",
"foo[em *bar*]hello");
MT("emInWordUnderscore",
"foo_bar_hello");
MT("emStrongUnderscore",
"[strong __][em&strong _foo__][em _] bar");
MT("fencedCodeBlocks",
"[comment ```]",
"[comment foo]",
"",
"[comment ```]",
"bar");
MT("fencedCodeBlockModeSwitching",
"[comment ```javascript]",
"[variable foo]",
"",
"[comment ```]",
"bar");
MT("taskListAsterisk",
"[variable-2 * []] foo]", // Invalid; must have space or x between []
"[variable-2 * [ ]]bar]", // Invalid; must have space after ]
"[variable-2 * [x]]hello]", // Invalid; must have space after ]
"[variable-2 * ][meta [ ]]][variable-2 [world]]]", // Valid; tests reference style links
" [variable-3 * ][property [x]]][variable-3 foo]"); // Valid; can be nested
MT("taskListPlus",
"[variable-2 + []] foo]", // Invalid; must have space or x between []
"[variable-2 + [ ]]bar]", // Invalid; must have space after ]
"[variable-2 + [x]]hello]", // Invalid; must have space after ]
"[variable-2 + ][meta [ ]]][variable-2 [world]]]", // Valid; tests reference style links
" [variable-3 + ][property [x]]][variable-3 foo]"); // Valid; can be nested
MT("taskListDash",
"[variable-2 - []] foo]", // Invalid; must have space or x between []
"[variable-2 - [ ]]bar]", // Invalid; must have space after ]
"[variable-2 - [x]]hello]", // Invalid; must have space after ]
"[variable-2 - ][meta [ ]]][variable-2 [world]]]", // Valid; tests reference style links
" [variable-3 - ][property [x]]][variable-3 foo]"); // Valid; can be nested
MT("taskListNumber",
"[variable-2 1. []] foo]", // Invalid; must have space or x between []
"[variable-2 2. [ ]]bar]", // Invalid; must have space after ]
"[variable-2 3. [x]]hello]", // Invalid; must have space after ]
"[variable-2 4. ][meta [ ]]][variable-2 [world]]]", // Valid; tests reference style links
" [variable-3 1. ][property [x]]][variable-3 foo]"); // Valid; can be nested
MT("SHA",
"foo [link be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2] bar");
MT("shortSHA",
"foo [link be6a8cc] bar");
MT("tooShortSHA",
"foo be6a8c bar");
MT("longSHA",
"foo be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd22 bar");
MT("badSHA",
"foo be6a8cc1c1ecfe9489fb51e4869af15a13fc2cg2 bar");
MT("userSHA",
"foo [link bar@be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2] hello");
MT("userProjectSHA",
"foo [link bar/hello@be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2] world");
MT("num",
"foo [link #1] bar");
MT("badNum",
"foo #1bar hello");
MT("userNum",
"foo [link bar#1] hello");
MT("userProjectNum",
"foo [link bar/hello#1] world");
MT("vanillaLink",
"foo [link http://www.example.com/] bar");
MT("vanillaLinkPunctuation",
"foo [link http://www.example.com/]. bar");
MT("vanillaLinkExtension",
"foo [link http://www.example.com/index.html] bar");
MT("notALink",
"[comment ```css]",
"[tag foo] {[property color][operator :][keyword black];}",
"[comment ```][link http://www.example.com/]");
MT("notALink",
"[comment ``foo `bar` http://www.example.com/``] hello");
MT("notALink",
"[comment `foo]",
"[link http://www.example.com/]",
"[comment `foo]",
"",
"[link http://www.example.com/]");
})();

View File

@ -1,349 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>CodeMirror: Markdown mode</title>
<link rel="stylesheet" href="../../lib/codemirror.css">
<script src="../../lib/codemirror.js"></script>
<script src="../../addon/edit/continuelist.js"></script>
<script src="../xml/xml.js"></script>
<script src="markdown.js"></script>
<style type="text/css">
.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}
.cm-s-default .cm-trailing-space-a:before,
.cm-s-default .cm-trailing-space-b:before {position: absolute; content: "\00B7"; color: #777;}
.cm-s-default .cm-trailing-space-new-line:before {position: absolute; content: "\21B5"; color: #777;}
</style>
<link rel="stylesheet" href="../../doc/docs.css">
</head>
<body>
<h1>CodeMirror: Markdown mode</h1>
<!-- source: http://daringfireball.net/projects/markdown/basics.text -->
<form><textarea id="code" name="code">
Markdown: Basics
================
&lt;ul id="ProjectSubmenu"&gt;
&lt;li&gt;&lt;a href="/projects/markdown/" title="Markdown Project Page"&gt;Main&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="selected" title="Markdown Basics"&gt;Basics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/projects/markdown/syntax" title="Markdown Syntax Documentation"&gt;Syntax&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/projects/markdown/license" title="Pricing and License Information"&gt;License&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="/projects/markdown/dingus" title="Online Markdown Web Form"&gt;Dingus&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
Getting the Gist of Markdown's Formatting Syntax
------------------------------------------------
This page offers a brief overview of what it's like to use Markdown.
The [syntax page] [s] provides complete, detailed documentation for
every feature, but Markdown should be very easy to pick up simply by
looking at a few examples of it in action. The examples on this page
are written in a before/after style, showing example syntax and the
HTML output produced by Markdown.
It's also helpful to simply try Markdown out; the [Dingus] [d] is a
web application that allows you type your own Markdown-formatted text
and translate it to XHTML.
**Note:** This document is itself written using Markdown; you
can [see the source for it by adding '.text' to the URL] [src].
[s]: /projects/markdown/syntax "Markdown Syntax"
[d]: /projects/markdown/dingus "Markdown Dingus"
[src]: /projects/markdown/basics.text
## Paragraphs, Headers, Blockquotes ##
A paragraph is simply one or more consecutive lines of text, separated
by one or more blank lines. (A blank line is any line that looks like
a blank line -- a line containing nothing but spaces or tabs is
considered blank.) Normal paragraphs should not be indented with
spaces or tabs.
Markdown offers two styles of headers: *Setext* and *atx*.
Setext-style headers for `&lt;h1&gt;` and `&lt;h2&gt;` are created by
"underlining" with equal signs (`=`) and hyphens (`-`), respectively.
To create an atx-style header, you put 1-6 hash marks (`#`) at the
beginning of the line -- the number of hashes equals the resulting
HTML header level.
Blockquotes are indicated using email-style '`&gt;`' angle brackets.
Markdown:
A First Level Header
====================
A Second Level Header
---------------------
Now is the time for all good men to come to
the aid of their country. This is just a
regular paragraph.
The quick brown fox jumped over the lazy
dog's back.
### Header 3
&gt; This is a blockquote.
&gt;
&gt; This is the second paragraph in the blockquote.
&gt;
&gt; ## This is an H2 in a blockquote
Output:
&lt;h1&gt;A First Level Header&lt;/h1&gt;
&lt;h2&gt;A Second Level Header&lt;/h2&gt;
&lt;p&gt;Now is the time for all good men to come to
the aid of their country. This is just a
regular paragraph.&lt;/p&gt;
&lt;p&gt;The quick brown fox jumped over the lazy
dog's back.&lt;/p&gt;
&lt;h3&gt;Header 3&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;This is a blockquote.&lt;/p&gt;
&lt;p&gt;This is the second paragraph in the blockquote.&lt;/p&gt;
&lt;h2&gt;This is an H2 in a blockquote&lt;/h2&gt;
&lt;/blockquote&gt;
### Phrase Emphasis ###
Markdown uses asterisks and underscores to indicate spans of emphasis.
Markdown:
Some of these words *are emphasized*.
Some of these words _are emphasized also_.
Use two asterisks for **strong emphasis**.
Or, if you prefer, __use two underscores instead__.
Output:
&lt;p&gt;Some of these words &lt;em&gt;are emphasized&lt;/em&gt;.
Some of these words &lt;em&gt;are emphasized also&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Use two asterisks for &lt;strong&gt;strong emphasis&lt;/strong&gt;.
Or, if you prefer, &lt;strong&gt;use two underscores instead&lt;/strong&gt;.&lt;/p&gt;
## Lists ##
Unordered (bulleted) lists use asterisks, pluses, and hyphens (`*`,
`+`, and `-`) as list markers. These three markers are
interchangable; this:
* Candy.
* Gum.
* Booze.
this:
+ Candy.
+ Gum.
+ Booze.
and this:
- Candy.
- Gum.
- Booze.
all produce the same output:
&lt;ul&gt;
&lt;li&gt;Candy.&lt;/li&gt;
&lt;li&gt;Gum.&lt;/li&gt;
&lt;li&gt;Booze.&lt;/li&gt;
&lt;/ul&gt;
Ordered (numbered) lists use regular numbers, followed by periods, as
list markers:
1. Red
2. Green
3. Blue
Output:
&lt;ol&gt;
&lt;li&gt;Red&lt;/li&gt;
&lt;li&gt;Green&lt;/li&gt;
&lt;li&gt;Blue&lt;/li&gt;
&lt;/ol&gt;
If you put blank lines between items, you'll get `&lt;p&gt;` tags for the
list item text. You can create multi-paragraph list items by indenting
the paragraphs by 4 spaces or 1 tab:
* A list item.
With multiple paragraphs.
* Another item in the list.
Output:
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A list item.&lt;/p&gt;
&lt;p&gt;With multiple paragraphs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Another item in the list.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
### Links ###
Markdown supports two styles for creating links: *inline* and
*reference*. With both styles, you use square brackets to delimit the
text you want to turn into a link.
Inline-style links use parentheses immediately after the link text.
For example:
This is an [example link](http://example.com/).
Output:
&lt;p&gt;This is an &lt;a href="http://example.com/"&gt;
example link&lt;/a&gt;.&lt;/p&gt;
Optionally, you may include a title attribute in the parentheses:
This is an [example link](http://example.com/ "With a Title").
Output:
&lt;p&gt;This is an &lt;a href="http://example.com/" title="With a Title"&gt;
example link&lt;/a&gt;.&lt;/p&gt;
Reference-style links allow you to refer to your links by names, which
you define elsewhere in your document:
I get 10 times more traffic from [Google][1] than from
[Yahoo][2] or [MSN][3].
[1]: http://google.com/ "Google"
[2]: http://search.yahoo.com/ "Yahoo Search"
[3]: http://search.msn.com/ "MSN Search"
Output:
&lt;p&gt;I get 10 times more traffic from &lt;a href="http://google.com/"
title="Google"&gt;Google&lt;/a&gt; than from &lt;a href="http://search.yahoo.com/"
title="Yahoo Search"&gt;Yahoo&lt;/a&gt; or &lt;a href="http://search.msn.com/"
title="MSN Search"&gt;MSN&lt;/a&gt;.&lt;/p&gt;
The title attribute is optional. Link names may contain letters,
numbers and spaces, but are *not* case sensitive:
I start my morning with a cup of coffee and
[The New York Times][NY Times].
[ny times]: http://www.nytimes.com/
Output:
&lt;p&gt;I start my morning with a cup of coffee and
&lt;a href="http://www.nytimes.com/"&gt;The New York Times&lt;/a&gt;.&lt;/p&gt;
### Images ###
Image syntax is very much like link syntax.
Inline (titles are optional):
![alt text](/path/to/img.jpg "Title")
Reference-style:
![alt text][id]
[id]: /path/to/img.jpg "Title"
Both of the above examples produce the same output:
&lt;img src="/path/to/img.jpg" alt="alt text" title="Title" /&gt;
### Code ###
In a regular paragraph, you can create code span by wrapping text in
backtick quotes. Any ampersands (`&amp;`) and angle brackets (`&lt;` or
`&gt;`) will automatically be translated into HTML entities. This makes
it easy to use Markdown to write about HTML example code:
I strongly recommend against using any `&lt;blink&gt;` tags.
I wish SmartyPants used named entities like `&amp;mdash;`
instead of decimal-encoded entites like `&amp;#8212;`.
Output:
&lt;p&gt;I strongly recommend against using any
&lt;code&gt;&amp;lt;blink&amp;gt;&lt;/code&gt; tags.&lt;/p&gt;
&lt;p&gt;I wish SmartyPants used named entities like
&lt;code&gt;&amp;amp;mdash;&lt;/code&gt; instead of decimal-encoded
entites like &lt;code&gt;&amp;amp;#8212;&lt;/code&gt;.&lt;/p&gt;
To specify an entire block of pre-formatted code, indent every line of
the block by 4 spaces or 1 tab. Just like with code spans, `&amp;`, `&lt;`,
and `&gt;` characters will be escaped automatically.
Markdown:
If you want your page to validate under XHTML 1.0 Strict,
you've got to put paragraph tags in your blockquotes:
&lt;blockquote&gt;
&lt;p&gt;For example.&lt;/p&gt;
&lt;/blockquote&gt;
Output:
&lt;p&gt;If you want your page to validate under XHTML 1.0 Strict,
you've got to put paragraph tags in your blockquotes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;blockquote&amp;gt;
&amp;lt;p&amp;gt;For example.&amp;lt;/p&amp;gt;
&amp;lt;/blockquote&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
</textarea></form>
<script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
mode: 'markdown',
lineNumbers: true,
theme: "default",
extraKeys: {"Enter": "newlineAndIndentContinueMarkdownList"}
});
</script>
<p>Optionally depends on the XML mode for properly highlighted inline XML blocks.</p>
<p><strong>MIME types defined:</strong> <code>text/x-markdown</code>.</p>
<p><strong>Parsing/Highlighting Tests:</strong> <a href="../../test/index.html#markdown_*">normal</a>, <a href="../../test/index.html#verbose,markdown_*">verbose</a>.</p>
</body>
</html>

View File

@ -1,551 +0,0 @@
CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
var htmlFound = CodeMirror.modes.hasOwnProperty("xml");
var htmlMode = CodeMirror.getMode(cmCfg, htmlFound ? {name: "xml", htmlMode: true} : "text/plain");
var aliases = {
html: "htmlmixed",
js: "javascript",
json: "application/json",
c: "text/x-csrc",
"c++": "text/x-c++src",
java: "text/x-java",
csharp: "text/x-csharp",
"c#": "text/x-csharp",
scala: "text/x-scala"
};
var getMode = (function () {
var i, modes = {}, mimes = {}, mime;
var list = [];
for (var m in CodeMirror.modes)
if (CodeMirror.modes.propertyIsEnumerable(m)) list.push(m);
for (i = 0; i < list.length; i++) {
modes[list[i]] = list[i];
}
var mimesList = [];
for (var m in CodeMirror.mimeModes)
if (CodeMirror.mimeModes.propertyIsEnumerable(m))
mimesList.push({mime: m, mode: CodeMirror.mimeModes[m]});
for (i = 0; i < mimesList.length; i++) {
mime = mimesList[i].mime;
mimes[mime] = mimesList[i].mime;
}
for (var a in aliases) {
if (aliases[a] in modes || aliases[a] in mimes)
modes[a] = aliases[a];
}
return function (lang) {
return modes[lang] ? CodeMirror.getMode(cmCfg, modes[lang]) : null;
};
}());
// Should underscores in words open/close em/strong?
if (modeCfg.underscoresBreakWords === undefined)
modeCfg.underscoresBreakWords = true;
// Turn on fenced code blocks? ("```" to start/end)
if (modeCfg.fencedCodeBlocks === undefined) modeCfg.fencedCodeBlocks = false;
// Turn on task lists? ("- [ ] " and "- [x] ")
if (modeCfg.taskLists === undefined) modeCfg.taskLists = false;
var codeDepth = 0;
var header = 'header'
, code = 'comment'
, quote1 = 'atom'
, quote2 = 'number'
, list1 = 'variable-2'
, list2 = 'variable-3'
, list3 = 'keyword'
, hr = 'hr'
, image = 'tag'
, linkinline = 'link'
, linkemail = 'link'
, linktext = 'link'
, linkhref = 'string'
, em = 'em'
, strong = 'strong';
var hrRE = /^([*\-=_])(?:\s*\1){2,}\s*$/
, ulRE = /^[*\-+]\s+/
, olRE = /^[0-9]+\.\s+/
, taskListRE = /^\[(x| )\](?=\s)/ // Must follow ulRE or olRE
, headerRE = /^(?:\={1,}|-{1,})$/
, textRE = /^[^!\[\]*_\\<>` "'(]+/;
function switchInline(stream, state, f) {
state.f = state.inline = f;
return f(stream, state);
}
function switchBlock(stream, state, f) {
state.f = state.block = f;
return f(stream, state);
}
// Blocks
function blankLine(state) {
// Reset linkTitle state
state.linkTitle = false;
// Reset EM state
state.em = false;
// Reset STRONG state
state.strong = false;
// Reset state.quote
state.quote = 0;
if (!htmlFound && state.f == htmlBlock) {
state.f = inlineNormal;
state.block = blockNormal;
}
// Reset state.trailingSpace
state.trailingSpace = 0;
state.trailingSpaceNewLine = false;
// Mark this line as blank
state.thisLineHasContent = false;
return null;
}
function blockNormal(stream, state) {
var prevLineIsList = (state.list !== false);
if (state.list !== false && state.indentationDiff >= 0) { // Continued list
if (state.indentationDiff < 4) { // Only adjust indentation if *not* a code block
state.indentation -= state.indentationDiff;
}
state.list = null;
} else if (state.list !== false && state.indentation > 0) {
state.list = null;
state.listDepth = Math.floor(state.indentation / 4);
} else if (state.list !== false) { // No longer a list
state.list = false;
state.listDepth = 0;
}
if (state.indentationDiff >= 4) {
state.indentation -= 4;
stream.skipToEnd();
return code;
} else if (stream.eatSpace()) {
return null;
} else if (stream.peek() === '#' || (state.prevLineHasContent && stream.match(headerRE)) ) {
state.header = true;
} else if (stream.eat('>')) {
state.indentation++;
state.quote = 1;
stream.eatSpace();
while (stream.eat('>')) {
stream.eatSpace();
state.quote++;
}
} else if (stream.peek() === '[') {
return switchInline(stream, state, footnoteLink);
} else if (stream.match(hrRE, true)) {
return hr;
} else if ((!state.prevLineHasContent || prevLineIsList) && (stream.match(ulRE, true) || stream.match(olRE, true))) {
state.indentation += 4;
state.list = true;
state.listDepth++;
if (modeCfg.taskLists && stream.match(taskListRE, false)) {
state.taskList = true;
}
} else if (modeCfg.fencedCodeBlocks && stream.match(/^```([\w+#]*)/, true)) {
// try switching mode
state.localMode = getMode(RegExp.$1);
if (state.localMode) state.localState = state.localMode.startState();
switchBlock(stream, state, local);
return code;
}
return switchInline(stream, state, state.inline);
}
function htmlBlock(stream, state) {
var style = htmlMode.token(stream, state.htmlState);
if (htmlFound && style === 'tag' && state.htmlState.type !== 'openTag' && !state.htmlState.context) {
state.f = inlineNormal;
state.block = blockNormal;
}
if (state.md_inside && stream.current().indexOf(">")!=-1) {
state.f = inlineNormal;
state.block = blockNormal;
state.htmlState.context = undefined;
}
return style;
}
function local(stream, state) {
if (stream.sol() && stream.match(/^```/, true)) {
state.localMode = state.localState = null;
state.f = inlineNormal;
state.block = blockNormal;
return code;
} else if (state.localMode) {
return state.localMode.token(stream, state.localState);
} else {
stream.skipToEnd();
return code;
}
}
// Inline
function getType(state) {
var styles = [];
if (state.taskOpen) { return "meta"; }
if (state.taskClosed) { return "property"; }
if (state.strong) { styles.push(strong); }
if (state.em) { styles.push(em); }
if (state.linkText) { styles.push(linktext); }
if (state.code) { styles.push(code); }
if (state.header) { styles.push(header); }
if (state.quote) { styles.push(state.quote % 2 ? quote1 : quote2); }
if (state.list !== false) {
var listMod = (state.listDepth - 1) % 3;
if (!listMod) {
styles.push(list1);
} else if (listMod === 1) {
styles.push(list2);
} else {
styles.push(list3);
}
}
if (state.trailingSpaceNewLine) {
styles.push("trailing-space-new-line");
} else if (state.trailingSpace) {
styles.push("trailing-space-" + (state.trailingSpace % 2 ? "a" : "b"));
}
return styles.length ? styles.join(' ') : null;
}
function handleText(stream, state) {
if (stream.match(textRE, true)) {
return getType(state);
}
return undefined;
}
function inlineNormal(stream, state) {
var style = state.text(stream, state);
if (typeof style !== 'undefined')
return style;
if (state.list) { // List marker (*, +, -, 1., etc)
state.list = null;
return getType(state);
}
if (state.taskList) {
var taskOpen = stream.match(taskListRE, true)[1] !== "x";
if (taskOpen) state.taskOpen = true;
else state.taskClosed = true;
state.taskList = false;
return getType(state);
}
state.taskOpen = false;
state.taskClosed = false;
var ch = stream.next();
if (ch === '\\') {
stream.next();
return getType(state);
}
// Matches link titles present on next line
if (state.linkTitle) {
state.linkTitle = false;
var matchCh = ch;
if (ch === '(') {
matchCh = ')';
}
matchCh = (matchCh+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
var regex = '^\\s*(?:[^' + matchCh + '\\\\]+|\\\\\\\\|\\\\.)' + matchCh;
if (stream.match(new RegExp(regex), true)) {
return linkhref;
}
}
// If this block is changed, it may need to be updated in GFM mode
if (ch === '`') {
var t = getType(state);
var before = stream.pos;
stream.eatWhile('`');
var difference = 1 + stream.pos - before;
if (!state.code) {
codeDepth = difference;
state.code = true;
return getType(state);
} else {
if (difference === codeDepth) { // Must be exact
state.code = false;
return t;
}
return getType(state);
}
} else if (state.code) {
return getType(state);
}
if (ch === '!' && stream.match(/\[[^\]]*\] ?(?:\(|\[)/, false)) {
stream.match(/\[[^\]]*\]/);
state.inline = state.f = linkHref;
return image;
}
if (ch === '[' && stream.match(/.*\](\(| ?\[)/, false)) {
state.linkText = true;
return getType(state);
}
if (ch === ']' && state.linkText) {
var type = getType(state);
state.linkText = false;
state.inline = state.f = linkHref;
return type;
}
if (ch === '<' && stream.match(/^(https?|ftps?):\/\/(?:[^\\>]|\\.)+>/, false)) {
return switchInline(stream, state, inlineElement(linkinline, '>'));
}
if (ch === '<' && stream.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/, false)) {
return switchInline(stream, state, inlineElement(linkemail, '>'));
}
if (ch === '<' && stream.match(/^\w/, false)) {
if (stream.string.indexOf(">")!=-1) {
var atts = stream.string.substring(1,stream.string.indexOf(">"));
if (/markdown\s*=\s*('|"){0,1}1('|"){0,1}/.test(atts)) {
state.md_inside = true;
}
}
stream.backUp(1);
return switchBlock(stream, state, htmlBlock);
}
if (ch === '<' && stream.match(/^\/\w*?>/)) {
state.md_inside = false;
return "tag";
}
var ignoreUnderscore = false;
if (!modeCfg.underscoresBreakWords) {
if (ch === '_' && stream.peek() !== '_' && stream.match(/(\w)/, false)) {
var prevPos = stream.pos - 2;
if (prevPos >= 0) {
var prevCh = stream.string.charAt(prevPos);
if (prevCh !== '_' && prevCh.match(/(\w)/, false)) {
ignoreUnderscore = true;
}
}
}
}
var t = getType(state);
if (ch === '*' || (ch === '_' && !ignoreUnderscore)) {
if (state.strong === ch && stream.eat(ch)) { // Remove STRONG
state.strong = false;
return t;
} else if (!state.strong && stream.eat(ch)) { // Add STRONG
state.strong = ch;
return getType(state);
} else if (state.em === ch) { // Remove EM
state.em = false;
return t;
} else if (!state.em) { // Add EM
state.em = ch;
return getType(state);
}
} else if (ch === ' ') {
if (stream.eat('*') || stream.eat('_')) { // Probably surrounded by spaces
if (stream.peek() === ' ') { // Surrounded by spaces, ignore
return getType(state);
} else { // Not surrounded by spaces, back up pointer
stream.backUp(1);
}
}
}
if (ch === ' ') {
if (stream.match(/ +$/, false)) {
state.trailingSpace++;
} else if (state.trailingSpace) {
state.trailingSpaceNewLine = true;
}
}
return getType(state);
}
function linkHref(stream, state) {
// Check if space, and return NULL if so (to avoid marking the space)
if(stream.eatSpace()){
return null;
}
var ch = stream.next();
if (ch === '(' || ch === '[') {
return switchInline(stream, state, inlineElement(linkhref, ch === '(' ? ')' : ']'));
}
return 'error';
}
function footnoteLink(stream, state) {
if (stream.match(/^[^\]]*\]:/, true)) {
state.f = footnoteUrl;
return linktext;
}
return switchInline(stream, state, inlineNormal);
}
function footnoteUrl(stream, state) {
// Check if space, and return NULL if so (to avoid marking the space)
if(stream.eatSpace()){
return null;
}
// Match URL
stream.match(/^[^\s]+/, true);
// Check for link title
if (stream.peek() === undefined) { // End of line, set flag to check next line
state.linkTitle = true;
} else { // More content on line, check if link title
stream.match(/^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/, true);
}
state.f = state.inline = inlineNormal;
return linkhref;
}
var savedInlineRE = [];
function inlineRE(endChar) {
if (!savedInlineRE[endChar]) {
// Escape endChar for RegExp (taken from http://stackoverflow.com/a/494122/526741)
endChar = (endChar+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
// Match any non-endChar, escaped character, as well as the closing
// endChar.
savedInlineRE[endChar] = new RegExp('^(?:[^\\\\]|\\\\.)*?(' + endChar + ')');
}
return savedInlineRE[endChar];
}
function inlineElement(type, endChar, next) {
next = next || inlineNormal;
return function(stream, state) {
stream.match(inlineRE(endChar));
state.inline = state.f = next;
return type;
};
}
return {
startState: function() {
return {
f: blockNormal,
prevLineHasContent: false,
thisLineHasContent: false,
block: blockNormal,
htmlState: CodeMirror.startState(htmlMode),
indentation: 0,
inline: inlineNormal,
text: handleText,
linkText: false,
linkTitle: false,
em: false,
strong: false,
header: false,
taskList: false,
list: false,
listDepth: 0,
quote: 0,
trailingSpace: 0,
trailingSpaceNewLine: false
};
},
copyState: function(s) {
return {
f: s.f,
prevLineHasContent: s.prevLineHasContent,
thisLineHasContent: s.thisLineHasContent,
block: s.block,
htmlState: CodeMirror.copyState(htmlMode, s.htmlState),
indentation: s.indentation,
localMode: s.localMode,
localState: s.localMode ? CodeMirror.copyState(s.localMode, s.localState) : null,
inline: s.inline,
text: s.text,
linkTitle: s.linkTitle,
em: s.em,
strong: s.strong,
header: s.header,
taskList: s.taskList,
list: s.list,
listDepth: s.listDepth,
quote: s.quote,
trailingSpace: s.trailingSpace,
trailingSpaceNewLine: s.trailingSpaceNewLine,
md_inside: s.md_inside
};
},
token: function(stream, state) {
if (stream.sol()) {
if (stream.match(/^\s*$/, true)) {
state.prevLineHasContent = false;
return blankLine(state);
} else {
state.prevLineHasContent = state.thisLineHasContent;
state.thisLineHasContent = true;
}
// Reset state.header
state.header = false;
// Reset state.taskList
state.taskList = false;
// Reset state.code
state.code = false;
// Reset state.trailingSpace
state.trailingSpace = 0;
state.trailingSpaceNewLine = false;
state.f = state.block;
var indentation = stream.match(/^\s*/, true)[0].replace(/\t/g, ' ').length;
var difference = Math.floor((indentation - state.indentation) / 4) * 4;
if (difference > 4) difference = 4;
var adjustedIndentation = state.indentation + difference;
state.indentationDiff = adjustedIndentation - state.indentation;
state.indentation = adjustedIndentation;
if (indentation > 0) return null;
}
return state.f(stream, state);
},
blankLine: blankLine,
getType: getType
};
}, "xml");
CodeMirror.defineMIME("text/x-markdown", "markdown");

View File

@ -1,656 +0,0 @@
(function() {
var mode = CodeMirror.getMode({tabSize: 4}, "markdown");
function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1)); }
MT("plainText",
"foo");
// Don't style single trailing space
MT("trailingSpace1",
"foo ");
// Two or more trailing spaces should be styled with line break character
MT("trailingSpace2",
"foo[trailing-space-a ][trailing-space-new-line ]");
MT("trailingSpace3",
"foo[trailing-space-a ][trailing-space-b ][trailing-space-new-line ]");
MT("trailingSpace4",
"foo[trailing-space-a ][trailing-space-b ][trailing-space-a ][trailing-space-new-line ]");
// Code blocks using 4 spaces (regardless of CodeMirror.tabSize value)
MT("codeBlocksUsing4Spaces",
" [comment foo]");
// Code blocks using 4 spaces with internal indentation
MT("codeBlocksUsing4SpacesIndentation",
" [comment bar]",
" [comment hello]",
" [comment world]",
" [comment foo]",
"bar");
// Code blocks using 4 spaces with internal indentation
MT("codeBlocksUsing4SpacesIndentation",
" foo",
" [comment bar]",
" [comment hello]",
" [comment world]");
// Code blocks using 1 tab (regardless of CodeMirror.indentWithTabs value)
MT("codeBlocksUsing1Tab",
"\t[comment foo]");
// Inline code using backticks
MT("inlineCodeUsingBackticks",
"foo [comment `bar`]");
// Block code using single backtick (shouldn't work)
MT("blockCodeSingleBacktick",
"[comment `]",
"foo",
"[comment `]");
// Unclosed backticks
// Instead of simply marking as CODE, it would be nice to have an
// incomplete flag for CODE, that is styled slightly different.
MT("unclosedBackticks",
"foo [comment `bar]");
// Per documentation: "To include a literal backtick character within a
// code span, you can use multiple backticks as the opening and closing
// delimiters"
MT("doubleBackticks",
"[comment ``foo ` bar``]");
// Tests based on Dingus
// http://daringfireball.net/projects/markdown/dingus
//
// Multiple backticks within an inline code block
MT("consecutiveBackticks",
"[comment `foo```bar`]");
// Multiple backticks within an inline code block with a second code block
MT("consecutiveBackticks",
"[comment `foo```bar`] hello [comment `world`]");
// Unclosed with several different groups of backticks
MT("unclosedBackticks",
"[comment ``foo ``` bar` hello]");
// Closed with several different groups of backticks
MT("closedBackticks",
"[comment ``foo ``` bar` hello``] world");
// atx headers
// http://daringfireball.net/projects/markdown/syntax#header
MT("atxH1",
"[header # foo]");
MT("atxH2",
"[header ## foo]");
MT("atxH3",
"[header ### foo]");
MT("atxH4",
"[header #### foo]");
MT("atxH5",
"[header ##### foo]");
MT("atxH6",
"[header ###### foo]");
// H6 - 7x '#' should still be H6, per Dingus
// http://daringfireball.net/projects/markdown/dingus
MT("atxH6NotH7",
"[header ####### foo]");
// Setext headers - H1, H2
// Per documentation, "Any number of underlining =s or -s will work."
// http://daringfireball.net/projects/markdown/syntax#header
// Ideally, the text would be marked as `header` as well, but this is
// not really feasible at the moment. So, instead, we're testing against
// what works today, to avoid any regressions.
//
// Check if single underlining = works
MT("setextH1",
"foo",
"[header =]");
// Check if 3+ ='s work
MT("setextH1",
"foo",
"[header ===]");
// Check if single underlining - works
MT("setextH2",
"foo",
"[header -]");
// Check if 3+ -'s work
MT("setextH2",
"foo",
"[header ---]");
// Single-line blockquote with trailing space
MT("blockquoteSpace",
"[atom > foo]");
// Single-line blockquote
MT("blockquoteNoSpace",
"[atom >foo]");
// No blank line before blockquote
MT("blockquoteNoBlankLine",
"foo",
"[atom > bar]");
// Nested blockquote
MT("blockquoteSpace",
"[atom > foo]",
"[number > > foo]",
"[atom > > > foo]");
// Single-line blockquote followed by normal paragraph
MT("blockquoteThenParagraph",
"[atom >foo]",
"",
"bar");
// Multi-line blockquote (lazy mode)
MT("multiBlockquoteLazy",
"[atom >foo]",
"[atom bar]");
// Multi-line blockquote followed by normal paragraph (lazy mode)
MT("multiBlockquoteLazyThenParagraph",
"[atom >foo]",
"[atom bar]",
"",
"hello");
// Multi-line blockquote (non-lazy mode)
MT("multiBlockquote",
"[atom >foo]",
"[atom >bar]");
// Multi-line blockquote followed by normal paragraph (non-lazy mode)
MT("multiBlockquoteThenParagraph",
"[atom >foo]",
"[atom >bar]",
"",
"hello");
// Check list types
MT("listAsterisk",
"foo",
"bar",
"",
"[variable-2 * foo]",
"[variable-2 * bar]");
MT("listPlus",
"foo",
"bar",
"",
"[variable-2 + foo]",
"[variable-2 + bar]");
MT("listDash",
"foo",
"bar",
"",
"[variable-2 - foo]",
"[variable-2 - bar]");
MT("listNumber",
"foo",
"bar",
"",
"[variable-2 1. foo]",
"[variable-2 2. bar]");
// Lists require a preceding blank line (per Dingus)
MT("listBogus",
"foo",
"1. bar",
"2. hello");
// Formatting in lists (*)
MT("listAsteriskFormatting",
"[variable-2 * ][variable-2&em *foo*][variable-2 bar]",
"[variable-2 * ][variable-2&strong **foo**][variable-2 bar]",
"[variable-2 * ][variable-2&strong **][variable-2&em&strong *foo**][variable-2&em *][variable-2 bar]",
"[variable-2 * ][variable-2&comment `foo`][variable-2 bar]");
// Formatting in lists (+)
MT("listPlusFormatting",
"[variable-2 + ][variable-2&em *foo*][variable-2 bar]",
"[variable-2 + ][variable-2&strong **foo**][variable-2 bar]",
"[variable-2 + ][variable-2&strong **][variable-2&em&strong *foo**][variable-2&em *][variable-2 bar]",
"[variable-2 + ][variable-2&comment `foo`][variable-2 bar]");
// Formatting in lists (-)
MT("listDashFormatting",
"[variable-2 - ][variable-2&em *foo*][variable-2 bar]",
"[variable-2 - ][variable-2&strong **foo**][variable-2 bar]",
"[variable-2 - ][variable-2&strong **][variable-2&em&strong *foo**][variable-2&em *][variable-2 bar]",
"[variable-2 - ][variable-2&comment `foo`][variable-2 bar]");
// Formatting in lists (1.)
MT("listNumberFormatting",
"[variable-2 1. ][variable-2&em *foo*][variable-2 bar]",
"[variable-2 2. ][variable-2&strong **foo**][variable-2 bar]",
"[variable-2 3. ][variable-2&strong **][variable-2&em&strong *foo**][variable-2&em *][variable-2 bar]",
"[variable-2 4. ][variable-2&comment `foo`][variable-2 bar]");
// Paragraph lists
MT("listParagraph",
"[variable-2 * foo]",
"",
"[variable-2 * bar]");
// Multi-paragraph lists
//
// 4 spaces
MT("listMultiParagraph",
"[variable-2 * foo]",
"",
"[variable-2 * bar]",
"",
" [variable-2 hello]");
// 4 spaces, extra blank lines (should still be list, per Dingus)
MT("listMultiParagraphExtra",
"[variable-2 * foo]",
"",
"[variable-2 * bar]",
"",
"",
" [variable-2 hello]");
// 4 spaces, plus 1 space (should still be list, per Dingus)
MT("listMultiParagraphExtraSpace",
"[variable-2 * foo]",
"",
"[variable-2 * bar]",
"",
" [variable-2 hello]",
"",
" [variable-2 world]");
// 1 tab
MT("listTab",
"[variable-2 * foo]",
"",
"[variable-2 * bar]",
"",
"\t[variable-2 hello]");
// No indent
MT("listNoIndent",
"[variable-2 * foo]",
"",
"[variable-2 * bar]",
"",
"hello");
// Blockquote
MT("blockquote",
"[variable-2 * foo]",
"",
"[variable-2 * bar]",
"",
" [variable-2&atom > hello]");
// Code block
MT("blockquoteCode",
"[variable-2 * foo]",
"",
"[variable-2 * bar]",
"",
" [comment > hello]",
"",
" [variable-2 world]");
// Code block followed by text
MT("blockquoteCodeText",
"[variable-2 * foo]",
"",
" [variable-2 bar]",
"",
" [comment hello]",
"",
" [variable-2 world]");
// Nested list
MT("listAsteriskNested",
"[variable-2 * foo]",
"",
" [variable-3 * bar]");
MT("listPlusNested",
"[variable-2 + foo]",
"",
" [variable-3 + bar]");
MT("listDashNested",
"[variable-2 - foo]",
"",
" [variable-3 - bar]");
MT("listNumberNested",
"[variable-2 1. foo]",
"",
" [variable-3 2. bar]");
MT("listMixed",
"[variable-2 * foo]",
"",
" [variable-3 + bar]",
"",
" [keyword - hello]",
"",
" [variable-2 1. world]");
MT("listBlockquote",
"[variable-2 * foo]",
"",
" [variable-3 + bar]",
"",
" [atom&variable-3 > hello]");
MT("listCode",
"[variable-2 * foo]",
"",
" [variable-3 + bar]",
"",
" [comment hello]");
// Code with internal indentation
MT("listCodeIndentation",
"[variable-2 * foo]",
"",
" [comment bar]",
" [comment hello]",
" [comment world]",
" [comment foo]",
" [variable-2 bar]");
// List nesting edge cases
MT("listNested",
"[variable-2 * foo]",
"",
" [variable-3 * bar]",
"",
" [variable-2 hello]"
);
MT("listNested",
"[variable-2 * foo]",
"",
" [variable-3 * bar]",
"",
" [variable-3 * foo]"
);
// Code followed by text
MT("listCodeText",
"[variable-2 * foo]",
"",
" [comment bar]",
"",
"hello");
// Following tests directly from official Markdown documentation
// http://daringfireball.net/projects/markdown/syntax#hr
MT("hrSpace",
"[hr * * *]");
MT("hr",
"[hr ***]");
MT("hrLong",
"[hr *****]");
MT("hrSpaceDash",
"[hr - - -]");
MT("hrDashLong",
"[hr ---------------------------------------]");
// Inline link with title
MT("linkTitle",
"[link [[foo]]][string (http://example.com/ \"bar\")] hello");
// Inline link without title
MT("linkNoTitle",
"[link [[foo]]][string (http://example.com/)] bar");
// Inline link with image
MT("linkImage",
"[link [[][tag ![[foo]]][string (http://example.com/)][link ]]][string (http://example.com/)] bar");
// Inline link with Em
MT("linkEm",
"[link [[][link&em *foo*][link ]]][string (http://example.com/)] bar");
// Inline link with Strong
MT("linkStrong",
"[link [[][link&strong **foo**][link ]]][string (http://example.com/)] bar");
// Inline link with EmStrong
MT("linkEmStrong",
"[link [[][link&strong **][link&em&strong *foo**][link&em *][link ]]][string (http://example.com/)] bar");
// Image with title
MT("imageTitle",
"[tag ![[foo]]][string (http://example.com/ \"bar\")] hello");
// Image without title
MT("imageNoTitle",
"[tag ![[foo]]][string (http://example.com/)] bar");
// Image with asterisks
MT("imageAsterisks",
"[tag ![[*foo*]]][string (http://example.com/)] bar");
// Not a link. Should be normal text due to square brackets being used
// regularly in text, especially in quoted material, and no space is allowed
// between square brackets and parentheses (per Dingus).
MT("notALink",
"[[foo]] (bar)");
// Reference-style links
MT("linkReference",
"[link [[foo]]][string [[bar]]] hello");
// Reference-style links with Em
MT("linkReferenceEm",
"[link [[][link&em *foo*][link ]]][string [[bar]]] hello");
// Reference-style links with Strong
MT("linkReferenceStrong",
"[link [[][link&strong **foo**][link ]]][string [[bar]]] hello");
// Reference-style links with EmStrong
MT("linkReferenceEmStrong",
"[link [[][link&strong **][link&em&strong *foo**][link&em *][link ]]][string [[bar]]] hello");
// Reference-style links with optional space separator (per docuentation)
// "You can optionally use a space to separate the sets of brackets"
MT("linkReferenceSpace",
"[link [[foo]]] [string [[bar]]] hello");
// Should only allow a single space ("...use *a* space...")
MT("linkReferenceDoubleSpace",
"[[foo]] [[bar]] hello");
// Reference-style links with implicit link name
MT("linkImplicit",
"[link [[foo]]][string [[]]] hello");
// @todo It would be nice if, at some point, the document was actually
// checked to see if the referenced link exists
// Link label, for reference-style links (taken from documentation)
MT("labelNoTitle",
"[link [[foo]]:] [string http://example.com/]");
MT("labelIndented",
" [link [[foo]]:] [string http://example.com/]");
MT("labelSpaceTitle",
"[link [[foo bar]]:] [string http://example.com/ \"hello\"]");
MT("labelDoubleTitle",
"[link [[foo bar]]:] [string http://example.com/ \"hello\"] \"world\"");
MT("labelTitleDoubleQuotes",
"[link [[foo]]:] [string http://example.com/ \"bar\"]");
MT("labelTitleSingleQuotes",
"[link [[foo]]:] [string http://example.com/ 'bar']");
MT("labelTitleParenthese",
"[link [[foo]]:] [string http://example.com/ (bar)]");
MT("labelTitleInvalid",
"[link [[foo]]:] [string http://example.com/] bar");
MT("labelLinkAngleBrackets",
"[link [[foo]]:] [string <http://example.com/> \"bar\"]");
MT("labelTitleNextDoubleQuotes",
"[link [[foo]]:] [string http://example.com/]",
"[string \"bar\"] hello");
MT("labelTitleNextSingleQuotes",
"[link [[foo]]:] [string http://example.com/]",
"[string 'bar'] hello");
MT("labelTitleNextParenthese",
"[link [[foo]]:] [string http://example.com/]",
"[string (bar)] hello");
MT("labelTitleNextMixed",
"[link [[foo]]:] [string http://example.com/]",
"(bar\" hello");
MT("linkWeb",
"[link <http://example.com/>] foo");
MT("linkWebDouble",
"[link <http://example.com/>] foo [link <http://example.com/>]");
MT("linkEmail",
"[link <user@example.com>] foo");
MT("linkEmailDouble",
"[link <user@example.com>] foo [link <user@example.com>]");
MT("emAsterisk",
"[em *foo*] bar");
MT("emUnderscore",
"[em _foo_] bar");
MT("emInWordAsterisk",
"foo[em *bar*]hello");
MT("emInWordUnderscore",
"foo[em _bar_]hello");
// Per documentation: "...surround an * or _ with spaces, itll be
// treated as a literal asterisk or underscore."
MT("emEscapedBySpaceIn",
"foo [em _bar _ hello_] world");
MT("emEscapedBySpaceOut",
"foo _ bar[em _hello_]world");
// Unclosed emphasis characters
// Instead of simply marking as EM / STRONG, it would be nice to have an
// incomplete flag for EM and STRONG, that is styled slightly different.
MT("emIncompleteAsterisk",
"foo [em *bar]");
MT("emIncompleteUnderscore",
"foo [em _bar]");
MT("strongAsterisk",
"[strong **foo**] bar");
MT("strongUnderscore",
"[strong __foo__] bar");
MT("emStrongAsterisk",
"[em *foo][em&strong **bar*][strong hello**] world");
MT("emStrongUnderscore",
"[em _foo][em&strong __bar_][strong hello__] world");
// "...same character must be used to open and close an emphasis span.""
MT("emStrongMixed",
"[em _foo][em&strong **bar*hello__ world]");
MT("emStrongMixed",
"[em *foo][em&strong __bar_hello** world]");
// These characters should be escaped:
// \ backslash
// ` backtick
// * asterisk
// _ underscore
// {} curly braces
// [] square brackets
// () parentheses
// # hash mark
// + plus sign
// - minus sign (hyphen)
// . dot
// ! exclamation mark
MT("escapeBacktick",
"foo \\`bar\\`");
MT("doubleEscapeBacktick",
"foo \\\\[comment `bar\\\\`]");
MT("escapeAsterisk",
"foo \\*bar\\*");
MT("doubleEscapeAsterisk",
"foo \\\\[em *bar\\\\*]");
MT("escapeUnderscore",
"foo \\_bar\\_");
MT("doubleEscapeUnderscore",
"foo \\\\[em _bar\\\\_]");
MT("escapeHash",
"\\# foo");
MT("doubleEscapeHash",
"\\\\# foo");
// Tests to make sure GFM-specific things aren't getting through
MT("taskList",
"[variable-2 * [ ]] bar]");
MT("fencedCodeBlocks",
"[comment ```]",
"foo",
"[comment ```]");
})();

View File

@ -1,377 +0,0 @@
/**
* Countable is a script to allow for live paragraph-, word- and character-
* counting on an HTML element.
*
* @author Sacha Schmid (<https://github.com/RadLikeWhoa>)
* @version 2.0.0
* @license MIT
* @see <http://radlikewhoa.github.io/Countable/>
*/
/**
* Note: For the purpose of this internal documentation, arguments of the type
* {Nodes} are to be interpreted as either {NodeList} or {Element}.
*/
;(function (global) {
'use strict'
/**
* @private
*
* `_liveElements` holds all elements that have the live-counting
* functionality bound to them.
*
* `_event` holds the event to handle the live counting, based on the
* browser's capabilities.
*/
var _liveElements = [],
_event = 'oninput' in document ? 'input' : 'keyup'
/**
* `String.trim()` polyfill for non-supporting browsers. This is the
* recommended polyfill on MDN.
*
* @see <http://goo.gl/uYveB>
* @see <http://goo.gl/xjIxJ>
*
* @return {String} The original string with leading and trailing whitespace
* removed.
*/
if (!String.prototype.trim) {
String.prototype.trim = function () {
return this.replace(/^\s+|\s+$/g, '')
}
}
/**
* `ucs2decode` function from the punycode.js library.
*
* Creates an array containing the decimal code points of each Unicode
* character in the string. While JavaScript uses UCS-2 internally, this
* function will convert a pair of surrogate halves (each of which UCS-2
* exposes as separate characters) into a single code point, matching
* UTF-16.
*
* @see <http://goo.gl/8M09r>
* @see <http://goo.gl/u4UUC>
*
* @param {String} string The Unicode input string (UCS-2).
*
* @return {Array} The new array of code points.
*/
function _decode (string) {
var output = [],
counter = 0,
length = string.length,
value, extra
while (counter < length) {
value = string.charCodeAt(counter++)
if ((value & 0xF800) == 0xD800 && counter < length) {
// High surrogate, and there is a next character.
extra = string.charCodeAt(counter++)
if ((extra & 0xFC00) == 0xDC00) {
// Low surrogate.
output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000)
} else {
output.push(value, extra)
}
} else {
output.push(value)
}
}
return output
}
/**
* `_validateArguments` validates the arguments given to each function call.
* Errors are logged to the console as warnings, but Countable fails silently.
*
* @private
*
* @param {Nodes} elements The (collection of) element(s) to
* validate.
*
* @param {Function} callback The callback function to validate.
*
* @return {Boolean} Returns whether all arguments are vaild.
*/
function _validateArguments (elements, callback) {
var elementsValid = elements && ((Object.prototype.toString.call(elements) === '[object NodeList]' && elements.length) || (elements.nodeType === 1)),
callbackValid = callback && typeof callback === 'function'
if ('console' in window && 'warn' in console) {
if (!elementsValid) console.warn('Countable: No valid elements were found')
if (!callbackValid) console.warn('Countable: "' + callback + '" is not a valid callback function')
}
return elementsValid && callbackValid
}
/**
* `_extendDefaults` is a function to extend a set of default options with the
* ones given in the function call. Available options are described below.
*
* {Boolean} hardReturns Use two returns to seperate a paragraph instead of
* one.
* {Boolean} stripTags Strip HTML tags before counting the values.
*
* @private
*
* @param {Object} options Countable allows the options described above.
* They can be used in a function call to override
* the default behaviour.
*
* @return {Object} The new options object.
*/
function _extendDefaults (options) {
var defaults = { hardReturns: false, stripTags: false }
for (var prop in options) {
if (defaults.hasOwnProperty(prop)) defaults[prop] = options[prop]
}
return defaults
}
/**
* `_count` trims an element's value, optionally strips HTML tags and counts
* paragraphs, words, characters and characters plus spaces.
*
* @private
*
* @param {Element} element The element whose value is to be counted.
*
* @param {Object} options The options to use for the counting.
*
* @return {Object} The object containing the number of paragraphs,
* words, characters and characters plus spaces.
*/
function _count (element, options) {
var original = 'value' in element ? element.value : element.innerText || element.textContent,
temp, trimmed
/**
* The initial implementation to allow for HTML tags stripping was created
* @craniumslows while the current one was created by @Rob--W.
*
* @see <http://goo.gl/Exmlr>
* @see <http://goo.gl/gFQQh>
*/
if (options.stripTags) original = original.replace(/<\/?[a-z][^>]*>/gi, '')
trimmed = original.trim()
/**
* Most of the performance improvements are based on the works of @epmatsw.
*
* @see <http://goo.gl/SWOLB>
*/
return {
paragraphs: trimmed ? (trimmed.match(options.hardReturns ? /\n{2,}/g : /\n+/g) || []).length + 1 : 0,
words: trimmed ? (trimmed.replace(/['";:,.?¿\-!¡]+/g, '').match(/\S+/g) || []).length : 0,
characters: trimmed ? _decode(trimmed.replace(/\s/g, '')).length : 0,
all: _decode(original.replace(/[\n\r]/g, '')).length
}
}
/**
* `_loop` is a helper function to iterate over a collection, e.g. a NodeList
* or an Array. The callback receives the current element as the single
* parameter.
*
* @private
*
* @param {Array} which The collection to iterate over.
*
* @param {Function} callback The callback function to call on each
* iteration.
*/
function _loop (which, callback) {
var len = which.length
if (typeof len !== 'undefined') {
while (len--) {
callback(which[len])
}
} else {
callback(which)
}
}
/**
* This is the main object that will later be exposed to other scripts. It
* holds all the public methods that can be used to enable the Countable
* functionality.
*/
var Countable = {
/**
* The `live` method binds the counting handler to all given elements. The
* event is either `oninput` or `onkeydown`, based on the capabilities of
* the browser.
*
* @param {Nodes} elements All elements that should receive the
* Countable functionality.
*
* @param {Function} callback The callback to fire whenever the
* element's value changes. The callback is
* called with the relevant element bound to
* `this` and the counted values as the
* single parameter.
*
* @param {Object} [options] An object to modify Countable's
* behaviour. Refer to `_extendDefaults` for
* a list of available options.
*
* @return {Object} Returns the Countable object to allow for chaining.
*/
live: function (elements, callback, options) {
var ops = _extendDefaults(options),
bind = function (element) {
var handler = function () {
callback.call(element, _count(element, ops))
}
_liveElements.push({ element: element, handler: handler })
handler()
if (element.addEventListener) {
element.addEventListener(_event, handler, false)
} else if (element.attachEvent) {
element.attachEvent('on' + _event, handler)
}
}
if (!_validateArguments(elements, callback)) return
if (elements.length) {
_loop(elements, bind)
} else {
bind(elements)
}
return this
},
/**
* The `die` method removes the Countable functionality from all given
* elements.
*
* @param {Nodes} elements All elements whose Countable functionality
* should be unbound.
*
* @return {Object} Returns the Countable object to allow for chaining.
*/
die: function (elements) {
if (!_validateArguments(elements, function () {})) return
_loop(elements, function (element) {
var liveElement
_loop(_liveElements, function (live) {
if (live.element === element) liveElement = live
})
if (!liveElement) return
if (element.removeEventListener) {
element.removeEventListener(_event, liveElement.handler, false)
} else if (element.detachEvent) {
element.detachEvent('on' + _event, liveElement.handler)
}
_liveElements.splice(_liveElements.indexOf(liveElement), 1)
})
return this
},
/**
* The `once` method works mostly like the `live` method, but no events are
* bound, the functionality is only executed once.
*
* @param {Nodes} elements All elements that should receive the
* Countable functionality.
*
* @param {Function} callback The callback to fire whenever the
* element's value changes. The callback is
* called with the relevant element bound to
* `this` and the counted values as the
* single parameter.
*
* @param {Object} [options] An object to modify Countable's
* behaviour. Refer to `_extendDefaults`
* for a list of available options.
*
* @return {Object} Returns the Countable object to allow for chaining.
*/
once: function (elements, callback, options) {
if (!_validateArguments(elements, callback)) return
_loop(elements, function (element) {
callback.call(element, _count(element, _extendDefaults(options)))
})
return this
},
/**
* The `enabled` method checks if the live-counting functionality is bound
* to an element.
*
* @param {Element} element A single Element.
*
* @return {Boolean} A boolean value representing whether Countable
* functionality is bound to the given element.
*/
enabled: function (element) {
var isEnabled = false
if (element && element.nodeType === 1) {
_loop(_liveElements, function (live) {
if (live.element === element) isEnabled = true
})
}
return isEnabled
}
}
/**
* Expose Countable depending on the module system used across the
* application. (Node / CommonJS, AMD, global)
*/
if (typeof exports === 'object') {
module.exports = Countable
} else if (typeof define === 'function' && define.amd) {
define(function () { return Countable })
} else {
global.Countable = Countable
}
}(this))

View File

@ -1,761 +0,0 @@
/**
* @preserve FastClick: polyfill to remove click delays on browsers with touch UIs.
*
* @version 0.6.9
* @codingstandard ftlabs-jsv2
* @copyright The Financial Times Limited [All Rights Reserved]
* @license MIT License (see LICENSE.txt)
*/
/*jslint browser:true, node:true*/
/*global define, Event, Node*/
/**
* Instantiate fast-clicking listeners on the specificed layer.
*
* @constructor
* @param {Element} layer The layer to listen on
*/
function FastClick(layer) {
'use strict';
var oldOnClick, self = this;
/**
* Whether a click is currently being tracked.
*
* @type boolean
*/
this.trackingClick = false;
/**
* Timestamp for when when click tracking started.
*
* @type number
*/
this.trackingClickStart = 0;
/**
* The element being tracked for a click.
*
* @type EventTarget
*/
this.targetElement = null;
/**
* X-coordinate of touch start event.
*
* @type number
*/
this.touchStartX = 0;
/**
* Y-coordinate of touch start event.
*
* @type number
*/
this.touchStartY = 0;
/**
* ID of the last touch, retrieved from Touch.identifier.
*
* @type number
*/
this.lastTouchIdentifier = 0;
/**
* Touchmove boundary, beyond which a click will be cancelled.
*
* @type number
*/
this.touchBoundary = 10;
/**
* The FastClick layer.
*
* @type Element
*/
this.layer = layer;
if (!layer || !layer.nodeType) {
throw new TypeError('Layer must be a document node');
}
/** @type function() */
this.onClick = function() { return FastClick.prototype.onClick.apply(self, arguments); };
/** @type function() */
this.onMouse = function() { return FastClick.prototype.onMouse.apply(self, arguments); };
/** @type function() */
this.onTouchStart = function() { return FastClick.prototype.onTouchStart.apply(self, arguments); };
/** @type function() */
this.onTouchMove = function() { return FastClick.prototype.onTouchMove.apply(self, arguments); };
/** @type function() */
this.onTouchEnd = function() { return FastClick.prototype.onTouchEnd.apply(self, arguments); };
/** @type function() */
this.onTouchCancel = function() { return FastClick.prototype.onTouchCancel.apply(self, arguments); };
if (FastClick.notNeeded(layer)) {
return;
}
// Set up event handlers as required
if (this.deviceIsAndroid) {
layer.addEventListener('mouseover', this.onMouse, true);
layer.addEventListener('mousedown', this.onMouse, true);
layer.addEventListener('mouseup', this.onMouse, true);
}
layer.addEventListener('click', this.onClick, true);
layer.addEventListener('touchstart', this.onTouchStart, false);
layer.addEventListener('touchmove', this.onTouchMove, false);
layer.addEventListener('touchend', this.onTouchEnd, false);
layer.addEventListener('touchcancel', this.onTouchCancel, false);
// Hack is required for browsers that don't support Event#stopImmediatePropagation (e.g. Android 2)
// which is how FastClick normally stops click events bubbling to callbacks registered on the FastClick
// layer when they are cancelled.
if (!Event.prototype.stopImmediatePropagation) {
layer.removeEventListener = function(type, callback, capture) {
var rmv = Node.prototype.removeEventListener;
if (type === 'click') {
rmv.call(layer, type, callback.hijacked || callback, capture);
} else {
rmv.call(layer, type, callback, capture);
}
};
layer.addEventListener = function(type, callback, capture) {
var adv = Node.prototype.addEventListener;
if (type === 'click') {
adv.call(layer, type, callback.hijacked || (callback.hijacked = function(event) {
if (!event.propagationStopped) {
callback(event);
}
}), capture);
} else {
adv.call(layer, type, callback, capture);
}
};
}
// If a handler is already declared in the element's onclick attribute, it will be fired before
// FastClick's onClick handler. Fix this by pulling out the user-defined handler function and
// adding it as listener.
if (typeof layer.onclick === 'function') {
// Android browser on at least 3.2 requires a new reference to the function in layer.onclick
// - the old one won't work if passed to addEventListener directly.
oldOnClick = layer.onclick;
layer.addEventListener('click', function(event) {
oldOnClick(event);
}, false);
layer.onclick = null;
}
}
/**
* Android requires exceptions.
*
* @type boolean
*/
FastClick.prototype.deviceIsAndroid = navigator.userAgent.indexOf('Android') > 0;
/**
* iOS requires exceptions.
*
* @type boolean
*/
FastClick.prototype.deviceIsIOS = /iP(ad|hone|od)/.test(navigator.userAgent);
/**
* iOS 4 requires an exception for select elements.
*
* @type boolean
*/
FastClick.prototype.deviceIsIOS4 = FastClick.prototype.deviceIsIOS && (/OS 4_\d(_\d)?/).test(navigator.userAgent);
/**
* iOS 6.0(+?) requires the target element to be manually derived
*
* @type boolean
*/
FastClick.prototype.deviceIsIOSWithBadTarget = FastClick.prototype.deviceIsIOS && (/OS ([6-9]|\d{2})_\d/).test(navigator.userAgent);
/**
* Determine whether a given element requires a native click.
*
* @param {EventTarget|Element} target Target DOM element
* @returns {boolean} Returns true if the element needs a native click
*/
FastClick.prototype.needsClick = function(target) {
'use strict';
switch (target.nodeName.toLowerCase()) {
// Don't send a synthetic click to disabled inputs (issue #62)
case 'button':
case 'select':
case 'textarea':
if (target.disabled) {
return true;
}
break;
case 'input':
// File inputs need real clicks on iOS 6 due to a browser bug (issue #68)
if ((this.deviceIsIOS && target.type === 'file') || target.disabled) {
return true;
}
break;
case 'label':
case 'video':
return true;
}
return (/\bneedsclick\b/).test(target.className);
};
/**
* Determine whether a given element requires a call to focus to simulate click into element.
*
* @param {EventTarget|Element} target Target DOM element
* @returns {boolean} Returns true if the element requires a call to focus to simulate native click.
*/
FastClick.prototype.needsFocus = function(target) {
'use strict';
switch (target.nodeName.toLowerCase()) {
case 'textarea':
case 'select':
return true;
case 'input':
switch (target.type) {
case 'button':
case 'checkbox':
case 'file':
case 'image':
case 'radio':
case 'submit':
return false;
}
// No point in attempting to focus disabled inputs
return !target.disabled && !target.readOnly;
default:
return (/\bneedsfocus\b/).test(target.className);
}
};
/**
* Send a click event to the specified element.
*
* @param {EventTarget|Element} targetElement
* @param {Event} event
*/
FastClick.prototype.sendClick = function(targetElement, event) {
'use strict';
var clickEvent, touch;
// On some Android devices activeElement needs to be blurred otherwise the synthetic click will have no effect (#24)
if (document.activeElement && document.activeElement !== targetElement) {
document.activeElement.blur();
}
touch = event.changedTouches[0];
// Synthesise a click event, with an extra attribute so it can be tracked
clickEvent = document.createEvent('MouseEvents');
clickEvent.initMouseEvent('click', true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
clickEvent.forwardedTouchEvent = true;
targetElement.dispatchEvent(clickEvent);
};
/**
* @param {EventTarget|Element} targetElement
*/
FastClick.prototype.focus = function(targetElement) {
'use strict';
var length;
if (this.deviceIsIOS && targetElement.setSelectionRange) {
length = targetElement.value.length;
targetElement.setSelectionRange(length, length);
} else {
targetElement.focus();
}
};
/**
* Check whether the given target element is a child of a scrollable layer and if so, set a flag on it.
*
* @param {EventTarget|Element} targetElement
*/
FastClick.prototype.updateScrollParent = function(targetElement) {
'use strict';
var scrollParent, parentElement;
scrollParent = targetElement.fastClickScrollParent;
// Attempt to discover whether the target element is contained within a scrollable layer. Re-check if the
// target element was moved to another parent.
if (!scrollParent || !scrollParent.contains(targetElement)) {
parentElement = targetElement;
do {
if (parentElement.scrollHeight > parentElement.offsetHeight) {
scrollParent = parentElement;
targetElement.fastClickScrollParent = parentElement;
break;
}
parentElement = parentElement.parentElement;
} while (parentElement);
}
// Always update the scroll top tracker if possible.
if (scrollParent) {
scrollParent.fastClickLastScrollTop = scrollParent.scrollTop;
}
};
/**
* @param {EventTarget} targetElement
* @returns {Element|EventTarget}
*/
FastClick.prototype.getTargetElementFromEventTarget = function(eventTarget) {
'use strict';
// On some older browsers (notably Safari on iOS 4.1 - see issue #56) the event target may be a text node.
if (eventTarget.nodeType === Node.TEXT_NODE) {
return eventTarget.parentNode;
}
return eventTarget;
};
/**
* On touch start, record the position and scroll offset.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.onTouchStart = function(event) {
'use strict';
var targetElement, touch, selection;
// Ignore multiple touches, otherwise pinch-to-zoom is prevented if both fingers are on the FastClick element (issue #111).
if (event.targetTouches.length > 1) {
return true;
}
targetElement = this.getTargetElementFromEventTarget(event.target);
touch = event.targetTouches[0];
if (this.deviceIsIOS) {
// Only trusted events will deselect text on iOS (issue #49)
selection = window.getSelection();
if (selection.rangeCount && !selection.isCollapsed) {
return true;
}
if (!this.deviceIsIOS4) {
// Weird things happen on iOS when an alert or confirm dialog is opened from a click event callback (issue #23):
// when the user next taps anywhere else on the page, new touchstart and touchend events are dispatched
// with the same identifier as the touch event that previously triggered the click that triggered the alert.
// Sadly, there is an issue on iOS 4 that causes some normal touch events to have the same identifier as an
// immediately preceeding touch event (issue #52), so this fix is unavailable on that platform.
if (touch.identifier === this.lastTouchIdentifier) {
event.preventDefault();
return false;
}
this.lastTouchIdentifier = touch.identifier;
// If the target element is a child of a scrollable layer (using -webkit-overflow-scrolling: touch) and:
// 1) the user does a fling scroll on the scrollable layer
// 2) the user stops the fling scroll with another tap
// then the event.target of the last 'touchend' event will be the element that was under the user's finger
// when the fling scroll was started, causing FastClick to send a click event to that layer - unless a check
// is made to ensure that a parent layer was not scrolled before sending a synthetic click (issue #42).
this.updateScrollParent(targetElement);
}
}
this.trackingClick = true;
this.trackingClickStart = event.timeStamp;
this.targetElement = targetElement;
this.touchStartX = touch.pageX;
this.touchStartY = touch.pageY;
// Prevent phantom clicks on fast double-tap (issue #36)
if ((event.timeStamp - this.lastClickTime) < 200) {
event.preventDefault();
}
return true;
};
/**
* Based on a touchmove event object, check whether the touch has moved past a boundary since it started.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.touchHasMoved = function(event) {
'use strict';
var touch = event.changedTouches[0], boundary = this.touchBoundary;
if (Math.abs(touch.pageX - this.touchStartX) > boundary || Math.abs(touch.pageY - this.touchStartY) > boundary) {
return true;
}
return false;
};
/**
* Update the last position.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.onTouchMove = function(event) {
'use strict';
if (!this.trackingClick) {
return true;
}
// If the touch has moved, cancel the click tracking
if (this.targetElement !== this.getTargetElementFromEventTarget(event.target) || this.touchHasMoved(event)) {
this.trackingClick = false;
this.targetElement = null;
}
return true;
};
/**
* Attempt to find the labelled control for the given label element.
*
* @param {EventTarget|HTMLLabelElement} labelElement
* @returns {Element|null}
*/
FastClick.prototype.findControl = function(labelElement) {
'use strict';
// Fast path for newer browsers supporting the HTML5 control attribute
if (labelElement.control !== undefined) {
return labelElement.control;
}
// All browsers under test that support touch events also support the HTML5 htmlFor attribute
if (labelElement.htmlFor) {
return document.getElementById(labelElement.htmlFor);
}
// If no for attribute exists, attempt to retrieve the first labellable descendant element
// the list of which is defined here: http://www.w3.org/TR/html5/forms.html#category-label
return labelElement.querySelector('button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea');
};
/**
* On touch end, determine whether to send a click event at once.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.onTouchEnd = function(event) {
'use strict';
var forElement, trackingClickStart, targetTagName, scrollParent, touch, targetElement = this.targetElement;
if (!this.trackingClick) {
return true;
}
// Prevent phantom clicks on fast double-tap (issue #36)
if ((event.timeStamp - this.lastClickTime) < 200) {
this.cancelNextClick = true;
return true;
}
this.lastClickTime = event.timeStamp;
trackingClickStart = this.trackingClickStart;
this.trackingClick = false;
this.trackingClickStart = 0;
// On some iOS devices, the targetElement supplied with the event is invalid if the layer
// is performing a transition or scroll, and has to be re-detected manually. Note that
// for this to function correctly, it must be called *after* the event target is checked!
// See issue #57; also filed as rdar://13048589 .
if (this.deviceIsIOSWithBadTarget) {
touch = event.changedTouches[0];
// In certain cases arguments of elementFromPoint can be negative, so prevent setting targetElement to null
targetElement = document.elementFromPoint(touch.pageX - window.pageXOffset, touch.pageY - window.pageYOffset) || targetElement;
targetElement.fastClickScrollParent = this.targetElement.fastClickScrollParent;
}
targetTagName = targetElement.tagName.toLowerCase();
if (targetTagName === 'label') {
forElement = this.findControl(targetElement);
if (forElement) {
this.focus(targetElement);
if (this.deviceIsAndroid) {
return false;
}
targetElement = forElement;
}
} else if (this.needsFocus(targetElement)) {
// Case 1: If the touch started a while ago (best guess is 100ms based on tests for issue #36) then focus will be triggered anyway. Return early and unset the target element reference so that the subsequent click will be allowed through.
// Case 2: Without this exception for input elements tapped when the document is contained in an iframe, then any inputted text won't be visible even though the value attribute is updated as the user types (issue #37).
if ((event.timeStamp - trackingClickStart) > 100 || (this.deviceIsIOS && window.top !== window && targetTagName === 'input')) {
this.targetElement = null;
return false;
}
this.focus(targetElement);
// Select elements need the event to go through on iOS 4, otherwise the selector menu won't open.
if (!this.deviceIsIOS4 || targetTagName !== 'select') {
this.targetElement = null;
event.preventDefault();
}
return false;
}
if (this.deviceIsIOS && !this.deviceIsIOS4) {
// Don't send a synthetic click event if the target element is contained within a parent layer that was scrolled
// and this tap is being used to stop the scrolling (usually initiated by a fling - issue #42).
scrollParent = targetElement.fastClickScrollParent;
if (scrollParent && scrollParent.fastClickLastScrollTop !== scrollParent.scrollTop) {
return true;
}
}
// Prevent the actual click from going though - unless the target node is marked as requiring
// real clicks or if it is in the whitelist in which case only non-programmatic clicks are permitted.
if (!this.needsClick(targetElement)) {
event.preventDefault();
this.sendClick(targetElement, event);
}
return false;
};
/**
* On touch cancel, stop tracking the click.
*
* @returns {void}
*/
FastClick.prototype.onTouchCancel = function() {
'use strict';
this.trackingClick = false;
this.targetElement = null;
};
/**
* Determine mouse events which should be permitted.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.onMouse = function(event) {
'use strict';
// If a target element was never set (because a touch event was never fired) allow the event
if (!this.targetElement) {
return true;
}
if (event.forwardedTouchEvent) {
return true;
}
// Programmatically generated events targeting a specific element should be permitted
if (!event.cancelable) {
return true;
}
// Derive and check the target element to see whether the mouse event needs to be permitted;
// unless explicitly enabled, prevent non-touch click events from triggering actions,
// to prevent ghost/doubleclicks.
if (!this.needsClick(this.targetElement) || this.cancelNextClick) {
// Prevent any user-added listeners declared on FastClick element from being fired.
if (event.stopImmediatePropagation) {
event.stopImmediatePropagation();
} else {
// Part of the hack for browsers that don't support Event#stopImmediatePropagation (e.g. Android 2)
event.propagationStopped = true;
}
// Cancel the event
event.stopPropagation();
event.preventDefault();
return false;
}
// If the mouse event is permitted, return true for the action to go through.
return true;
};
/**
* On actual clicks, determine whether this is a touch-generated click, a click action occurring
* naturally after a delay after a touch (which needs to be cancelled to avoid duplication), or
* an actual click which should be permitted.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.onClick = function(event) {
'use strict';
var permitted;
// It's possible for another FastClick-like library delivered with third-party code to fire a click event before FastClick does (issue #44). In that case, set the click-tracking flag back to false and return early. This will cause onTouchEnd to return early.
if (this.trackingClick) {
this.targetElement = null;
this.trackingClick = false;
return true;
}
// Very odd behaviour on iOS (issue #18): if a submit element is present inside a form and the user hits enter in the iOS simulator or clicks the Go button on the pop-up OS keyboard the a kind of 'fake' click event will be triggered with the submit-type input element as the target.
if (event.target.type === 'submit' && event.detail === 0) {
return true;
}
permitted = this.onMouse(event);
// Only unset targetElement if the click is not permitted. This will ensure that the check for !targetElement in onMouse fails and the browser's click doesn't go through.
if (!permitted) {
this.targetElement = null;
}
// If clicks are permitted, return true for the action to go through.
return permitted;
};
/**
* Remove all FastClick's event listeners.
*
* @returns {void}
*/
FastClick.prototype.destroy = function() {
'use strict';
var layer = this.layer;
if (this.deviceIsAndroid) {
layer.removeEventListener('mouseover', this.onMouse, true);
layer.removeEventListener('mousedown', this.onMouse, true);
layer.removeEventListener('mouseup', this.onMouse, true);
}
layer.removeEventListener('click', this.onClick, true);
layer.removeEventListener('touchstart', this.onTouchStart, false);
layer.removeEventListener('touchmove', this.onTouchMove, false);
layer.removeEventListener('touchend', this.onTouchEnd, false);
layer.removeEventListener('touchcancel', this.onTouchCancel, false);
};
/**
* Check whether FastClick is needed.
*
* @param {Element} layer The layer to listen on
*/
FastClick.notNeeded = function(layer) {
'use strict';
var metaViewport;
// Devices that don't support touch don't need FastClick
if (typeof window.ontouchstart === 'undefined') {
return true;
}
if ((/Chrome\/[0-9]+/).test(navigator.userAgent)) {
// Chrome on Android with user-scalable="no" doesn't need FastClick (issue #89)
if (FastClick.prototype.deviceIsAndroid) {
metaViewport = document.querySelector('meta[name=viewport]');
if (metaViewport && metaViewport.content.indexOf('user-scalable=no') !== -1) {
return true;
}
// Chrome desktop doesn't need FastClick (issue #15)
} else {
return true;
}
}
// IE10 with -ms-touch-action: none, which disables double-tap-to-zoom (issue #97)
if (layer.style.msTouchAction === 'none') {
return true;
}
return false;
};
/**
* Factory method for creating a FastClick object
*
* @param {Element} layer The layer to listen on
*/
FastClick.attach = function(layer) {
'use strict';
return new FastClick(layer);
};
if (typeof define !== 'undefined' && define.amd) {
// AMD. Register as an anonymous module.
define(function() {
'use strict';
return FastClick;
});
} else if (typeof module !== 'undefined' && module.exports) {
module.exports = FastClick.attach;
module.exports.FastClick = FastClick;
} else {
window.FastClick = FastClick;
}

View File

@ -1,11 +0,0 @@
/*!
* iCheck v0.8.5 jQuery plugin, http://git.io/uhUPMA
*/
(function(f,m,z,u,k,r,l,n,D,t,v,s){function A(a,c,j){var d=a[0],b=/ble/.test(j)?r:k;active="update"==j?{checked:d[k],disabled:d[r]}:d[b];if(/^ch|di/.test(j)&&!active)w(a,b);else if(/^un|en/.test(j)&&active)x(a,b);else if("update"==j)for(var b in active)active[b]?w(a,b,!0):x(a,b,!0);else if(!c||"toggle"==j)c||a.trigger("ifClicked"),active?d[l]!==u&&x(a,b):w(a,b)}function w(a,c,j){var d=a[0],b=a.parent(),E=c==r?"enabled":"un"+k,n=e(a,E+g(d[l])),h=e(a,c+g(d[l]));if(!0!==d[c]&&!j&&(d[c]=!0,a.trigger("ifChanged").trigger("if"+
g(c)),c==k&&d[l]==u&&d.name)){j=a.closest("form");var p='input[name="'+d.name+'"]',p=j.length?j.find(p):f(p);p.each(function(){this!==d&&f(this).data(m)&&x(f(this),c)})}d[r]&&e(a,s,!0)&&b.find("."+m+"-helper").css(s,"default");b[t](h||e(a,c));b[v](n||e(a,E)||"")}function x(a,c,j){var d=a[0],b=a.parent(),f=c==r?"enabled":"un"+k,n=e(a,f+g(d[l])),h=e(a,c+g(d[l]));!1!==d[c]&&!j&&(d[c]=!1,a.trigger("ifChanged").trigger("if"+g(f)));!d[r]&&e(a,s,!0)&&b.find("."+m+"-helper").css(s,"pointer");b[v](h||e(a,
c)||"");b[t](n||e(a,f))}function F(a,c){a.data(m)&&(a.parent().html(a.attr("style",a.data(m).s||"").trigger(c||"")),a.off(".i").unwrap(),f('label[for="'+a[0].id+'"]').add(a.closest("label")).off(".i"))}function e(a,c,f){if(a.data(m))return a.data(m).o[c+(f?"":"Class")]}function g(a){return a.charAt(0).toUpperCase()+a.slice(1)}f.fn[m]=function(a,c){var j=navigator.userAgent,d=/ipad|iphone|ipod/i.test(j),b=":"+z+", :"+u,e=f(),g=function(a){a.each(function(){var a=f(this);e=a.is(b)?e.add(a):e.add(a.find(b))})};
if(/^(check|uncheck|toggle|disable|enable|update|destroy)$/.test(a))return g(this),e.each(function(){var d=f(this);"destroy"==a?F(d,"ifDestroyed"):A(d,!0,a);f.isFunction(c)&&c()});if("object"==typeof a||!a){var h=f.extend({checkedClass:k,disabledClass:r,labelHover:!0},a),p=h.handle,s=h.hoverClass||"hover",I=h.focusClass||"focus",G=h.activeClass||"active",H=!!h.labelHover,C=h.labelHoverClass||"hover",y=(""+h.increaseArea).replace("%","")|0;if(p==z||p==u)b=":"+p;-50>y&&(y=-50);g(this);return e.each(function(){var a=
f(this);F(a);var c=this,b=c.id,e=-y+"%",g=100+2*y+"%",g={position:"absolute",top:e,left:e,display:"block",width:g,height:g,margin:0,padding:0,background:"#fff",border:0,opacity:0},e=d||/android|blackberry|windows phone|opera mini/i.test(j)?{position:"absolute",visibility:"hidden"}:y?g:{position:"absolute",opacity:0},p=c[l]==z?h.checkboxClass||"i"+z:h.radioClass||"i"+u,B=f('label[for="'+b+'"]').add(a.closest("label")),q=a.wrap('<div class="'+p+'"/>').trigger("ifCreated").parent().append(h.insert),
g=f('<ins class="'+m+'-helper"/>').css(g).appendTo(q);a.data(m,{o:h,s:a.attr("style")}).css(e);h.inheritClass&&q[t](c.className);h.inheritID&&b&&q.attr("id",m+"-"+b);"static"==q.css("position")&&q.css("position","relative");A(a,!0,"update");if(B.length)B.on(n+".i mouseenter.i mouseleave.i "+D,function(b){var e=b[l],g=f(this);if(!c[r])if(e==n?A(a,!1,!0):H&&(/ve|nd/.test(e)?(q[v](s),g[v](C)):(q[t](s),g[t](C))),d)b.stopPropagation();else return!1});a.on(n+".i focus.i blur.i keyup.i keydown.i keypress.i",
function(b){var d=b[l];b=b.keyCode;if(d==n)return!1;if("keydown"==d&&32==b)return c[l]==u&&c[k]||(c[k]?x(a,k):w(a,k)),!1;if("keyup"==d&&c[l]==u)!c[k]&&w(a,k);else if(/us|ur/.test(d))q["blur"==d?v:t](I)});g.on(n+" mousedown mouseup mouseover mouseout "+D,function(b){var e=b[l],f=/wn|up/.test(e)?G:s;if(!c[r]){if(e==n)A(a,!1,!0);else{if(/wn|er|in/.test(e))q[t](f);else q[v](f+" "+G);if(B.length&&H&&f==s)B[/ut|nd/.test(e)?v:t](C)}if(d)b.stopPropagation();else return!1}})})}return this}})(jQuery,"iCheck",
"checkbox","radio","checked","disabled","type","click","touchbegin.i touchend.i","addClass","removeClass","cursor");

File diff suppressed because one or more lines are too long

View File

@ -1,275 +0,0 @@
/*! NProgress (c) 2013, Rico Sta. Cruz
* http://ricostacruz.com/nprogress */
;(function(factory) {
if (typeof module === 'function') {
module.exports = factory(this.jQuery || require('jquery'));
} else {
this.NProgress = factory(this.jQuery);
}
})(function($) {
var NProgress = {};
NProgress.version = '0.1.2';
var Settings = NProgress.settings = {
minimum: 0.08,
easing: 'ease',
positionUsing: '',
speed: 200,
trickle: true,
trickleRate: 0.02,
trickleSpeed: 800,
showSpinner: true,
template: '<div class="bar" role="bar"><div class="peg"></div></div><div class="spinner" role="spinner"><div class="spinner-icon"></div></div>'
};
/**
* Updates configuration.
*
* NProgress.configure({
* minimum: 0.1
* });
*/
NProgress.configure = function(options) {
$.extend(Settings, options);
return this;
};
/**
* Last number.
*/
NProgress.status = null;
/**
* Sets the progress bar status, where `n` is a number from `0.0` to `1.0`.
*
* NProgress.set(0.4);
* NProgress.set(1.0);
*/
NProgress.set = function(n) {
var started = NProgress.isStarted();
n = clamp(n, Settings.minimum, 1);
NProgress.status = (n === 1 ? null : n);
var $progress = NProgress.render(!started),
$bar = $progress.find('[role="bar"]'),
speed = Settings.speed,
ease = Settings.easing;
$progress[0].offsetWidth; /* Repaint */
$progress.queue(function(next) {
// Set positionUsing if it hasn't already been set
if (Settings.positionUsing === '') Settings.positionUsing = NProgress.getPositioningCSS();
// Add transition
$bar.css(barPositionCSS(n, speed, ease));
if (n === 1) {
// Fade out
$progress.css({ transition: 'none', opacity: 1 });
$progress[0].offsetWidth; /* Repaint */
setTimeout(function() {
$progress.css({ transition: 'all '+speed+'ms linear', opacity: 0 });
setTimeout(function() {
NProgress.remove();
next();
}, speed);
}, speed);
} else {
setTimeout(next, speed);
}
});
return this;
};
NProgress.isStarted = function() {
return typeof NProgress.status === 'number';
};
/**
* Shows the progress bar.
* This is the same as setting the status to 0%, except that it doesn't go backwards.
*
* NProgress.start();
*
*/
NProgress.start = function() {
if (!NProgress.status) NProgress.set(0);
var work = function() {
setTimeout(function() {
if (!NProgress.status) return;
NProgress.trickle();
work();
}, Settings.trickleSpeed);
};
if (Settings.trickle) work();
return this;
};
/**
* Hides the progress bar.
* This is the *sort of* the same as setting the status to 100%, with the
* difference being `done()` makes some placebo effect of some realistic motion.
*
* NProgress.done();
*
* If `true` is passed, it will show the progress bar even if its hidden.
*
* NProgress.done(true);
*/
NProgress.done = function(force) {
if (!force && !NProgress.status) return this;
return NProgress.inc(0.3 + 0.5 * Math.random()).set(1);
};
/**
* Increments by a random amount.
*/
NProgress.inc = function(amount) {
var n = NProgress.status;
if (!n) {
return NProgress.start();
} else {
if (typeof amount !== 'number') {
amount = (1 - n) * clamp(Math.random() * n, 0.1, 0.95);
}
n = clamp(n + amount, 0, 0.994);
return NProgress.set(n);
}
};
NProgress.trickle = function() {
return NProgress.inc(Math.random() * Settings.trickleRate);
};
/**
* (Internal) renders the progress bar markup based on the `template`
* setting.
*/
NProgress.render = function(fromStart) {
if (NProgress.isRendered()) return $("#nprogress");
$('html').addClass('nprogress-busy');
var $el = $("<div id='nprogress'>")
.html(Settings.template);
var perc = fromStart ? '-100' : toBarPerc(NProgress.status || 0);
$el.find('[role="bar"]').css({
transition: 'all 0 linear',
transform: 'translate3d('+perc+'%,0,0)'
});
if (!Settings.showSpinner)
$el.find('[role="spinner"]').remove();
$el.appendTo(document.body);
return $el;
};
/**
* Removes the element. Opposite of render().
*/
NProgress.remove = function() {
$('html').removeClass('nprogress-busy');
$('#nprogress').remove();
};
/**
* Checks if the progress bar is rendered.
*/
NProgress.isRendered = function() {
return ($("#nprogress").length > 0);
};
/**
* Determine which positioning CSS rule to use.
*/
NProgress.getPositioningCSS = function() {
// Sniff on document.body.style
var bodyStyle = document.body.style;
// Sniff prefixes
var vendorPrefix = ('WebkitTransform' in bodyStyle) ? 'Webkit' :
('MozTransform' in bodyStyle) ? 'Moz' :
('msTransform' in bodyStyle) ? 'ms' :
('OTransform' in bodyStyle) ? 'O' : '';
if (vendorPrefix + 'Perspective' in bodyStyle) {
// Modern browsers with 3D support, e.g. Webkit, IE10
return 'translate3d';
} else if (vendorPrefix + 'Transform' in bodyStyle) {
// Browsers without 3D support, e.g. IE9
return 'translate';
} else {
// Browsers without translate() support, e.g. IE7-8
return 'margin';
}
};
/**
* Helpers
*/
function clamp(n, min, max) {
if (n < min) return min;
if (n > max) return max;
return n;
}
/**
* (Internal) converts a percentage (`0..1`) to a bar translateX
* percentage (`-100%..0%`).
*/
function toBarPerc(n) {
return (-1 + n) * 100;
}
/**
* (Internal) returns the correct CSS for changing the bar's
* position given an n percentage, and speed and ease from Settings
*/
function barPositionCSS(n, speed, ease) {
var barCSS;
if (Settings.positionUsing === 'translate3d') {
barCSS = { transform: 'translate3d('+toBarPerc(n)+'%,0,0)' };
} else if (Settings.positionUsing === 'translate') {
barCSS = { transform: 'translate('+toBarPerc(n)+'%,0)' };
} else {
barCSS = { 'margin-left': toBarPerc(n)+'%' };
}
barCSS.transition = 'all '+speed+'ms '+ease;
return barCSS;
}
return NProgress;
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
/*globals window, $, _, Backbone, Validator */
/*globals window, $, _, Backbone, validator */
(function () {
'use strict';
@ -17,7 +17,6 @@
Views : {},
Collections : {},
Models : {},
Validate : new Validator(),
paths: ghostPaths(),
@ -62,21 +61,16 @@
});
};
Ghost.Validate.error = function (object) {
this._errors.push(object);
return this;
};
Ghost.Validate.handleErrors = function () {
validator.handleErrors = function (errors) {
Ghost.notifications.clearEverything();
_.each(Ghost.Validate._errors, function (errorObj) {
_.each(errors, function (errorObj) {
Ghost.notifications.addItem({
type: 'error',
message: errorObj.message || errorObj,
status: 'passive'
});
if (errorObj.hasOwnProperty('el')) {
errorObj.el.addClass('input-error');
}

View File

@ -1,6 +1,6 @@
// # Surrounds given text with Markdown syntax
/*global $, window, CodeMirror, Showdown, moment */
/*global $, CodeMirror, Showdown, moment */
(function () {
'use strict';
var Markdown = {

View File

@ -1,4 +1,4 @@
/*global window, document, setTimeout, Ghost, $, _, Backbone, JST, shortcut, NProgress */
/*global Ghost, _, Backbone, NProgress */
(function () {
"use strict";
@ -10,8 +10,10 @@
if (options !== undefined && _.isObject(options)) {
NProgress.start();
/*jshint validthis:true */
var self = this,
oldSuccess = options.success;
/*jshint validthis:false */
options.success = function () {
NProgress.done();
@ -19,6 +21,7 @@
};
}
/*jshint validthis:true */
return Backbone.sync.call(this, method, model, options);
}
@ -29,4 +32,4 @@
Ghost.ProgressCollection = Backbone.Collection.extend({
sync: wrapSync
});
}());
}());

View File

@ -1,4 +1,4 @@
/*global window, document, Ghost, $, _, Backbone */
/*global Ghost, _, Backbone */
(function () {
'use strict';

View File

@ -1,9 +1,9 @@
/*global window, document, Ghost, $, _, Backbone */
/*global Ghost */
(function () {
'use strict';
//id:0 is used to issue PUT requests
Ghost.Models.Settings = Ghost.ProgressModel.extend({
url: Ghost.paths.apiRoot + '/settings/?type=blog,theme',
url: Ghost.paths.apiRoot + '/settings/?type=blog,theme,app',
id: '0'
});

View File

@ -1,4 +1,4 @@
/*global window, document, Ghost, $, _, Backbone */
/*global Ghost */
(function () {
'use strict';

View File

@ -1,4 +1,4 @@
/*global window, document, Ghost, $, _, Backbone */
/*global Ghost, Backbone */
(function () {
'use strict';

View File

@ -1,4 +1,4 @@
/*global window, document, Ghost, $, _, Backbone */
/*global Ghost */
(function () {
'use strict';

View File

@ -1,4 +1,4 @@
/*global window, document, Ghost, $, _, Backbone */
/*global Ghost */
(function () {
'use strict';

View File

@ -1,602 +0,0 @@
this["JST"] = this["JST"] || {};
this["JST"]["forgotten"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
return "<form id=\"forgotten\" class=\"forgotten-form\" method=\"post\" novalidate=\"novalidate\">\n <div class=\"email-wrap\">\n <input class=\"email\" type=\"email\" placeholder=\"Email Address\" name=\"email\" autocapitalize=\"off\" autocorrect=\"off\">\n </div>\n <button class=\"button-save\" type=\"submit\">Send new password</button>\n</form>\n";
});
this["JST"]["list-item"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
var buffer = "", stack1, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, self=this, functionType="function";
function program1(depth0,data) {
return " featured";
}
function program3(depth0,data) {
return " page";
}
function program5(depth0,data) {
var buffer = "", stack1;
buffer += "\n ";
stack1 = helpers['if'].call(depth0, (depth0 && depth0.page), {hash:{},inverse:self.program(8, program8, data),fn:self.program(6, program6, data),data:data});
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += "\n ";
return buffer;
}
function program6(depth0,data) {
return "\n <span class=\"page\">Page</span>\n ";
}
function program8(depth0,data) {
var buffer = "", stack1, options;
buffer += "\n <time datetime=\"";
options = {hash:{
'format': ("YYYY-MM-DD hh:mm")
},data:data};
buffer += escapeExpression(((stack1 = helpers.date || (depth0 && depth0.date)),stack1 ? stack1.call(depth0, (depth0 && depth0.published_at), options) : helperMissing.call(depth0, "date", (depth0 && depth0.published_at), options)))
+ "\" class=\"date published\">\n Published ";
options = {hash:{
'timeago': ("True")
},data:data};
buffer += escapeExpression(((stack1 = helpers.date || (depth0 && depth0.date)),stack1 ? stack1.call(depth0, (depth0 && depth0.published_at), options) : helperMissing.call(depth0, "date", (depth0 && depth0.published_at), options)))
+ "\n </time>\n ";
return buffer;
}
function program10(depth0,data) {
return "\n <span class=\"draft\">Draft</span>\n ";
}
buffer += "<a class=\"permalink";
stack1 = helpers['if'].call(depth0, (depth0 && depth0.featured), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
if(stack1 || stack1 === 0) { buffer += stack1; }
stack1 = helpers['if'].call(depth0, (depth0 && depth0.page), {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += "\" href=\"#\" title=\"Edit this post\">\n <h3 class=\"entry-title\">";
if (stack1 = helpers.title) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
else { stack1 = (depth0 && depth0.title); stack1 = typeof stack1 === functionType ? stack1.call(depth0, {hash:{},data:data}) : stack1; }
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += "</h3>\n <section class=\"entry-meta\">\n <span class=\"status\">\n ";
stack1 = helpers['if'].call(depth0, (depth0 && depth0.published), {hash:{},inverse:self.program(10, program10, data),fn:self.program(5, program5, data),data:data});
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += "\n </span>\n </section>\n</a>\n";
return buffer;
});
this["JST"]["login"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression;
buffer += "<form id=\"login\" class=\"login-form\" method=\"post\" novalidate=\"novalidate\">\n <div class=\"email-wrap\">\n <input class=\"email\" type=\"email\" placeholder=\"Email Address\" name=\"email\" autocapitalize=\"off\" autocorrect=\"off\">\n </div>\n <div class=\"password-wrap\">\n <input class=\"password\" type=\"password\" placeholder=\"Password\" name=\"password\">\n </div>\n <button class=\"button-save\" type=\"submit\">Log in</button>\n <section class=\"meta\">\n <a class=\"forgotten-password\" href=\"";
if (stack1 = helpers.admin_url) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
else { stack1 = (depth0 && depth0.admin_url); stack1 = typeof stack1 === functionType ? stack1.call(depth0, {hash:{},data:data}) : stack1; }
buffer += escapeExpression(stack1)
+ "/forgotten/\">Forgotten password?</a>\n </section>\n</form>\n";
return buffer;
});
this["JST"]["modal"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
var buffer = "", stack1, stack2, functionType="function", escapeExpression=this.escapeExpression, self=this;
function program1(depth0,data) {
var buffer = "", stack1;
buffer += "-"
+ escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.type)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1));
return buffer;
}
function program3(depth0,data) {
var stack1, stack2;
stack2 = helpers.each.call(depth0, ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.style), {hash:{},inverse:self.noop,fn:self.program(4, program4, data),data:data});
if(stack2 || stack2 === 0) { return stack2; }
else { return ''; }
}
function program4(depth0,data) {
var buffer = "";
buffer += "modal-style-"
+ escapeExpression((typeof depth0 === functionType ? depth0.apply(depth0) : depth0))
+ " ";
return buffer;
}
function program6(depth0,data) {
var buffer = "", stack1;
buffer += "<header class=\"modal-header\"><h1>"
+ escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.content)),stack1 == null || stack1 === false ? stack1 : stack1.title)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ "</h1></header>";
return buffer;
}
function program8(depth0,data) {
return "<a class=\"close\" href=\"#\" title=\"Close\"><span class=\"hidden\">Close</span></a>";
}
function program10(depth0,data) {
var buffer = "", stack1, stack2;
buffer += "\n <footer class=\"modal-footer\">\n <button class=\"js-button-accept ";
stack2 = helpers['if'].call(depth0, ((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.confirm)),stack1 == null || stack1 === false ? stack1 : stack1.accept)),stack1 == null || stack1 === false ? stack1 : stack1.buttonClass), {hash:{},inverse:self.program(13, program13, data),fn:self.program(11, program11, data),data:data});
if(stack2 || stack2 === 0) { buffer += stack2; }
buffer += "\">"
+ escapeExpression(((stack1 = ((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.confirm)),stack1 == null || stack1 === false ? stack1 : stack1.accept)),stack1 == null || stack1 === false ? stack1 : stack1.text)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ "</button>\n <button class=\"js-button-reject ";
stack2 = helpers['if'].call(depth0, ((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.confirm)),stack1 == null || stack1 === false ? stack1 : stack1.reject)),stack1 == null || stack1 === false ? stack1 : stack1.buttonClass), {hash:{},inverse:self.program(17, program17, data),fn:self.program(15, program15, data),data:data});
if(stack2 || stack2 === 0) { buffer += stack2; }
buffer += "\">"
+ escapeExpression(((stack1 = ((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.confirm)),stack1 == null || stack1 === false ? stack1 : stack1.reject)),stack1 == null || stack1 === false ? stack1 : stack1.text)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ "</button>\n </footer>\n ";
return buffer;
}
function program11(depth0,data) {
var stack1;
return escapeExpression(((stack1 = ((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.confirm)),stack1 == null || stack1 === false ? stack1 : stack1.accept)),stack1 == null || stack1 === false ? stack1 : stack1.buttonClass)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1));
}
function program13(depth0,data) {
return "button-add";
}
function program15(depth0,data) {
var stack1;
return escapeExpression(((stack1 = ((stack1 = ((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.confirm)),stack1 == null || stack1 === false ? stack1 : stack1.reject)),stack1 == null || stack1 === false ? stack1 : stack1.buttonClass)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1));
}
function program17(depth0,data) {
return "button-delete";
}
buffer += "<article class=\"modal";
stack2 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.type), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
if(stack2 || stack2 === 0) { buffer += stack2; }
buffer += " ";
stack2 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.style), {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});
if(stack2 || stack2 === 0) { buffer += stack2; }
buffer += escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.animation)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ " js-modal\">\n <section class=\"modal-content\">\n ";
stack2 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.content)),stack1 == null || stack1 === false ? stack1 : stack1.title), {hash:{},inverse:self.noop,fn:self.program(6, program6, data),data:data});
if(stack2 || stack2 === 0) { buffer += stack2; }
buffer += "\n ";
stack2 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.close), {hash:{},inverse:self.noop,fn:self.program(8, program8, data),data:data});
if(stack2 || stack2 === 0) { buffer += stack2; }
buffer += "\n <section class=\"modal-body\">\n </section>\n ";
stack2 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.confirm), {hash:{},inverse:self.noop,fn:self.program(10, program10, data),data:data});
if(stack2 || stack2 === 0) { buffer += stack2; }
buffer += "\n </section>\n</article>";
return buffer;
});
this["JST"]["modals/blank"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
var stack1, stack2, functionType="function";
stack2 = ((stack1 = ((stack1 = (depth0 && depth0.content)),stack1 == null || stack1 === false ? stack1 : stack1.text)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1);
if(stack2 || stack2 === 0) { return stack2; }
else { return ''; }
});
this["JST"]["modals/copyToHTML"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
return "Press Ctrl / Cmd + C to copy the following HTML.\n<pre>\n<code class=\"modal-copyToHTML-content\"></code>\n</pre>";
});
this["JST"]["modals/markdown"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
return "<section class=\"markdown-help-container\">\n <table class=\"modal-markdown-help-table\">\n <thead>\n <tr>\n <th>Result</th>\n <th>Markdown</th>\n <th>Shortcut</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td><strong>Bold</strong></td>\n <td>**text**</td>\n <td>Ctrl / Cmd + B</td>\n </tr>\n <tr>\n <td><em>Emphasize</em></td>\n <td>*text*</td>\n <td>Ctrl / Cmd + I</td>\n </tr>\n <tr>\n <td><code>Inline Code</code></td>\n <td>`code`</td>\n <td>Cmd + K / Ctrl + Shift + K</td>\n </tr>\n <tr>\n <td>Strike-through</td>\n <td>~~text~~</td>\n <td>Ctrl + Alt + U</td>\n </tr>\n <tr>\n <td><a href=\"#\">Link</a></td>\n <td>[title](http://)</td>\n <td>Ctrl + Shift + L</td>\n </tr>\n <tr>\n <td>Image</td>\n <td>![alt](http://)</td>\n <td>Ctrl + Shift + I</td>\n </tr>\n <tr>\n <td>List</td>\n <td>* item</td>\n <td>Ctrl + L</td>\n </tr>\n <tr>\n <td>Blockquote</td>\n <td>> quote</td>\n <td>Ctrl + Q</td>\n </tr>\n <tr>\n <td>H1</td>\n <td># Heading</td>\n <td>Ctrl + Alt + 1</td>\n </tr>\n <tr>\n <td>H2</td>\n <td>## Heading</td>\n <td>Ctrl + Alt + 2</td>\n </tr>\n <tr>\n <td>H3</td>\n <td>### Heading</td>\n <td>Ctrl + Alt + 3</td>\n </tr>\n <tr>\n <td>H4</td>\n <td>#### Heading</td>\n <td>Ctrl + Alt + 4</td>\n </tr>\n <tr>\n <td>H5</td>\n <td>##### Heading</td>\n <td>Ctrl + Alt + 5</td>\n </tr>\n <tr>\n <td>H6</td>\n <td>###### Heading</td>\n <td>Ctrl + Alt + 6</td>\n </tr>\n <tr>\n <td>Select Word</td>\n <td></td>\n <td>Ctrl + Alt + W</td>\n </tr>\n <tr>\n <td>New Paragraph</td>\n <td></td>\n <td>Ctrl / Cmd + Enter</td>\n </tr>\n <tr>\n <td>Uppercase</td>\n <td></td>\n <td>Ctrl + U</td>\n </tr>\n <tr>\n <td>Lowercase</td>\n <td></td>\n <td>Ctrl + Shift + U</td>\n </tr>\n <tr>\n <td>Titlecase</td>\n <td></td>\n <td>Ctrl + Alt + Shift + U</td>\n </tr>\n <tr>\n <td>Insert Current Date</td>\n <td></td>\n <td>Ctrl + Shift + 1</td>\n </tr>\n </tbody>\n </table>\n For further Markdown syntax reference: <a href=\"http://daringfireball.net/projects/markdown/syntax\" target=\"_blank\">Markdown Documentation</a>\n</section>\n";
});
this["JST"]["modals/uploadImage"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
var buffer = "", stack1, stack2, functionType="function", escapeExpression=this.escapeExpression, self=this;
function program1(depth0,data) {
return " style=\"display: none\"";
}
function program3(depth0,data) {
var buffer = "", stack1;
buffer += "accept=\""
+ escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.acceptEncoding)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ "\"";
return buffer;
}
buffer += "<section class=\"js-drop-zone\">\n <img class=\"js-upload-target\" src=\""
+ escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.src)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ "\"";
stack2 = helpers.unless.call(depth0, ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.src), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
if(stack2 || stack2 === 0) { buffer += stack2; }
buffer += " alt=\"logo\">\n <input data-url=\"upload\" class=\"js-fileupload main\" type=\"file\" name=\"uploadimage\" ";
stack2 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.options)),stack1 == null || stack1 === false ? stack1 : stack1.acceptEncoding), {hash:{},inverse:self.noop,fn:self.program(3, program3, data),data:data});
if(stack2 || stack2 === 0) { buffer += stack2; }
buffer += ">\n</section>\n";
return buffer;
});
this["JST"]["notification"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
function program1(depth0,data) {
var buffer = "", stack1;
buffer += "-";
if (stack1 = helpers.type) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
else { stack1 = (depth0 && depth0.type); stack1 = typeof stack1 === functionType ? stack1.call(depth0, {hash:{},data:data}) : stack1; }
buffer += escapeExpression(stack1);
return buffer;
}
buffer += "<section class=\"notification";
stack1 = helpers['if'].call(depth0, (depth0 && depth0.type), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += " notification-";
if (stack1 = helpers.status) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
else { stack1 = (depth0 && depth0.status); stack1 = typeof stack1 === functionType ? stack1.call(depth0, {hash:{},data:data}) : stack1; }
buffer += escapeExpression(stack1)
+ " js-notification\">\n ";
if (stack1 = helpers.message) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
else { stack1 = (depth0 && depth0.message); stack1 = typeof stack1 === functionType ? stack1.call(depth0, {hash:{},data:data}) : stack1; }
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += "\n <a class=\"close\" href=\"#\"><span class=\"hidden\">Close</span></a>\n</section>\n";
return buffer;
});
this["JST"]["preview"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
var buffer = "", stack1, stack2, functionType="function", escapeExpression=this.escapeExpression, self=this;
function program1(depth0,data) {
return "featured";
}
function program3(depth0,data) {
return "unfeatured";
}
function program5(depth0,data) {
return "Unfeature";
}
function program7(depth0,data) {
return "Feature";
}
function program9(depth0,data) {
return "Published";
}
function program11(depth0,data) {
return "Written";
}
function program13(depth0,data) {
var stack1;
return escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.author)),stack1 == null || stack1 === false ? stack1 : stack1.name)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1));
}
function program15(depth0,data) {
var stack1;
return escapeExpression(((stack1 = ((stack1 = (depth0 && depth0.author)),stack1 == null || stack1 === false ? stack1 : stack1.email)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1));
}
function program17(depth0,data) {
var buffer = "", stack1;
buffer += "\n <div class=\"no-posts-box\">\n <div class=\"no-posts\">\n <h3>You Haven't Written Any Posts Yet!</h3>\n <form action=\"";
if (stack1 = helpers.admin_url) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
else { stack1 = (depth0 && depth0.admin_url); stack1 = typeof stack1 === functionType ? stack1.call(depth0, {hash:{},data:data}) : stack1; }
buffer += escapeExpression(stack1)
+ "/editor/\"><button class=\"button-add large\" title=\"New Post\">Write a new Post</button></form>\n </div>\n </div>\n";
return buffer;
}
buffer += "<header class=\"floatingheader\">\n <button class=\"button-back\" href=\"#\">Back</button>\n <a class=\"";
stack1 = helpers['if'].call(depth0, (depth0 && depth0.featured), {hash:{},inverse:self.program(3, program3, data),fn:self.program(1, program1, data),data:data});
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += "\" href=\"#\" title=\"";
stack1 = helpers['if'].call(depth0, (depth0 && depth0.featured), {hash:{},inverse:self.program(7, program7, data),fn:self.program(5, program5, data),data:data});
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += " this post\">\n <span class=\"hidden\">Star</span>\n </a>\n <small>\n <span class=\"status\">";
stack1 = helpers['if'].call(depth0, (depth0 && depth0.published), {hash:{},inverse:self.program(11, program11, data),fn:self.program(9, program9, data),data:data});
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += "</span>\n <span class=\"normal\">by</span>\n <span class=\"author\">";
stack2 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.author)),stack1 == null || stack1 === false ? stack1 : stack1.name), {hash:{},inverse:self.program(15, program15, data),fn:self.program(13, program13, data),data:data});
if(stack2 || stack2 === 0) { buffer += stack2; }
buffer += "</span>\n </small>\n <section class=\"post-controls\">\n <a class=\"post-edit\" href=\"#\" title=\"Edit Post\"><span class=\"hidden\">Edit Post</span></a>\n <a class=\"post-settings\" href=\"#\" data-toggle=\".post-settings-menu\" title=\"Post Settings\"><span class=\"hidden\">Post Settings</span></a>\n <div class=\"post-settings-menu menu-drop-right overlay\">\n <form>\n <table class=\"plain\">\n <tr class=\"post-setting\">\n <td class=\"post-setting-label\">\n <label for=\"url\">URL</label>\n </td>\n <td class=\"post-setting-field\">\n <input id=\"url\" class=\"post-setting-slug\" type=\"text\" value=\"\" />\n </td>\n </tr>\n <tr class=\"post-setting\">\n <td class=\"post-setting-label\">\n <label for=\"pub-date\">Pub Date</label>\n </td>\n <td class=\"post-setting-field\">\n <input id=\"pub-date\" class=\"post-setting-date\" type=\"text\" value=\"\"><!--<span class=\"post-setting-calendar\"></span>-->\n </td>\n </tr>\n <tr class=\"post-setting\">\n <td class=\"post-setting-label\">\n <span class=\"label\">Static Page</span>\n </td>\n <td class=\"post-setting-item\">\n <input id=\"static-page\" class=\"post-setting-static-page\" type=\"checkbox\" value=\"\">\n <label class=\"checkbox\" for=\"static-page\"></label>\n </td>\n </tr>\n </table>\n </form>\n <a class=\"delete\" href=\"#\">Delete This Post</a>\n </div>\n </section>\n</header>\n<section class=\"content-preview-content\">\n <div class=\"wrapper\"><h1>";
if (stack2 = helpers.title) { stack2 = stack2.call(depth0, {hash:{},data:data}); }
else { stack2 = (depth0 && depth0.title); stack2 = typeof stack2 === functionType ? stack2.call(depth0, {hash:{},data:data}) : stack2; }
if(stack2 || stack2 === 0) { buffer += stack2; }
buffer += "</h1>";
if (stack2 = helpers.html) { stack2 = stack2.call(depth0, {hash:{},data:data}); }
else { stack2 = (depth0 && depth0.html); stack2 = typeof stack2 === functionType ? stack2.call(depth0, {hash:{},data:data}) : stack2; }
if(stack2 || stack2 === 0) { buffer += stack2; }
buffer += "</div>\n</section>\n";
stack2 = helpers.unless.call(depth0, (depth0 && depth0.title), {hash:{},inverse:self.noop,fn:self.program(17, program17, data),data:data});
if(stack2 || stack2 === 0) { buffer += stack2; }
buffer += "\n";
return buffer;
});
this["JST"]["reset"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
return "<form id=\"reset\" class=\"reset-form\" method=\"post\" novalidate=\"novalidate\">\n <div class=\"password-wrap\">\n <input class=\"password\" type=\"password\" placeholder=\"Password\" name=\"newpassword\" />\n </div>\n <div class=\"password-wrap\">\n <input class=\"password\" type=\"password\" placeholder=\"Confirm Password\" name=\"ne2password\" />\n </div>\n <button class=\"button-save\" type=\"submit\">Reset Password</button>\n</form>\n";
});
this["JST"]["settings/general"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this;
function program1(depth0,data) {
var buffer = "", stack1;
buffer += "\n <a class=\"js-modal-logo\" href=\"#\"><img id=\"blog-logo\" src=\"";
if (stack1 = helpers.logo) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
else { stack1 = (depth0 && depth0.logo); stack1 = typeof stack1 === functionType ? stack1.call(depth0, {hash:{},data:data}) : stack1; }
buffer += escapeExpression(stack1)
+ "\" alt=\"logo\"></a>\n ";
return buffer;
}
function program3(depth0,data) {
return "\n <a class=\"button-add js-modal-logo\" >Upload Image</a>\n ";
}
function program5(depth0,data) {
var buffer = "", stack1;
buffer += "\n <a class=\"js-modal-cover\" href=\"#\"><img id=\"blog-cover\" src=\"";
if (stack1 = helpers.cover) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
else { stack1 = (depth0 && depth0.cover); stack1 = typeof stack1 === functionType ? stack1.call(depth0, {hash:{},data:data}) : stack1; }
buffer += escapeExpression(stack1)
+ "\" alt=\"cover photo\"></a>\n ";
return buffer;
}
function program7(depth0,data) {
return "\n <a class=\"button-add js-modal-cover\">Upload Image</a>\n ";
}
function program9(depth0,data) {
var buffer = "", stack1;
buffer += "\n <option value=\"";
if (stack1 = helpers.name) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
else { stack1 = (depth0 && depth0.name); stack1 = typeof stack1 === functionType ? stack1.call(depth0, {hash:{},data:data}) : stack1; }
buffer += escapeExpression(stack1)
+ "\" ";
stack1 = helpers['if'].call(depth0, (depth0 && depth0.active), {hash:{},inverse:self.noop,fn:self.program(10, program10, data),data:data});
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += ">";
stack1 = helpers['if'].call(depth0, (depth0 && depth0['package']), {hash:{},inverse:self.program(14, program14, data),fn:self.program(12, program12, data),data:data});
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += "</option>\n ";
stack1 = helpers.unless.call(depth0, (depth0 && depth0['package']), {hash:{},inverse:self.noop,fn:self.program(16, program16, data),data:data});
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += "\n ";
return buffer;
}
function program10(depth0,data) {
return "selected";
}
function program12(depth0,data) {
var buffer = "", stack1;
buffer += escapeExpression(((stack1 = ((stack1 = (depth0 && depth0['package'])),stack1 == null || stack1 === false ? stack1 : stack1.name)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ " - "
+ escapeExpression(((stack1 = ((stack1 = (depth0 && depth0['package'])),stack1 == null || stack1 === false ? stack1 : stack1.version)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1));
return buffer;
}
function program14(depth0,data) {
var stack1;
if (stack1 = helpers.name) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
else { stack1 = (depth0 && depth0.name); stack1 = typeof stack1 === functionType ? stack1.call(depth0, {hash:{},data:data}) : stack1; }
return escapeExpression(stack1);
}
function program16(depth0,data) {
var buffer = "", stack1;
buffer += "<script>console.log('Hi! The theme named \"";
if (stack1 = helpers.name) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
else { stack1 = (depth0 && depth0.name); stack1 = typeof stack1 === functionType ? stack1.call(depth0, {hash:{},data:data}) : stack1; }
buffer += escapeExpression(stack1)
+ "\" does not have a package.json file or it\\'s malformed. This will be required in the future. For more info, see http://docs.ghost.org/themes/.');</script>";
return buffer;
}
buffer += "<header>\n <button class=\"button-back\">Back</button>\n <h2 class=\"title\">General</h2>\n <section class=\"page-actions\">\n <button class=\"button-save\">Save</button>\n </section>\n</header>\n\n<section class=\"content\">\n <form id=\"settings-general\" novalidate=\"novalidate\">\n <fieldset>\n\n <div class=\"form-group\">\n <label for=\"blog-title\">Blog Title</label>\n <input id=\"blog-title\" name=\"general[title]\" type=\"text\" value=\"";
if (stack1 = helpers.title) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
else { stack1 = (depth0 && depth0.title); stack1 = typeof stack1 === functionType ? stack1.call(depth0, {hash:{},data:data}) : stack1; }
buffer += escapeExpression(stack1)
+ "\" />\n <p>The name of your blog</p>\n </div>\n\n <div class=\"form-group description-container\">\n <label for=\"blog-description\">Blog Description</label>\n <textarea id=\"blog-description\">";
if (stack1 = helpers.description) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
else { stack1 = (depth0 && depth0.description); stack1 = typeof stack1 === functionType ? stack1.call(depth0, {hash:{},data:data}) : stack1; }
buffer += escapeExpression(stack1)
+ "</textarea>\n <p>\n Describe what your blog is about\n <span class=\"word-count\">0</span>\n </p>\n\n </div>\n </fieldset>\n <div class=\"form-group\">\n <label for=\"blog-logo\">Blog Logo</label>\n ";
stack1 = helpers['if'].call(depth0, (depth0 && depth0.logo), {hash:{},inverse:self.program(3, program3, data),fn:self.program(1, program1, data),data:data});
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += "\n <p>Display a sexy logo for your publication</p>\n </div>\n\n <div class=\"form-group\">\n <label for=\"blog-cover\">Blog Cover</label>\n ";
stack1 = helpers['if'].call(depth0, (depth0 && depth0.cover), {hash:{},inverse:self.program(7, program7, data),fn:self.program(5, program5, data),data:data});
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += "\n <p>Display a cover image on your site</p>\n </div>\n <fieldset>\n <div class=\"form-group\">\n <label for=\"email-address\">Email Address</label>\n <input id=\"email-address\" name=\"general[email-address]\" type=\"email\" value=\"";
if (stack1 = helpers.email) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
else { stack1 = (depth0 && depth0.email); stack1 = typeof stack1 === functionType ? stack1.call(depth0, {hash:{},data:data}) : stack1; }
buffer += escapeExpression(stack1)
+ "\" autocapitalize=\"off\" autocorrect=\"off\" />\n <p>Address to use for admin notifications</p>\n </div>\n\n <div class=\"form-group\">\n <label for=\"postsPerPage\">Posts per page</label>\n <input id=\"postsPerPage\" name=\"general[postsPerPage]\" type=\"number\" value=\"";
if (stack1 = helpers.postsPerPage) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
else { stack1 = (depth0 && depth0.postsPerPage); stack1 = typeof stack1 === functionType ? stack1.call(depth0, {hash:{},data:data}) : stack1; }
buffer += escapeExpression(stack1)
+ "\" />\n <p>How many posts should be displayed on each page</p>\n </div>\n\n <div class=\"form-group\">\n <label for=\"permalinks\">Dated Permalinks</label>\n <input id=\"permalinks\" name=\"general[permalinks]\" type=\"checkbox\" value='permalink'/>\n <label class=\"checkbox\" for=\"permalinks\"></label>\n <p>Include the date in your post URLs</p>\n </div>\n\n <div class=\"form-group\">\n <label for=\"activeTheme\">Theme</label>\n <select id=\"activeTheme\" name=\"general[activeTheme]\">\n ";
stack1 = helpers.each.call(depth0, (depth0 && depth0.availableThemes), {hash:{},inverse:self.noop,fn:self.program(9, program9, data),data:data});
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += "\n </select>\n <p>Select a theme for your blog</p>\n </div>\n\n </fieldset>\n </form>\n</section>\n";
return buffer;
});
this["JST"]["settings/sidebar"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
return "<header>\n <h1 class=\"title\">Settings</h1>\n</header>\n<nav class=\"settings-menu\">\n <ul>\n <li class=\"general\"><a href=\"#general\">General</a></li>\n <li class=\"users\"><a href=\"#user\">User</a></li>\n </ul>\n</nav>";
});
this["JST"]["settings/user-profile"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, helperMissing=helpers.helperMissing, self=this;
function program1(depth0,data) {
var stack1;
if (stack1 = helpers.cover) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
else { stack1 = (depth0 && depth0.cover); stack1 = typeof stack1 === functionType ? stack1.call(depth0, {hash:{},data:data}) : stack1; }
return escapeExpression(stack1);
}
function program3(depth0,data) {
var stack1, options;
options = {hash:{},data:data};
return escapeExpression(((stack1 = helpers.asset || (depth0 && depth0.asset)),stack1 ? stack1.call(depth0, "shared/img/user-cover.png", options) : helperMissing.call(depth0, "asset", "shared/img/user-cover.png", options)));
}
function program5(depth0,data) {
var stack1;
if (stack1 = helpers.image) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
else { stack1 = (depth0 && depth0.image); stack1 = typeof stack1 === functionType ? stack1.call(depth0, {hash:{},data:data}) : stack1; }
return escapeExpression(stack1);
}
function program7(depth0,data) {
var stack1, options;
options = {hash:{},data:data};
return escapeExpression(((stack1 = helpers.asset || (depth0 && depth0.asset)),stack1 ? stack1.call(depth0, "shared/img/user-image.png", options) : helperMissing.call(depth0, "asset", "shared/img/user-image.png", options)));
}
buffer += "<header>\n <button class=\"button-back\">Back</button>\n <h2 class=\"title\">Your Profile</h2>\n <section class=\"page-actions\">\n <button class=\"button-save\">Save</button>\n </section>\n</header>\n\n<section class=\"content no-padding\">\n\n <header class=\"user-profile-header\">\n <img id=\"user-cover\" class=\"cover-image\" src=\"";
stack1 = helpers['if'].call(depth0, (depth0 && depth0.cover), {hash:{},inverse:self.program(3, program3, data),fn:self.program(1, program1, data),data:data});
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += "\" title=\"";
if (stack1 = helpers.name) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
else { stack1 = (depth0 && depth0.name); stack1 = typeof stack1 === functionType ? stack1.call(depth0, {hash:{},data:data}) : stack1; }
buffer += escapeExpression(stack1)
+ "'s Cover Image\"/>\n\n <a class=\"edit-cover-image js-modal-cover button\" href=\"#\">Change Cover</a>\n </header>\n\n <form class=\"user-profile\" novalidate=\"novalidate\">\n\n <fieldset class=\"user-details-top\">\n\n <figure class=\"user-image\">\n <div id=\"user-image\" class=\"img\" style=\"background-image: url(";
stack1 = helpers['if'].call(depth0, (depth0 && depth0.image), {hash:{},inverse:self.program(7, program7, data),fn:self.program(5, program5, data),data:data});
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += ");\" href=\"#\"><span class=\"hidden\">";
if (stack1 = helpers.name) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
else { stack1 = (depth0 && depth0.name); stack1 = typeof stack1 === functionType ? stack1.call(depth0, {hash:{},data:data}) : stack1; }
buffer += escapeExpression(stack1)
+ "'s Picture</span></div>\n <a href=\"#\" class=\"edit-user-image js-modal-image\">Edit Picture</a>\n </figure>\n\n <div class=\"form-group\">\n <label for=\"user-name\" class=\"hidden\">Full Name</label>\n <input type=\"url\" value=\"";
if (stack1 = helpers.name) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
else { stack1 = (depth0 && depth0.name); stack1 = typeof stack1 === functionType ? stack1.call(depth0, {hash:{},data:data}) : stack1; }
buffer += escapeExpression(stack1)
+ "\" id=\"user-name\" placeholder=\"Full Name\" autocorrect=\"off\" />\n <p>Use your real name so people can recognise you</p>\n </div>\n\n </fieldset>\n\n <fieldset class=\"user-details-bottom\">\n\n <div class=\"form-group\">\n <label for\"user-email\">Email</label>\n <input type=\"email\" value=\"";
if (stack1 = helpers.email) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
else { stack1 = (depth0 && depth0.email); stack1 = typeof stack1 === functionType ? stack1.call(depth0, {hash:{},data:data}) : stack1; }
buffer += escapeExpression(stack1)
+ "\" id=\"user-email\" placeholder=\"Email Address\" autocapitalize=\"off\" autocorrect=\"off\" />\n <p>Used for notifications</p>\n </div>\n\n <div class=\"form-group\">\n <label for=\"user-location\">Location</label>\n <input type=\"text\" value=\"";
if (stack1 = helpers.location) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
else { stack1 = (depth0 && depth0.location); stack1 = typeof stack1 === functionType ? stack1.call(depth0, {hash:{},data:data}) : stack1; }
buffer += escapeExpression(stack1)
+ "\" id=\"user-location\" />\n <p>Where in the world do you live?</p>\n </div>\n\n <div class=\"form-group\">\n <label for=\"user-website\">Website</label>\n <input type=\"text\" value=\"";
if (stack1 = helpers.website) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
else { stack1 = (depth0 && depth0.website); stack1 = typeof stack1 === functionType ? stack1.call(depth0, {hash:{},data:data}) : stack1; }
buffer += escapeExpression(stack1)
+ "\" id=\"user-website\" autocapitalize=\"off\" autocorrect=\"off\" />\n <p>Have a website or blog other than this one? Link it!</p>\n </div>\n\n <div class=\"form-group bio-container\">\n <label for=\"user-bio\">Bio</label>\n <textarea id=\"user-bio\">";
if (stack1 = helpers.bio) { stack1 = stack1.call(depth0, {hash:{},data:data}); }
else { stack1 = (depth0 && depth0.bio); stack1 = typeof stack1 === functionType ? stack1.call(depth0, {hash:{},data:data}) : stack1; }
buffer += escapeExpression(stack1)
+ "</textarea>\n <p>\n Write about you, in 200 characters or less.\n <span class=\"word-count\">0</span>\n </p>\n </div>\n\n <hr />\n\n </fieldset>\n\n <fieldset>\n\n <div class=\"form-group\">\n <label for=\"user-password-old\">Old Password</label>\n <input type=\"password\" id=\"user-password-old\" />\n </div>\n\n <div class=\"form-group\">\n <label for=\"user-password-new\">New Password</label>\n <input type=\"password\" id=\"user-password-new\" />\n </div>\n\n <div class=\"form-group\">\n <label for=\"user-new-password-verification\">Verify Password</label>\n <input type=\"password\" id=\"user-new-password-verification\" />\n </div>\n <div class=\"form-group\">\n <button type=\"button\" class=\"button-delete button-change-password\">Change Password</button>\n </div>\n\n </fieldset>\n\n </form>\n</section>\n";
return buffer;
});
this["JST"]["signup"] = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
return "<form id=\"signup\" class=\"signup-form\" method=\"post\" novalidate=\"novalidate\">\n <div class=\"name-wrap\">\n <input class=\"name\" type=\"text\" placeholder=\"Full Name\" name=\"name\" autocorrect=\"off\" />\n </div>\n <div class=\"email-wrap\">\n <input class=\"email\" type=\"email\" placeholder=\"Email Address\" name=\"email\" autocapitalize=\"off\" autocorrect=\"off\" />\n </div>\n <div class=\"password-wrap\">\n <input class=\"password\" type=\"password\" placeholder=\"Password\" name=\"password\" />\n </div>\n <button class=\"button-save\" type=\"submit\">Sign Up</button>\n</form>\n";
});

View File

@ -0,0 +1,15 @@
<header>
<button class="button-back">Back</button>
<h2 class="title">Apps</h2>
</header>
<section class="content">
<ul class="js-apps">
{{#each availableApps}}
<li>
{{#if package}}{{package.name}} - {{package.version}}{{else}}{{name}} - package.json missing :({{/if}}
<button data-app="{{name}}" class="{{#if active}}button-delete js-button-deactivate js-button-active">Deactivate{{else}}button-add js-button-activate">Activate{{/if}}</button>
</li>
{{/each}}
</ul>
</section>

View File

@ -5,5 +5,6 @@
<ul>
<li class="general"><a href="#general">General</a></li>
<li class="users"><a href="#user">User</a></li>
<li class="apps"><a href="#apps">Apps</a></li>
</ul>
</nav>

View File

@ -215,7 +215,7 @@
},
url: Ghost.paths.apiRoot + '/notifications/' + $(self).find('.close').data('id')
}).done(function (result) {
/*jslint unparam:true*/
/*jshint unused:false*/
bbSelf.$el.slideUp(250, function () {
$(this).show().css({height: "auto"});
$(self).remove();
@ -249,7 +249,7 @@
},
url: Ghost.paths.apiRoot + '/notifications/' + $(self).data('id')
}).done(function (result) {
/*jslint unparam:true*/
/*jshint unused:false*/
var height = bbSelf.$('.js-notification').outerHeight(true),
$parent = $(self).parent();
bbSelf.$el.css({height: height});

View File

@ -1,4 +1,4 @@
/*global window, document, Ghost, $, _, Backbone, JST, NProgress */
/*global window, Ghost, $, _, Backbone, NProgress */
(function () {
"use strict";
@ -10,7 +10,7 @@
// ----------
Ghost.Views.Blog = Ghost.View.extend({
initialize: function (options) {
/*jslint unparam:true*/
/*jshint unused:false*/
var self = this,
finishProgress = function () {
NProgress.done();
@ -108,7 +108,7 @@
staticPages: 'all'
}
}).then(function onSuccess(response) {
/*jslint unparam:true*/
/*jshint unused:false*/
self.render();
self.isLoading = false;
}, function onError(e) {
@ -245,7 +245,7 @@
});
},
error : function (model, xhr) {
/*jslint unparam:true*/
/*jshint unused:false*/
Ghost.notifications.addItem({
type: 'error',
message: Ghost.Views.Utils.getRequestErrorMessage(xhr),

View File

@ -1,4 +1,4 @@
/*global window, document, Ghost, $, _, Backbone, JST */
/*global Ghost, $ */
(function () {
"use strict";
@ -25,7 +25,7 @@
},
dataType: 'json',
add: function (e, data) {
/*jslint unparam:true*/
/*jshint unused:false*/
// Bind the upload data to the view, so it is
// available to the click handler, and enable the
@ -34,7 +34,7 @@
data.context = view.uploadButton.removeProp('disabled');
},
done: function (e, data) {
/*jslint unparam:true*/
/*jshint unused:false*/
$('#startupload').text('Import');
if (!data.result) {
throw new Error('No response received from server.');

View File

@ -1,6 +1,6 @@
// The Tag UI area associated with a post
/*global window, document, setTimeout, $, _, Backbone, Ghost */
/*global window, document, setTimeout, $, _, Ghost */
(function () {
"use strict";

View File

@ -1,6 +1,6 @@
// # Article Editor
/*global window, document, setTimeout, navigator, $, _, Backbone, Ghost, Showdown, CodeMirror, shortcut, Countable, JST */
/*global window, document, setTimeout, navigator, $, _, Backbone, Ghost, Showdown, CodeMirror, shortcut, Countable */
(function () {
"use strict";
@ -78,7 +78,7 @@
},
//TODO: This has to be moved to the I18n localization file.
//This structure is supposed to be close to the i18n-localization which will be used soon.
//This structure is supposed to be close to the i18n-localization which will be used soon.
messageMap: {
errors: {
post: {
@ -449,7 +449,7 @@
initMarkdown: function () {
var self = this;
this.converter = new Showdown.converter({extensions: ['ghostdown', 'github']});
this.converter = new Showdown.converter({extensions: ['typography', 'ghostdown', 'github']});
this.editor = CodeMirror.fromTextArea(document.getElementById('entry-markdown'), {
mode: 'gfm',
tabMode: 'indent',
@ -704,7 +704,7 @@
var value = editor.getValue();
_.each(markerMgr.markers, function (marker, id) {
/*jslint unparam:true*/
/*jshint unused:false*/
value = value.replace(markerMgr.getMarkerRegexForId(id), '');
});
@ -720,7 +720,7 @@
// initialise
editor.on('change', function (cm, changeObj) {
/*jslint unparam:true*/
/*jshint unused:false*/
var linesChanged = _.range(changeObj.from.line, changeObj.from.line + changeObj.text.length);
_.each(linesChanged, function (ln) {

View File

@ -1,4 +1,4 @@
/*global window, document, Ghost, $, _, Backbone, JST */
/*global window, Ghost, $, validator */
(function () {
"use strict";
@ -25,14 +25,19 @@
event.preventDefault();
var email = this.$el.find('.email').val(),
password = this.$el.find('.password').val(),
redirect = Ghost.Views.Utils.getUrlVariables().r;
redirect = Ghost.Views.Utils.getUrlVariables().r,
validationErrors = [];
Ghost.Validate._errors = [];
Ghost.Validate.check(email).isEmail();
Ghost.Validate.check(password, "Please enter a password").len(0);
if (!validator.isEmail(email)) {
validationErrors.push("Invalid Email");
}
if (Ghost.Validate._errors.length > 0) {
Ghost.Validate.handleErrors();
if (!validator.isLength(password, 0)) {
validationErrors.push("Please enter a password");
}
if (validationErrors.length) {
validator.handleErrors(validationErrors);
} else {
$.ajax({
url: Ghost.paths.subdir + '/ghost/signin/',
@ -88,18 +93,27 @@
event.preventDefault();
var name = this.$('.name').val(),
email = this.$('.email').val(),
password = this.$('.password').val();
password = this.$('.password').val(),
validationErrors = [];
// This is needed due to how error handling is done. If this is not here, there will not be a time
// when there is no error.
Ghost.Validate._errors = [];
Ghost.Validate.check(name, "Please enter a name").len(1);
Ghost.Validate.check(email, "Please enter a correct email address").isEmail();
Ghost.Validate.check(password, "Your password is not long enough. It must be at least 8 characters long.").len(8);
Ghost.Validate.check(this.submitted, "Ghost is signing you up. Please wait...").equals("no");
if (!validator.isLength(name, 1)) {
validationErrors.push("Please enter a name.");
}
if (Ghost.Validate._errors.length > 0) {
Ghost.Validate.handleErrors();
if (!validator.isEmail(email)) {
validationErrors.push("Please enter a correct email address.");
}
if (!validator.isLength(password, 0)) {
validationErrors.push("Please enter a password");
}
if (!validator.equals(this.submitted, "no")) {
validationErrors.push("Ghost is signing you up. Please wait...");
}
if (validationErrors.length) {
validator.handleErrors(validationErrors);
} else {
this.submitted = "yes";
$.ajax({
@ -152,13 +166,15 @@
submitHandler: function (event) {
event.preventDefault();
var email = this.$el.find('.email').val();
var email = this.$el.find('.email').val(),
validationErrors = [];
Ghost.Validate._errors = [];
Ghost.Validate.check(email).isEmail();
if (!validator.isEmail(email)) {
validationErrors.push("Please enter a correct email address.");
}
if (Ghost.Validate._errors.length > 0) {
Ghost.Validate.handleErrors();
if (validationErrors.length) {
validator.handleErrors(validationErrors);
} else {
$.ajax({
url: Ghost.paths.subdir + '/ghost/forgotten/',

View File

@ -1,6 +1,6 @@
// The Post Settings Menu available in the content preview screen, as well as the post editor.
/*global window, document, $, _, Backbone, Ghost, moment */
/*global window, $, _, Ghost, moment */
(function () {
"use strict";
@ -124,7 +124,7 @@
slug: newSlug
}, {
success : function (model, response, options) {
/*jslint unparam:true*/
/*jshint unused:false*/
// Repopulate slug in case it changed on the server (e.g. 'new-slug-2')
slugEl.value = model.get('slug');
Ghost.notifications.addItem({
@ -134,7 +134,7 @@
});
},
error : function (model, xhr) {
/*jslint unparam:true*/
/*jshint unused:false*/
slugEl.value = model.previous('slug');
Ghost.notifications.addItem({
type: 'error',
@ -244,7 +244,7 @@
});
},
error : function (model, xhr) {
/*jslint unparam:true*/
/*jshint unused:false*/
// Reset back to original value
pubDateEl.value = pubDateMoment ? pubDateMoment.format(displayDateFormat) : '';
Ghost.notifications.addItem({
@ -275,7 +275,7 @@
page: page
}, {
success : function (model, response, options) {
/*jslint unparam:true*/
/*jshint unused:false*/
pageEl.prop('checked', page);
Ghost.notifications.addItem({
type: 'success',
@ -284,7 +284,7 @@
});
},
error : function (model, xhr) {
/*jslint unparam:true*/
/*jshint unused:false*/
pageEl.prop('checked', model.previous('page'));
Ghost.notifications.addItem({
type: 'error',

View File

@ -1,4 +1,4 @@
/*global window, document, Ghost, $, _, Backbone, Countable */
/*global document, Ghost, $, _, Countable, validator */
(function () {
"use strict";
@ -38,6 +38,11 @@
initialize: function (options) {
this.render();
this.menu = this.$('.settings-menu');
// Hides apps UI unless config.js says otherwise
// This will stay until apps UI is ready to ship
if ($(this.el).attr('data-apps') !== "true") {
this.menu.find('.apps').hide();
}
this.showContent(options.pane);
},
@ -61,7 +66,7 @@
Ghost.router.navigate('/settings/' + id + '/');
Ghost.trigger('urlchange');
if (this.pane && id === this.pane.el.id) {
if (this.pane && id === this.pane.id) {
return;
}
_.result(this.pane, 'destroy');
@ -112,7 +117,7 @@
this.$el.addClass('active');
},
saveSuccess: function (model, response, options) {
/*jslint unparam:true*/
/*jshint unused:false*/
Ghost.notifications.clearEverything();
Ghost.notifications.addItem({
type: 'success',
@ -121,7 +126,7 @@
});
},
saveError: function (model, xhr) {
/*jslint unparam:true*/
/*jshint unused:false*/
Ghost.notifications.clearEverything();
Ghost.notifications.addItem({
type: 'error',
@ -155,28 +160,32 @@
description = this.$('#blog-description').val(),
email = this.$('#email-address').val(),
postsPerPage = this.$('#postsPerPage').val(),
permalinks = this.$('#permalinks').is(':checked') ? '/:year/:month/:day/:slug/' : '/:slug/';
permalinks = this.$('#permalinks').is(':checked') ? '/:year/:month/:day/:slug/' : '/:slug/',
validationErrors = [];
Ghost.Validate._errors = [];
Ghost.Validate
.check(title, {message: "Title is too long", el: $('#blog-title')})
.len(0, 150);
Ghost.Validate
.check(description, {message: "Description is too long", el: $('#blog-description')})
.len(0, 200);
Ghost.Validate
.check(email, {message: "Please supply a valid email address", el: $('#email-address')})
.isEmail().len(0, 254);
Ghost.Validate
.check(postsPerPage, {message: "Please use a number less than 1000", el: $('postsPerPage')})
.isInt().max(1000);
Ghost.Validate
.check(postsPerPage, {message: "Please use a number greater than 0", el: $('postsPerPage')})
.isInt().min(0);
if (!validator.isLength(title, 0, 150)) {
validationErrors.push({message: "Title is too long", el: $('#blog-title')});
}
if (!validator.isLength(description, 0, 200)) {
validationErrors.push({message: "Description is too long", el: $('#blog-description')});
}
if (!validator.isEmail(email) || !validator.isLength(email, 0, 254)) {
validationErrors.push({message: "Please supply a valid email address", el: $('#email-address')});
}
if (!validator.isInt(postsPerPage) || postsPerPage > 1000) {
validationErrors.push({message: "Please use a number less than 1000", el: $('postsPerPage')});
}
if (!validator.isInt(postsPerPage) || postsPerPage < 0) {
validationErrors.push({message: "Please use a number greater than 0", el: $('postsPerPage')});
}
if (Ghost.Validate._errors.length > 0) {
Ghost.Validate.handleErrors();
if (validationErrors.length) {
validator.handleErrors(validationErrors);
} else {
this.model.save({
title: title,
@ -338,30 +347,33 @@
userEmail = this.$('#user-email').val(),
userLocation = this.$('#user-location').val(),
userWebsite = this.$('#user-website').val(),
userBio = this.$('#user-bio').val();
userBio = this.$('#user-bio').val(),
validationErrors = [];
Ghost.Validate._errors = [];
Ghost.Validate
.check(userName, {message: "Name is too long", el: $('#user-name')})
.len(0, 150);
Ghost.Validate
.check(userBio, {message: "Bio is too long", el: $('#user-bio')})
.len(0, 200);
Ghost.Validate
.check(userEmail, {message: "Please supply a valid email address", el: $('#user-email')})
.isEmail();
Ghost.Validate
.check(userLocation, {message: "Location is too long", el: $('#user-location')})
.len(0, 150);
if (userWebsite.length > 0) {
Ghost.Validate
.check(userWebsite, {message: "Please use a valid url", el: $('#user-website')})
.isUrl()
.len(0, 2000);
if (!validator.isLength(userName, 0, 150)) {
validationErrors.push({message: "Name is too long", el: $('#user-name')});
}
if (Ghost.Validate._errors.length > 0) {
Ghost.Validate.handleErrors();
if (!validator.isLength(userBio, 0, 200)) {
validationErrors.push({message: "Bio is too long", el: $('#user-bio')});
}
if (!validator.isEmail(userEmail)) {
validationErrors.push({message: "Please supply a valid email address", el: $('#user-email')});
}
if (!validator.isLength(userLocation, 0, 150)) {
validationErrors.push({message: "Location is too long", el: $('#user-location')});
}
if (userWebsite.length) {
if (!validator.isURL(userWebsite) || !validator.isLength(userWebsite, 0, 2000)) {
validationErrors.push({message: "Please use a valid url", el: $('#user-website')});
}
}
if (validationErrors.length) {
validator.handleErrors(validationErrors);
} else {
this.model.save({
@ -384,16 +396,20 @@
var self = this,
oldPassword = this.$('#user-password-old').val(),
newPassword = this.$('#user-password-new').val(),
ne2Password = this.$('#user-new-password-verification').val();
ne2Password = this.$('#user-new-password-verification').val(),
validationErrors = [];
Ghost.Validate._errors = [];
Ghost.Validate.check(newPassword, {message: 'Your new passwords do not match'}).equals(ne2Password);
Ghost.Validate.check(newPassword, {message: 'Your password is not long enough. It must be at least 8 characters long.'}).len(8);
if (!validator.equals(newPassword, ne2Password)) {
validationErrors.push("Your new passwords do not match");
}
if (Ghost.Validate._errors.length > 0) {
Ghost.Validate.handleErrors();
if (!validator.isLength(newPassword, 8)) {
validationErrors.push("Your password is not long enough. It must be at least 8 characters long.");
}
if (validationErrors.length) {
validator.handleErrors(validationErrors);
} else {
$.ajax({
url: Ghost.paths.subdir + '/ghost/changepw/',
type: 'POST',
@ -446,4 +462,70 @@
}
});
// ### Apps page
Settings.apps = Settings.Pane.extend({
id: "apps",
events: {
'click .js-button-activate': 'activateApp',
'click .js-button-deactivate': 'deactivateApp'
},
beforeRender: function () {
this.availableApps = this.model.toJSON().availableApps;
},
activateApp: function (event) {
var button = $(event.currentTarget);
button.removeClass('button-add').addClass('button js-button-active').text('Working');
this.saveStates();
},
deactivateApp: function (event) {
var button = $(event.currentTarget);
button.removeClass('button-delete js-button-active').addClass('button').text('Working');
this.saveStates();
},
saveStates: function () {
var activeButtons = this.$el.find('.js-apps .js-button-active'),
toSave = [],
self = this;
_.each(activeButtons, function (app) {
toSave.push($(app).data('app'));
});
this.model.save({
activeApps: JSON.stringify(toSave)
}, {
success: this.saveSuccess,
error: this.saveError
}).then(function () { self.render(); });
},
saveSuccess: function () {
Ghost.notifications.addItem({
type: 'success',
message: 'Active applications updated.',
status: 'passive',
id: 'success-1100'
});
},
saveError: function (xhr) {
Ghost.notifications.addItem({
type: 'error',
message: Ghost.Views.Utils.getRequestErrorMessage(xhr),
status: 'passive'
});
},
templateName: 'settings/apps'
});
}());

View File

@ -2,12 +2,10 @@ var dataExport = require('../data/export'),
dataImport = require('../data/import'),
dataProvider = require('../models'),
fs = require('fs-extra'),
path = require('path'),
when = require('when'),
nodefn = require('when/node/function'),
_ = require('lodash'),
schema = require('../data/schema').tables,
config = require('../config'),
validation = require('../data/validation'),
errors = require('../../server/errorHandling'),
api = {},
db;
@ -16,32 +14,10 @@ api.notifications = require('./notifications');
api.settings = require('./settings');
db = {
'exportContent': function (req, res) {
/*jslint unparam:true*/
return dataExport().then(function (exportedData) {
// Save the exported data to the file system for download
var fileName = path.join(config().paths.exportPath, 'exported-' + (new Date().getTime()) + '.json');
return nodefn.call(fs.writeFile, fileName, JSON.stringify(exportedData)).then(function () {
return when(fileName);
});
}).then(function (exportedFilePath) {
// Send the exported data file
res.download(exportedFilePath, 'GhostData.json');
}).otherwise(function (error) {
// Notify of an error if it occurs
return api.notifications.browse().then(function (notifications) {
var notification = {
type: 'error',
message: error.message || error,
status: 'persistent',
id: 'per-' + (notifications.length + 1)
};
return api.notifications.add(notification).then(function () {
res.redirect(config().paths.debugPath);
});
});
'exportContent': function () {
// Export data, otherwise send error 500
return dataExport().otherwise(function (error) {
return when.reject({errorCode: 500, message: error.message || error});
});
},
'importContent': function (options) {
@ -56,7 +32,7 @@ db = {
* - If there is no path
* - If the name doesn't have json in it
*/
return when.reject({errorCode: 500, message: 'Please select a .json file to import.'});
return when.reject({code: 500, message: 'Please select a .json file to import.'});
}
return api.settings.read({ key: 'databaseVersion' }).then(function (setting) {
@ -69,8 +45,7 @@ db = {
return nodefn.call(fs.readFile, options.importfile.path);
}).then(function (fileContents) {
var importData,
error = '',
tableKeys = _.keys(schema);
error = '';
// Parse the json data
try {
@ -84,28 +59,13 @@ db = {
return when.reject(new Error("Import data does not specify version"));
}
_.each(tableKeys, function (constkey) {
_.each(importData.data[constkey], function (elem) {
var prop;
for (prop in elem) {
if (elem.hasOwnProperty(prop)) {
if (schema[constkey].hasOwnProperty(prop)) {
if (!_.isNull(elem[prop])) {
if (elem[prop].length > schema[constkey][prop].maxlength) {
error += error !== "" ? "<br>" : "";
error += "Property '" + prop + "' exceeds maximum length of " + schema[constkey][prop].maxlength + " (element:" + constkey + " / id:" + elem.id + ")";
}
} else {
if (!schema[constkey][prop].nullable) {
error += error !== "" ? "<br>" : "";
error += "Property '" + prop + "' is not nullable (element:" + constkey + " / id:" + elem.id + ")";
}
}
} else {
error += error !== "" ? "<br>" : "";
error += "Property '" + prop + "' is not allowed (element:" + constkey + " / id:" + elem.id + ")";
}
}
_.each(_.keys(importData.data), function (tableName) {
_.each(importData.data[tableName], function (importValues) {
try {
validation.validateSchema(tableName, importValues);
} catch (err) {
error += error !== "" ? "<br>" : "";
error += err.message;
}
});
});
@ -121,7 +81,7 @@ db = {
}).then(function () {
return when.resolve({message: 'Posts, tags and other data successfully imported'});
}).otherwise(function importFailure(error) {
return when.reject({errorCode: 500, message: error.message || error});
return when.reject({code: 500, message: error.message || error});
});
},
'deleteAllContent': function () {
@ -129,7 +89,7 @@ db = {
.then(function () {
return when.resolve({message: 'Successfully deleted all content from your blog.'});
}, function (error) {
return when.reject({errorCode: 500, message: error.message || error});
return when.reject({code: 500, message: error.message || error});
});
}
};

View File

@ -4,7 +4,6 @@
var _ = require('lodash'),
when = require('when'),
config = require('../config'),
errors = require('../errorHandling'),
db = require('./db'),
settings = require('./settings'),
notifications = require('./notifications'),
@ -61,7 +60,7 @@ requestHandler = function (apiMethod) {
}
});
}, function (error) {
var errorCode = error.errorCode || 500,
var errorCode = error.code || 500,
errorMsg = {error: _.isString(error) ? error : (_.isObject(error) ? error.message : 'Unknown API Error')};
res.json(errorCode, errorMsg);
});

View File

@ -1,8 +1,7 @@
var when = require('when'),
_ = require('lodash'),
dataProvider = require('../models'),
permissions = require('../permissions'),
canThis = permissions.canThis,
canThis = require('../permissions').canThis,
filteredUserAttributes = require('./users').filteredAttributes,
posts;
@ -15,7 +14,7 @@ posts = {
options = options || {};
// **returns:** a promise for a page of posts in a json object
//return dataProvider.Post.findPage(options);
return dataProvider.Post.findPage(options).then(function (result) {
var i = 0,
omitted = result;
@ -43,7 +42,7 @@ posts = {
omitted.user = _.omit(omitted.user, filteredUserAttributes);
return omitted;
}
return when.reject({errorCode: 404, message: 'Post not found'});
return when.reject({code: 404, message: 'Post not found'});
});
},
@ -53,7 +52,7 @@ posts = {
if (slug) {
return slug;
}
return when.reject({errorCode: 500, message: 'Could not generate slug'});
return when.reject({code: 500, message: 'Could not generate slug'});
});
},
@ -63,7 +62,7 @@ posts = {
edit: function edit(postData) {
// **returns:** a promise for the resulting post in a json object
if (!this.user) {
return when.reject({errorCode: 403, message: 'You do not have permission to edit this post.'});
return when.reject({code: 403, message: 'You do not have permission to edit this post.'});
}
var self = this;
return canThis(self.user).edit.post(postData.id).then(function () {
@ -74,17 +73,17 @@ posts = {
omitted.user = _.omit(omitted.user, filteredUserAttributes);
return omitted;
}
return when.reject({errorCode: 404, message: 'Post not found'});
return when.reject({code: 404, message: 'Post not found'});
}).otherwise(function (error) {
return dataProvider.Post.findOne({id: postData.id, status: 'all'}).then(function (result) {
if (!result) {
return when.reject({errorCode: 404, message: 'Post not found'});
return when.reject({code: 404, message: 'Post not found'});
}
return when.reject({message: error.message});
});
});
}, function () {
return when.reject({errorCode: 403, message: 'You do not have permission to edit this post.'});
return when.reject({code: 403, message: 'You do not have permission to edit this post.'});
});
},
@ -94,13 +93,13 @@ posts = {
add: function add(postData) {
// **returns:** a promise for the resulting post in a json object
if (!this.user) {
return when.reject({errorCode: 403, message: 'You do not have permission to add posts.'});
return when.reject({code: 403, message: 'You do not have permission to add posts.'});
}
return canThis(this.user).create.post().then(function () {
return dataProvider.Post.add(postData);
}, function () {
return when.reject({errorCode: 403, message: 'You do not have permission to add posts.'});
return when.reject({code: 403, message: 'You do not have permission to add posts.'});
});
},
@ -110,7 +109,7 @@ posts = {
destroy: function destroy(args) {
// **returns:** a promise for a json response with the id of the deleted post
if (!this.user) {
return when.reject({errorCode: 403, message: 'You do not have permission to remove posts.'});
return when.reject({code: 403, message: 'You do not have permission to remove posts.'});
}
return canThis(this.user).remove.post(args.id).then(function () {
@ -121,7 +120,7 @@ posts = {
});
});
}, function () {
return when.reject({errorCode: 403, message: 'You do not have permission to remove posts.'});
return when.reject({code: 403, message: 'You do not have permission to remove posts.'});
});
}
};

View File

@ -9,6 +9,7 @@ var _ = require('lodash'),
settingsFilter,
updateSettingsCache,
readSettingsResult,
filterPaths,
// Holds cached settings
settingsCache = {};
@ -78,36 +79,69 @@ readSettingsResult = function (result) {
}
})).then(function () {
return when(config().paths.availableThemes).then(function (themes) {
var themeKeys = Object.keys(themes),
res = [],
i,
item;
for (i = 0; i < themeKeys.length; i += 1) {
//do not include hidden files or _messages
if (themeKeys[i].indexOf('.') !== 0 && themeKeys[i] !== '_messages') {
item = {};
item.name = themeKeys[i];
if (themes[themeKeys[i]].hasOwnProperty('package.json')) {
item.package = themes[themeKeys[i]]['package.json'];
} else {
item.package = false;
}
//data about files currently not used
//item.details = themes[themeKeys[i]];
if (themeKeys[i] === settings.activeTheme.value) {
item.active = true;
}
res.push(item);
}
}
settings.availableThemes = {};
settings.availableThemes.value = res;
settings.availableThemes.type = 'theme';
var res = filterPaths(themes, settings.activeTheme.value);
settings.availableThemes = {
value: res,
type: 'theme'
};
return settings;
});
}).then(function () {
return when(config().paths.availableApps).then(function (apps) {
var res = filterPaths(apps, JSON.parse(settings.activeApps.value));
settings.availableApps = {
value: res,
type: 'app'
};
return settings;
});
});
};
/**
* Normalizes paths read by require-tree so that the apps and themes modules can use them.
* Creates an empty array (res), and populates it with useful info about the read packages
* like name, whether they're active (comparison with the second argument), and if they
* have a package.json, that, otherwise false
* @param object paths as returned by require-tree()
* @param array/string active as read from the settings object
* @return array of objects with useful info about
* apps / themes
*/
filterPaths = function (paths, active) {
var pathKeys = Object.keys(paths),
res = [],
item;
// turn active into an array (so themes and apps can be checked the same)
if (!Array.isArray(active)) {
active = [active];
}
_.each(pathKeys, function (key) {
//do not include hidden files or _messages
if (key.indexOf('.') !== 0
&& key !== '_messages'
&& key !== 'README.md'
) {
item = {
name: key
};
if (paths[key].hasOwnProperty('package.json')) {
item.package = paths[key]['package.json'];
} else {
item.package = false;
}
if (_.indexOf(active, key) !== -1) {
item.active = true;
}
res.push(item);
}
});
return res;
};
settings = {
// #### Browse
@ -133,7 +167,7 @@ settings = {
if (settingsCache) {
return when(settingsCache[options.key]).then(function (setting) {
if (!setting) {
return when.reject({errorCode: 404, message: 'Unable to find setting: ' + options.key});
return when.reject({code: 404, message: 'Unable to find setting: ' + options.key});
}
var res = {};
res.key = options.key;
@ -153,6 +187,7 @@ settings = {
var type = key.type;
delete key.type;
delete key.availableThemes;
delete key.availableApps;
key = settingsCollection(key);
return dataProvider.Settings.edit(key).then(function (result) {
@ -167,7 +202,7 @@ settings = {
}).otherwise(function (error) {
return dataProvider.Settings.read(key.key).then(function (result) {
if (!result) {
return when.reject({errorCode: 404, message: 'Unable to find setting: ' + key});
return when.reject({code: 404, message: 'Unable to find setting: ' + key});
}
return when.reject({message: error.message});
});
@ -175,7 +210,7 @@ settings = {
}
return dataProvider.Settings.read(key).then(function (setting) {
if (!setting) {
return when.reject({errorCode: 404, message: 'Unable to find setting: ' + key});
return when.reject({code: 404, message: 'Unable to find setting: ' + key});
}
if (!_.isString(value)) {
value = JSON.stringify(value);

View File

@ -3,12 +3,14 @@ var dataProvider = require('../models'),
tags = {
// #### All
// #### Browse
// **takes:** Nothing yet
all: function browse() {
browse: function browse() {
// **returns:** a promise for all tags which have previously been used in a json object
return dataProvider.Tag.findAll();
return dataProvider.Tag.findAll().then(function (result) {
return result.toJSON();
});
}
};

View File

@ -8,8 +8,8 @@ var when = require('when'),
// ## Users
users = {
// #### Browse
// #### Browse
// **takes:** options object
browse: function browse(options) {
// **returns:** a promise for a collection of users in a json object
@ -31,7 +31,6 @@ users = {
},
// #### Read
// **takes:** an identifier (id or slug?)
read: function read(args) {
// **returns:** a promise for a single user in a json object
@ -45,12 +44,11 @@ users = {
return omitted;
}
return when.reject({errorCode: 404, message: 'User not found'});
return when.reject({code: 404, message: 'User not found'});
});
},
// #### Edit
// **takes:** a json object representing a user
edit: function edit(userData) {
// **returns:** a promise for the resulting user in a json object
@ -60,12 +58,11 @@ users = {
var omitted = _.omit(result.toJSON(), filteredAttributes);
return omitted;
}
return when.reject({errorCode: 404, message: 'User not found'});
return when.reject({code: 404, message: 'User not found'});
});
},
// #### Add
// **takes:** a json object representing a user
add: function add(userData) {
@ -83,7 +80,6 @@ users = {
},
// #### Change Password
// **takes:** a json object representing a user
changePassword: function changePassword(userData) {
// **returns:** on success, returns a promise for the resulting user in a json object

View File

@ -15,7 +15,7 @@ var proxy = {
},
api: {
posts: _.pick(api.posts, 'browse', 'read'),
tags: api.tags,
tags: _.pick(api.tags, 'browse'),
notifications: _.pick(api.notifications, 'add'),
settings: _.pick(api.settings, 'read')
}

View File

@ -1,6 +1,5 @@
var fs = require('fs'),
path = require('path'),
var path = require('path'),
Module = require('module'),
_ = require('lodash');
@ -88,4 +87,4 @@ AppSandbox.defaults = {
blacklist: ['knex', 'fs', 'http', 'sqlite3', 'pg', 'mysql', 'ghost']
};
module.exports = AppSandbox;
module.exports = AppSandbox;

View File

@ -7,7 +7,7 @@ var path = require('path'),
when = require('when'),
url = require('url'),
_ = require('lodash'),
requireTree = require('../require-tree'),
requireTree = require('../require-tree').readAll,
theme = require('./theme'),
configUrl = require('./url'),
ghostConfig = {},
@ -100,7 +100,7 @@ function config() {
if (_.isEmpty(ghostConfig)) {
try {
ghostConfig = require(path.resolve(__dirname, '../../../', 'config.js'))[process.env.NODE_ENV] || {};
} catch (ignore) {/*jslint sloppy: true */}
} catch (ignore) {/*jslint strict: true */}
ghostConfig = updateConfig(ghostConfig);
}
@ -111,4 +111,4 @@ module.exports = config;
module.exports.init = initConfig;
module.exports.theme = theme;
module.exports.urlFor = configUrl.urlFor;
module.exports.urlForPost = configUrl.urlForPost;
module.exports.urlForPost = configUrl.urlForPost;

View File

@ -93,7 +93,7 @@ adminControllers = {
// Method: GET
'settings': function (req, res, next) {
// TODO: Centralise list/enumeration of settings panes, so we don't run into trouble in future.
var allowedSections = ['', 'general', 'user'],
var allowedSections = ['', 'general', 'user', 'apps'],
section = req.url.replace(/(^\/ghost\/settings[\/]*|\/$)/ig, '');
if (allowedSections.indexOf(section) < 0) {
@ -115,6 +115,28 @@ adminControllers = {
bodyClass: 'settings',
adminNav: setSelected(adminNavbar, 'settings')
});
},
// frontend route for downloading a file
exportContent: function (req, res) {
/*jslint unparam:true*/
api.db.exportContent().then(function (exportData) {
// send a file to the client
res.set('Content-Disposition', 'attachment; filename="GhostData.json"');
res.json(exportData);
}).otherwise(function (err) {
var notification = {
type: 'error',
message: 'Your export file could not be generated.',
status: 'persistent',
id: 'errorexport'
};
errors.logError(err, 'admin.js', "Your export file could not be generated.");
return api.notifications.add(notification).then(function () {
res.redirect(config().paths.subdir + '/ghost/debug');
});
});
}
},
// Route: upload

View File

@ -13,8 +13,8 @@ var moment = require('moment'),
api = require('../api'),
config = require('../config'),
errors = require('../errorHandling'),
filters = require('../../server/filters'),
template = require('../helpers/template'),
frontendControllers,
// Cache static post permalink regex
@ -58,7 +58,7 @@ function formatPageResponse(posts, page) {
function handleError(next) {
return function (err) {
var e = new Error(err.message);
e.status = err.errorCode;
e.status = err.code;
return next(e);
};
}
@ -124,7 +124,7 @@ frontendControllers = {
filters.doFilter('prePostsRender', page.posts).then(function (posts) {
api.settings.read('activeTheme').then(function (activeTheme) {
var paths = config().paths.availableThemes[activeTheme.value],
view = paths.hasOwnProperty('tag') ? 'tag' : 'index',
view = paths.hasOwnProperty('tag.hbs') ? 'tag' : 'index',
// Format data for template
response = _.extend(formatPageResponse(posts, page), {
@ -186,7 +186,8 @@ frontendControllers = {
filters.doFilter('prePostsRender', post).then(function (post) {
api.settings.read('activeTheme').then(function (activeTheme) {
var paths = config().paths.availableThemes[activeTheme.value],
view = post.page && paths.hasOwnProperty('page.hbs') ? 'page' : 'post';
view = template.getThemeViewForPost(paths, post);
res.render(view, {post: post});
});
});

View File

@ -23,7 +23,7 @@
"email": {
"defaultValue": "ghost@example.com",
"validations": {
"notNull": true,
"isNull": false,
"isEmail": true
}
},
@ -36,29 +36,29 @@
"defaultLang": {
"defaultValue": "en_US",
"validations": {
"notNull": true
"isNull": false
}
},
"postsPerPage": {
"defaultValue": "6",
"validations": {
"notNull": true,
"isNull": false,
"isInt": true,
"max": 1000
"isLength": [0, 1000]
}
},
"forceI18n": {
"defaultValue": "true",
"validations": {
"notNull": true,
"isIn": ["true", "false"]
"isNull": false,
"isIn": [["true", "false"]]
}
},
"permalinks": {
"defaultValue": "/:slug/",
"validations": {
"is": "^(\/:?[a-z0-9_-]+){1,5}\/$",
"regex": "(:id|:slug|:year|:month|:day)",
"matches": "^(\/:?[a-z0-9_-]+){1,5}\/$",
"matches": "(:id|:slug|:year|:month|:day)",
"notContains": "/ghost/"
}
}

View File

@ -3,8 +3,7 @@ var sequence = require('when/sequence'),
Post = require('../../models/post').Post,
Tag = require('../../models/tag').Tag,
Role = require('../../models/role').Role,
Permission = require('../../models/permission').Permission,
uuid = require('node-uuid');
Permission = require('../../models/permission').Permission;
var fixtures = {
posts: [
@ -102,4 +101,4 @@ module.exports = {
return sequence(ops);
}
};
};

View File

@ -1,7 +1,6 @@
var when = require('when'),
_ = require('lodash'),
models = require('../../models'),
errors = require('../../errorHandling'),
Importer000;
@ -112,13 +111,13 @@ function importUsers(ops, tableData, transaction) {
function importSettings(ops, tableData, transaction) {
// for settings we need to update individual settings, and insert any missing ones
// settings we MUST NOT update are the databaseVersion, dbHash, and activeTheme
// settings we MUST NOT update are 'core' and 'theme' settings
// as all of these will cause side effects which don't make sense for an import
var blackList = ['core', 'theme'];
var blackList = ['databaseVersion', 'dbHash', 'activeTheme'];
tableData = stripProperties(['id'], tableData);
tableData = _.filter(tableData, function (data) {
return blackList.indexOf(data.key) === -1;
return blackList.indexOf(data.type) === -1;
});
ops.push(models.Settings.edit(tableData, transaction)
@ -197,4 +196,4 @@ module.exports = {
importData: function (data) {
return new Importer000().importData(data);
}
};
};

View File

@ -1,14 +1,14 @@
var db = {
posts: {
id: {type: 'increments', nullable: false, primary: true},
uuid: {type: 'string', maxlength: 36, nullable: false},
uuid: {type: 'string', maxlength: 36, nullable: false, validations: {'isUUID': true}},
title: {type: 'string', maxlength: 150, nullable: false},
slug: {type: 'string', maxlength: 150, nullable: false, unique: true},
markdown: {type: 'text', maxlength: 16777215, fieldtype: 'medium', nullable: true},
html: {type: 'text', maxlength: 16777215, fieldtype: 'medium', nullable: true},
image: {type: 'text', maxlength: 2000, nullable: true},
featured: {type: 'bool', nullable: false, defaultTo: false},
page: {type: 'bool', nullable: false, defaultTo: false},
page: {type: 'bool', nullable: false, defaultTo: false, validations: {'isIn': [['0', '1']]}},
status: {type: 'string', maxlength: 150, nullable: false, defaultTo: 'draft'},
language: {type: 'string', maxlength: 6, nullable: false, defaultTo: 'en_US'},
meta_title: {type: 'string', maxlength: 150, nullable: true},
@ -23,15 +23,15 @@ var db = {
},
users: {
id: {type: 'increments', nullable: false, primary: true},
uuid: {type: 'string', maxlength: 36, nullable: false},
uuid: {type: 'string', maxlength: 36, nullable: false, validations: {'isUUID': true}},
name: {type: 'string', maxlength: 150, nullable: false, unique: true},
slug: {type: 'string', maxlength: 150, nullable: false},
password: {type: 'string', maxlength: 60, nullable: false},
email: {type: 'string', maxlength: 254, nullable: false, unique: true},
email: {type: 'string', maxlength: 254, nullable: false, unique: true, validations: {'isEmail': true}},
image: {type: 'text', maxlength: 2000, nullable: true},
cover: {type: 'text', maxlength: 2000, nullable: true},
bio: {type: 'string', maxlength: 200, nullable: true},
website: {type: 'text', maxlength: 2000, nullable: true},
website: {type: 'text', maxlength: 2000, nullable: true, validations: {'isURL': true}},
location: {type: 'text', maxlength: 65535, nullable: true},
accessibility: {type: 'text', maxlength: 65535, nullable: true},
status: {type: 'string', maxlength: 150, nullable: false, defaultTo: 'active'},
@ -46,7 +46,7 @@ var db = {
},
roles: {
id: {type: 'increments', nullable: false, primary: true},
uuid: {type: 'string', maxlength: 36, nullable: false},
uuid: {type: 'string', maxlength: 36, nullable: false, validations: {'isUUID': true}},
name: {type: 'string', maxlength: 150, nullable: false},
description: {type: 'string', maxlength: 200, nullable: true},
created_at: {type: 'dateTime', nullable: false},
@ -61,7 +61,7 @@ var db = {
},
permissions: {
id: {type: 'increments', nullable: false, primary: true},
uuid: {type: 'string', maxlength: 36, nullable: false},
uuid: {type: 'string', maxlength: 36, nullable: false, validations: {'isUUID': true}},
name: {type: 'string', maxlength: 150, nullable: false},
object_type: {type: 'string', maxlength: 150, nullable: false},
action_type: {type: 'string', maxlength: 150, nullable: false},
@ -88,10 +88,10 @@ var db = {
},
settings: {
id: {type: 'increments', nullable: false, primary: true},
uuid: {type: 'string', maxlength: 36, nullable: false},
uuid: {type: 'string', maxlength: 36, nullable: false, validations: {'isUUID': true}},
key: {type: 'string', maxlength: 150, nullable: false, unique: true},
value: {type: 'text', maxlength: 65535, nullable: true},
type: {type: 'string', maxlength: 150, nullable: false, defaultTo: 'core'},
type: {type: 'string', maxlength: 150, nullable: false, defaultTo: 'core', validations: {'isIn': [['core', 'blog', 'theme', 'app', 'plugin']]}},
created_at: {type: 'dateTime', nullable: false},
created_by: {type: 'integer', nullable: false},
updated_at: {type: 'dateTime', nullable: true},
@ -99,7 +99,7 @@ var db = {
},
tags: {
id: {type: 'increments', nullable: false, primary: true},
uuid: {type: 'string', maxlength: 36, nullable: false},
uuid: {type: 'string', maxlength: 36, nullable: false, validations: {'isUUID': true}},
name: {type: 'string', maxlength: 150, nullable: false},
slug: {type: 'string', maxlength: 150, nullable: false, unique: true},
description: {type: 'string', maxlength: 200, nullable: true},
@ -132,4 +132,4 @@ module.exports.tables = db;
module.exports.checks = {
isPost: isPost,
isTag: isTag
};
};

View File

@ -0,0 +1,112 @@
var schema = require('../schema').tables,
_ = require('lodash'),
validator = require('validator'),
validateSchema,
validateSettings,
validate;
// Provide a few custom validators
//
validator.extend('empty', function (str) {
return _.isEmpty(str);
});
validator.extend('notContains', function (str, badString) {
return !_.contains(str, badString);
});
// Validation validation against schema attributes
// values are checked against the validation objects
// form schema.js
validateSchema = function (tableName, model) {
var columns = _.keys(schema[tableName]);
_.each(columns, function (columnKey) {
// check nullable
if (model.hasOwnProperty(columnKey) && schema[tableName][columnKey].hasOwnProperty('nullable')
&& schema[tableName][columnKey].nullable !== true) {
if (validator.isNull(model[columnKey]) || validator.empty(model[columnKey])) {
throw new Error('Value in [' + tableName + '.' + columnKey + '] cannot be blank.');
}
}
// TODO: check if mandatory values should be enforced
if (model[columnKey]) {
// check length
if (schema[tableName][columnKey].hasOwnProperty('maxlength')) {
if (!validator.isLength(model[columnKey], 0, schema[tableName][columnKey].maxlength)) {
throw new Error('Value in [' + tableName + '.' + columnKey +
'] exceeds maximum length of ' + schema[tableName][columnKey].maxlength + ' characters.');
}
}
//check validations objects
if (schema[tableName][columnKey].hasOwnProperty('validations')) {
validate(model[columnKey], columnKey, schema[tableName][columnKey].validations);
}
//check type
if (schema[tableName][columnKey].hasOwnProperty('type')) {
if (schema[tableName][columnKey].type === 'integer' && !validator.isInt(model[columnKey])) {
throw new Error('Value in [' + tableName + '.' + columnKey + '] is no valid integer.');
}
}
}
});
};
// Validation for settings
// settings are checked against the validation objects
// form default-settings.json
validateSettings = function (defaultSettings, model) {
var values = model.toJSON(),
matchingDefault = defaultSettings[values.key];
if (matchingDefault && matchingDefault.validations) {
validate(values.value, values.key, matchingDefault.validations);
}
};
// Validate default settings using the validator module.
// Each validation's key is a method name and its value is an array of options
//
// eg:
// validations: { isUrl: true, isLength: [20, 40] }
//
// will validate that a setting's length is a URL between 20 and 40 chars.
//
// If you pass a boolean as the value, it will specify the "good" result. By default
// the "good" result is assumed to be true.
//
// eg:
// validations: { isNull: false } // means the "good" result would
// // fail the `isNull` check, so
// // not null.
//
// available validators: https://github.com/chriso/validator.js#validators
validate = function (value, key, validations) {
_.each(validations, function (validationOptions, validationName) {
var goodResult = true;
if (_.isBoolean(validationOptions)) {
goodResult = validationOptions;
validationOptions = [];
} else if (!_.isArray(validationOptions)) {
validationOptions = [validationOptions];
}
validationOptions.unshift(value);
// equivalent of validator.isSomething(option1, option2)
if (validator[validationName].apply(validator, validationOptions) !== goodResult) {
throw new Error('Settings validation (' + validationName + ') failed for ' + key);
}
validationOptions.shift();
}, this);
};
module.exports = {
validateSchema: validateSchema,
validateSettings: validateSettings
};

View File

@ -1,7 +1,6 @@
/*jslint regexp: true */
var _ = require('lodash'),
colors = require('colors'),
fs = require('fs'),
config = require('./config'),
path = require('path'),
when = require('when'),
@ -15,6 +14,9 @@ var _ = require('lodash'),
ONE_HOUR_S = 60 * 60;
// This is not useful but required for jshint
colors.setTheme({silly: 'rainbow'});
/**
* Basic error handling helpers
*/
@ -107,7 +109,7 @@ errors = {
},
logErrorWithRedirect: function (msg, context, help, redirectTo, req, res) {
/*jslint unparam:true*/
/*jshint unused:false*/
var self = this;
return function () {
@ -120,7 +122,7 @@ errors = {
},
renderErrorPage: function (code, err, req, res, next) {
/*jslint unparam:true*/
/*jshint unused:false*/
var self = this;

View File

@ -25,7 +25,7 @@ var Filters = function () {
// Register a new filter callback function
Filters.prototype.registerFilter = function (name, priority, fn) {
// Curry the priority optional parameter to a default of 5
// Carry the priority optional parameter to a default of 5
if (_.isFunction(priority)) {
fn = priority;
priority = null;

View File

@ -1,7 +1,6 @@
var downsize = require('downsize'),
hbs = require('express-hbs'),
moment = require('moment'),
path = require('path'),
polyglot = require('node-polyglot').instance,
_ = require('lodash'),
when = require('when'),
@ -96,7 +95,7 @@ coreHelpers.encode = function (context, str) {
// context.
//
coreHelpers.page_url = function (context, block) {
/*jslint unparam:true*/
/*jshint unused:false*/
var url = config().paths.subdir;
if (this.tagSlug !== undefined) {
@ -125,7 +124,7 @@ coreHelpers.pageUrl = function (context, block) {
'The helper pageUrl has been replaced with page_url in Ghost 0.5, and will be removed entirely in Ghost 0.6\n' +
'In your theme\'s pagination.hbs file, pageUrl should be renamed to page_url');
/*jslint unparam:true*/
/*jshint unused:false*/
var self = this;
return coreHelpers.page_url.call(self, context, block);
@ -198,7 +197,7 @@ coreHelpers.asset = function (context, options) {
// if the author could not be determined.
//
coreHelpers.author = function (context, options) {
/*jslint unparam:true*/
/*jshint unused:false*/
return this.author ? this.author.name : '';
};
@ -320,11 +319,25 @@ coreHelpers.excerpt = function (options) {
//
// Returns the config value for fileStorage.
coreHelpers.file_storage = function (context, options) {
/*jslint unparam:true*/
/*jshint unused:false*/
if (config().hasOwnProperty('fileStorage')) {
return config().fileStorage.toString();
}
return "true";
return 'true';
};
// ### Apps helper
//
// *Usage example:*
// `{{apps}}`
//
// Returns the config value for apps.
coreHelpers.apps = function (context, options) {
/*jshint unused:false*/
if (config().hasOwnProperty('apps')) {
return config().apps.toString();
}
return 'false';
};
coreHelpers.ghost_script_tags = function () {
@ -345,8 +358,9 @@ coreHelpers.ghost_script_tags = function () {
*/
coreHelpers.body_class = function (options) {
/*jslint unparam:true*/
/*jshint unused:false*/
var classes = [],
post = this.post,
tags = this.post && this.post.tags ? this.post.tags : this.tags || [],
page = this.post && this.post.page ? this.post.page : this.page || false;
@ -366,14 +380,31 @@ coreHelpers.body_class = function (options) {
classes.push('page');
}
return filters.doFilter('body_class', classes).then(function (classes) {
var classString = _.reduce(classes, function (memo, item) { return memo + ' ' + item; }, '');
return new hbs.handlebars.SafeString(classString.trim());
return api.settings.read('activeTheme').then(function (activeTheme) {
var paths = config().paths.availableThemes[activeTheme.value],
view;
if (post) {
view = template.getThemeViewForPost(paths, post).split('-');
// If this is a page and we have a custom page template
// then we need to modify the class name we inject
// e.g. 'page-contact' is outputted as 'page-template-contact'
if (view[0] === 'page' && view.length > 1) {
view.splice(1, 0, 'template');
classes.push(view.join('-'));
}
}
return filters.doFilter('body_class', classes).then(function (classes) {
var classString = _.reduce(classes, function (memo, item) { return memo + ' ' + item; }, '');
return new hbs.handlebars.SafeString(classString.trim());
});
});
};
coreHelpers.post_class = function (options) {
/*jslint unparam:true*/
/*jshint unused:false*/
var classes = ['post'],
tags = this.post && this.post.tags ? this.post.tags : this.tags || [],
featured = this.post && this.post.featured ? this.post.featured : this.featured || false,
@ -398,7 +429,7 @@ coreHelpers.post_class = function (options) {
};
coreHelpers.ghost_head = function (options) {
/*jslint unparam:true*/
/*jshint unused:false*/
var self = this,
blog = config.theme(),
head = [],
@ -423,11 +454,11 @@ coreHelpers.ghost_head = function (options) {
};
coreHelpers.ghost_foot = function (options) {
/*jslint unparam:true*/
/*jshint unused:false*/
var foot = [];
foot.push(scriptTemplate({
source: config().paths.subdir + '/shared/vendor/jquery/jquery.js',
source: config().paths.subdir + '/public/jquery.js',
version: coreHelpers.assetHash
}));
@ -438,16 +469,18 @@ coreHelpers.ghost_foot = function (options) {
};
coreHelpers.meta_title = function (options) {
/*jslint unparam:true*/
/*jshint unused:false*/
var title = "",
blog;
if (_.isString(this.relativeUrl)) {
blog = config.theme();
if (!this.relativeUrl || this.relativeUrl === '/' || this.relativeUrl === '' || this.relativeUrl.match(/\/page/)) {
blog = config.theme();
title = blog.title;
} else if (this.post) {
title = this.post.title;
} else if (this.tag) {
title = this.tag.name + ' - ' + blog.title;
}
}
@ -458,7 +491,7 @@ coreHelpers.meta_title = function (options) {
};
coreHelpers.meta_description = function (options) {
/*jslint unparam:true*/
/*jshint unused:false*/
var description,
blog;
@ -572,11 +605,42 @@ coreHelpers.foreach = function (context, options) {
return ret;
};
// ### Has Helper
// `{{#has tag="video, music"}}`
// Checks whether a post has at least one of the tags
coreHelpers.has = function (options) {
var tags = _.pluck(this.tags, 'name'),
tagList = options && options.hash ? options.hash.tag : false;
function evaluateTagList(expr, tags) {
return expr.split(',').map(function (v) {
return v.trim();
}).reduce(function (p, c) {
return p || (_.findIndex(tags, function (item) {
// Escape regex special characters
item = item.replace(/[\-\/\\\^$*+?.()|\[\]{}]/g, '\\$&');
item = new RegExp(item, 'i');
return item.test(c);
}) !== -1);
}, false);
}
if (!tagList) {
errors.logWarn("Invalid or no attribute given to has helper");
return;
}
if (tagList && evaluateTagList(tagList, tags)) {
return options.fn(this);
}
return options.inverse(this);
};
// ### Pagination Helper
// `{{pagination}}`
// Outputs previous and next buttons, along with info about the current page
coreHelpers.pagination = function (options) {
/*jslint unparam:true*/
/*jshint unused:false*/
if (!_.isObject(this.pagination) || _.isFunction(this.pagination)) {
errors.logAndThrowError('pagination data is not an object or is a function');
return;
@ -702,6 +766,8 @@ registerHelpers = function (adminHbs, assetHash) {
registerThemeHelper('foreach', coreHelpers.foreach);
registerThemeHelper('has', coreHelpers.has);
registerThemeHelper('page_url', coreHelpers.page_url);
registerThemeHelper('pageUrl', coreHelpers.pageUrl);
@ -734,6 +800,8 @@ registerHelpers = function (adminHbs, assetHash) {
registerAdminHelper('file_storage', coreHelpers.file_storage);
registerAdminHelper('apps', coreHelpers.apps);
registerAdminHelper('admin_url', coreHelpers.admin_url);
registerAsyncAdminHelper('update_notification', coreHelpers.update_notification);

View File

@ -23,4 +23,26 @@ templates.execute = function (name, context) {
return new hbs.handlebars.SafeString(partial(context));
};
// Given a theme object and a post object this will return
// which theme template page should be used.
// If given a post object that is a regular post
// it will return 'post'.
// If given a static post object it will return 'page'.
// If given a static post object and a custom page template
// exits it will return that page.
templates.getThemeViewForPost = function (themePaths, post) {
var customPageView = 'page-' + post.slug,
view = 'post';
if (post.page) {
if (themePaths.hasOwnProperty(customPageView + '.hbs')) {
view = customPageView;
} else if (themePaths.hasOwnProperty('page.hbs')) {
view = 'page';
}
}
return view;
};
module.exports = templates;

View File

@ -247,7 +247,7 @@ function setup(server) {
if (getSocket()) {
// Make sure the socket is gone before trying to create another
fs.unlink(getSocket(), function (err) {
/*jslint unparam:true*/
/*jshint unused:false*/
server.listen(
getSocket(),
startGhost

View File

@ -1,5 +1,4 @@
var cp = require('child_process'),
url = require('url'),
_ = require('lodash'),
when = require('when'),
nodefn = require('when/node/function'),
@ -82,6 +81,22 @@ GhostMailer.prototype.emailDisabled = function () {
this.transport = null;
};
GhostMailer.prototype.fromAddress = function () {
var from = config().mail && config().mail.fromaddress,
domain;
if (!from) {
// Extract the domain name from url set in config.js
domain = config().url.match(new RegExp("^https?://([^/:?#]+)(?:[/:?#]|$)", "i"));
domain = domain && domain[1];
// Default to webmaster@[blog.url]
from = 'webmaster@' + domain;
}
return from;
};
// Sends an e-mail message enforcing `to` (blog owner) and `from` fields
GhostMailer.prototype.send = function (message) {
var self = this;
@ -94,11 +109,10 @@ GhostMailer.prototype.send = function (message) {
}
return api.settings.read('email').then(function (email) {
var from = (config().mail && config().mail.fromaddress) || email.value,
to = message.to || email.value;
var to = message.to || email.value;
message = _.extend(message, {
from: from,
from: self.fromAddress(),
to: to,
generateTextFromHTML: true
});

View File

@ -55,7 +55,7 @@ function ghostBusBoy(req, res, next) {
busboy.on('limit', function () {
hasError = true;
res.send(413, { errorCode: 413, message: 'File size limit breached.' });
res.send(413, {code: 413, message: 'File size limit breached.'});
});
busboy.on('error', function (error) {

View File

@ -10,7 +10,6 @@ var api = require('../api'),
fs = require('fs'),
hbs = require('express-hbs'),
middleware = require('./middleware'),
models = require('../models'),
packageInfo = require('../../../package.json'),
path = require('path'),
slashes = require('connect-slashes'),
@ -225,6 +224,7 @@ module.exports = function (server, dbHash) {
expressServer.use(subdir + '/shared', express['static'](path.join(corePath, '/shared'), {maxAge: ONE_HOUR_MS}));
expressServer.use(subdir + '/content/images', storage.get_storage().serve());
expressServer.use(subdir + '/ghost/scripts', express['static'](path.join(corePath, '/built/scripts'), {maxAge: ONE_YEAR_MS}));
expressServer.use(subdir + '/public', express['static'](path.join(corePath, '/built/public'), {maxAge: ONE_YEAR_MS}));
// First determine whether we're serving admin or theme content
expressServer.use(manageAdminAndTheme);

View File

@ -4,10 +4,10 @@ var Bookshelf = require('bookshelf'),
_ = require('lodash'),
uuid = require('node-uuid'),
config = require('../config'),
Validator = require('validator').Validator,
unidecode = require('unidecode'),
sanitize = require('validator').sanitize,
schema = require('../data/schema'),
validation = require('../data/validation'),
ghostBookshelf;
@ -15,7 +15,6 @@ var Bookshelf = require('bookshelf'),
ghostBookshelf = Bookshelf.ghost = Bookshelf.initialize(config().database);
ghostBookshelf.client = config().database.client;
ghostBookshelf.validator = new Validator();
// The Base Model which other Ghost objects will inherit from,
// including some convenience functions as static properties on the model.
@ -45,7 +44,7 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
},
validate: function () {
return true;
validation.validateSchema(this.tableName, this.toJSON());
},
creating: function () {

View File

@ -9,11 +9,6 @@ Permission = ghostBookshelf.Model.extend({
tableName: 'permissions',
validate: function () {
// TODO: validate object_type, action_type and object_id
ghostBookshelf.validator.check(this.get('name'), "Permission name cannot be blank").notEmpty();
},
roles: function () {
return this.belongsToMany(Role);
},
@ -30,4 +25,4 @@ Permissions = ghostBookshelf.Collection.extend({
module.exports = {
Permission: Permission,
Permissions: Permissions
};
};

View File

@ -3,12 +3,14 @@ var _ = require('lodash'),
when = require('when'),
errors = require('../errorHandling'),
Showdown = require('showdown'),
github = require('../../shared/vendor/showdown/extensions/github'),
converter = new Showdown.converter({extensions: [github]}),
github = require('../../shared/lib/showdown/extensions/github'),
typography = require('../../shared/lib/showdown/extensions/typography'),
converter = new Showdown.converter({extensions: [typography, github]}),
User = require('./user').User,
Tag = require('./tag').Tag,
Tags = require('./tag').Tags,
ghostBookshelf = require('./base'),
validation = require('../data/validation'),
Post,
Posts;
@ -36,16 +38,11 @@ Post = ghostBookshelf.Model.extend({
},
validate: function () {
ghostBookshelf.validator.check(this.get('title'), "Post title cannot be blank").notEmpty();
ghostBookshelf.validator.check(this.get('title'), 'Post title maximum length is 150 characters.').len(0, 150);
ghostBookshelf.validator.check(this.get('slug'), "Post title cannot be blank").notEmpty();
ghostBookshelf.validator.check(this.get('slug'), 'Post title maximum length is 150 characters.').len(0, 150);
return true;
validation.validateSchema(this.tableName, this.toJSON());
},
saving: function (newPage, attr, options) {
/*jslint unparam:true*/
/*jshint unused:false*/
var self = this;
// keep tags for 'saved' event
@ -79,7 +76,7 @@ Post = ghostBookshelf.Model.extend({
},
creating: function (newPage, attr, options) {
/*jslint unparam:true*/
/*jshint unused:false*/
// set any dynamic default properties
if (!this.get('author_id')) {
@ -90,7 +87,7 @@ Post = ghostBookshelf.Model.extend({
},
updateTags: function (newPost, attr, options) {
/*jslint unparam:true*/
/*jshint unused:false*/
var self = this;
options = options || {};
@ -453,4 +450,4 @@ Posts = ghostBookshelf.Collection.extend({
module.exports = {
Post: Post,
Posts: Posts
};
};

View File

@ -9,11 +9,6 @@ Role = ghostBookshelf.Model.extend({
tableName: 'roles',
validate: function () {
ghostBookshelf.validator.check(this.get('name'), "Role name cannot be blank").notEmpty();
ghostBookshelf.validator.check(this.get('description'), "Role description cannot be blank").notEmpty();
},
users: function () {
return this.belongsToMany(User);
},

View File

@ -10,8 +10,8 @@ Session = ghostBookshelf.Model.extend({
}, {
destroyAll: function (options) {
options = options || {};
return ghostBookshelf.Collection.forge([], {model: this}).fetch().
then(function (collection) {
return ghostBookshelf.Collection.forge([], {model: this}).fetch()
.then(function (collection) {
collection.invokeThen('destroy', options);
});
}
@ -24,4 +24,4 @@ Sessions = ghostBookshelf.Collection.extend({
module.exports = {
Session: Session,
Sessions: Sessions
};
};

View File

@ -1,10 +1,10 @@
var Settings,
ghostBookshelf = require('./base'),
validator = ghostBookshelf.validator,
uuid = require('node-uuid'),
_ = require('lodash'),
errors = require('../errorHandling'),
when = require('when'),
validation = require('../data/validation'),
defaultSettings;
@ -41,37 +41,9 @@ Settings = ghostBookshelf.Model.extend({
};
},
// Validate default settings using the validator module.
// Each validation's key is a name and its value is an array of options
// Use true (boolean) if options aren't applicable
//
// eg:
// validations: { isUrl: true, len: [20, 40] }
//
// will validate that a setting's length is a URL between 20 and 40 chars,
// available validators: https://github.com/chriso/node-validator#list-of-validation-methods
validate: function () {
validator.check(this.get('key'), "Setting key cannot be blank").notEmpty();
validator.check(this.get('type'), "Setting type cannot be blank").notEmpty();
var matchingDefault = defaultSettings[this.get('key')];
if (matchingDefault && matchingDefault.validations) {
_.each(matchingDefault.validations, function (validationOptions, validationName) {
var validation = validator.check(this.get('value'));
if (validationOptions === true) {
validationOptions = null;
}
if (typeof validationOptions !== 'array') {
validationOptions = [validationOptions];
}
// equivalent of validation.isSomething(option1, option2)
validation[validationName].apply(validation, validationOptions);
}, this);
}
validation.validateSchema(this.tableName, this.toJSON());
validation.validateSettings(defaultSettings, this);
},

View File

@ -8,11 +8,6 @@ Tag = ghostBookshelf.Model.extend({
tableName: 'tags',
validate: function () {
return true;
},
saving: function () {
var self = this;
ghostBookshelf.Model.prototype.saving.apply(this, arguments);

View File

@ -1,5 +1,4 @@
var _ = require('lodash'),
uuid = require('node-uuid'),
when = require('when'),
errors = require('../errorHandling'),
nodefn = require('when/node/function'),
@ -10,6 +9,7 @@ var _ = require('lodash'),
Permission = require('./permission').Permission,
http = require('http'),
crypto = require('crypto'),
validator = require('validator'),
tokenSecurity = {},
User,
@ -17,11 +17,12 @@ var _ = require('lodash'),
function validatePasswordLength(password) {
try {
ghostBookshelf.validator.check(password, "Your password must be at least 8 characters long.").len(8);
if (!validator.isLength(password, 8)) {
throw new Error('Your password must be at least 8 characters long.');
}
} catch (error) {
return when.reject(error);
}
return when.resolve();
}
@ -37,16 +38,6 @@ User = ghostBookshelf.Model.extend({
tableName: 'users',
validate: function () {
ghostBookshelf.validator.check(this.get('email'), "Please enter a valid email address. That one looks a bit dodgy.").isEmail();
ghostBookshelf.validator.check(this.get('bio'), "We're not writing a novel here! I'm afraid your bio has to stay under 200 characters.").len(0, 200);
if (this.get('website') && this.get('website').length > 0) {
ghostBookshelf.validator.check(this.get('website'), "Looks like your website is not actually a website. Try again?").isUrl();
}
ghostBookshelf.validator.check(this.get('location'), 'This seems a little too long! Please try and keep your location under 150 characters.').len(0, 150);
return true;
},
saving: function () {
var self = this;
// disabling sanitization until we can implement a better version
@ -122,7 +113,7 @@ User = ghostBookshelf.Model.extend({
// Add this user to the admin role (assumes admin = role_id: 1)
return userData.roles().attach(1);
}).then(function (addedUserRole) {
/*jslint unparam:true*/
/*jshint unused:false*/
// Return the added user as expected
return when.resolve(userData);

View File

@ -3,8 +3,13 @@ var _ = require('lodash'),
keys = require('when/keys'),
path = require('path'),
when = require('when'),
messages = {errors: [], warns: []},
parsePackageJson = function (path) {
parsePackageJson = function (path, messages) {
// Default the messages if non were passed
messages = messages || {
errors: [],
warns: []
};
var packageDeferred = when.defer(),
packagePromise = packageDeferred.promise,
jsonContainer;
@ -30,8 +35,12 @@ var _ = require('lodash'),
});
return when(packagePromise);
},
readDir = function (dir, options, depth) {
readDir = function (dir, options, depth, messages) {
depth = depth || 0;
messages = messages || {
errors: [],
warns: []
};
options = _.extend({
index: true
@ -60,9 +69,9 @@ var _ = require('lodash'),
fs.lstat(fpath, function (error, result) {
/*jslint unparam:true*/
if (result.isDirectory()) {
fileDeferred.resolve(readDir(fpath, options, depth + 1));
fileDeferred.resolve(readDir(fpath, options, depth + 1, messages));
} else if (depth === 1 && file === "package.json") {
fileDeferred.resolve(parsePackageJson(fpath));
fileDeferred.resolve(parsePackageJson(fpath, messages));
} else {
fileDeferred.resolve(fpath);
}
@ -79,7 +88,13 @@ var _ = require('lodash'),
});
},
readAll = function (dir, options, depth) {
return when(readDir(dir, options, depth)).then(function (paths) {
// Start with clean messages, pass down along traversal
var messages = {
errors: [],
warns: []
};
return when(readDir(dir, options, depth, messages)).then(function (paths) {
// for all contents of the dir, I'm interested in the ones that are directories and within /theme/
if (typeof paths === "object" && dir.indexOf('theme') !== -1) {
_.each(paths, function (path, index) {
@ -93,4 +108,8 @@ var _ = require('lodash'),
});
};
module.exports = readAll;
module.exports = {
readAll: readAll,
readDir: readDir,
parsePackageJson: parsePackageJson
};

View File

@ -43,6 +43,8 @@ module.exports = function (server) {
server.get('/ghost/settings*', admin.settings);
server.get('/ghost/debug/', admin.debug.index);
server.get('/ghost/export/', admin.debug.exportContent);
server.post('/ghost/upload/', middleware.busboy, admin.upload);
// redirect to /ghost and let that do the authentication to prevent redirects to /ghost//admin etc.

View File

@ -19,12 +19,12 @@ module.exports = function (server) {
server.get('/ghost/api/v0.1/users/:id/', api.requestHandler(api.users.read));
server.put('/ghost/api/v0.1/users/:id/', api.requestHandler(api.users.edit));
// #### Tags
server.get('/ghost/api/v0.1/tags/', api.requestHandler(api.tags.all));
server.get('/ghost/api/v0.1/tags/', api.requestHandler(api.tags.browse));
// #### Notifications
server.del('/ghost/api/v0.1/notifications/:id', api.requestHandler(api.notifications.destroy));
server.post('/ghost/api/v0.1/notifications/', api.requestHandler(api.notifications.add));
// #### Import/Export
server.get('/ghost/api/v0.1/db/', api.db.exportContent);
server.get('/ghost/api/v0.1/db/', api.requestHandler(api.db.exportContent));
server.post('/ghost/api/v0.1/db/', middleware.busboy, api.requestHandler(api.db.importContent));
server.del('/ghost/api/v0.1/db/', api.requestHandler(api.db.deleteAllContent));
};

View File

@ -1,5 +1,4 @@
var _ = require('lodash'),
moment = require('moment'),
var moment = require('moment'),
path = require('path'),
when = require('when'),
baseStore;
@ -50,4 +49,4 @@ baseStore = {
}
};
module.exports = baseStore;
module.exports = baseStore;

View File

@ -192,7 +192,7 @@ function showUpdateNotification() {
// Version 0.4 used boolean to indicate the need for an update. This special case is
// translated to the version string.
// TODO: remove in future version.
if (display.value === 'false' || display.value === 'true') {
if (display.value === 'false' || display.value === 'true' || display.value === '1' || display.value === '0') {
display.value = '0.4.0';
}

View File

@ -20,7 +20,7 @@
<fieldset>
<div class="form-group">
<label>Export</label>
<a href="{{admin_url}}/api/v0.1/db/" class="button-save">Export</a>
<a href="{{admin_url}}/export/" class="button-save">Export</a>
<p>Export the blog settings and data.</p>
</div>
</fieldset>

View File

@ -1,6 +1,6 @@
{{!< default}}
<div class="wrapper">
<aside class="settings-sidebar" role="complementary">
<aside class="settings-sidebar" role="complementary" data-apps={{apps}}>
</aside>

View File

@ -0,0 +1,114 @@
/*global module */
//
// Replaces straight quotes with curly ones, -- and --- with en dash and em
// dash respectively, and ... with horizontal ellipses.
//
(function () {
var typography = function () {
return [
{
type: "lang",
filter: function (text) {
var fCodeblocks = {}, nCodeblocks = {}, iCodeblocks = {},
e = {
endash: '\u2009\u2013\u2009', // U+2009 = thin space
emdash: '\u2014',
lsquo: '\u2018',
rsquo: '\u2019',
ldquo: '\u201c',
rdquo: '\u201d',
hellip: '\u2026'
},
i;
// Extract fenced code blocks.
i = -1;
text = text.replace(/```((?:.|\n)+?)```/g,
function (match, code) {
i += 1;
fCodeblocks[i] = "```" + code + "```";
return "{typog-fcb-" + i + "}";
});
// Extract indented code blocks.
i = -1;
text = text.replace(/((\n+([ ]{4}|\t).+)+)/g,
function (match, code) {
i += 1;
nCodeblocks[i] = " " + code;
return "{typog-ncb-" + i + "}";
});
// Extract inline code blocks
i = -1;
text = text.replace(/`(.+)`/g, function (match, code) {
i += 1;
iCodeblocks[i] = "`" + code + "`";
return "{typog-icb-" + i + "}";
});
// Perform typographic symbol replacement.
// Double quotes. There might be a reason this doesn't use
// the same \b matching style as the single quotes, but I
// can't remember what it is :(
text = text.
// Opening quotes
replace(/"([\w'])/g, e.ldquo + "$1").
// All the rest
replace(/"/g, e.rdquo);
// Single quotes/apostrophes
text = text.
// Apostrophes first
replace(/\b'\b/g, e.rsquo).
// Opening quotes
replace(/'\b/g, e.lsquo).
// All the rest
replace(/'/g, e.rsquo);
// Dashes
text = text.
// Don't replace lines containing only hyphens
replace(/^-+$/gm, "{typog-hr}").
replace(/---/g, e.emdash).
replace(/ -- /g, e.endash).
replace(/{typog-hr}/g, "----");
// Ellipses.
text = text.replace(/\.{3}/g, e.hellip);
// Restore fenced code blocks.
text = text.replace(/{typog-fcb-([0-9]+)}/g, function (x, y) {
return fCodeblocks[y];
});
// Restore indented code blocks.
text = text.replace(/{typog-ncb-([0-9]+)}/g, function (x, y) {
return nCodeblocks[y];
});
// Restore inline code blocks.
text = text.replace(/{typog-icb-([0-9]+)}/g, function (x, y) {
return iCodeblocks[y];
});
return text;
}
}
];
};
// Client-side export
if (typeof window !== 'undefined' && window.Showdown && window.Showdown.extensions) {
window.Showdown.extensions.typography = typography;
}
// Server-side export
if (typeof module !== 'undefined') {
module.exports = typography;
}
}());

File diff suppressed because it is too large Load Diff

View File

@ -1,211 +0,0 @@
// ==========================================================================
// Project: Ember - JavaScript Application Framework
// Copyright: Copyright 2013 Stefan Penner and Ember App Kit Contributors
// License: Licensed under MIT license
// See https://raw.github.com/stefanpenner/ember-jj-abrams-resolver/master/LICENSE
// ==========================================================================
// Version: 0.0.1
(function() {
/*globals define registry requirejs */
define("ember/resolver",
[],
function() {
"use strict";
/*
* This module defines a subclass of Ember.DefaultResolver that adds two
* important features:
*
* 1) The resolver makes the container aware of es6 modules via the AMD
* output. The loader's _seen is consulted so that classes can be
* resolved directly via the module loader, without needing a manual
* `import`.
* 2) is able provide injections to classes that implement `extend`
* (as is typical with Ember).
*/
function classFactory(klass) {
return {
create: function (injections) {
if (typeof klass.extend === 'function') {
return klass.extend(injections);
} else {
return klass;
}
}
};
}
var underscore = Ember.String.underscore;
var classify = Ember.String.classify;
var get = Ember.get;
function parseName(fullName) {
/*jshint validthis:true */
var nameParts = fullName.split(":"),
type = nameParts[0], fullNameWithoutType = nameParts[1],
name = fullNameWithoutType,
namespace = get(this, 'namespace'),
root = namespace;
return {
fullName: fullName,
type: type,
fullNameWithoutType: fullNameWithoutType,
name: name,
root: root,
resolveMethodName: "resolve" + classify(type)
};
}
function chooseModuleName(seen, moduleName) {
var underscoredModuleName = Ember.String.underscore(moduleName);
if (moduleName !== underscoredModuleName && seen[moduleName] && seen[underscoredModuleName]) {
throw new TypeError("Ambiguous module names: `" + moduleName + "` and `" + underscoredModuleName + "`");
}
if (seen[moduleName]) {
return moduleName;
} else if (seen[underscoredModuleName]) {
return underscoredModuleName;
} else {
var parts = moduleName.split('/'),
lastPart = parts[parts.length - 1],
partializedModuleName;
parts[parts.length - 1] = lastPart.replace(/^-/, '_');
partializedModuleName = parts.join('/');
if (seen[partializedModuleName]) {
Ember.deprecate('Modules should not contain underscores. ' +
'Attempted to lookup "'+moduleName+'" which ' +
'was not found. Please rename "'+partializedModuleName+'" '+
'to "'+moduleName+'" instead.', false);
return partializedModuleName;
} else {
return moduleName;
}
}
}
function logLookup(found, parsedName, moduleName) {
if (Ember.ENV.LOG_MODULE_RESOLVER) {
var symbol;
if (found) { symbol = '[✓]'; }
else { symbol = '[ ]'; }
Ember.Logger.info(symbol, parsedName.fullName, new Array(40 - parsedName.fullName.length).join('.'), moduleName);
}
}
function resolveOther(parsedName) {
/*jshint validthis:true */
var moduleName, tmpModuleName, prefix, podPrefix, moduleRegistry;
prefix = this.namespace.modulePrefix;
podPrefix = this.namespace.podModulePrefix || prefix;
moduleRegistry = requirejs._eak_seen;
Ember.assert('module prefix must be defined', prefix);
var pluralizedType = parsedName.type + 's';
var name = parsedName.fullNameWithoutType;
// lookup using POD formatting first
tmpModuleName = podPrefix + '/' + name + '/' + parsedName.type;
if (moduleRegistry[tmpModuleName]) {
moduleName = tmpModuleName;
}
// if not using POD format, use the custom prefix
if (this.namespace[parsedName.type + 'Prefix']) {
prefix = this.namespace[parsedName.type + 'Prefix'];
}
// if router:main or adapter:main look for a module with just the type first
tmpModuleName = prefix + '/' + parsedName.type;
if (!moduleName && name === 'main' && moduleRegistry[tmpModuleName]) {
moduleName = prefix + '/' + parsedName.type;
}
// fallback if not type:main or POD format
if (!moduleName) { moduleName = prefix + '/' + pluralizedType + '/' + name; }
// allow treat all dashed and all underscored as the same thing
// supports components with dashes and other stuff with underscores.
var normalizedModuleName = chooseModuleName(moduleRegistry, moduleName);
if (moduleRegistry[normalizedModuleName]) {
var module = require(normalizedModuleName, null, null, true /* force sync */);
if (module && module['default']) { module = module['default']; }
if (module === undefined) {
throw new Error(" Expected to find: '" + parsedName.fullName + "' within '" + normalizedModuleName + "' but got 'undefined'. Did you forget to `export default` within '" + normalizedModuleName + "'?");
}
if (this.shouldWrapInClassFactory(module, parsedName)) {
module = classFactory(module);
}
logLookup(true, parsedName, moduleName);
return module;
} else {
logLookup(false, parsedName, moduleName);
return this._super(parsedName);
}
}
// Ember.DefaultResolver docs:
// https://github.com/emberjs/ember.js/blob/master/packages/ember-application/lib/system/resolver.js
var Resolver = Ember.DefaultResolver.extend({
resolveTemplate: resolveOther,
resolveOther: resolveOther,
makeToString: function(factory, fullName) {
return '' + this.namespace.modulePrefix + '@' + fullName + ':';
},
parseName: parseName,
shouldWrapInClassFactory: function(module, parsedName){
return false;
},
normalize: function(fullName) {
// replace `.` with `/` in order to make nested controllers work in the following cases
// 1. `needs: ['posts/post']`
// 2. `{{render "posts/post"}}`
// 3. `this.render('posts/post')` from Route
var split = fullName.split(':');
if (split.length > 1) {
return split[0] + ':' + Ember.String.dasherize(split[1].replace(/\./g, '/'));
} else {
return fullName;
}
}
});
Resolver['default'] = Resolver;
return Resolver;
});
define("resolver",
["ember/resolver"],
function (Resolver) {
Ember.deprecate('Importing/requiring Ember Resolver as "resolver" is deprecated, please use "ember/resolver" instead');
return Resolver;
});
})();
(function() {
})();

File diff suppressed because it is too large Load Diff

View File

@ -1,362 +0,0 @@
/*
Copyright (C) 2011 by Yehuda Katz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// lib/handlebars/browser-prefix.js
var Handlebars = {};
(function(Handlebars, undefined) {
;
// lib/handlebars/base.js
Handlebars.VERSION = "1.0.0";
Handlebars.COMPILER_REVISION = 4;
Handlebars.REVISION_CHANGES = {
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
2: '== 1.0.0-rc.3',
3: '== 1.0.0-rc.4',
4: '>= 1.0.0'
};
Handlebars.helpers = {};
Handlebars.partials = {};
var toString = Object.prototype.toString,
functionType = '[object Function]',
objectType = '[object Object]';
Handlebars.registerHelper = function(name, fn, inverse) {
if (toString.call(name) === objectType) {
if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); }
Handlebars.Utils.extend(this.helpers, name);
} else {
if (inverse) { fn.not = inverse; }
this.helpers[name] = fn;
}
};
Handlebars.registerPartial = function(name, str) {
if (toString.call(name) === objectType) {
Handlebars.Utils.extend(this.partials, name);
} else {
this.partials[name] = str;
}
};
Handlebars.registerHelper('helperMissing', function(arg) {
if(arguments.length === 2) {
return undefined;
} else {
throw new Error("Missing helper: '" + arg + "'");
}
});
Handlebars.registerHelper('blockHelperMissing', function(context, options) {
var inverse = options.inverse || function() {}, fn = options.fn;
var type = toString.call(context);
if(type === functionType) { context = context.call(this); }
if(context === true) {
return fn(this);
} else if(context === false || context == null) {
return inverse(this);
} else if(type === "[object Array]") {
if(context.length > 0) {
return Handlebars.helpers.each(context, options);
} else {
return inverse(this);
}
} else {
return fn(context);
}
});
Handlebars.K = function() {};
Handlebars.createFrame = Object.create || function(object) {
Handlebars.K.prototype = object;
var obj = new Handlebars.K();
Handlebars.K.prototype = null;
return obj;
};
Handlebars.logger = {
DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3,
methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'},
// can be overridden in the host environment
log: function(level, obj) {
if (Handlebars.logger.level <= level) {
var method = Handlebars.logger.methodMap[level];
if (typeof console !== 'undefined' && console[method]) {
console[method].call(console, obj);
}
}
}
};
Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); };
Handlebars.registerHelper('each', function(context, options) {
var fn = options.fn, inverse = options.inverse;
var i = 0, ret = "", data;
var type = toString.call(context);
if(type === functionType) { context = context.call(this); }
if (options.data) {
data = Handlebars.createFrame(options.data);
}
if(context && typeof context === 'object') {
if(context instanceof Array){
for(var j = context.length; i<j; i++) {
if (data) { data.index = i; }
ret = ret + fn(context[i], { data: data });
}
} else {
for(var key in context) {
if(context.hasOwnProperty(key)) {
if(data) { data.key = key; }
ret = ret + fn(context[key], {data: data});
i++;
}
}
}
}
if(i === 0){
ret = inverse(this);
}
return ret;
});
Handlebars.registerHelper('if', function(conditional, options) {
var type = toString.call(conditional);
if(type === functionType) { conditional = conditional.call(this); }
if(!conditional || Handlebars.Utils.isEmpty(conditional)) {
return options.inverse(this);
} else {
return options.fn(this);
}
});
Handlebars.registerHelper('unless', function(conditional, options) {
return Handlebars.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn});
});
Handlebars.registerHelper('with', function(context, options) {
var type = toString.call(context);
if(type === functionType) { context = context.call(this); }
if (!Handlebars.Utils.isEmpty(context)) return options.fn(context);
});
Handlebars.registerHelper('log', function(context, options) {
var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
Handlebars.log(level, context);
});
;
// lib/handlebars/utils.js
var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
Handlebars.Exception = function(message) {
var tmp = Error.prototype.constructor.apply(this, arguments);
// Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
for (var idx = 0; idx < errorProps.length; idx++) {
this[errorProps[idx]] = tmp[errorProps[idx]];
}
};
Handlebars.Exception.prototype = new Error();
// Build out our basic SafeString type
Handlebars.SafeString = function(string) {
this.string = string;
};
Handlebars.SafeString.prototype.toString = function() {
return this.string.toString();
};
var escape = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#x27;",
"`": "&#x60;"
};
var badChars = /[&<>"'`]/g;
var possible = /[&<>"'`]/;
var escapeChar = function(chr) {
return escape[chr] || "&amp;";
};
Handlebars.Utils = {
extend: function(obj, value) {
for(var key in value) {
if(value.hasOwnProperty(key)) {
obj[key] = value[key];
}
}
},
escapeExpression: function(string) {
// don't escape SafeStrings, since they're already safe
if (string instanceof Handlebars.SafeString) {
return string.toString();
} else if (string == null || string === false) {
return "";
}
// Force a string conversion as this will be done by the append regardless and
// the regex test will do this transparently behind the scenes, causing issues if
// an object's to string has escaped characters in it.
string = string.toString();
if(!possible.test(string)) { return string; }
return string.replace(badChars, escapeChar);
},
isEmpty: function(value) {
if (!value && value !== 0) {
return true;
} else if(toString.call(value) === "[object Array]" && value.length === 0) {
return true;
} else {
return false;
}
}
};
;
// lib/handlebars/runtime.js
Handlebars.VM = {
template: function(templateSpec) {
// Just add water
var container = {
escapeExpression: Handlebars.Utils.escapeExpression,
invokePartial: Handlebars.VM.invokePartial,
programs: [],
program: function(i, fn, data) {
var programWrapper = this.programs[i];
if(data) {
programWrapper = Handlebars.VM.program(i, fn, data);
} else if (!programWrapper) {
programWrapper = this.programs[i] = Handlebars.VM.program(i, fn);
}
return programWrapper;
},
merge: function(param, common) {
var ret = param || common;
if (param && common) {
ret = {};
Handlebars.Utils.extend(ret, common);
Handlebars.Utils.extend(ret, param);
}
return ret;
},
programWithDepth: Handlebars.VM.programWithDepth,
noop: Handlebars.VM.noop,
compilerInfo: null
};
return function(context, options) {
options = options || {};
var result = templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data);
var compilerInfo = container.compilerInfo || [],
compilerRevision = compilerInfo[0] || 1,
currentRevision = Handlebars.COMPILER_REVISION;
if (compilerRevision !== currentRevision) {
if (compilerRevision < currentRevision) {
var runtimeVersions = Handlebars.REVISION_CHANGES[currentRevision],
compilerVersions = Handlebars.REVISION_CHANGES[compilerRevision];
throw "Template was precompiled with an older version of Handlebars than the current runtime. "+
"Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").";
} else {
// Use the embedded version info since the runtime doesn't know about this revision yet
throw "Template was precompiled with a newer version of Handlebars than the current runtime. "+
"Please update your runtime to a newer version ("+compilerInfo[1]+").";
}
}
return result;
};
},
programWithDepth: function(i, fn, data /*, $depth */) {
var args = Array.prototype.slice.call(arguments, 3);
var program = function(context, options) {
options = options || {};
return fn.apply(this, [context, options.data || data].concat(args));
};
program.program = i;
program.depth = args.length;
return program;
},
program: function(i, fn, data) {
var program = function(context, options) {
options = options || {};
return fn(context, options.data || data);
};
program.program = i;
program.depth = 0;
return program;
},
noop: function() { return ""; },
invokePartial: function(partial, name, context, helpers, partials, data) {
var options = { helpers: helpers, partials: partials, data: data };
if(partial === undefined) {
throw new Handlebars.Exception("The partial " + name + " could not be found");
} else if(partial instanceof Function) {
return partial(context, options);
} else if (!Handlebars.compile) {
throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
} else {
partials[name] = Handlebars.compile(partial, {data: data !== undefined});
return partials[name](context, options);
}
}
};
Handlebars.template = Handlebars.VM.template;
;
// lib/handlebars/browser-suffix.js
})(Handlebars);
;

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More