This commit is contained in:
The Webmaker Team 2013-05-10 15:41:57 -04:00 коммит произвёл Jon Buckley
Коммит bacfdb514b
482 изменённых файлов: 44515 добавлений и 0 удалений

27
.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,27 @@
*.swp
*~
dist
.DS_Store
*.bak
*.webm
*.mov
*.avi
*.ogg
*.ogv
*.oga
*.mp4
*.wav
*.mp3
*.sqlite
node_modules
cornfield/view
css/butter.ui.css
css/transitions.css
css/embed-shell.css
cornfield/config/runtime.json
cornfield/config/versions.json
docs
css/embed.css
templates/assets/plugins/wikipedia/popcorn.wikipedia.css
templates/assets/css/jquery-ui/jquery.ui.butter.css
src/ui/webmakernav/webmakernav.css

6
.gitmodules поставляемый Normal file
Просмотреть файл

@ -0,0 +1,6 @@
[submodule "external/popcorn-js"]
path = external/popcorn-js
url = git://github.com/mozilla/popcorn-js.git
[submodule "external/html5-lint"]
path = external/html5-lint
url = git://github.com/mozilla/html5-lint.git

65
.jshintrc Normal file
Просмотреть файл

@ -0,0 +1,65 @@
{
// Environments
"browser" : true,
"devel": true,
"node" : true,
"nonstandard": true,
"predef": [
"Popcorn",
"requirejs",
"require",
"define"
],
// Enforcing
//"bitwise": true,
//"camelcase": true,
"curly": true,
"eqeqeq": true,
"forin": true,
"immed": true,
//"indent": 2,
"latedef": true,
"newcap": true,
"noarg": true,
"noempty": true,
"nonew": true,
//"plusplus": true,
//"quotmark": true,
//"regexp": true,
"undef": true,
"unused": true,
"strict": false,
"trailing": true,
//"maxparams": 4,
//"maxdepth": 3,
//"maxstatements": 4,
//"maxcomplexity": 5,
// Relaxing
//"asi": true,
//"boss": true,
//"debug": true,
//"eqnull": true,
"es5" : true,
//"esnext": true,
//"evil": true,
//"expr": true,
//"funcscope": true,
"globalstrict": true
//"iterator": true,
//"lastsemic": true,
//"laxbreak": true,
//"laxcomma": true,
//"loopfunc": true,
//"multistr": true,
//"onecase": true,
//"proto": true,
//"regexdash": true,
//"scripturl": true,
//"smarttabs": true,
//"shadow": true,
//"sub": true
//"supernew": true,
//"validthis": true,
}

9
.travis.yml Normal file
Просмотреть файл

@ -0,0 +1,9 @@
language: node_js
node_js:
- 0.8
before_script: node make check
notifications:
irc:
channels:
- "irc.mozilla.org#popcorn"
on_success: change

20
LICENSE Normal file
Просмотреть файл

@ -0,0 +1,20 @@
Copyright (c) 2011, 2012 Mozilla Foundation
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.

3
LICENSE_HEADER Normal file
Просмотреть файл

@ -0,0 +1,3 @@
/*! This Source Code Form is subject to the terms of the MIT license
* If a copy of the MIT license was not distributed with this file, you can
* obtain one at http://www.mozillapopcorn.org/butter-license.txt */

278
README.md Normal file
Просмотреть файл

@ -0,0 +1,278 @@
Butter
======
An SDK for authoring Popcorn projects.
Supported Platforms
-------------------
We're writing Butter so that it runs in modern, HTML5 compatible browsers. For version 1.0, we're targeting modern HTML5 desktop browsers. In the current version we are supporting:
### Desktop:
* Chrome stable
* Firefox stable
* Internet Explorer 9+
* Safari stable
Prerequisites
-------------
* node v0.8 or higher
* npm (comes with node v0.8 installer)
* mongodb v2.0.8 or higher
* A working build environment:
* Mac OS X - Xcode or Command Line Tools package
* Windows - Python 2.5+ and Visual Studio 2010 (C++ Express edition works fine)
* Linux - build-essential package on Debian/Ubuntu, or the equivalent for your distro
Environment Setup
-----------------
1. `git clone --recursive https://github.com/mozilla/butter.git`
2. `cd butter`
3. `npm install`
Running Butter in development mode
----------------------------------
1. Run `node make server`.
2. Navigate to [http://localhost:8888/](http://localhost:8888/) in your favourite browser.
If you want to change the bind IP or port check the Configuration section below.
Packaging and Distributing Butter
--------------------------------
Running `node make deploy` will compile all the necessary files into the `dist/` folder.
Run `NODE_ENV=production node app.js` in the `dist/cornfield` directory in order to run the server in production mode.
Cornfield
---------
Cornfield is PopcornMaker's back-end server system, designed to serve content to users, store their ongoing work, and publish what they've done.
### Storage
There are two types of storage Cornfield needs to run:
* A database: To store user project data, a database is required. Cornfield uses the `Sequelize` middleware, so multiple database options are available. You are responsible for setting up and maintaining a PostgreSQL or MySQL database enrivonment if you choose to use either, so make sure a user has access to a database called `popcorn` for cornfield to run correctly.
* A data-blob store: To store published projects, Cornfield can use the filesystem, or Amazon's S3. See the configuration options below for setting up this feature for your environment.
### Configuration
There are several configuration files in cornfield/config/ that control how cornfield works.
They are applied in order from most general to most specific to present one configuration
to the server:
1. default.json
2. _hostname_.json
3. _environment_.json
4. _hostname_-_environment_.json
5. runtime.json
_hostname_ and _environment_ are variable:
* _hostname_ - The hostname of the machine. Defaults to the output of `hostname` on the cli.
* _environment_ - The value of the `NODE_ENV` environment variable. Defaults to `development`.
To change the cornfield configuration for your deployment of Butter, it's best to create a
new file called _hostname_-_environment_.json that overrides the cornfield defaults.
#### Configuration Options
- `server` settings for the cornfield server
- `bindIP` the IP or hostname to use for the server (e.g., localhost).
- `bindPort` the Port number to use for the server (e.g., 8888). If using a port number lower than 1024, the server will have to be run as root.
- `logger` settings for server logging
- `format` the logging format to use. Possible values include: default, short, tiny, dev.
- `session` settings for user sessions
- `secret` the sessions secret (i.e., some long string)
- `duration` the session's duration (e.g., 2419200000)
- `staticMiddleware` settings for cornfield Connect middleware
- `maxAge` the max age of static assests
- `dirs` settings for various directories, paths, hostnames
- `wwwRoot` the server's WWW root directory (e.g., `../`)
- `templates` the location of templates (e.g., `../templates`)
- `appHostname` the hostname URL for the application, usually the same as `server.bindIP` and `server.bindPort` (e.g., `http://localhost:8888`)
- `embedHostname` <i>[optional]</i> the hostname URL where published embed documents are stored, if different from `dirs.appHostname` (e.g., `http://s3.amazonaws.com/your-bucket`)
- `templates` list of templates to serve. The format is as follows:
`<template-name>`: `{{templateBase}}<path/to/template/config.json>`. The `{{templateBase}}` string will be replaced by the value in `dirs.templates` (e.g., "basic": "{{templateBase}}basic/config.json")
- `exportedAssets` list of scripts to include in exported assets. These are things like popcorn.js or other scripts that your exported projects depend upon in order to run.
- `additionalStaticRoots` list of additional roots to use.
- `database` database configuration options
- `database` the database name. Used by mysql and postgresql
- `username` the username to use when connecting to the database. Used by mysql and postgresql
- `password` the password for the username. Used by mysql and postgresql
- `options` additional sequelize options. Please see the [sequelize manual](http://www.sequelizejs.com/#usage-options) for the complete listing.
- `dialect` the sql dialect of the database. Default is `mysql`, must be one of `mysql`, `sqlite`, or `postgresql`
- `storage` the storage engine for sqlite. Default is `:memory:`, an in-memory db, must be a string representing a file path or `:memory:`
- `logging` function to print sql queries to console. Default is `console.log`, must be a function or `false`
- `host` hostname of the mysql or postgresql server. Default is `localhost`
- `port` port of the mysql or postgresql server. Default is `3306`
- `pool` connection pooling options for mysql and postgresql. Default is none
- `maxConnections` - maximum number of connections open in the pool
- `maxIdleTime` - maximum time in seconds to leave an idle connection open in the pool
- `publishStore` a `fileStore` used to publish project HTML files (see `fileStore` below for details)
- `feedbackStore` a `fileStore` used to publish feedback from the user as JSON (see `fileStore` below for details)
- `crashStore` a `fileStore` used to publish crash reports from the user as JSON (see `fileStore` below for details)
The `fileStore` type is used to setup a backend for storing data:
- `type` the type of file store to use. Possible values include `local` (i.e., local file system) and `s3` (i.e., Amazon S3)
- `options` options for the file store, which depends on the type chosen.
- local options
- `root` the root directory under which all exported files are placed (e.g., `./view`)
- `namePrefix` <i>[optional]</i> the path prefix to add to any filenames passed to the local file store. For example, if using "v" all filenames will become "v/<key>"
- `nameSuffix` <i>[optional]</i> the filename suffix to use for all filenames (e.g., ".html")
- s3 options
- `key` the AWS S3 key to use for authentication
- `secret` the AWS S3 secret to use for authentication
- `bucket` the AWS S3 bucket name to use for storing key/value pairs
- `namePrefix` <i>[optional]</i> the prefix to add to any key names passed to the s3 file store. For example, if using "v" all keys will become "v/<key>"
- `nameSuffix` <i>[optional]</i> the suffix to add to any key names passed to the s3 file store. For example, if using ".json" all keys will end in ".json"
- `contentType` <i>[optional]</i> the mime type to use for data written to S3. If none given `text/plain` is used.
### Sample production config
`alice-production.json:`
This sample config uses a mix of the local file system as well as Amazon S3 for storage.
```javascript
{
"server" : {
"bindIP" : "0.0.0.0",
"bindPort" : "80"
},
"logger" : {
"format" : "default"
},
"session" : {
"secret": "1721f7a15316469fa4a9-5117d0d20e9f"
},
"staticMiddleware": {
"maxAge": "3600000"
},
"dirs": {
"appHostname": "http://example.org",
"embedHostname": "http://s3.amazonaws.com/my-bucket"
},
"publishStore": {
"type": "s3",
"options": {
"namePrefix": "v",
"key": "my-s3-key",
"secret": "my-s3-secret",
"contentType": "text/html"
}
},
"feedbackStore": {
"type": "local",
"options": {
"root": "./view",
"namePrefix": "feedback",
"nameSuffix": ".json"
}
},
"crashStore": {
"type": "local",
"options": {
"root": "./view",
"namePrefix": "crash",
"nameSuffix": ".json"
}
}
}
```
Testing
-------
Before contributing a new patch be sure to run the following:
* Run `node make check` to lint butter
* Run `node make server` and navigate to `http://localhost:8888/test` to run the browser tests
If you are contributing changes to cornfield, make sure you run the cornfield tests from the butter root directory this command:
```
npm test
```
Cornfield will attempt to run tests for using a mysql backend, but they will not run if mysql is not set up properly. To ensure that they run, use the `DB_HOST`, `DB_DATABASE`, `DB_USERNAME`, and `DB_PASSWORD` environment variables to tell the tests which host, database, username, and password to use respectively. If `DB_HOST` is left blank, `localhost` is assumed.
Getting Involved
----------------
* Chat with the Popcorn community on irc.mozilla.org in the [#popcorn](irc://irc.mozilla.org/popcorn) channel. The developers hang out here on a daily basis.
* We also have a [mailing list](https://mail.mozilla.org/listinfo/community-popcorn) that you can subscribe to.
* File bugs and feature requests on our [issue tracker](https://webmademovies.lighthouseapp.com/projects/65733-butter/).
* The latest code can be found on our [Github repository](https://github.com/mozilla/butter/).
* If you'd like to contribute code, file a ticket on our issue tracker, and link to it from your Github pull request.
Contributing Design
-------------------
### Where to find/drop files
Our design files are organized on dropbox, at this link:
https://www.dropbox.com/sh/7vm2rvw3axvkp0k/Tk4MKH4nZe
You can ask Kate ( k88hudson on IRC or Twitter ) to be added as a collaborator if you want to drop your files in here.
### File System
* References: screen shots and other reference work from the webmaker project or HTML5 ecosystem
* Wireframes: documents about the functionality/interaction/description of features
* Visual Comps: UI mock-ups and style guide
### Working with lighthouse
When someone assigns you a ticket, it will show up in your lighthouse queue with a status of `ui-comps-requested` or `assigned`.
If you want someone to review or give feedback on your work, the best thing to do is:
* Put a link to your files in the ticket ( on our dropbox or somewhere externally )
* Change the status of the ticket to `peer-review-requested` or `feedback-requested`, and choose a member of the team to be responsible.
Contributing Code
-----------------
### Working with Lighthouse
All of our code changes to Butter are documented in tickets, and go through two levels of peer-review. If you are interested in working on a ticket chose one from the [list of open tickets](https://webmademovies.lighthouseapp.com/projects/65733-popcorn-maker/milestones/current), ping any of the developers on IRC, and they will assign it to you.
### Working with branches and making a pull-request
* Each ticket is a separate branch, usually named after the ticket number. IE `t1234`
* All commit messages must include the ticket number. IE `Bug 1234 - Fixed a thing` or `[t1234] Fixed a thing`
* Before submitting a pull request, make sure you have rebased against the latest revision of master.
### Getting review
All code changes in butter have to go through two levels of peer review. This improves the integrity of our code, and everyone goes through the process, from casual contributors to our most senior developers.
1. After you have made a pull-request, post the link (e.g. https://github.com/mozilla/butter/pull/662) in the corresponding ticket.
2. Set the status of the ticket to `peer-review requested`, and choose someone to review your code. If you're not sure who should review your code, ask in #popcorn on IRC.
3. After you get a review, you will see (1) comments in the diff in your pull-request and (2) comments in lighthouse. Keep an eye on the ticket to see when your review is done.
4. Complete the changes that were requested, or if you disagree or need more information, comment in the pull-request or lighthouse. Commit and push up.
5. After your review passes, your reviewer will pass the ticket on to `super-review-requested`. You will likely have more changes after that review.
6. After your final round of changes, your ticket will change to `review-looks-good`. Hurrah! Ask someone to help you merge your code into master.
### Landing (for those with commit rights to github.com/mozilla/butter)
Once code has been reviewed (PR+ and SR+), and you want to land it, you need to follow our rebase strategy. Don't use Github's "Merge pull request". Instead, please do the following:
1. `git checkout master`
2. `git pull mozilla master` This assumes your remote to Mozilla's butter is called mozilla
3. `git checkout mybranch`
4. `git rebase -i master` Squash your commits here, where it makes sense
5. Make sure everything still works in Butter at this point
6. `git push origin mybranch --force`
7. `git checkout master`
8. `git merge mybranch --ff-only` No need for a commit message, as we're not doing a merge commit
9. `git push mozilla master`
10. Paste URL to commits in ticket, and mark it as "Staged"
We use this landing strategy in order to make backing-out failed commits easier.

253
changelog Normal file
Просмотреть файл

@ -0,0 +1,253 @@
0.3 Release, "Breakfast Club" - April 10, 2012
609 Add configuration that includes all plugins
604 [Meta] Install 0.3 on mozillapopcorn.org/edge
634 Need a way to set options on Popcorn
639 Create music video template
653 Saving doesn't work twice.
652 Simple URL replacement in Cornfield
650 Fix publish path
648 Dialogs need relative paths
647 Fix media element loading edgecase
646 Give editors own comm lib
643 Make cornfield's root file-serve path configurable
637 Fix positioning of butter-media-highlight
622 cornfield server should run by default, not simple node server
641 Editor module should auto-add editors from config
642 Update Popcorn submodule
596 replace Layla as default video
631 In-editor update of trackevent does not update the corresponding view
621 Server needs mongoose to run
626 Firefox textbox elements don't have proper height in dialogs
557 Fix import in test/selenium/testcases_tracks.py
616 Fix UI/Asset directory structure
598 Ability to turn Logger off
431 Common CSS for Dialogs
615 Remove cornfield/publish.js from repo
601 Test template exclude browser ID on publish
610 Fix inline-block inconsistency with Google Chrome on header buttons
611 Fix final lint errors
597 Published projects don't work
594 Bring cornfield tests back up to par
603 Turn off Media UI when butter.ui is disabled
600 src/core/trackevents.js is accessing butter when it doesn't exist
602 Make export button yellow like the others
320 Ability to import/export popcorn code per track
605 Export buttons aren't enabled
330 Implement publish project UI
466 Save indicator
563 Remove popcorn_outer.png
536 UI for specifying/changing timeline media
534 Name your project
535 Implement load project uI
537 Make Popcorn Maker header functional
585 Header behavior tweaks
584 After importing a project, trackevents aren't clickable.
402 Plugin tray UI needs to be brought up to par
529 Scrubber position resets to 0 if you attempt to drag it from the far right
458 Protect from invalid times in trackevents
436 have butter.io.cornfield write out a script tag to load BrowserID if it's not loaded
358 Add tooltip to unreadable trackevent title
579 Events are added 1 frame too early
569 PopcornWrapper breaks tests
583 Fix readme to indicate that --recursive clone is needed
364 Change HTML Generation to use Double quotes rather than single
242 generatePopcornString needs to pass options to Popcorn object
543 "When the tray shows anything other than the timeline, ""Add Popcorn"" should be ""Done""
581 have we removed the ability to delete a track?
365 HTML Generation Moves Video Containing Element to Different Spot on Page
544 Add Track button
510 Remove reference to secretrobotron's github account
462 Phantom tracks when moving an event over a droppable target
276 in/out should indicate what they are measuring
539 Vertical scroll in timeline area with mousewheel
524 Refactor Media/Track/TrackEvent structure to be more light-weight
457 Dynamically load popcorn players on-demand
252 Don't let a user commit a blank field for timeline media change
494 Pasting video URL should not fail when no HTTP
437 Port all keyboard shortcuts from Popcorn Maker .1 into Butter
467 Elegant load for tray
482 clearProject should probably remove all tracks + trackEvents as well
495 At the end of the timeline, play should restart video
525 "Amalgamate ""cancel"" and ""ok"" buttons in default editor"
530 DragNDrop on Media elements is broken
460 "Create and implement UI for ""Add Popcorn"" button"
552 importButter not recognizing event types/info
489 default editor breaks with youtube player
421 Fix linting errors
329 Up/Down arrows should move to next/prev event
514 resizing track events does not update start or end time
432 Track events can't lose focus
243 Create a rudimentary testing process
443 Butter should always reliably load
538 Confirm that single press of esc key dismisses event editors
271 Weird design bug in chrome
498 Tests fail first pass through on Chrome
441 Scubber Line Behaviour erratic.
553 `node make` doesn't work
500 Start using selenium
231 Replace Makefiles with shelljs
492 Timebar canvas is too large
301 Use the require order plugin
527 Remove plugin addition from template.js
485 trackevent timing buggy
526 Tests broken as a result of merge
506 Remove jquery and jquery-ui
218 bodyReady crashes on SVG elements
502 util/time doesn't have license header
516 Fix tests after DragNDrop broke them
481 uncaught typeError in Chrome only
461 Move editor dialogues to tray, create quick edit flow
505 Replace $sortable
94 Dragging the scrubber/a track event along the timeline does not force the timeline to scroll
490 Zooming doesn't move the scrubber
477 Dragging Footnote in template.html to the edge of track 2 causes it to disappear
455 "Stop target from ""blinking"" if mouse is dragging something"
342 We should roll out our own drag & drop
404 Clean up start-up and organization of Butter
262 Object has no method 'slider' issue still around
314 Changes to track list UI
133 "Make ""standard target"" default target"
493 If a track event is double clicked and the editor is already opened, focus the editor.
474 Update Popcorn.js to v1.2
408 Attempt to clean up timeline module
487 "Use ""on"" instead of ""listen"" for Popcorn interface"
480 Get rid of Butter instancing
488 Remove CSS checking in plugin module
348 Fix module order dependency
429 Style Guide for Source
352 Performance when zooming/moving around on timeline is slow with numerous trackevents
132 Footnote data stays in workspace preview too long
130 """Subtitle-container"" is confusing"
45 resizing page UI problem
131 Export>Preview not working
107 google maps exception in butter
105 adding tracks doesn't fire timeupdate on youtube
121 Image plugin broken on experimental branch
134 Youtube doesn't export/preview
104 Clicking New Project should re-open the start dialog
102 Hide youtube's native flash controls and annotations by default
96 Scrubber doesn't reset when time is reset
139 You should be able to set the project title when you create it
106 Youtube seek bar fires play
127 Selection needs to be turned off for Chrome
140 Track events broken in chrome
64 Reassign track representation for manual track -> DOM assignment
112 image and webpage refresh issue
72 Show one object in preview window
71 DOM Object database for tracks
70 Enable the user of arbitrary player plugins
63 Implement timeline using CSS
118 Review - Import, Export, Load, Save, CSS, Player Plugins, DOM Database, Preview Isolation
109 Wordriver plug-in start doesn't fire
69 Click & Drag plugins to create events
67 Project Details
85 what to do on a refresh
84 Remove butter.html
97 $popcorn is undefined when resizing window
92 Update AUTHORS
90 Changing url for media needs to actually change the media
66 Help Button
88 Video continues to play when red timeline bar is held
68 Remove accordion
17 Play/Pause should be controllable with the space bar.
35 Focus on one command at a time in preview pane
18 Better handles on the timeline to grab each instance of a plugin
49 UI Revamp
36 Youtube support in Butter?
22 Multiple timelines per plugin
0.2 Release, "Ghostbusters" - March 1, 2012
337 Ability to login/save/load from a cornfield server
390 Setting trackEvent out time > video duration causes issues
268 Fade in of modal dialogues far too slow
332 Ensure all player types work
427 Better UX for highlighting elements
446 Warning when closing a window dialog manually
428 Change per-file licenses
395 Butter readme should include info on submodules
454 Flash target on click, not hover
438 Tray UI should be no bigger than is necessary to accomodate 4 tracks
256 Timecodes should round to 3 decimal places
449 <style> needs to be removed for getHTML
444 Rogue Track Events appear after moving a track event
452 Template has unneeded html elements
375 Push butter up to mozilla's github account
451 Hovering over new track event does not blink it's default target
391 Creating a new trackEvent should provide a default target
448 Target should blink once when a track event is created
298 Have templates show their ID in the editor view
447 unit tests have errors
401 Timeline tracks allow track events to be position at -0.01 seconds
211 previewer should provide a getHTML function
440 Scrolling Horizontally does not move the vertical scrollbar
277 We lost different colours for plugins
388 Track Editor UI
416 Red "X" for deleting tracks
248 Need to close editor windows
420 Dialog module cleanup
413 Clicking on handle makes it jump forward
425 Polling scrubber spins too often at start
319 Ability to have a UI cue for currently edited element
423 Shouldn't be able to re-order tracks from within trackliner.
424 Zoombar is broken (exception)
280 "Media element" is confusing name for a target
392 importProject is slightly broken
418 Get rid of old and busted scraper code
366 Move code for making targets droppable into core/target
410 Change all occurrences of const to var
389 Calling preview explicitly shouldn't be necessary
351 Timeline scrolling should be triggered by mouse wheel
384 Test template file should not create default trackEvents or too many tracks
409 Dragging scrubber is off by some factor of the timelines duration
255 When collapsing timeline, playhead and play button should remain
411 spacebar for play/pause missing from new tray
405 Remove css that causes timeline media div to grow/shrink
237 Ability to seek
289 Mute button doesn't mute.
372 Default editor needs a better home
399 Track Event Selection
381 Add Tracks UI segment
368 Target object's id should reflect the element's id
397 Add play/pause and mute buttons.
335 Click on timeline to move playhead
254 Much confusion re: "Apply" and "OK"
400 Moving the playhead/scrubber while playing should mimic HTML5 behaviour
382 Right-click on slider doesn't let go on mouse up
186 Event editor should update when track events are moved
370 Click on scrollbars to move timeline
386 I cloned the latest Butter repo - no events appear on timeline
234 EventEditor chops "http:" off of webpage plugin src.
266 plugins should default to a target, not media element
305 Timeline changes from editors are slow to update in Chrome
294 Error checking only works when creating new project
275 Hard to use apostrophes
283 In edit track dialogue, buttons are weird
270 edge grabbers for events should be larger and more obvious
349 Extra default editor
257 Add .DS_Store to .gitignore
369 Draggable issues in Chrome
377 Fix Failing Core Tests
376 Upgrade libs
367 getTarget should be changed to match getMedia
378 Timeline zooming still causing resize issue in Chrome
239 Escape key should dismiss dialog windows
334 Add helper to event draggables
374 Dialog module has some poor naming conventions
311 Make zoom match timeline style
26 Timeline should be zoomable
86 hitting enter to accept plugin/track changes
350 core/media.js should access Popcorn directly
192 Editor dialogs should be non-modal
331 Crash on loading editor for certain plugins (base.manifest)
345 Get playhead to follow currentTime of video
346 Editors to use dialog module (and work at all)
236 Fix Failing Core Tests
264 Ugly scrollbars on timeline
333 Target-droppable events
336 Currentime looping causes video jitter
235 We need better names for track.js/trackevent.js
282 Editor dialogues should be draggable
263 Passing no Arguments into butter.getMedia throws an Exception
216 When butter finds data-butter="media", should check if target is a <video>

333
cornfield/app.js Normal file
Просмотреть файл

@ -0,0 +1,333 @@
/*jshint eqeqeq:false */
console.log( __dirname );
// Given foo/ return foo
function stripSlash( path ) {
return path.replace( /\/$/, '' );
}
var express = require('express'),
fs = require('fs'),
path = require('path'),
jade = require('jade'),
app = express.createServer(),
clientSessions = require('client-sessions'),
lessMiddleware = require('less-middleware'),
CONFIG = require('config'),
User = require( './lib/user' )( CONFIG.database ),
filter = require( './lib/filter' )( User.isDBOnline ),
sanitizer = require( './lib/sanitizer' ),
FileStore = require('./lib/file-store.js'),
stores = {},
TEMPLATES_DIR = CONFIG.dirs.templates,
APP_HOSTNAME = stripSlash( CONFIG.dirs.appHostname ),
// If a separate hostname is given for embed, use it, otherwise use app's hostname
EMBED_HOSTNAME = CONFIG.dirs.embedHostname ? stripSlash( CONFIG.dirs.embedHostname ) : APP_HOSTNAME,
EMBED_SUFFIX = '_',
WWW_ROOT = path.resolve( CONFIG.dirs.wwwRoot || path.join( __dirname, ".." ) ),
VALID_TEMPLATES = CONFIG.templates,
EXPORT_ASSETS = CONFIG.exportAssets;
var templateConfigs = {};
function readTemplateConfig( templateName, templatedPath ) {
var configPath = templatedPath.replace( '{{templateBase}}', TEMPLATES_DIR + '/' );
fs.readFile( configPath, 'utf8', function( err, conf ) {
var configPathBase = configPath.substring( 0, configPath.lastIndexOf( '/' ) );
conf = JSON.parse( conf );
conf.template = configPathBase + '/' + conf.template;
templateConfigs[ templateName ] = conf;
});
}
// parse configs ahead of any action that has to happen with them
for ( var templateName in VALID_TEMPLATES ) {
if ( VALID_TEMPLATES.hasOwnProperty( templateName ) ) {
readTemplateConfig( templateName, VALID_TEMPLATES[ templateName ] );
}
}
console.log( "Templates Dir:", TEMPLATES_DIR );
app.configure( 'development', function() {
app.use( lessMiddleware( WWW_ROOT ));
CONFIG.additionalStaticRoots.forEach( function( dir ) {
app.use( express.static( dir ) );
});
});
function setupStore( config ) {
var store = FileStore.create( config.type, config.options );
if( store.requiresFileSystem ) {
app.use( express.static( store.root, JSON.parse( JSON.stringify( CONFIG.staticMiddleware ) ) ) );
}
return store;
}
app.configure( function() {
app.use( express.logger( CONFIG.logger ) )
.use( express.static( WWW_ROOT, JSON.parse( JSON.stringify( CONFIG.staticMiddleware ) ) ) )
.use( express.bodyParser() )
.use( clientSessions( CONFIG.session ) )
.use( express.csrf() )
/* Show Zeus who's boss
* This only affects requests under /api and /persona, not static files
* because the static file writes the response header before we hit this middleware
*/
.use( function( req, res, next ) {
res.header( 'Cache-Control', 'no-store' );
return next();
})
.set('view options', {layout: false});
// File Store types and options come from JSON config file.
stores.publish = setupStore( CONFIG.publishStore );
stores.crash = setupStore( CONFIG.crashStore );
stores.feedback = setupStore( CONFIG.feedbackStore );
});
require( 'express-persona' )( app, {
audience: CONFIG.dirs.appHostname
});
require('./routes')( app, User, filter, sanitizer, stores, EMBED_SUFFIX );
function writeEmbedShell( path, url, data, callback ) {
if( !writeEmbedShell.templateFn ) {
writeEmbedShell.templateFn = jade.compile( fs.readFileSync( 'views/embed-shell.jade', 'utf8' ),
{ filename: 'embed-shell.jade', pretty: true } );
}
stores.publish.write( path, writeEmbedShell.templateFn( data ), callback );
}
function writeEmbed( path, url, data, callback ) {
if( !writeEmbed.templateFn ) {
writeEmbed.templateFn = jade.compile( fs.readFileSync( 'views/embed.jade', 'utf8' ),
{ filename: 'embed.jade', pretty: true } );
}
stores.publish.write( path, writeEmbed.templateFn( data ), callback );
}
app.post( '/api/publish/:id',
filter.isLoggedIn, filter.isStorageAvailable, filter.isXHR,
function publishRoute( req, res ) {
var email = req.session.email,
id = parseInt( req.params.id, 10 );
if ( isNaN( id ) ) {
res.json( { error: "ID was not a number" }, 500 );
return;
}
User.findProject( email, id, function( err, project ) {
if ( err ) {
res.json( { error: err }, 500);
return;
}
if ( !project ) {
res.json( { error: 'project not found' }, 404);
return;
}
var i = 0,
template = project.template;
if( !( template && VALID_TEMPLATES[ template ] ) ) {
res.json({ error: 'template not found' }, 500);
return;
}
var projectData = JSON.parse( project.data, sanitizer.escapeHTMLinJSON ),
templateConfig = templateConfigs[ template ],
templateFile = templateConfig.template,
baseHref;
fs.readFile( templateFile, 'utf8', function( err, data ){
if ( err ) {
res.json( { error: 'error reading template file' }, 500 );
return;
}
var headEndTagIndex,
bodyEndTagIndex,
externalAssetsString = '',
popcornString = '',
currentMedia,
currentTrack,
currentTrackEvent,
mediaPopcornOptions,
templateURL,
baseString,
headStartTagIndex,
templateScripts,
startString,
numSources,
j, k, len;
templateURL = templateFile.substring( templateFile.indexOf( '/templates' ), templateFile.lastIndexOf( '/' ) );
baseHref = APP_HOSTNAME + templateURL + "/";
baseString = '\n <base href="' + baseHref + '"/>';
// look for script tags with data-butter-exclude in particular (e.g. butter's js script)
data = data.replace( /\s*<script[\.\/='":_\-\w\s]*data-butter-exclude[\.\/='":_\-\w\s]*><\/script>/g, '' );
// Adding to cut out the actual head tag
headStartTagIndex = data.indexOf( '<head>' ) + 6;
headEndTagIndex = data.indexOf( '</head>' );
bodyEndTagIndex = data.indexOf( '</body>' );
templateScripts = data.substring( headStartTagIndex, headEndTagIndex );
startString = data.substring( 0, headStartTagIndex );
externalAssetsString += '\n';
for ( i = 0; i < EXPORT_ASSETS.length; ++i ) {
externalAssetsString += ' <script src="' + path.relative( path.dirname( templateFile ), EXPORT_ASSETS[ i ] ) + '"></script>\n';
}
// If the template has custom plugins defined in it's config, add them to our exported page
if ( templateConfig.plugin && templateConfig.plugin.plugins ) {
var plugins = templateConfig.plugin.plugins;
for ( i = 0, len = plugins.length; i < len; i++ ) {
externalAssetsString += '\n <script src="' + APP_HOSTNAME + '/' + plugins[ i ].path.split( '{{baseDir}}' ).pop() + '"></script>';
}
externalAssetsString += '\n';
}
popcornString += '<script>';
for ( i = 0; i < projectData.media.length; ++i ) {
var mediaUrls,
mediaUrlsString = '[ "';
currentMedia = projectData.media[ i ];
// We expect a string (one url) or an array of url strings.
// Turn a single url into an array of 1 string.
mediaUrls = typeof currentMedia.url === "string" ? [ currentMedia.url ] : currentMedia.url;
mediaPopcornOptions = currentMedia.popcornOptions || {};
// Force the Popcorn instance we generate to have an ID we can query.
mediaPopcornOptions.id = "Butter-Generated";
numSources = mediaUrls.length;
for ( k = 0; k < numSources - 1; k++ ) {
mediaUrlsString += mediaUrls[ k ] + '" , "';
}
mediaUrlsString += mediaUrls[ numSources - 1 ] + '" ]';
popcornString += '\n(function(){';
popcornString += '\nvar popcorn = Popcorn.smart("#' + currentMedia.target + '", ' +
mediaUrlsString + ', ' + JSON.stringify( mediaPopcornOptions ) + ');';
for ( j = 0; j < currentMedia.tracks.length; ++ j ) {
currentTrack = currentMedia.tracks[ j ];
for ( k = 0; k < currentTrack.trackEvents.length; ++k ) {
currentTrackEvent = currentTrack.trackEvents[ k ];
popcornString += '\npopcorn.' + currentTrackEvent.type + '(';
popcornString += JSON.stringify( currentTrackEvent.popcornOptions, null, 2 );
popcornString += ');';
}
}
popcornString += '}());\n';
}
popcornString += '</script>\n';
data = startString + baseString + templateScripts + externalAssetsString +
data.substring( headEndTagIndex, bodyEndTagIndex ) +
popcornString + data.substring( bodyEndTagIndex );
// Convert 1234567890 => "kf12oi"
var idBase36 = id.toString( 36 ),
publishUrl = EMBED_HOSTNAME + '/' + stores.publish.expand( idBase36 ),
iframeUrl = EMBED_HOSTNAME + '/' + stores.publish.expand( idBase36 + EMBED_SUFFIX );
function finished( err ) {
if ( err ) {
res.json({ error: 'internal server error' }, 500);
} else {
res.json({ error: 'okay', publishUrl: publishUrl, iframeUrl: iframeUrl });
}
}
function publishEmbedShell() {
// Write out embed shell HTML
writeEmbedShell( idBase36, publishUrl,
{
author: project.author,
projectName: project.name,
embedShellSrc: publishUrl,
embedSrc: iframeUrl,
baseHref: APP_HOSTNAME
},
finished );
}
// This is a query string-only URL because of the <base> tag
var remixUrl = "?savedDataUrl=/api/remix/" + project.id,
mediaUrl = projectData.media[ 0 ].url,
attribURL = Array.isArray( mediaUrl ) ? mediaUrl[ 0 ] : mediaUrl;
writeEmbed( idBase36 + EMBED_SUFFIX, iframeUrl,
{
id: id,
author: project.author,
title: project.name,
mediaSrc: attribURL,
embedShellSrc: publishUrl,
baseHref: baseHref,
remixUrl: remixUrl,
templateScripts: templateScripts,
externalAssets: externalAssetsString,
// XXX: need a better way to wrap function, DOM needs to be ready
popcorn: popcornString.replace( /^\(function\(\)\{/m, "Popcorn( function(){" )
.replace( /\}\(\)\);$/m, "});" )
},
publishEmbedShell );
});
});
});
app.get( '/dashboard', filter.isStorageAvailable, function( req, res ) {
var email = req.session.email;
if ( !email ) {
res.render( 'dashboard-unauthorized.jade' );
return;
}
User.findAllProjects( email, function( err, docs ) {
var userProjects = [];
docs.forEach( function( project ) {
if ( project.template && VALID_TEMPLATES[ project.template ] ) {
userProjects.push({
// make sure _id is a string. saw some strange double-quotes on output otherwise
_id: String(project.id),
name: sanitizer.escapeHTML( project.name ),
template: project.template,
href: path.relative( WWW_ROOT, templateConfigs[ project.template ].template ) +
"?savedDataUrl=/api/project/" + project.id
});
}
});
res.render( 'dashboard.jade', {
user: {
csrf: req.session._csrf,
email: email
},
projects: userProjects
});
});
});
var port = process.env.PORT || CONFIG.server.bindPort;
app.listen(port, CONFIG.server.bindIP, function() {
var addy = app.address();
console.log('HTTP Server started on http://' + CONFIG.server.bindIP + ':' + addy.port);
console.log('Press Ctrl+C to stop');
});

Просмотреть файл

@ -0,0 +1,81 @@
{
"server" : {
"bindIP" : "localhost",
"bindPort" : "8888"
},
"logger" : {
"format" : "dev"
},
"session" : {
"secret": "thisisareallyreallylongsecrettoencryptcookies",
"duration": 2419200000
},
"staticMiddleware": {
"maxAge": "0"
},
"dirs": {
"wwwRoot": "../",
"templates": "../templates",
"appHostname": "http://localhost:8888"
},
"publishStore": {
"type": "local",
"options": {
"root": "./view",
"namePrefix": "v",
"nameSuffix": ".html"
}
},
"feedbackStore": {
"type": "local",
"options": {
"root": "./view",
"namePrefix": "feedback",
"nameSuffix": ".json"
}
},
"crashStore": {
"type": "local",
"options": {
"root": "./view",
"namePrefix": "crash",
"nameSuffix": ".json"
}
},
"templates": {
"basic": "{{templateBase}}basic/config.json"
},
"exportAssets": [
"../external/popcorn-js/ie8/popcorn.ie8.js",
"../external/popcorn-js/popcorn.js",
"../external/popcorn-js/wrappers/common/popcorn._MediaElementProto.js",
"../external/popcorn-js/wrappers/html5/popcorn.HTMLMediaElement.js",
"../external/popcorn-js/wrappers/null/popcorn.HTMLNullVideoElement.js",
"../external/popcorn-js/wrappers/soundcloud/popcorn.HTMLSoundCloudAudioElement.js",
"../external/popcorn-js/wrappers/vimeo/popcorn.HTMLVimeoVideoElement.js",
"../external/popcorn-js/wrappers/youtube/popcorn.HTMLYouTubeVideoElement.js",
"../external/popcorn-js/modules/player/popcorn.player.js",
"../external/popcorn-js/players/youtube/popcorn.youtube.js",
"../external/popcorn-js/players/vimeo/popcorn.vimeo.js",
"../external/popcorn-js/players/soundcloud/popcorn.soundcloud.js",
"../templates/assets/plugins/text/popcorn.text.js",
"../templates/assets/plugins/popup/popcorn.popup.js",
"../templates/assets/plugins/googlemap/popcorn.googlemap.js",
"../templates/assets/plugins/twitter/popcorn.twitter.js",
"../templates/assets/plugins/image/popcorn.image.js",
"../templates/assets/plugins/loopPlugin/popcorn.loopPlugin.js",
"../templates/assets/plugins/skip/popcorn.skip.js",
"../templates/assets/plugins/pausePlugin/popcorn.pausePlugin.js",
"../templates/assets/plugins/wikipedia/popcorn.wikipedia.js"
],
"database": {
"database": "popcorn",
"username": null,
"password": null,
"options": {
"logging": false,
"dialect": "sqlite",
"storage": "popcorn.sqlite"
}
}
}

Просмотреть файл

@ -0,0 +1,5 @@
{
"additionalStaticRoots": [
"../public"
]
}

Просмотреть файл

@ -0,0 +1,9 @@
{
"dirs": {
"wwwRoot": "../public",
"templates": "../public/templates"
},
"exportAssets": [
"../external/popcorn-js/popcorn.js"
]
}

159
cornfield/lib/file-store.js Normal file
Просмотреть файл

@ -0,0 +1,159 @@
var fs = require( 'fs' ),
knox = require( 'knox' ),
Path = require( 'path' );
// Make sure the dir exists, and its parent. Create if not.
function ensurePathExistsSync( path ) {
var parent = Path.dirname( path );
// Build paths above too, if not present. Check for relative or absolute paths
if ( parent !== "." && parent !== "/" ) {
ensurePathExistsSync( parent );
}
if ( !fs.existsSync( path ) ) {
fs.mkdirSync( path );
}
}
var BaseFileStore = {
// By default, we use no prefix/suffix unless user-supplied.
namePrefix: '',
nameSuffix: '',
// If we have a root directory, we need to work with the FS
get requiresFileSystem() {
return !!this.root;
},
// Expand a name to include the namePrefix
expand: function( name ) {
// Make sure name is a string, since non-strings are ignored by join
name = name + '';
return Path.join( this.namePrefix, name ) + this.nameSuffix;
}
};
/**
* LocalFileStore - stores data in local file system using root dir
*/
function LocalFileStore( options ) {
// A root path for all filenames
if( !options.root ) {
throw 'LocalFileStore Error: expected root';
}
this.root = options.root;
// An optional prefix for all filenames. Will be joined with /
// For example: filename=foo namePrefix=v becomes v/foo
if( options.namePrefix ) {
this.namePrefix = options.namePrefix;
}
ensurePathExistsSync( Path.join( this.root, this.namePrefix ) );
// An optional suffix for all filenames. Will be joined directly
// For example: filename=foo nameSuffix=.html becomes foo.html
if( options.nameSuffix ) {
this.nameSuffix = options.nameSuffix;
}
}
LocalFileStore.prototype = Object.create( BaseFileStore );
LocalFileStore.prototype.write = function( path, data, callback ) {
path = Path.join( this.root, this.expand( path ) );
ensurePathExistsSync( Path.dirname( path ) );
fs.writeFile( path, data, function( err ) {
if (err) {
callback( err );
} else {
callback();
}
});
};
LocalFileStore.prototype.remove = function( path, callback ) {
path = Path.join( this.root, this.expand( path ) );
fs.unlink( path, callback );
};
/**
* S3FileStore - store data using Amazon S3
*/
function S3FileStore( options ) {
// Amazon S3 credentials for accessing a bucket
this.client = knox.createClient({
key: options.key,
secret: options.secret,
bucket: options.bucket
});
// An optional prefix for all keys. Will be joined with /
// For example: key=foo namePrefix=v becomes v/foo
if( options.namePrefix ) {
this.namePrefix = options.namePrefix;
}
// An optional suffix for all filenames. Will be joined directly
// For example: filename=foo nameSuffix=.html becomes foo.html
if( options.nameSuffix ) {
this.nameSuffix = options.nameSuffix;
}
// An optional mime type for the files to be written. Defaults to
// text/plain if none given.
this.contentType = options.contentType || 'text/plain';
}
S3FileStore.prototype = Object.create( BaseFileStore );
S3FileStore.prototype.write = function( key, data, callback ) {
this.client.put( this.expand( key ), {
'x-amz-acl': 'public-read',
'Content-Length': data.length,
'Content-Type': this.contentType
})
.on( 'response', function( res ) {
if( res.statusCode === 200 ) {
callback();
} else {
callback( res.statusCode );
}
})
.end( data );
};
S3FileStore.prototype.remove = function( key, callback ) {
this.client.del( this.expand( key ) )
.on( 'response', function( res ) {
if( res.statusCode === 200 ) {
callback();
} else {
callback( res.statusCode );
}
})
.end();
};
var FileStores = {
'S3': S3FileStore,
'LOCAL': LocalFileStore
};
module.exports = {
// Pass a type (one of 'local' or 's3') to build a FileStore object.
create: function( type, options ) {
options = options || {};
var Constructor = FileStores[ type.toUpperCase() ];
if( !Constructor ) {
throw 'Unknown FileStore type: ' + type;
}
return new Constructor( options );
}
};

38
cornfield/lib/filter.js Normal file
Просмотреть файл

@ -0,0 +1,38 @@
'use strict';
var dbCheckFn, filters;
filters = {
isLoggedIn: function( req, res, next ) {
if ( req.session.email ) {
next();
} else {
res.json({
error: 'unauthorized'
}, 403 );
}
},
isStorageAvailable: function( req, res, next ) {
if ( dbCheckFn() ) {
next();
} else {
res.json({
error: 'storage service is not running'
}, 500 );
}
},
isXHR: function( req, res, next ) {
if ( req.header( 'X-Requested-With' ) === 'XMLHttpRequest' ) {
next();
} else {
res.json({
error: 'X-Requested-With is not set to XMLHttpRequest'
}, 412 );
}
}
};
module.exports = function ctor( fn ) {
dbCheckFn = fn;
return filters;
};

Просмотреть файл

@ -0,0 +1,55 @@
"use strict";
module.exports = function(sequelize, DataTypes) {
return sequelize.define( "Project", {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
data: {
type: DataTypes.TEXT,
allowNull: false
},
email: {
type: DataTypes.STRING,
allowNull: false,
validate: {
isEmail: true
}
},
name: {
type: DataTypes.STRING,
allowNull: false,
validate: {
isAlphanumeric: true
}
},
author: {
type: DataTypes.STRING,
allowNull: false
},
template: {
type: DataTypes.STRING,
allowNull: false,
validate: {
isAlphanumeric: true
}
},
// The original version of Butter that was used when project
// was first created. This will usually be the same as
// latestButterVersion, but could be different (i.e., a newer
// version of Butter was used to edit a project), and gives
// some insight into what was used originally, in case of
// breaking changes.
originalButterVersion: {
type: DataTypes.STRING,
allowNull: false
},
// The latest version of Butter that was used to save the project.
latestButterVersion: {
type: DataTypes.STRING,
allowNull: false
}
});
};

Просмотреть файл

@ -0,0 +1,20 @@
var sanitizer = {
// From https://github.com/mozilla/zamboni/blob/a4b32033/media/js/mkt/utils.js#L15
escapeHTML: function escapeHTML( s ) {
if ( s && typeof s === "string" ) {
s = s.replace( /&/g, '&amp;' ).replace( />/g, '&gt;' ).replace( /</g, '&lt;' )
.replace( /'/g, '&#39;' ).replace( /"/g, '&#34;' );
}
return s;
},
escapeHTMLinJSON: function escapeHTMLinJSON( key, value ) {
if ( typeof value === "string" ) {
return sanitizer.escapeHTML( value );
}
return value;
}
};
module.exports = sanitizer;

144
cornfield/lib/user.js Normal file
Просмотреть файл

@ -0,0 +1,144 @@
"use strict";
function defaultDBReadyFunction( err ) {
if ( err ) {
err = Array.isArray( err ) ? err[ 0 ] : err;
console.warn( "lib/user.js: DB setup error\n", err.number ? err.number : '[No Error Number]', err.message );
}
}
module.exports = function( config, dbReadyFn ) {
config = config || {};
dbReadyFn = dbReadyFn || defaultDBReadyFunction;
var username = config.username || "";
var password = config.password || "";
var dbOnline = false,
Sequelize = require( "sequelize" ),
sequelize = new Sequelize( config.database, username, password, config.options ),
Project = sequelize.import( __dirname + "/models/project" ),
versions;
// travis-ci doesn't create this file when running `npm test` so we need a workaround
try {
versions = require( "../config/versions.json" );
} catch (ex) {
versions = {
butter: "travis-ci"
};
}
sequelize.sync().complete(function( err ) {
if ( !err ) {
dbOnline = true;
}
dbReadyFn( err );
});
return {
getSequelizeInstance: function(){
return sequelize;
},
createProject: function( email, data, callback ) {
if ( !email || !data ) {
callback( "not enough parameters to update" );
return;
}
var project = Project.build({
data: JSON.stringify( data.data ),
email: email,
name: data.name,
author: data.author || "",
template: data.template,
originalButterVersion: versions.butter,
latestButterVersion: versions.butter
});
project.save().complete(function( err, result ) {
callback( err, result );
});
},
deleteProject: function( email, pid, callback ) {
if ( !email || !pid ) {
callback( "not enough parameters to delete" );
return;
}
Project.find( { where: { email: email, id: pid } } )
.success(function( project ) {
if ( project ) {
project.destroy().complete( function( err ) {
callback( err );
});
} else {
callback( "the project has already been deleted" );
}
})
.error(function( error ) {
callback( error );
});
},
findAllProjects: function findAllProjects( email, callback ) {
if ( !email ) {
callback( "not enough parameters to search" );
return;
}
Project.findAll( { where: { email: email } } ).complete( function( err, projects ) {
callback( err, projects );
});
},
findProject: function findProject( email, pid, callback ) {
if ( !email || !pid ) {
callback( "not enough parameters to search" );
return;
}
Project.find( { where: { email: email, id: pid } } ).complete( function( err, project ) {
callback( err, project );
});
},
findById: function findById( pid, callback ) {
if ( !pid ) {
callback( "not enough parameters for search" );
return;
}
Project.find({ where: { id: pid } } ).complete( function( err, project ) {
callback( err, project );
});
},
isDBOnline: function isDBOnline() {
return dbOnline;
},
updateProject: function updateProject( email, pid, data, callback ) {
if ( !email || !pid || !data ) {
callback( "not enough parameters to update" );
return;
}
Project.find( { where: { email: email, id: pid } } )
.success(function( project ) {
project.updateAttributes({
data: JSON.stringify( data.data ),
email: email,
name: data.name,
author: data.author || "",
template: data.template,
latestButterVersion: versions.butter
})
.complete( function(err, result) {
callback( err, result );
});
})
.error(function( error ) {
callback( error );
});
}
};
};

197
cornfield/routes/index.js Normal file
Просмотреть файл

@ -0,0 +1,197 @@
'use strict';
module.exports = function routesCtor( app, User, filter, sanitizer, stores, EMBED_SUFFIX ) {
var uuid = require( "node-uuid" ),
// Keep track of whether this is production or development
deploymentType = app.settings.env === "production" ? "production" : "development";
app.get( '/api/whoami', filter.isXHR, function( req, res ) {
var email = req.session.email;
if (email) {
res.json({
status: "okay",
csrf: req.session._csrf,
email: email,
name: email,
username: email
});
} else {
res.json({
error: 'unauthorized',
csrf: req.session._csrf,
}, 403 );
}
});
app.get( '/api/project/:id?',
filter.isLoggedIn, filter.isStorageAvailable, filter.isXHR,
function( req, res ) {
User.findProject( req.session.email, req.params.id, function( err, doc ) {
if ( err ) {
res.json( { error: err }, 500 );
return;
}
if ( !doc ) {
res.json( { error: "project not found" }, 404 );
return;
}
var projectJSON = JSON.parse( doc.data );
projectJSON.name = doc.name;
projectJSON.projectID = doc.id;
projectJSON.author = doc.author;
projectJSON.template = doc.template;
res.json( projectJSON );
});
});
app.post( '/api/delete/:id?',
filter.isLoggedIn, filter.isStorageAvailable, filter.isXHR,
function( req, res ) {
var id = parseInt( req.params.id, 10 );
if ( isNaN( id ) ) {
res.json( { error: "ID was not a number" }, 500 );
return;
}
User.deleteProject( req.session.email, req.params.id, function( err ) {
if ( err ) {
res.json( { error: 'project not found' }, 404 );
return;
}
// Delete published projects, too
var embedShell = id.toString( 36 ),
embedDoc = embedShell + EMBED_SUFFIX;
stores.publish.remove( embedShell, function( e ) {
if( e ) {
res.json( { error: 'unable to remove file: ' + embedShell }, 500 );
return;
}
stores.publish.remove( embedDoc, function( e ) {
if( e ) {
res.json( { error: 'unable to remove file: ' + embedDoc }, 500 );
return;
}
res.json( { error: 'okay' }, 200 );
});
});
});
});
app.post( '/api/project/:id?',
filter.isLoggedIn, filter.isStorageAvailable, filter.isXHR,
function( req, res ) {
if ( req.body.id ) {
User.updateProject( req.session.email, req.body.id, req.body, function( err, doc ) {
if ( err ) {
res.json( { error: err }, 500 );
return;
}
res.json( { error: 'okay', project: doc } );
});
} else {
User.createProject( req.session.email, req.body, function( err, doc ) {
if ( err ) {
res.json( { error: err }, 500 );
return;
}
// Send back the newly added row's ID
res.json( { error: 'okay', projectId: doc.id } );
});
}
});
// We have a separate remix API for unsecured and sanitized access to projects
app.get( '/api/remix/:id',
filter.isStorageAvailable, filter.isXHR,
function( req, res ) {
User.findById( req.params.id, function( err, project ) {
if ( err ) {
res.json( { error: err }, 500 );
return;
}
if ( !project ) {
res.json( { error: 'project not found' }, 404 );
return;
}
var projectJSON = JSON.parse( project.data, sanitizer.escapeHTMLinJSON );
projectJSON.name = "Remix of " + sanitizer.escapeHTML( project.name );
projectJSON.template = sanitizer.escapeHTML( project.template );
res.json( projectJSON );
});
});
function formatDate( d ) {
// YYYY-MM-DD
d = d || new Date();
function pad( n ) {
return n < 10 ? '0' + n : n;
}
return ( d.getUTCFullYear() + '-' +
pad( d.getUTCMonth() + 1 ) + '-' +
pad( d.getUTCDate() ) );
}
function generateUniqueName( keys ) {
// Generate a unique name, with formatting to support analysis later on.
// The format is:
// <key1>=<value1>/<key2>=<value2>/<key..>=<value..>/<unique blob name>
// For example:
// dt=2012-05-31T20:00/deployment=production/64432AE8-7132-4C01-BD5E-AE49BC343CC8
// Serialize keys array
var keysString = '';
keys.forEach( function( key ) {
keysString += key.name + '=' + key.value + '/';
});
keysString = keysString.replace( /\/$/, '' );
return keysString + '/' + uuid.v4();
}
function storeData( req, res, store ) {
var s = '';
req.addListener( 'data', function( data ) {
s += data;
});
req.addListener( 'end', function() {
var name = generateUniqueName([
{ name: 'dt', value: formatDate() },
{ name: 'deployment', value: deploymentType }
]);
store.write( name, s, function() {
res.writeHead( 200, { 'content-type': 'text/plain' } );
res.end();
});
});
}
// Store crash reports
app.post( '/crash', function( req, res ) {
storeData( req, res, stores.crash );
});
// Store feedback reports
app.post( '/feedback', function( req, res ) {
storeData( req, res, stores.feedback );
});
};

Просмотреть файл

@ -0,0 +1,119 @@
var test = require("tap").test;
var filter = require( "../lib/filter" );
test( "isLoggedIn filter allow", function( t ) {
t.plan( 1 );
var mockReq = { session: { email: "test.example.org" } },
mockRes = { json: function() {
t.ok( false, "this should not be called when session email is set" );
}},
mockNext = function() {
t.ok( true, "next() was called when session was set" );
};
filter().isLoggedIn( mockReq, mockRes, mockNext );
t.end();
});
test( "isLoggedIn filter deny", function( t ) {
t.plan( 2 );
var mockReq = { session: {} },
mockRes = { json: function( data, statusCode ) {
t.deepEqual(data,
{ error: "unauthorized" },
"error should be unauthorized" );
t.equal( statusCode, 403, "status should be 403 unauthorized" );
}},
mockNext = function() {
t.ok( false, "next() should not be called when session email is null" );
};
filter().isLoggedIn( mockReq, mockRes, mockNext );
t.end();
});
test( "isStorageAvailable filter allow", function( t ) {
t.plan( 1 );
var mockRes = { json: function() {
t.ok( false, "this should not be called when storage is available" );
}},
mockNext = function() {
t.ok( true, "next() was called when storage was available" );
};
filter(function() {
return true;
}).isStorageAvailable( null, mockRes, mockNext );
t.end();
});
test( "isStorageAvailable filter deny", function( t ) {
t.plan( 2 );
var mockRes = { json: function( data, statusCode ) {
t.deepEqual(data,
{ error: "storage service is not running" },
"error should be storage service is not running" );
t.equal( statusCode, 500, "status should be 500 server error" );
}},
mockNext = function() {
t.ok( false, "next() should not be called when storage is not available" );
};
filter(function() {
return false;
}).isStorageAvailable( null, mockRes, mockNext );
t.end();
});
test( "isXHR filter allow", function( t ) {
t.plan( 1 );
var mockReq = { header: function( key ) {
if ( key === "X-Requested-With" ) {
return "XMLHttpRequest";
} else {
return "";
}
}},
mockRes = { json: function() {
t.ok( false, "this should not be called when X-Requested-With is set" );
}},
mockNext = function() {
t.ok( true, "next() was called when X-Requested-With was set" );
};
filter().isXHR( mockReq, mockRes, mockNext );
t.end();
});
test( "isLoggedIn filter deny", function( t ) {
t.plan( 2 );
var mockReq = { header: function( key ) {
return "";
}},
mockRes = { json: function( data, statusCode ) {
t.deepEqual(data,
{ error: "X-Requested-With is not set to XMLHttpRequest" },
"error should be X-Requested-With is not set to XMLHttpRequest" );
t.equal( statusCode, 412, "status should be 412 unauthorized" );
}},
mockNext = function() {
t.ok( false, "next() should not be called when X-Requested-With is null" );
};
filter().isXHR( mockReq, mockRes, mockNext );
t.end();
});

Просмотреть файл

@ -0,0 +1,47 @@
var test = require("tap").test;
var sanitizer = require( "../lib/sanitizer" );
test( "String sanitization", function( t ) {
t.equal( sanitizer.escapeHTML( "hello world" ),
"hello world",
"no sanitization needed" );
t.equal( sanitizer.escapeHTML( "<script>alert()</script>" ),
"&lt;script&gt;alert()&lt;/script&gt;",
"sanitize less than and greater than" );
t.equal( sanitizer.escapeHTML( "bill & ted" ),
"bill &amp; ted",
"sanitize ampersand" );
t.equal( sanitizer.escapeHTML( "'\"" ),
"&#39;&#34;",
"sanitize single and double quotes" );
t.end();
});
test( "JSON sanitization", function( t ) {
t.deepEqual(JSON.parse('{"hello":"world"}', sanitizer.escapeHTMLinJSON ),
{ hello: "world" },
"no sanitization needed" );
t.deepEqual(JSON.parse('{"test":"<script>alert()</script>"}', sanitizer.escapeHTMLinJSON ),
{ test: "&lt;script&gt;alert()&lt;/script&gt;" },
"sanitize less than and greater than" );
t.deepEqual(JSON.parse('{"test":"bill & ted"}', sanitizer.escapeHTMLinJSON ),
{ test: "bill &amp; ted" },
"sanitize ampersand" );
t.deepEqual(JSON.parse('{"test":"\'\\""}', sanitizer.escapeHTMLinJSON ),
{ test: "&#39;&#34;" },
"sanitize single and double quotes" );
t.deepEqual(JSON.parse('{"testing\'\\"alert()": "uh oh" }', sanitizer.escapeHTMLinJSON ),
{ "testing'\"alert()": "uh oh" },
"key names are not sanitized" );
t.end();
});

Просмотреть файл

@ -0,0 +1,126 @@
var test = require( "tap" ).test,
userLibrary = require( "../lib/user" ),
Sequelize = require( "sequelize" );
var DB_USERNAME = process.env.DB_USERNAME || "popcorntest";
var DB_DATABASE = process.env.DB_DATABASE || "popcorntest";
var DB_PASSWORD = process.env.DB_PASSWORD || "";
var DB_HOST = process.env.DB_HOST;
var mockEmail = "test@example.org",
mockData = {
data: {
test: "Hey Test Values"
},
email: mockEmail,
name: "Test User",
author: "Test User",
template: "basic"
};
var configWithPool = {
database: DB_DATABASE,
username: DB_USERNAME,
password: DB_PASSWORD,
options: {
host: DB_HOST,
dialect: "mysql",
logging: false,
pool: {
maxConnections: 5,
maxIdleTime: 1
}
}
};
var configWithoutPool = {
database: DB_DATABASE,
username: DB_USERNAME,
password: DB_PASSWORD,
options: {
host: DB_HOST,
dialect: "mysql",
logging: false
}
};
function Waiter( numItems, onCompleted, onCancelled ) {
var _callbacks = [],
_usedCallbacked = 0,
_cancelled = false;
this.wait = function( callback ) {
_callbacks.push( callback );
return function() {
if ( ++_usedCallbacked === _callbacks.length ) {
for ( var i = 0; i < _callbacks.length; ++i ) {
if ( !_cancelled ) {
_callbacks[ i ].apply( this, arguments );
if ( _cancelled ) {
onCancelled();
return;
}
}
}
onCompleted();
}
};
};
this.cancel = function() {
_cancelled = true;
};
}
test( "mysql db pooling", function( t ) {
var poolingUser, nonPoolingUser;
var waiter = new Waiter( 2,
function(){
var projectWaiter = new Waiter( 2,
function() {
t.end();
},
function() {
t.end();
});
poolingUser.createProject( mockEmail, mockData, projectWaiter.wait( function( err, project ) {
t.ok( !err, "Pooling project created" );
if ( err ) {
projectWaiter.cancel();
}
}));
nonPoolingUser.createProject( mockEmail, mockData, projectWaiter.wait( function( err, project ) {
t.ok( !err, "Non-pooling project created" );
if ( err ) {
projectWaiter.cancel();
}
}));
},
function() {
t.comment( "\nWARNING: MySQL tests did NOT run.\n" +
"Make sure that the mysql server is running and a user '" + DB_USERNAME + "' exists with no password for the '" + DB_DATABASE + "' database." );
// Just to leave room in comments.
t.comment( "" );
t.end();
});
poolingUser = userLibrary( configWithPool, waiter.wait( function( err ) {
if ( err ) {
waiter.cancel();
return;
}
t.ok( poolingUser.getSequelizeInstance().connectorManager.pool, "Pool exists" );
}));
nonPoolingUser = userLibrary( configWithoutPool, waiter.wait( function( err ) {
if ( err ) {
waiter.cancel();
return;
}
t.ok( !nonPoolingUser.getSequelizeInstance().connectorManager.pool, "No pool exists" );
}));
});

Просмотреть файл

@ -0,0 +1,293 @@
var test = require( "tap" ).test,
user,
mockEmail = "test@example.org",
mockData = {
data: {
test: "Hey Test Values"
},
email: mockEmail,
name: "Test User",
author: "Test User",
template: "basic"
},
id,
callback;
test( "sqlite db setup with incorrect pool params", function( t ) {
var poolUser = require( "../lib/user" )({
database: "popcorn",
options: {
dialect: "sqlite",
storage: ":memory:",
logging: false,
pool: {
maxConnections: 5,
maxIdleTime: 1
}
}
}, function( err ) {
t.ok( !poolUser.getSequelizeInstance().connectorManager.pool, "No pool exists" );
t.ok( !err, "User created with sqlite db and ignored pool param" );
t.end();
});
});
test( "sqlite db setup", function( t ) {
user = require( "../lib/user" )({
database: "popcorn",
options: {
dialect: "sqlite",
storage: ":memory:",
logging: false
}
}, function( err ) {
t.ok( !err, "User created with sqlite db" );
t.end();
});
});
test( "createProject valid parameters", function( t ) {
t.plan( 6 );
var mockCallback = function( err, project ) {
// Store ID for later tests
id = project.id;
t.ok( project, "Project has data" );
t.equal( project.data, JSON.stringify( mockData.data ), "Properly Set Data of Project" );
t.equal( project.email, mockData.email, "Properly Set Email of Project" );
t.equal( project.name, mockData.name, "Properly Set Name of Project" );
t.equal( project.author, mockData.author, "Properly Set Author of Project" );
t.equal( project.template, mockData.template, "Properly Set Template of Project" );
t.end();
};
user.createProject( mockEmail, mockData, mockCallback );
});
test( "createProject invalid parameters - Project Data", function( t ) {
t.plan( 2 );
var mockCallback = function( err, project ) {
t.ok( err, "Successfully received an error with invalid parameters" );
t.equal( err, "not enough parameters to update", "Reported expected error message" );
t.end();
};
user.createProject( mockEmail, null, mockCallback );
});
test( "createProject invalid parameters - Email", function( t ) {
t.plan( 2 );
var mockCallback = function( err, project ) {
t.ok( err, "Successfully received an error with invalid parameters" );
t.equal( err, "not enough parameters to update", "Reported expected error message for creation" );
t.end();
};
user.createProject( null, mockData, mockCallback );
});
test( "deleteProject invalid parameters - Project ID", function( t ) {
t.plan( 2 );
var mockCallback = function( err ) {
t.ok( err, "Successfully received an error with invalid parameters" );
t.equal( err, "not enough parameters to delete", "Reported expected error message for delete" );
t.end();
};
user.deleteProject( mockEmail, null, mockCallback );
});
test( "deleteProject invalid parameters - Email", function( t ) {
t.plan( 2 );
var mockCallback = function( err ) {
t.ok( err, "Successfully received an error with invalid parameters" );
t.equal( err, "not enough parameters to delete", "Reported expected error message for delete" );
t.end();
};
user.deleteProject( null, id, mockCallback );
});
test( "deleteProject valid parameters", function( t ) {
t.plan( 1 );
var mockCallback = function( err, project ) {
var deleteCallback = function( err ) {
t.false( err, "No error was passed back. Project successfully removed." );
t.end();
};
user.deleteProject( mockEmail, project.id, deleteCallback );
};
user.createProject( mockEmail, mockData, mockCallback );
});
test( "findAllProjects valid parameters", function( t ) {
t.plan( 2 );
var mockCallback = function( err, docs ) {
t.ok( Array.isArray( docs ), "Returned an array of projects" );
t.ok( docs, "Successfully returned all projects for " + mockEmail );
t.end();
};
user.findAllProjects( mockEmail, mockCallback );
});
test( "findAllProjects invalid parameters", function( t ) {
t.plan( 2 );
var mockCallback = function( err, docs ) {
t.ok( err, "Successfully received an error with invalid parameters" );
t.equal( err, "not enough parameters to search", "Reported expected error message for retrieving all projects" );
t.end();
};
user.findAllProjects( null, mockCallback );
});
test( "findById valid parameters", function( t ) {
t.plan( 2 );
var mockCallback = function( err, project ) {
t.ok( project, "Successfully received a project" );
t.deepEqual( project.id, id, "ID of retrieved project matches." );
t.end();
};
user.findById( id, mockCallback );
});
test( "findById invalid parameters", function( t ) {
t.plan( 2 );
var mockCallback = function( err, project ) {
t.ok( err, "Successfully received an error with invalid parameters" );
t.equal( err, "not enough parameters for search", "Reported expected error message for retrieving by ID" );
t.end();
};
user.findById( null, mockCallback );
});
test( "findProject valid parameters", function( t ) {
t.plan( 7 );
var mockCallback = function( err, project ) {
t.ok( project, "Project was retrieved" );
t.deepEqual( project.id, id, "Project has correct id" );
t.equal( project.data, JSON.stringify( mockData.data ), "Properly Set Data of Project" );
t.equal( project.email, mockData.email, "Properly Set Email of Project" );
t.equal( project.name, mockData.name, "Properly Set Name of Project" );
t.equal( project.author, mockData.author, "Properly Set Author of Project" );
t.equal( project.template, mockData.template, "Properly Set Template of Project" );
t.end();
};
user.findProject( mockEmail, id, mockCallback );
});
test( "findProject invalid parameters - Project ID", function( t ) {
t.plan( 2 );
var mockCallback = function( err, project ) {
t.ok( err, "Successfully received an error with invalid parameters" );
t.equal( err, "not enough parameters to search", "Reported expected error message for project retrieval" );
t.end();
};
user.findProject( mockEmail, null, mockCallback );
});
test( "findProject invalid parameters - Email", function( t ) {
t.plan( 2 );
var mockCallback = function( err, project ) {
t.ok( err, "Successfully received an error with invalid parameters" );
t.equal( err, "not enough parameters to search", "Reported expected error message for project retrieval" );
t.end();
};
user.findProject( null, id, mockCallback );
});
test( "updateProject valid parameters", function( t ) {
t.plan( 4 );
var updateData = {
data: {
test: "NEW TEXT"
},
name: "Test Userd",
author: "Test Userd",
template: "advanced"
},
mockCallback = function( err, project ) {
t.equal( project.data, JSON.stringify( updateData.data ), "Properly updated Data of Project" );
t.equal( project.name, updateData.name, "Properly updated Name of Project" );
t.equal( project.author, updateData.author, "Properly updated Author of Project" );
t.equal( project.template, updateData.template, "Properly updated Template of Project" );
t.end();
};
user.updateProject( mockEmail, id, updateData, mockCallback );
});
test( "updateProject invalid parameters - Project Data", function( t ) {
t.plan( 2 );
var mockCallback = function( err, project ) {
t.ok( err, "Successfully received an error with invalid parameters" );
t.equal( err, "not enough parameters to update", "Reported expected error message for project updating" );
t.end();
};
user.updateProject( mockEmail, id, null, mockCallback );
});
test( "updateProject invalid parameters - Project ID", function( t ) {
t.plan( 2 );
var mockCallback = function( err, project ) {
t.ok( err, "Successfully received an error with invalid parameters" );
t.equal( err, "not enough parameters to update", "Reported expected error message for project updating" );
t.end();
};
user.updateProject( mockEmail, null, mockData, mockCallback );
});
test( "updateProject invalid parameters - Email", function( t ) {
t.plan( 2 );
var mockCallback = function( err, project ) {
t.ok( err, "Successfully received an error with invalid parameters" );
t.equal( err, "not enough parameters to update", "Reported expected error message for project updating" );
t.end();
};
user.updateProject( null, id, mockData, mockCallback );
});

Просмотреть файл

@ -0,0 +1,9 @@
function mockFilter(req, res, next) {
next();
}
module.exports = {
isLoggedIn: mockFilter,
isStorageAvailable: mockFilter,
isXHR: mockFilter
};

Просмотреть файл

@ -0,0 +1,8 @@
module.exports = {
escapeHTML: function escapeHTML( s ) {
return s;
},
escapeHTMLinJSON: function escapeHTMLinJSON( key, value ) {
return value;
}
};

Просмотреть файл

@ -0,0 +1,13 @@
module.exports = function(data) {
data = data || {};
return function(req, res, next) {
req.session = {};
Object.keys(data).forEach(function(key) {
req.session[key] = data[key];
});
next();
};
};

Просмотреть файл

@ -0,0 +1,7 @@
module.exports = {
publish: {
remove: function(path, callback) {
callback();
}
}
};

Просмотреть файл

@ -0,0 +1,88 @@
function generateMockData(id) {
id = id || parseInt( Math.random()*10000, 10 );
return {
id: id,
data: JSON.stringify({
hello: "world",
adventure: "bill & ted's"
}),
email: "test@example.org",
name: "My Mock Project",
author: "Test User",
template: "basic"
};
}
module.exports = function() {
return {
error: false,
doc: true,
generateMockData: generateMockData,
findProject: function(email, id, callback) {
if (this.error) {
callback("mock error");
return;
}
if (!this.doc) {
callback();
return;
}
callback(null, generateMockData(id));
},
deleteProject: function(email, id, callback) {
if (this.error) {
callback("mock error");
return;
}
callback();
},
findById: function(id, callback) {
if (this.error) {
callback("mock error");
return;
}
if (!this.doc) {
callback();
return;
}
callback(null, generateMockData(id));
},
createProject: function(email, data, callback) {
if (this.error) {
callback("mock error");
return;
}
if (!data.data) {
callback("not enough parameters to update");
return;
}
data.id = parseInt( Math.random()*10000, 10 );
data.data = JSON.stringify( data.data );
callback(null, data);
},
updateProject: function(email, id, data, callback) {
if (this.error) {
callback("mock error");
return;
}
if (!this.doc) {
callback("project id not found");
return;
}
data.data = JSON.stringify( data.data );
callback(null, data);
}
};
};

Просмотреть файл

@ -0,0 +1,303 @@
var test = require("tap").test,
request = require("supertest");
var mockEmail = "test@example.org",
mockSession = require("./mock.session"),
mockUser = require("./mock.user")(),
mockFilter = require("./mock.filter"),
mockSanitizer = require("./mock.sanitizer"),
mockStore = require("./mock.store");
var express = require("express");
var app = express.createServer();
app.use(mockSession({
email: mockEmail,
_csrf: "FDaS435D2z"
}))
.use(express.bodyParser());
require("../routes")(app, mockUser, mockFilter, mockSanitizer, mockStore);
test("whoami API valid", function(t) {
request(app)
.get("/api/whoami")
.end(function(err, res) {
t.equal(res.statusCode, 200, "status code is 200");
t.equal(res.type, "application/json", "response type is json");
t.deepEqual(res.body, {
status: "okay",
email: mockEmail,
name: mockEmail,
username: mockEmail,
csrf: "FDaS435D2z"
}, "response should have 5 attributes");
t.end();
});
});
test("project data get with error", function(t) {
mockUser.error = true;
mockUser.doc = false;
request(app)
.get("/api/project/1234")
.end(function(err, res) {
t.equal(res.statusCode, 500, "status code is 500");
t.equal(res.type, "application/json", "response type is json");
t.ok(res.body.error, "json contains an error");
t.end();
});
});
test("project data get not found", function(t) {
mockUser.error = false;
mockUser.doc = false;
request(app)
.get("/api/project/1234")
.end(function(err, res) {
t.equal(res.statusCode, 404, "status code is 404");
t.equal(res.type, "application/json", "response type is json");
t.ok(res.body.error, "json contains an error");
t.end();
});
});
test("project data get valid", function(t) {
mockUser.error = false;
mockUser.doc = true;
request(app)
.get("/api/project/1234")
.end(function(err, res) {
t.equal(res.statusCode, 200, "status code is 200");
t.equal(res.type, "application/json", "response type is json");
// This is very obtuse...
var mockData = mockUser.generateMockData(1234);
mockData.data = JSON.parse(mockData.data);
mockData.data.name = mockData.name;
mockData.data.projectID = mockData.id;
mockData.data.author = mockData.author;
mockData.data.template = mockData.template;
mockData = mockData.data;
t.deepEqual(res.body, mockData, "saved data is equal");
t.end();
});
});
test("delete project not found", function(t) {
mockUser.error = true;
request(app)
.post("/api/delete/1234")
.end(function(err, res) {
t.equal(res.statusCode, 404, "status code is 404");
t.equal(res.type, "application/json", "response type is json");
t.ok(res.body.error, "json contains an error");
t.end();
});
});
test("delete project found", function(t) {
mockUser.error = false;
request(app)
.post("/api/delete/1234")
.end(function(err, res) {
t.equal(res.statusCode, 200, "status code is 200");
t.equal(res.type, "application/json", "response type is json");
// wtf was I think when returning error is a good thing?
t.equal(res.body.error, "okay", "json returns okay");
t.end();
});
});
/*******************
* DANGER ZONE *
* HERE BE DRAGONS *
*******************/
test("create project with error", function(t) {
mockUser.error = true;
var mockData = mockUser.generateMockData(1234);
delete mockData.id;
request(app)
.post("/api/project/1234")
.send(mockData)
.end(function(err, res) {
t.equal(res.statusCode, 500, "status code is 500");
t.equal(res.type, "application/json", "response type is json");
t.equal(res.body.error, "mock error", "error message is correct");
t.end();
});
});
test("create project with no data", function(t) {
mockUser.error = false;
var mockData = mockUser.generateMockData(1234);
delete mockData.id;
delete mockData.data;
request(app)
.post("/api/project/1234")
.send(mockData)
.end(function(err, res) {
t.equal(res.statusCode, 500, "status code is 500");
t.equal(res.type, "application/json", "response type is json");
t.equal(res.body.error, "not enough parameters to update", "error message is correct");
t.end();
});
});
test("create project valid", function(t) {
mockUser.error = false;
var mockData = mockUser.generateMockData();
mockData.data = JSON.parse(mockData.data);
delete mockData.id;
request(app)
.post("/api/project/1234")
.send(mockData)
.end(function(err, res) {
t.equal(res.statusCode, 200, "status code is 200");
t.equal(res.type, "application/json", "response type is json");
t.equal(res.body.error, "okay", "status is okay");
t.ok(res.body.projectId, "id is present");
t.end();
});
});
test("update project with error", function(t) {
mockUser.error = true;
mockUser.doc = true;
var mockData = mockUser.generateMockData(1234);
mockData.data = JSON.parse(mockData.data);
mockData.id = mockData.id;
request(app)
.post("/api/project/1234")
.send(mockData)
.end(function(err, res) {
t.equal(res.statusCode, 500, "status code is 500");
t.equal(res.type, "application/json", "response type is json");
t.ok(res.body.error, "mock error");
t.end();
});
});
test("update project with no matching id", function(t) {
mockUser.error = false;
mockUser.doc = false;
var mockData = mockUser.generateMockData(1234);
mockData.data = JSON.parse(mockData.data);
mockData.id = mockData.id;
request(app)
.post("/api/project/1234")
.send(mockData)
.end(function(err, res) {
t.equal(res.statusCode, 500, "status code is 500");
t.equal(res.type, "application/json", "response type is json");
t.ok(res.body.error, "project id not found");
t.end();
});
});
test("update project valid", function(t) {
mockUser.error = false;
mockUser.doc = true;
var mockData = mockUser.generateMockData(1234);
mockData.data = JSON.parse(mockData.data);
mockData.id = mockData.id;
request(app)
.post("/api/project/1234")
.send(mockData)
.end(function(err, res) {
t.equal(res.statusCode, 200, "status code is 200");
t.equal(res.type, "application/json", "response type is json");
t.equal(res.body.error, "okay", "status is okay");
mockData.data = JSON.stringify(mockData.data);
t.deepEqual(res.body.project, mockData, "saved data is equal");
t.end();
});
});
test("remix project with error", function(t) {
mockUser.error = true;
mockUser.doc = false;
request(app)
.get("/api/remix/1234")
.end(function(err, res) {
t.equal(res.statusCode, 500, "status code is 500");
t.equal(res.type, "application/json", "response type is json");
t.ok(res.body.error, "json contains an error");
t.end();
});
});
test("remix project with no doc", function(t) {
mockUser.error = false;
mockUser.doc = false;
request(app)
.get("/api/remix/1234")
.end(function(err, res) {
t.equal(res.statusCode, 404, "status code is 404");
t.equal(res.type, "application/json", "response type is json");
t.ok(res.body.error, "json contains an error");
t.end();
});
});
test("remix project valid", function(t) {
mockUser.error = false;
mockUser.doc = true;
request(app)
.get("/api/remix/1234")
.end(function(err, res) {
t.equal(res.statusCode, 200, "status code is 200");
t.equal(res.type, "application/json", "response type is json");
// This is very obtuse...
var mockData = mockUser.generateMockData(1234);
mockData.data = JSON.parse(mockData.data);
mockData.data.name = "Remix of " + mockData.name;
mockData.data.template = "basic";
mockData = mockData.data;
t.deepEqual(res.body, mockData, "saved data is equal");
t.end();
});
});
test("clean up server connections", function(t) {
app.close();
t.end();
});

Просмотреть файл

@ -0,0 +1,22 @@
doctype 5
html
head
title Dashboard
style
body {
font-family: "Open Sans", "Helvetica Neue", sans-serif;
background: #3D3F44;
color: #EEE;
width: 650px;
margin: 0 auto;
font-size: 1.5em;
text-align: center;
}
img {
width: 650px;
}
body
h1 Sorry...
img(src="/struggling-dino.gif")
p Please login before attempting to view your dashboard.

Просмотреть файл

@ -0,0 +1,223 @@
doctype 5
html
head
title Dashboard
link(rel='stylesheet',href='../css/butter.ui.css')
script
(function(){
var currentDeletionId;
function setDeletionHeaderState( state, projectName ) {
var header = document.querySelector( 'div.butter-delete-confirmation' ),
nameSpan = header.querySelector( 'span' ),
table = document.querySelector( 'table' );
if ( !state ) {
//avoid shim problems completely
header.className = 'butter-delete-confirmation';
table.className = '';
}
else {
nameSpan.innerHTML = projectName;
//avoid shim problems completely
header.className = 'butter-delete-confirmation open';
table.className = 'confirm-delete';
}
}
function createDeleteHandler( deleteElement ) {
var projectId = deleteElement.getAttribute( 'data-project-id' ),
projectName = deleteElement.getAttribute( 'data-project-name' );
deleteElement.addEventListener( 'click', function( e ) {
setDeletionHeaderState( true, projectName );
currentDeletionId = projectId;
}, false );
}
document.addEventListener( 'DOMContentLoaded', function( e ) {
var deleteButtons = document.querySelectorAll( 'td > a.butter-delete-button' ),
i = deleteButtons ? deleteButtons.length : 0,
noButton = document.querySelector( 'div.butter-delete-confirmation > button.no' ),
yesButton = document.querySelector( 'div.butter-delete-confirmation > button.yes' );
noButton.addEventListener( 'click', function( e ) {
setDeletionHeaderState( false );
currentDeletionId = null;
}, false );
yesButton.addEventListener( 'click', function( e ) {
var req,
row;
if ( currentDeletionId ) {
req = new XMLHttpRequest();
req.open( 'POST', '/api/delete/' + currentDeletionId, false );
req.setRequestHeader( "X-Requested-With", "XMLHttpRequest" );
req.setRequestHeader( "x-csrf-token", document.getElementById( "csrf" ).value );
row = document.querySelector( 'tr[data-project-id="' + currentDeletionId + '"]' );
currentDeletionId = null;
req.send( null );
if ( req.status == 200 ) {
row.parentNode.removeChild( row );
}
setDeletionHeaderState( false );
}
}, false );
while ( i-- ) {
createDeleteHandler( deleteButtons[ i ] );
}
}, false );
}());
style
body {
font-family: "Open Sans", "Helvetica Neue", sans-serif;
background: #3D3F44;
color: #888;
width: 650px;
margin: 0 auto;
font-size: .9em;
overflow: scroll;
}
.butter-logo {
background: url( "logo-words.png" ) no-repeat;
}
table, th, tr, td {
border: none;
border-spacing: 0;
padding: 0;
margin: 0;
overflow: hidden;
white-space: nowrap;
max-width: 350px;
}
table {
margin-top: 20px;
margin-bottom: 40px;
-moz-transition: margin-top 0.3s;
-webkit-transition: margin-top 0.3s;
-ms-transition: margin-top 0.3s;
-o-transition: margin-top 0.3s;
transition: margin-top 0.3s;
}
table.confirm-delete {
margin-top: 45px;
}
th {
text-align: left;
color: #EEE;
border-bottom: 1px solid #222;
font-size: 1.2em;
}
td, th {
padding: 5px 10px;
}
td a {
text-decoration: none;
color: #fff;
}
td a:hover {
text-decoration: underline;
color: #fff;
}
td:first-child {
width: 350px;
}
td:first-child:hover {
background: rgb(255,239,158);
-webkit-transition: .2s all;
-moz-transition: .2s all;
-ms-transition: .2s all;
-o-transition: .2s all;
transition: .2s all;
}
td:first-child:hover a {
color: #242428;
}
td:nth-child(2) {
width: 300px;
}
tr:nth-child(odd) {
background: rgba(0,0,0,.3);
}
tr:nth-child(even) {
background: rgba(0,0,0,.4);
}
.butter-projects-title {
display: block;
text-align: left;
text-decoration: none;
color: #EEE;
width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.butter-projects-title:hover {
color: #FFF;
}
.butter-delete-confirmation {
position: fixed;
width: 100%;
left: 0;
top: 51px;
background: rgb(241,218,54); /* Old browsers */
background: -moz-linear-gradient(top, rgba(241,218,54,1) 0%, rgba(252,239,118,1) 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(241,218,54,1)), color-stop(100%,rgba(252,239,118,1))); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, rgba(241,218,54,1) 0%,rgba(252,239,118,1) 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, rgba(241,218,54,1) 0%,rgba(252,239,118,1) 100%); /* Opera 11.10+ */
background: -ms-linear-gradient(top, rgba(241,218,54,1) 0%,rgba(252,239,118,1) 100%); /* IE10+ */
background: linear-gradient(top, rgba(241,218,54,1) 0%,rgba(252,239,118,1) 100%); /* W3C */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f1da36', endColorstr='#fcef76',GradientType=0 ); /* IE6-9 */
-moz-transition: all 0.3s;
-webkit-transition: all 0.3s;
-ms-transition: all 0.3s;
-o-transition: all 0.3s;
transition: all 0.3s;
padding: 0px;
color: #000;
text-align: center;
height: 0px;
overflow: hidden;
}
.butter-delete-confirmation span {
max-width: 200px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.butter-delete-confirmation.open {
height: 20px;
padding: 8px;
}
body.butter-header-spacing
div#butter-header
div.butter-header-inner
div.butter-logo
span.butter-name My Dashboard &ndash;
= user.email
div.butter-delete-confirmation
| Are you sure you want to delete "
span.project-name
| "?
button.yes Yes
button.no No
table
tr
th Project Title
th Template
th
each project in projects
tr(data-project-id=project._id)
td
a(href= project.href).butter-projects-title
= project.name
td
= project.template
td
a(href= "#", data-project-id=project._id, data-project-name=project.name).butter-delete-button
| Delete
input(id="csrf", type="hidden", value=user.csrf)

Просмотреть файл

@ -0,0 +1,22 @@
!!! 5
html
head
meta(charset='utf-8')
meta(http-equiv='X-UA-Compatible', content='IE=edge,chrome=1')
meta(name='author', content='#{author}')
meta(name='title', content='#{projectName}')
meta(name='og:title', content='#{projectName}')
meta(name='og:type', content='video.other')
meta(name='og:url', content='#{embedShellSrc}')
meta(name='og:image', content='#{baseHref}/resources/logo/popcorn.png')
meta(name='og:image:type', content='image/png')
meta(name='og:image:width', content='33')
meta(name='og:image:width', content='50')
meta(name='og:description', content='This was created with Popcorn Maker - part of the Mozilla Webmaker initiative')
meta(name='og:site_name', content='https://webmaker.org')
meta(name='description', content='This was created with Popcorn Maker - part of the Mozilla Webmaker initiative')
title #{projectName} - Popcorn Maker
meta(name='viewport', content='width=device-width')
link(rel='stylesheet', href='#{baseHref}/css/embed-shell.css')
body
iframe(src='#{embedSrc}', width='1280', height='745', frameborder='0', mozallowfullscreen='mozallowfullscreen', webkitallowfullscreen='webkitallowfullscreen', allowfullscreen='allowfullscreen')

Просмотреть файл

@ -0,0 +1,98 @@
!!! 5
html(lang='en')
head
base(href='#{baseHref}')
!{templateScripts}
!{externalAssets}
!{popcorn}
link(rel='stylesheet', href='/css/embed.css')
link(rel='stylesheet', href="/css/controls.css")
// This is the embed, the actual content is at the following URL
link(rel='cannonical', href='#{embedShellSrc}')
style
@-o-viewport { width: device-width; }
@-moz-viewport { width: device-width; }
@-ms-viewport { width: device-width; }
@-webkit-viewport { width: device-width; }
@viewport { width: device-width; }
script(src='/src/embed.js')
body.embed
#container.container
#video-container.video
#video
#controls-big-play-button
#post-roll.embed-overlay(style='display: none;')
.post-roll-inner
a(href='#').embed-logo
.embed-project
div
.mozpopcorn Mozilla Popcorn
.embed-title #{title}
.embed-author #{author}
.post-roll-description
| This was created with Mozilla Popcorn.
br
a(href='/', target='_blank') Create a project like this!
ul.embed-nav.post-roll-options
li
a#replay-post
span Replay
li
a(href='#{remixUrl}', target='_blank')#remix-post
span Remix
li
a#share-post
span Share
#share.embed-overlay(style='display: none;')
.share-inner
a#share-close X
a.embed-logo
.embed-project
div
.mozpopcorn Mozilla Popcorn
.embed-title #{title}
.embed-author #{author}
#share-options.share-options
fieldset
label URL
input#share-url.share-ul(type='url')
ul.embed-nav.share-buttons
li
a#replay-share(href='#') Twitter
li
a#share-share(href='#') Google+
fieldset
label Embed
textarea#share-iframe
iframe
.share-size
div.size-options
a.small.option(href='#')
span.icon
span.size Small
span.dimensions 560x358
a.medium.option.current(href='#')
span.icon
span.size Medium
span.dimensions 640x403
a.large.option(href='#')
span.icon
span.size Large
span.dimensions 853x523
a.xlarge.option(href='#')
span.icon
span.size X-Large
span.dimensions 1280x763
#attribution-info.attribution-info
#attribution-logo.attribution-logo
span.attribution-text Credits
#attribution-details.attribution-details
span.attribution-close
div.attribution-mopop mozilla Popcorn
div.attribution-title #{title}
if author
div.attribution-author #{author}
div.attribution-media
span.media-icon
a.attribution-media-src(href='#{mediaSrc}', target='_blank') #{mediaSrc}
#controls.controls

26
css/butter.ui.less Normal file
Просмотреть файл

@ -0,0 +1,26 @@
/* This Source Code Form is subject to the terms of the MIT license
* If a copy of the MIT license was not distributed with this file, you can
* obtain one at http://www.mozillapopcorn.org/butter-license.txt */
@import "globals";
@import "webfonts";
@import "normalize";
@import "utilities";
@import "common";
@import "ui-states";
@import "buttons";
@import "dialog";
@import "media-editor";
@import "editors";
@import "header";
@import "tray";
@import "tray-status-bar";
@import "tray-timeline";
@import "plugin-module";
@import "share-editor";
@import "super-scrollbar";

570
css/buttons.less Normal file
Просмотреть файл

@ -0,0 +1,570 @@
/*********************************************************
* BUTTONS
*/
@_btnLineHeight: 14px;
@_btnTotalHeight: 35px; // It turns out that the total height of our buttons is 35px.
// Base
.butter-btn {
display: inline-block;
text-decoration: none;
font-size: 13px;
line-height: @_btnTotalHeight - 2; //borders
padding: 0 10px;
border-radius: 2px;
margin-bottom: 0;
vertical-align: middle;
border: 1px solid @baseLight;
background: @baseLight;
color: @baseText;
&:hover {
cursor: pointer;
}
}
// Disabled states
.butter-btn-slide-out {
display: block;
overflow: hidden;
.butter-btn {
float: left;
.transition( all 0.2s ease-in );
}
.butter-disabled {
margin-top: -@_btnTotalHeight;
float: left;
}
}
.butter-btn.butter-disabled {
opacity: 0.5;
cursor: not-allowed;
}
// Colors
.btn-light {
background: @baseLight;
color: @baseText;
border: 1px solid darken( @baseLight, 20% );
border-top-color: darken( @baseLight, 15% );
border-bottom-color: darken( @baseLight, 25% );
box-shadow: inset 0 1px 0 lighten( @baseLight, 10% );
.box-sizing( border-box );
&:hover {
color: darken( @baseText, 10% );
}
}
.btn-green {
background: @green;
color: #FFF;
border: 1px solid darken( @green, 15% );
border-top-color: darken( @green, 10% );
border-bottom-color: darken( @green, 20% );
box-shadow: inset 0 1px 0 lighten( @green, 10% );
&:hover {
color: #FFF;
}
}
.btn-red {
background: #D3473C;
color: #FFF;
border: 1px solid #A32630;
box-shadow: inset 0 1px 0 lighten( #D3473C, 10% );
}
// Button group
.btn-group {
display: inline-block;
a, button {
margin-left: -5px;
border-right-width: 0;
}
.butter-btn:first-of-type {
margin-left: 0;
border-radius: 2px 0 0 2px;
}
.butter-btn:last-of-type {
border-radius: 0 2px 2px 0;
border-right-width: 1px;
}
}
/*********************************************************
* Media Icons
*/
.media-icon {
position: relative;
display: block;
float: left;
margin-right: 5px;
background: url( "../resources/media-icons.png" ) no-repeat;
height: 16px;
width: 16px;
&.html5-icon {
background-position: 0 0;
}
&.youtube-icon {
background-position: -16px 0;
}
&.vimeo-icon {
background-position: -32px 0;
}
&.soundcloud-icon {
background-position: -48px 0;
}
}
/*********************************************************
* Icons
*/
.icon {
display: inline-block;
background-image: url("../resources/glyphicons-halflings-alt.png");
width: 14px;
height: 14px;
margin-left: -4px;
margin-right: 4px;
line-height: @_btnLineHeight;
background-repeat: no-repeat;
vertical-align: text-top;
}
.icon-only {
margin-right: 0;
margin-left: 0;
}
.icon-grear-sign {
background-position: -240px 0;
}
.icon-white {
background-image: url("../resources/glyphicons-halflings-white.png");
}
.icon-glass {
background-position: 0 0;
}
.icon-music {
background-position: -24px 0;
}
.icon-search {
background-position: -48px 0;
}
.icon-envelope {
background-position: -72px 0;
}
.icon-heart {
background-position: -96px 0;
}
.icon-star {
background-position: -120px 0;
}
.icon-star-empty {
background-position: -144px 0;
}
.icon-user {
background-position: -168px 0;
}
.icon-film {
background-position: -192px 0;
}
.icon-th-large {
background-position: -216px 0;
}
.icon-th {
background-position: -240px 0;
}
.icon-th-list {
background-position: -264px 0;
}
.icon-ok {
background-position: -288px 0;
}
.icon-remove {
background-position: -312px 0;
}
.icon-x,
.icon-close {
background-position: -432px -24px;
}
.icon-zoom-in {
background-position: -336px 0;
}
.icon-zoom-out {
background-position: -360px 0;
}
.icon-off {
background-position: -384px 0;
}
.icon-signal {
background-position: -408px 0;
}
.icon-cog {
background-position: -432px 0;
}
.icon-trash {
background-position: -456px 0;
}
.icon-home {
background-position: 0 -24px;
}
.icon-file {
background-position: -24px -24px;
}
.icon-time {
background-position: -48px -24px;
}
.icon-road {
background-position: -72px -24px;
}
.icon-download-alt {
background-position: -96px -24px;
}
.icon-download {
background-position: -120px -24px;
}
.icon-upload {
background-position: -144px -24px;
}
.icon-inbox {
background-position: -168px -24px;
}
.icon-play-circle {
background-position: -192px -24px;
}
.icon-repeat {
background-position: -216px -24px;
}
.icon-refresh {
background-position: -240px -24px;
}
.icon-list-alt {
background-position: -264px -24px;
}
.icon-lock {
background-position: -287px -24px;
}
.icon-flag {
background-position: -312px -24px;
}
.icon-headphones {
background-position: -336px -24px;
}
.icon-volume-off {
background-position: -360px -24px;
}
.icon-volume-down {
background-position: -384px -24px;
}
.icon-volume-up {
background-position: -408px -24px;
}
.icon-grabhandle {
background-position: -456px -24px;
}
.icon-tag {
background-position: 0 -48px;
}
.icon-tags {
background-position: -25px -48px;
}
.icon-book {
background-position: -48px -48px;
}
.icon-bookmark {
background-position: -72px -48px;
}
.icon-print {
background-position: -96px -48px;
}
.icon-camera {
background-position: -120px -48px;
}
.icon-font {
background-position: -144px -48px;
}
.icon-bold {
background-position: -167px -48px;
}
.icon-italic {
background-position: -192px -48px;
}
.icon-text-height {
background-position: -216px -48px;
}
.icon-text-width {
background-position: -240px -48px;
}
.icon-align-left {
background-position: -264px -48px;
}
.icon-align-center {
background-position: -288px -48px;
}
.icon-align-right {
background-position: -312px -48px;
}
.icon-align-justify {
background-position: -336px -48px;
}
.icon-list {
background-position: -360px -48px;
}
.icon-indent-left {
background-position: -384px -48px;
}
.icon-indent-right {
background-position: -408px -48px;
}
.icon-facetime-video {
background-position: -432px -48px;
}
.icon-picture {
background-position: -456px -48px;
}
.icon-pencil {
background-position: 0 -72px;
}
.icon-map-marker {
background-position: -24px -72px;
}
.icon-adjust {
background-position: -48px -72px;
}
.icon-tint {
background-position: -72px -72px;
}
.icon-edit {
background-position: -96px -72px;
}
.icon-share {
background-position: -120px -72px;
}
.icon-check {
background-position: -144px -72px;
}
.icon-move {
background-position: -168px -72px;
}
.icon-step-backward {
background-position: -192px -72px;
}
.icon-fast-backward {
background-position: -216px -72px;
}
.icon-backward {
background-position: -240px -72px;
}
.icon-play {
background-position: -264px -72px;
}
.icon-pause {
background-position: -288px -72px;
}
.icon-stop {
background-position: -312px -72px;
}
.icon-forward {
background-position: -336px -72px;
}
.icon-fast-forward {
background-position: -360px -72px;
}
.icon-step-forward {
background-position: -384px -72px;
}
.icon-eject {
background-position: -408px -72px;
}
.icon-chevron-left {
background-position: -432px -72px;
}
.icon-chevron-right {
background-position: -456px -72px;
}
.icon-plus-sign {
background-position: 0 -96px;
}
.icon-minus-sign {
background-position: -24px -96px;
}
.icon-remove-sign {
background-position: -48px -96px;
}
.icon-ok-sign {
background-position: -72px -96px;
}
.icon-question-sign {
background-position: -96px -96px;
}
.icon-info-sign {
background-position: -120px -96px;
}
.icon-screenshot {
background-position: -144px -96px;
}
.icon-remove-circle {
background-position: -168px -96px;
}
.icon-ok-circle {
background-position: -192px -96px;
}
.icon-ban-circle {
background-position: -216px -96px;
}
.icon-arrow-left {
background-position: -240px -96px;
}
.icon-arrow-right {
background-position: -264px -96px;
}
.icon-arrow-up {
background-position: -289px -96px;
}
.icon-arrow-down {
background-position: -312px -96px;
}
.icon-share-alt {
background-position: -336px -96px;
}
.icon-resize-full {
background-position: -360px -96px;
}
.icon-resize-small {
background-position: -384px -96px;
}
.icon-plus {
background-position: -408px -96px;
}
.icon-minus {
background-position: -433px -96px;
}
.icon-asterisk {
background-position: -456px -96px;
}
.icon-exclamation-sign {
background-position: 0 -120px;
}
.icon-downtick {
margin-left: 0;
background-position: -24px -120px;
}
.icon-leaf {
background-position: -48px -120px;
}
.icon-fire {
background-position: -72px -120px;
}
.icon-eye-open {
background-position: -96px -120px;
}
.icon-eye-close {
background-position: -120px -120px;
}
.icon-warning-sign {
background-position: -144px -120px;
}
.icon-plane {
background-position: -168px -120px;
}
.icon-calendar {
background-position: -192px -120px;
}
.icon-random {
background-position: -216px -120px;
}
.icon-comment {
background-position: -240px -120px;
}
.icon-magnet {
background-position: -264px -120px;
}
.icon-chevron-up {
background-position: -288px -120px;
}
.icon-chevron-down {
background-position: -313px -119px;
}
.icon-retweet {
background-position: -336px -120px;
}
.icon-shopping-cart {
background-position: -360px -120px;
}
.icon-folder-close {
background-position: -384px -120px;
}
.icon-folder-open {
background-position: -408px -120px;
}
.icon-resize-vertical {
background-position: -432px -119px;
}
.icon-resize-horizontal {
background-position: -456px -118px;
}
.icon-hdd {
background-position: 0 -144px;
}
.icon-bullhorn {
background-position: -24px -144px;
}
.icon-bell {
background-position: -48px -144px;
}
.icon-certificate {
background-position: -72px -144px;
}
.icon-thumbs-up {
background-position: -96px -144px;
}
.icon-thumbs-down {
background-position: -120px -144px;
}
.icon-hand-right {
background-position: -144px -144px;
}
.icon-hand-left {
background-position: -168px -144px;
}
.icon-hand-up {
background-position: -192px -144px;
}
.icon-hand-down {
background-position: -216px -144px;
}
.icon-circle-arrow-right {
background-position: -240px -144px;
}
.icon-circle-arrow-left {
background-position: -264px -144px;
}
.icon-circle-arrow-up {
background-position: -288px -144px;
}
.icon-circle-arrow-down {
background-position: -312px -144px;
}
.icon-globe {
background-position: -336px -144px;
}
.icon-wrench {
background-position: -360px -144px;
}
.icon-tasks {
background-position: -384px -144px;
}
.icon-filter {
background-position: -408px -144px;
}
.icon-briefcase {
background-position: -432px -144px;
}
.icon-fullscreen {
background-position: -456px -144px;
}

8
css/common.less Normal file
Просмотреть файл

@ -0,0 +1,8 @@
/*********************************************************
* Base Styles
*/
body {
background: @baseStage;
overflow: hidden;
}

273
css/controls.css Executable file
Просмотреть файл

@ -0,0 +1,273 @@
/* @override
http://ocupopdev.com/popcorn-embed/css/controls.css */
.controls {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: white;
border: 1px solid #d5d6d5;
height: 41px;
visibility: visible;
opacity: 1;
}
.controls-hide {
opacity: 0;
}
#butter-controls {
color: #F8FDE2;
position: relative;
height: 22px;
width: 100%;
display: block;
background:white;
}
/* Big Button */
#controls-big-play-button {
display: none;
width: 57px;
height: 66px;
background: url(../resources/controls/icon_play.png) no-repeat;
position: absolute;
top: 50%;
left: 50%;
margin-top: -33px;
margin-left: -26px;
z-index: 9999;
cursor: pointer;
}
#controls-big-play-button.controls-ready {
display: block;
}
#butter-controls.controls-active {
/* active happens on mouse over and is reverted to default on mouse out */
display: block;
}
#butter-controls.controls-ready/*, #controls-big-play-button.controls-ready*/ {
/* ready happens when the template is exported, and not while in the editor */
/* remove this if you want the big play button and active to show controls */
display: block;
}
#controls-big-play-button.controls-ready {
display: block;
}
.controls-left {
height: 22px;
position: absolute;
width: 35px;
}
.controls-middle {
margin-top: 0;
height: 41px;
position: absolute;
left: 185px;
right: 215px;
padding-right: 20px;
border-right: 1px solid #d5d6d5;
}
.controls-right {
padding-top: 0;
height: 41px;
position: absolute;
width: 108px;
right: 45px;
background: #f2f2f2;
border-right: 1px solid #d5d6d5;
border-left: 1px solid #d5d6d5;
}
#controls-timebar {
position: absolute;
background: #e4e4e4;
height: 15px;
left: 0;
right: 0;
top: 12px;
margin-right: 12px;
border-radius: 0;
box-shadow: none;
cursor: pointer;
}
#controls-progressbar {
height: 100%;
background: #1eb771;
position: absolute;
border-radius: 0;
}
/* Time */
.time {
position: absolute;
left: 43px;
height: 27px;
background: url(../resources/controls/controls_time_arrowL.gif) no-repeat left top, url(../resources/controls/controls_time_arrowR.gif) right top no-repeat;
background-color: #f2f2f2;
font-size: 12px;
line-height: 1em;
padding-top: 14px;
padding-right: 15px;
padding-left: 15px;
color: #58595b;
}
#controls-currenttime {
color: #1eb771;
position: relative;
text-align: left;
left: auto;
right: auto;
font-size: 12px;
line-height: 1em;
}
#controls-duration {
left: auto;
right: auto;
position: relative;
text-align: left;
font-size: 12px;
line-height: 1em;
}
/* Volume */
#controls-volume-container {
position: absolute;
right: 165px;
width: 38px;
}
#controls-volume {
position: absolute;
background: #e4e4e4;
height: 15px;
left: 7px;
right: 0;
top: 12px;
border-radius: 0;
box-shadow: none;
cursor: pointer;
}
#controls-volume-progressbar {
height: 100%;
background: #1eb771;
position: absolute;
border-radius: 0;
}
#controls-volume-scrubber {
height: 100%;
width: 2px;
background: #EEE;
position: absolute;
cursor: pointer;
}
#controls-volume-scrubber:after {
content: "";
position: absolute;
width: 8px;
height: 15px;
border-radius: 15px;
background: #EEE;
top: -2px;
left: -5px;
}
/* Buttons */
#controls-play {
width: 28px;
height: 28px;
background: #1eb771 url(../resources/controls/controls_icon_playPause.png) no-repeat;
padding: 0;
border-radius: 3px;
margin-top: 7px;
margin-left: 7px;
cursor: pointer;
}
#controls-mute {
width: 14px;
height: 14px;
background-image: url(../resources/glyphicons-halflings.png);
cursor: pointer;
padding: 0;
left: -8px;
top: 12px;
}
.controls-btn {
padding: 10px;
position: absolute;
top: 0;
}
.controls-playing {
background-position: center -15px !important;
}
.controls-paused {
background-position: center 10px !important;
}
.controls-muted {
background-position: -360px -24px;
}
.controls-unmuted {
background-position: -384px -24px;
}
.controls-right div {
background: #cecfcf url(../resources/controls/controls_icons.png) no-repeat;
margin-top: 6px;
margin-left: 6px;
width: 28px;
height: 28px;
border-radius: 3px;
float: left;
cursor: pointer;
}
.controls-right div:hover {
background-color: #1eb771;
}
#controls-share {
background-position: -27px -2px;
}
#controls-remix {
background-position: -61px -2px;
}
#controls-fullscreen {
background-position: 7px -2px;
}
#controls-logo {
width: 45px;
height: 42px;
background: url(../resources/controls/controls_icons.png) no-repeat -85px 2px;
position: absolute;
right: 0;
cursor: pointer;
}

241
css/dialog.less Normal file
Просмотреть файл

@ -0,0 +1,241 @@
/*********************************************************
* DIALOGS
*/
@_overlayOpacity: 0.75; // How dark is the overlay?
@_dialogWidth: 600px; // Width of dialog
@_dialogPadding: 25px; // Inner padding
@_dialogTopMargin: 100px; // How far should the dialog be from the top of the window?
.butter-modal-overlay {
z-index: @MODAL_Z_INDEX;
position: fixed;
top: 0;
left: 0;
opacity: 0;
&.fade-in {
opacity: 1;
.transition( opacity 0.3s );
}
&.butter-modal-overlay-dark-bg {
right: 0;
bottom: 0;
background: rgba( 0, 0, 0, @_overlayOpacity );
}
}
.butter-dialog {
font-size: 14px;
line-height: 1.4;
color: @baseText;
position: relative;
width: @_dialogWidth;
padding: @_dialogPadding;
margin: @_dialogTopMargin auto;
background: #FFF;
border-radius: 2px;
box-shadow: 0 10px 10px -10px #000;
.box-sizing( border-box );
&.small {
width: 330px;
text-align: center;
}
.close-button {
position: absolute;
top: 10px;
right: 10px;
cursor: pointer;
opacity: 0.5;
&:hover {
opacity: 1;
}
}
.butter-dialog-title {
margin: 0 0 10px 0;
}
.butter-form {
margin-bottom: 10px;
textarea {
padding: 10px;
min-height: 150px;
}
}
.butter-dialog-body {
padding: 20px;
}
}
.butter-feedback-form {
padding: 0;
background: lighten( #EEE, 3% );
font-size: 13px;
.butter-dialog-title {
border-bottom: 1px solid #CCC;
.gradient( "vertical", #FFF, #EEE );
box-shadow: 0 1px #FFF;
border-radius: 3px 3px 0 0;
padding: 10px 35px;
font-size: 18px;
font-weight: 700;
margin: 0;
.icon-info-sign {
opacity: 0.2;
margin: 3px 0 0 3px;
cursor: pointer;
&:hover {
opacity: 0.7;
}
}
p {
font-weight: normal;
font-size: 13px;
}
}
.butter-logo {
position: absolute;
top: -35px;
left: -25px;
width: 50px;
}
.butter-form {
padding: 8px 0;
label {
position: relative;
padding-left: 60px;
padding-bottom: 20px;
}
textarea {
font-size: 11px;
font-family: menlo, mono;
box-shadow: 0 1px 0 #FFF;
&:focus {
box-shadow: inset 0 2px 4px -2px #CCC,
0 1px 0 #FFF;
border-color: #AAA;
.transition( all 0.5s ease );
}
}
}
strong {
color: @red;
}
.butter-btn {
font-weight: 700;
font-size: 11px;
text-transform: uppercase;
}
}
.dialog-hidden {
display: none;
}
.ticket-icon {
display: block;
position: absolute;
top: 5px;
left: 0;
height: 50px;
width: 50px;
background: url( "/resources/icons/ticket.png" ) top center no-repeat;
}
/*********************************************************
* First-run
*/
.first-run {
.butter-editor-area {
z-index: @MODAL_Z_INDEX + 1;
}
.butter-modal-container {
position: relative;
z-index: @MODAL_Z_INDEX + 1;
}
}
.butter-first-run-dialog {
position: fixed;
right: 360px;
top: 80px;
-moz-animation: bounce 1s infinite;
-webkit-animation: bounce 1s infinite;
-o-animation: bounce 1s infinite;
animation: bounce 1s infinite;
.butter-logo {
top: -15px;
left: 0;
}
.triangle( "right", 20px, @baseLight, 1px, #CCC );
.butter-dialog-title {
padding: 10px 0 10px 50px;
}
p {
margin-top: 0;
}
width: 400px;
}
.overlay-highlight {
z-index: @MODAL_Z_INDEX + 1;
position: relative;
}
.butter-tooltip.tooltip-popup,
.butter-tooltip.tooltip-media {
background: #F8EDAB;
h3 {
margin: 0 0 5px 0;
font-style: normal;
font-weight: 700;
border-radius: 3px;
}
&:after {
border-bottom-color: #F8EDAB;
}
}
.butter-tooltip.tooltip-popup {
text-transform: none;
}
.butter-tooltip.tooltip-media {
width: 150px;
margin-left: -75px;
.center-div {
display: inline-block;
}
}
@-moz-keyframes bounce {
0% { right: 360px; }
50% { right: 345px; }
100% { right: 360px; }
}
@-webkit-keyframes bounce {
0% { right: 360px; }
50% { right: 345px; }
100% { right: 360px; }
}
@-o-keyframes bounce {
0% { right: 360px; }
50% { right: 345px; }
100% { right: 360px; }
}
@keyframes bounce {
0% { right: 360px; }
50% { right: 345px; }
100% { right: 360px; }
}

567
css/editors.less Normal file
Просмотреть файл

@ -0,0 +1,567 @@
/*********************************************************
* EDITORS
*/
@_editorHeaderHeight: 45px;
@_editorHeaderBorder: 5px;
@_editorTitle: 50px;
@_editorTabs: 30px;
@_editorScrollbarWidth: 15px;
/*********************************************************
* Editor Header
*/
.butter-editor-header {
position: absolute;
top: 0;
right: 0;
left: -1px;
border-bottom: @_editorHeaderBorder solid @green;
height: @_editorHeaderHeight;
background: @baseDark;
> ul {
margin: 0;
padding: 0;
width: 100%;
list-style: none;
.clearfix();
> li {
float: left;
}
}
} // .butter-editor-header
.butter-editor-header .butter-btn {
background: none;
border: none;
color: #FFF;
font-size: 14px;
padding: 0 10px;
border-radius: 0;
height: @_editorHeaderHeight;
line-height: @_editorHeaderHeight;
>.icon {
margin-top: 1px;
margin-left: 0;
margin-right: 2px;
}
&.butter-active {
background: @green;
}
}
.butter-editor-close-btn {
position: fixed;
top: @HEADER_SPACING;
right: 0;
z-index: @HEADER_Z_INDEX + 1;
&.toggled .icon {
background-image: url("../resources/glyphicons-halflings.png");
background-position: -432px -72px; /* from icon-chevron-right */
}
}
/*********************************************************
* Main Editor Area
*/
.butter-editor-area {
position: fixed;
z-index: @EDITOR_Z_INDEX;
top: @HEADER_SPACING;
bottom: 0;
right: 0;
width: @EDITOR_WIDTH;
background: @baseLight;
.transition( right 0.35s );
border-left: 1px solid @baseLightOutline;
&.minimized {
right: -@EDITOR_WIDTH - 2;
}
// Tabs, breadcrumbs
.butter-editor {
color: @baseText;
font-size: 13px;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
.butter-editor-tabs {
padding: 0 20px;
margin: 0;
list-style: none;
height: @_editorTabs;
> li {
float: left;
margin-right: 3px;
margin-top: -1px;
a:hover {
color: #555;
}
}
.butter-btn {
background: lighten( @baseLightOutline, 10% );
border-radius: 0;
border: 1px solid @baseLightOutline;
&.butter-active {
background: #FFF;
border-top-color: #FFF;
}
}
}
h1, .butter-breadcrumbs { // Breadcrumbs
color: @green;
font-weight: 700;
font-size: 16px;
text-transform: capitalize;
height: @_editorTitle;
line-height: @_editorTitle;
margin: 0;
padding: 0 20px;
background: #FFF;
border-bottom: 1px solid @baseLightOutline;
.box-sizing( border-box );
.butter-breadcrumbs-back {
opacity: 0.6;
position: relative;
padding-right: 15px;
margin-right: 15px;
display: inline-block;
cursor: pointer;
border-right: 1px solid @baseLightOutline;
.triangle( "right", 5px, #FFF, 1px, @baseLightOutline );
&:hover {
opacity: 1;
}
}
.close-btn {
position: relative;
float: right;
top: 10px;
left: 10px;
line-height: 14px;
cursor: pointer;
}
}
// Tabs
.editor-tabs {
position: absolute;
top: 60px;
left: 5px;
bottom: 5px;
right: 5px;
border-radius: 5px;
> button {
margin: 10px;
}
}
}
// States
.display-off {
display: none;
}
.butter-editor-disabled {
opacity: 0.5;
}
// Errors
.error-message-container {
.transition( height 0.35s, margin 0.35s, padding 0.35s );
overflow: hidden;
height: 0;
color: #D93B21;
visibility: hidden;
width: 300px;
}
.error-message-container.open {
padding: 10px 20px;
padding-right: 15px; //scrollbars
background: #F0DDDD;
}
// Spacing
.butter-editor-spacing {
padding-right: @EDITOR_WIDTH;
}
.butter-editor-spacing.editor-minimized {
padding-right: 0;
}
.butter-editor-content {
position: absolute;
top: @_editorHeaderHeight + @_editorHeaderBorder;
bottom: 0;
width: 100%;
}
.butter-editor-body {
position: absolute;
top: @_editorTitle;
bottom: 0;
width: 100%;
}
.butter-editor-body.butter-tabs-spacing {
top: @_editorTitle + @_editorTabs;
}
// Scrollbars
.scrollbar-outer {
position: absolute;
top: 0;
right: @_editorScrollbarWidth;
left: 0;
bottom: 0;
overflow: hidden;
}
.allow-scrollbar {
.butter-scroll-bar.butter-scroll-bar-v {
top: 5px;
right: 5px;
width: 10px;
bottom: 5px;
}
}
} //editor area
/*********************************************************
* Editor UI styles
*/
@_editorInnerWidth: 286px;
@_editorTextIndent: 40px;
@_editorInputHeight: 32px;
.butter-form {
// Container building blocks
.trackevent-property,
.trackevent-warning,
fieldset {
border: none;
margin: 10px 20px;
padding: 0;
.clearfix();
}
.trackevent-warning {
background: darken( @baseLight, 10% );
padding: 10px;
border-radius: 5px;
}
// Label
label.property-name,
label {
display: block;
font-size: 13px;
padding: 5px 0;
}
.editor-section-header {
font-weight: 700;
font-size: 16px;
margin-bottom: 5px;
}
// Inputs
select,
input,
textarea {
height: @_editorInputHeight;
width: 100%;
padding: 6px;
margin-bottom: 5px;
border: 1px solid @baseLightOutline;
border-top-color: darken( @baseLightOutline, 10% );
border-radius: 2px;
.box-sizing( border-box );
&:-moz-placeholder {
color: #AAA;
}
&:focus {
outline: none;
border: 1px solid @green;
.transition( border 0.25s ease );
}
}
// Textareas
textarea {
height: 100px;
line-height: 1.3;
resize: none;
}
// Radios
.butter-form-radio,
.butter-form-checkbox {
line-height: 1.3em;
padding-left: @_editorTextIndent;
padding-top: 0;
border: none;
}
input[type="radio"],
input[type="checkbox"] {
float: left;
vertical-align: text-top;
width: 12px;
height: 13px;
margin-left: -@_editorTextIndent + 2;
margin-top: 2px;
}
.checkbox-group {
float: left;
margin-right: 10px;
input[type="checkbox"] {
float: right;
}
}
// Error/Invalid
input:invalid,
textarea:invalid,
input:invalid + .butter-unit {
border-color: @red;
}
// Units
.butter-form-append {
position: relative;
display: inline-block;
.clearfix();
>input {
height: @_editorInputHeight;
float: left;
border-radius: 2px 0 0 2px;
width: 200px;
}
}
.butter-unit {
height: @_editorInputHeight - 2;
position: absolute;
right: -2px;
bottom: 5px;
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
font-size: 10px;
line-height: @_editorInputHeight;
padding: 0 5px;
min-width: 16px;
font-weight: normal;
text-align: center;
background-color: @baseLight;
color: #777;
border: 1px solid @baseLightOutline;
border-radius: 0 3px 3px 0;
}
// Inline form styles
.butter-form-inline.form-single {
.clearfix();
label {
.box-sizing( border-box );
width: 80px;
float: left;
padding: 0;
margin-top: 5px;
}
> input, textarea, select {
float: left;
margin-left: 5px;
width: @_editorInnerWidth - 85;
}
}
.butter-form-inline.form-half{
.butter-form-radio,
.butter-form-checkbox {
.box-sizing( border-box );
width: @_editorInnerWidth / 2;
float: left;
}
> input,
> select,
.butter-form-append {
&:last-child {
margin-left: 10px;
}
position: relative;
width: @_editorInnerWidth / 2 - 10;
float: left;
> input {
width: 100%;
}
}
}
code, pre {
font-size: 10px;
}
} // Fieldset
/*********************************************************
* Scrollbars
*/
.butter-scroll-bar {
position: absolute;
background: darken( @baseLight, 30% );
box-shadow: 0 0 1px rgba( 0, 0, 0, 0.3 );
border-radius: 15px;
.selectable( none );
overflow: hidden;
}
.butter-scroll-handle {
height: 8px;
top: 1px;
position: absolute;
background: darken( @baseLight, 10% );
border-radius: 15px;
&:hover,
&.butter-scollbar-active {
background: darken( @baseLight, 50% );
cursor: pointer;
}
}
.butter-scroll-bar-v {
.butter-scroll-handle {
width: 8px;
left: 1px;
}
}
/*********************************************************
* Tooltip
*/
* {
&:hover > .butter-tooltip:not(.tooltip-no-hover) {
opacity: 1;
visibility: visible;
.transition( opacity 0.3s ease 0.5s );
}
}
.butter-tooltip {
@_tooltipWidth: 110px;
&.tooltip-on {
opacity: 1;
visibility: visible;
.transition( opacity 0.3s ease 0.5s );
}
&.tooltip-no-transition-on {
opacity: 1;
visibility: visible;
}
&.tooltip-error {
color: red;
}
visibility: hidden;
opacity: 0;
position: absolute;
top: 100%;
left: 50%;
margin-top: -7px;
margin-left: -@_tooltipWidth / 2;
z-index: @MODAL_Z_INDEX;
width: @_tooltipWidth;
padding: 10px;
background: #FFF;
text-shadow: none;
color: #555;
line-height: 15px;
font-size: 11px;
.box-sizing( border-box );
border-radius: 2px;
text-align: center;
border: 1px solid #CCC;
box-shadow: 0 5px 6px -5px rgba( 0, 0, 0, .3 );
pointer-events: none; /* csslint-ignore: better for browsers that support it, not critical for those that don't */
.transition( opacity 0.3s ease 0 );
&:after,
&:before {
content: "";
position: absolute;
top: -5px;
left: 50%;
margin-left: -5px;
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-bottom: 5px solid #FFF;
}
&:after {
z-index: @MODAL_Z_INDEX + 1;
}
&:before {
top: -6px;
border-bottom: 5px solid #CCC;
}
}
.butter-no-top-margin {
margin-top: 0;
}
/*********************************************************
* Draggable/Resizable
*/
.editor-disable-pointer-events {
pointer-events: none; /* csslint-ignore: better for browsers that support it, not critical for those that don't */
}
.editor-drag-handle {
-webkit-transition: opacity .3s ease;
-moz-transition: opacity .3s ease;
-ms-transition: opacity .3s ease;
-o-transition: opacity .3s ease;
transition: opacity .3s ease;
opacity: 0.4;
z-index: 5000;
height: 32px;
width: 32px;
position: absolute;
top: 15px;
left: 15px;
&:hover {
opacity: 1;
}
}

41
css/embed-shell.less Normal file
Просмотреть файл

@ -0,0 +1,41 @@
@controlsHeight: 43px;
@small: 560px;
@medium: 640px;
@large: 853px;
@xlarge: 1280px;
.height-width( @width ) {
width: @width;
height: (.5625 * @width ) + @controlsHeight;
}
iframe {
display: block;
margin: 60px auto;
border: 0;
}
@media only screen and ( max-width: @medium ) {
iframe {
.height-width( @small );
}
}
@media only screen and ( min-width: @medium ) {
iframe {
.height-width( @medium );
}
}
@media only screen and ( min-width: @large ) {
iframe {
.height-width( @large );
}
}
@media only screen and ( min-width: @xlarge ) {
iframe {
.height-width( @xlarge );
}
}

813
css/embed.less Normal file
Просмотреть файл

@ -0,0 +1,813 @@
// GLOBALS
.transition(@transition1) {
-webkit-transition: @transition1;
-moz-transition: @transition1;
-o-transition: @transition1;
-ms-transition: @transition1;
transition: @transition1;
}
@creditsLogoSize: 25px;
@creditsZindex: 100000000000000;
@red: #CB3E21;
html {
height: 100%;
width: 100%;
overflow: hidden;
}
body {
padding: 0;
margin: 0;
color: #F8FDE2;
font-weight: 300;
font-size: 14px;
font-family: "Helvetica Neue", sans-serif;
background: #000;
}
#controls-big-play-button {
width: 57px;
height: 66px;
background: url( "../resources/controls/icon_play.png" ) no-repeat;
position: absolute;
top: 50%;
left: 50%;
margin-top: -33px;
margin-left: -26px;
z-index: 9999;
cursor: pointer;
}
.embed {
width: 100%;
height: 100%;
}
.container {
position: relative;
width: 100%;
height: 100%;
}
.video {
position: relative;
width: 100%;
padding-bottom: 56.25%;
height: 0;
}
.video > div video,
.video > div iframe,
.video > object,
.embed-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.embed-overlay {
background: rgba( 255, 255, 255, 0.95 );
color: #676867;
}
.embed-nav {
@_embedIconsSize: 50px;
list-style: none;
padding: 0;
> li {
display: inline-block;
margin-right: 10px;
> a {
display: block;
padding-top: @_embedIconsSize;
width: @_embedIconsSize;
text-decoration: none;
color: #EEE;
text-align: center;
opacity: 0.7;
.transition( opacity .3s ease);
&:hover {
opacity: 1;
}
}
}
}
/*********************************************************
* Credits
*/
.attribution-info {
font-family: "Meta", "Open Sans", "Helvetica Neue", sans-serif;
z-index: @creditsZindex;
}
.attribution-logo {
position: absolute;
top: 15px;
left: 15px;
width: @creditsLogoSize;
height: @creditsLogoSize;
border-radius: @creditsLogoSize;
text-align: center;
background-color: @red;
color: #FFF;
z-index: 1;
opacity: 0.5;
cursor: pointer;
.transition( all 0.1s ease );
&:before {
position: relative;
top: 3px;
font-size: 30px;
font-family: Georgia, serif;
content: '\0201c';
}
&:hover {
opacity: 1;
.attribution-text {
visibility: visible;
}
}
}
// "Credits"
.attribution-text {
position: absolute;
height: @creditsLogoSize;
line-height: @creditsLogoSize;
left: @creditsLogoSize + 6;
visibility: hidden;
text-shadow: 0 0 5px rgba( 0, 0, 0, 0.3 );
.transition( all 0.1s ease );
}
@media only screen and ( min-width: @large ) {
.attribution-logo {
width: @creditsLogoSize + 5;
height: @creditsLogoSize + 5;
border-radius: @creditsLogoSize + 5;
&:before { font-size: 40px; }
}
.attribution-text {
left: @creditsLogoSize + 14;
line-height: @creditsLogoSize + 5;
}
}
.attribution-details {
position: absolute;
top: 0;
left: 0;
right: 0;
padding-left: 3.5em;
padding-top: 1em;
padding-bottom: 1em;
background: rgba( 255, 255, 255, 0.9 );
color: black;
border-bottom: 1px solid rgba( 0, 0, 0, 0.2 );
font-size: 16px;
.transition( opacity .1s ease );
}
// Adjust font size for other sizes
@media only screen and ( min-width: @medium ) {
.attribution-details {
font-size: 18px;
}
}
@media only screen and ( min-width: @large ) {
.attribution-details {
font-size: 24px;
}
}
.attribution-mopop {
color: @red;
font-size: 0.9em;
font-weight: 700;
}
.attribution-title {
font-size: 1.3em;
line-height: 1.6;
}
.attribution-author {
font-style: italic;
}
.attribution-media {
margin-top: 0.5em;
font-size: 0.8em;
&:before {
display: block;
float: left;
content: "Media";
text-transform: uppercase;
font-size: 0.8em;
font-weight: 700;
margin-right: 2em;
}
.media-btn {
float: left;
}
.attribution-media-src {
float: left;
white-space: nowrap;
text-decoration: none;
color: #555;
max-width: 70%;
overflow: hidden;
text-overflow: ellipsis;
}
}
// The x in the top right corner
.attribution-close {
position: absolute;
top: 10px;
right: 10px;
font-size: 8px;
font-weight: 700;
opacity: 0.5;
cursor: pointer;
&:hover {
opacity: 1;
}
}
// CREDITS STATES
// State: default
// hide credits panel
.attribution-details {
opacity: 0;
visibility: hidden;
}
// State: attribution-on
// when the logo is clicked, show the credits
.attribution-on {
.attribution-logo {
opacity: 1;
.attribution-text {
visibility: hidden;
}
}
.attribution-details {
opacity: 1;
visibility: visible;
}
}
/*********************************************************
* Icons for credits
*/
.media-icon {
position: relative;
float: left;
margin-right: 5px;
margin-top: 2px;
background: url( "../resources/media-icons.png" ) no-repeat;
height: 16px;
width: 16px;
&.html5-icon {
background-position: 0 0;
}
&.youtube-icon {
background-position: -16px 0;
}
&.vimeo-icon {
background-position: -32px 0;
}
&.soundcloud-icon {
background-position: -48px 0;
}
}
.attribution-close {
background-image: url("../resources/glyphicons-halflings-alt.png");
width: 14px;
height: 14px;
background-position: -432px -24px;
}
/*********************************************************
SHARED STYLES BETWEEN POSTROLL AND SHARE STATES */
#post-roll,
#share {
font-family: "Meta", "Open Sans", "Helvetica Neue", sans-serif;
text-shadow: 1px 1px 0 rgba( 255, 255, 255, 0.4 );
font-size: 1.5em;
line-height: 1.4em;
color: #676867;
a {
color: #676867;
}
.post-roll-inner, .share-inner {
position: relative;
padding: 10%;
font-family: "Meta", "Open Sans", "Helvetica Neue", sans-serif;
text-shadow: 1px 1px 0 rgba( 255, 255, 255, 0.4 );
}
.embed-project,
.share-options {
margin-left: 30%;
margin-top: 0;
padding-left: 0;
}
.mozpopcorn {
color: #D53B2A;
font-weight: bold;
font-size: 16px;
margin: 0;
line-height: 1em;
}
.embed-logo {
float: left;
background: url( "../resources/controls/postroll_logo.png" ) 0 0 no-repeat;
width: 30%;
background-size: 100%;
max-width: 231px;
height: 388px;
margin-left: 5%;
margin-right: 5%;
margin-top: -5%;
}
.embed-title {
font-size: 32px;
font-weight: 700;
color: #676867;
line-height: 1em;
}
.embed-author {
margin-bottom: 1em;
color: #676867;
font-size: 20px;
}
}
/*********************************************************
* POST ROLL
*/
#post-roll {
.post-roll-description {
color: #676867;
font-size: 20px;
}
.embed-nav > li > a {
background: rgba( 0, 0, 0, 0.3 ) url( "../resources/controls/controls_icons.png" ) no-repeat;
margin: 0;
padding: 0;
position: relative;
border-radius: 7px;
width: 58px;
height: 58px;
}
.embed-nav > li > a span {
color: #676867;
display: block;
position: absolute;
top: 60px;
font-size: 16px;
}
#replay-post {
background-position: 9px -28px;
cursor: pointer;
}
#replay-post:hover {
background-color: #2BB673;
}
#remix-post {
background-position: 9px -78px;
cursor: pointer;
}
#remix-post:hover {
background-color: #002839;
}
#share-post {
background-position: 9px -123px;
cursor: pointer;
}
#share-post:hover {
background-color: #D53B2A;
}
} // post-roll
/*********************************************************
* SHARE
*/
#share {
.embed-nav {
@_shareIconsSize: 80px;
> li {
width: @_shareIconsSize;
height: @_shareIconsSize / 2;
}
}
#share-close {
position: fixed;
right: 10px;
top: 10px;
font-size: 16px;
text-decoration: none;
background: rgba( 0, 0, 0, 0.3 );
text-shadow: 1px 1px 0 rgba( 0, 0, 0, 0.2 );
border-radius: 3px;
display: block;
color: white;
text-align: center;
padding-top: 6px;
line-height: 1em;
width: 28px;
height: 23px;
cursor: pointer;
}
#share-close:hover {
background: #1eb771;
}
.share-options input,
.share-options textarea {
font-size: 12px;
resize: none;
width: 70%;
padding: 10px;
float: left;
border-radius: 2px;
background: white;
border: 1px solid #d5d6d5;
&:focus {
outline: none;
box-shadow: 0 0 0 1px #2BB673;
}
}
.share-options textarea {
height: 90px;
font-family: menlo, monospace;
}
.share-options fieldset {
border: none;
padding: 5px 0;
}
.share-options label {
display: block;
padding-bottom: 0;
font-size: 16px;
}
.share-buttons {
float: left;
margin: 0;
margin-left: 20px;
display: none;
}
.share-buttons > li > a {
padding: 3px;
}
.share-size {
float: left;
margin-left: 10px;
line-height: 1em;
position: relative;
}
.size-options {
background: #ababab;
border-radius: 5px;
position: absolute;
z-index: 9999;
width: 113px;
a {
font-size: 12px;
color: white;
text-shadow: 1px 1px 0 rgba( 0, 0, 0, 0.2 );
text-decoration: none;
border-bottom: 1px solid rgba( 255, 255, 255, 0.3 );
padding: 3px 0;
display: none;
}
a:last-child {
border-bottom: none;
}
a:hover {
background:#1EB771;
}
a.current {
display: block;
background: url( "../resources/controls/embed_size_sprite.png" ) no-repeat 90px 15px;
}
a.current:hover {
display: block;
background: #1EB771;
}
a:first-child:hover {
border-top-left-radius: 5px;
border-top-right-radius: 5px;
}
a:last-child:hover {
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
}
span {
display: block;
}
span.icon {
background: url( "../resources/controls/embed_size_sprite.png" ) no-repeat;
width: 36px;
height: 29px;
margin: 0 3px;
float: left;
}
span.size{
font-weight: bold;
line-height: 1.2em;
}
span.dimensions {
line-height: 1.2em;
}
.small .icon {
background-position: center -14px;
}
.medium .icon {
background-position: center -52px;
}
.large .icon {
background-position: center -91px;
}
.xlarge .icon {
background-position: center -131px;
}
}
.size-options:hover a {
display: block;
background-image: none;
}
.size-options:hover a.current {
background: url( "../resources/controls/embed_size_sprite.png" ) no-repeat 88px -162px;
}
}
/*********************************************************
* MEDIA QUERIES
*/
@small: 560px;
@medium: 640px;
@large: 853px;
@xlarge: 1280px;
@media only screen and ( max-width: @large ) {
#post-roll, #share {
.embed-logo {
width: 20%;
background-size: 100%;
margin-left: 10%;
margin-top: 0;
height: 255px;
}
.embed-author {
margin-bottom: 1em;
font-size: 18px;
}
.post-roll-inner, .share-inner {
left: 0;
width: 80%;
padding: 5%;
top: 10%;
}
}
#post-roll .post-roll-description {
font-size: 16px;
line-height: 1.2em;
}
#share {
.share-options {
input {
width: 70%;
}
fieldset {
padding: 3px 0;
min-width: 0;
width: 90%;
}
textarea {
height: 110px;
width: 70%;
min-width: 0;
}
input {
width: 70%;
min-width: 0;
}
label {
float: left;
width: 20%;
padding-top: 8px;
}
}
.share-size {
float: none;
margin-left: 97%;
width: 100%;
.size-options {
bottom: -35px;
}
}
.share-size label {
float: left;
width: 20%;
padding-top: 2px;
}
}
}
@media only screen and ( max-width: @small ) {
#post-roll, #share {
font-size: 13px;
.embed-title {
margin-bottom: 3px;
font-size: 25px;
}
.mozpopcorn {
font-size: 12px;
}
.embed-author {
font-size: 14px;
}
.embed-logo {
width: 20%;
margin-left: 20px;
margin-top: 0;
height: 175px;
}
.embed-project {
margin-top: 0;
padding-left: 0;
}
.post-roll-inner, .share-inner {
width: 90%;
top: 0;
}
}
.post-roll-description {
display: none;
}
#share {
.embed-logo {
display: none;
}
.embed-project, .share-options {
margin-left: 0;
}
.share-options {
fieldset {
padding: 3px 0;
min-width: 0;
width: 100%;
}
textarea {
height: 90px;
width: 70%;
min-width: 0;
}
input {
width: 70%;
min-width: 0;
}
label {
float: left;
width: 20%;
padding-top: 8px;
}
.share-size {
float: none;
width: 100%;
margin-top: 115px;
margin-left: 20%;
}
}
}
.embed-info {
.embed-logo-small {
width: 63px;
height: 63px;
margin-right: 10px;
}
.mozpop {
font-size: .9em;
}
.embed-title {
font-size: 1.3em;
}
.embed-author {
font-size: 1em;
}
.embed-details {
margin-top: 0;
}
}
#play {
width: 40px;
background-size: 100%;
margin-top: 0;
}
}

205
css/globals.less Normal file
Просмотреть файл

@ -0,0 +1,205 @@
// SPACING
@WEBMAKERNAV_SPACING: 28px;
@PRIMARYHEADER_SPACING: 62px;
@HEADER_SPACING: @WEBMAKERNAV_SPACING + @PRIMARYHEADER_SPACING;
@TRAY_HEIGHT: 220px;
@TRAY_SPACING: @TRAY_HEIGHT - 10;
@TRAY_SPACING_MINIMIZED: 35px;
@TABZILLA_HEIGHT: 210px;
// Z INDEXES
@BASE_Z_INDEX: 100000000;
@HEADER_Z_INDEX: @BASE_Z_INDEX + 10;
@TRAY_Z_INDEX: @BASE_Z_INDEX + 1;
@EDITOR_Z_INDEX: @BASE_Z_INDEX + 1;
@MODAL_Z_INDEX: @BASE_Z_INDEX + 100;
// EDITOR
@EDITOR_WIDTH: 350px;
// COLORS
@COLOR_SELECTION: #FEF49C;
@baseDark: #052938;
@baseLight: #F4F5F5;
@baseStage: #E7EDF0;
@green: #3FB58E;
@greyGreen: #c2c6b3;
@red: #CB3E21;
@baseLightOutline: #CCC;
@baseText: #555;
@editorOutline: #CCC;
@midGrey: #D9DDDD;
@darkGrey: #3A3C3C;
// MIXINS
.selectable(@param) {
-webkit-touch-callout: @param;
-webkit-user-select: @param;
-moz-user-select: @param;
-ms-user-select: @param;
user-select: @param;
}
// GRADIENTS
// Usage:
// .gradient( "vertical", red, blue ); -> Creates a gradient from top = red to bottom = blue.
// .gradient( "horizonal", red, blue ); -> Creates a gradient from left = red to right = blue.
// .gradient( "vertical", red, blue, true ); -> Creates a gradient with a solid-color fallback. This is
// necessary for IE9 when the container has a border-radius of 2+
._gradient( @color1, @color2, @startLoc, @endLoc, @from, @to, @ieFallback: false, @ieGradientType: 0 ) when ( @ieFallback ) {
// Internal mixin for generating gradients when there are rounded corners
background: @color1;
background: -webkit-gradient( linear, @startLoc, @endLoc, color-stop( @from, @color1 ), color-stop( @to, @color2 ) );
background: -webkit-linear-gradient( @startLoc, @color1 @from, @color2 @to );
background: -moz-linear-gradient( @startLoc, @color1 @from, @color2 @to );
background: -ms-linear-gradient( @startLoc, @color1 @from, @color2 @to );
background: -o-linear-gradient( @startLoc, @color1 @from, @color2 @to );
background: linear-gradient( @startLoc, @color1 );
}
._gradient( @color1, @color2, @startLoc, @endLoc, @from, @to, @ieFallback: false, @ieGradientType: 0 ) when not ( @ieFallback ) {
// Internal mixin for generating gradients when there are no rounded corners
filter: e( %( "progid:DXImageTransform.Microsoft.Gradient(startColorstr='%d', endColorstr='%d', GradientType=%d)", @color1, @color2, @ieGradientType ) );
background: -webkit-gradient( linear, @startLoc, @endLoc, color-stop( @from, @color1 ), color-stop( @to, @color2 ) );
background: -webkit-linear-gradient( @startLoc, @color1 @from, @color2 @to );
background: -moz-linear-gradient( @startLoc, @color1 @from, @color2 @to );
background: -ms-linear-gradient( @startLoc, @color1 @from, @color2 @to );
background: -o-linear-gradient( @startLoc, @color1 @from, @color2 @to );
background: linear-gradient( @startLoc, @color1 );
}
.gradient( "vertical", @color1, @color2, @fallback: false ) {
._gradient( @color1, @color2, top, bottom, 0%, 100%, @fallback, 0 );
}
.gradient( "horizontal", @color1, @color2, @fallback: false ) {
._gradient( @color1, @color2, left, right, 0%, 100%, @fallback, 1 );
}
.gradient( "center top", @color1, @color2, @fallback: false ) {
._gradient( @color1, @color2, left, right, 0%, 100%, @fallback, 1 );
}
.transition(@transition1) {
-webkit-transition: @transition1;
-moz-transition: @transition1;
-o-transition: @transition1;
-ms-transition: @transition1;
transition: @transition1;
}
.transition(@transition1, @transition2) {
-webkit-transition: @transition1, @transition2;
-moz-transition: @transition1, @transition2;
-o-transition: @transition1, @transition2;
-ms-transition: @transition1, @transition2;
transition: @transition1, @transition2;
}
.transition(@transition1, @transition2, @transition3) {
-webkit-transition: @transition1, @transition2, @transition3;
-moz-transition: @transition1, @transition2, @transition3;
-o-transition: @transition1, @transition2, @transition3;
-ms-transition: @transition1, @transition2, @transition3;
transition: @transition1, @transition2, @transition3;
}
.animation( @animation ) {
-webkit-animation: @animation;
-moz-animation: @animation;
-o-animation: @animation;
-ms-animation: @animation;
}
.transform( @transformation ) {
-webkit-transform: @transformation;
-moz-transform: @transformation;
-o-transform: @transformation;
-ms-transform: @transformation;
transform: @transformation;
}
.transform-origin( @transformation ) {
-webkit-transform-origin: @transformation;
-moz-transform-origin: @transformation;
-o-transform-origin: @transformation;
-ms-transform-origin: @transformation;
transform-origin: @transformation;
}
.transformAnimation(@startProperty, @endProperty) {
@-webkit-keyframes spin {
0% {
-webkit-transform: @startProperty;
transform: @startProperty;
}
100% {
-webkit-transform: @endProperty;
transform: @endProperty;
}
}
@-moz-keyframes spin {
0% {
-moz-transform: @startProperty;
transform: @startProperty;
}
100% {
-moz-transform: @endProperty;
transform: @endProperty;
}
}
}
.boxOrient(@direction) {
-webkit-box-orient: @direction;
-moz-box-orient: @direction;
-ms-box-orient: @direction;
box-orient: @direction;
}
.boxAlign(@property) {
-webkit-box-align: @property;
-moz-box-align: @property;
-ms-box-align: @property;
box-align: @property;
}
.box-sizing( @property ) {
// https://developer.mozilla.org/En/CSS/Box-sizing
-webkit-box-sizing: @property; // Chrome 10-
-moz-box-sizing: @property;
-ms-box-sizing: @property; // IE 8+
box-sizing: @property; // Opera, Safari 5.1+, Chrome 10+
}
// *********************************************************
// POSITIONING
//
.clearfix() {
*zoom: 1;
&:before,
&:after {
content: "";
display: table;
}
&:after { clear: both; }
}
.triangle( "right", @size, @color, @borderSize, @borderColor ) {
&:after, &:before {
left: 100%;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none; /* csslint-ignore */
}
&:after {
border-left-color: @color;
border-width: @size;
top: 50%;
margin-top: -@size;
}
&:before {
border-left-color: @borderColor;
border-width: @size + @borderSize + 1;
top: 50%;
margin-top: -( @size + @borderSize + 1 );
}
}

185
css/header.less Normal file
Просмотреть файл

@ -0,0 +1,185 @@
/*********************************************************
* HEADER
*/
@_logoWidth: 180px;
// Used to position absolute elements in the middle of our header bar area
@HEADER_ALIGN_MIDDLE: 2.7;
// Adds room to template to account for header
.butter-header-spacing {
padding-top: @HEADER_SPACING;
}
.butter-editor-spacing.editor-minimized {
.butter-header {
right: 0;
}
}
.butter-header {
z-index: @HEADER_Z_INDEX;
position: fixed;
left: 0;
top: 0;
right: 0;
height: @HEADER_SPACING;
.transition( all .2s ease-in-out );
}
/*********************************************************
* Primary white nav bar
*/
.butter-header-primary {
position: relative;
background: #FFF;
color: lighten( @baseText, 10% );
text-shadow: 0 1px 0 #FFF;
font-size: 0.95em;
border-bottom: 1px solid darken( @editorOutline, 10% );
box-shadow: 0 -1px 10px 0 rgba( 0, 0, 0, 0.1 );
.clearfix();
}
.butter-logo {
position: absolute;
top: 0;
left: 30px;
height: 100px;
width: @_logoWidth;
background: url( "../resources/logo.png" ) no-repeat;
}
.butter-nav {
position: relative;
margin: 0;
padding: 0;
margin-left: @_logoWidth + 50;
list-style: none;
height: @PRIMARYHEADER_SPACING;
line-height: @PRIMARYHEADER_SPACING;
> li {
float: left;
padding: 0 20px;
&.butter-breadcrumbs {
position: relative;
padding: 0;
margin-right: 10px;
width: 1px;
height: 100%;
border-right: 1px solid @baseLightOutline;
.triangle( "right", 5px, #FFF, 1px, @baseLightOutline );
}
&.butter-project-title {
text-align: center;
font-weight: 600;
font-size: 16px;
color: @green;
cursor: pointer;
}
}
}
#tabzilla {
display: block;
position: absolute;
top: 0;
right: 0;
z-index: @HEADER_Z_INDEX + 100;
}
#tabzilla-panel {
position: absolute;
top: 0;
left: 0;
width: 100%;
z-index: @MODAL_Z_INDEX;
}
/*********************************************************
* Buttons
*/
.butter-header {
.butter-btn {
text-shadow: none;
}
}
.butter-project-title > input {
font-size: 14px;
border: 1px solid @green;
border-radius: 2px;
padding: 5px;
color: @green;
&:focus {
outline: none;
}
}
.butter-project-title {
max-width: 35%;
min-width: 8%;
height: 100%;
a {
&.butter-project-name {
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
.icon {
opacity: 0.4;
position: absolute;
visibility: hidden;
right: 0;
top: @PRIMARYHEADER_SPACING / @HEADER_ALIGN_MIDDLE;
}
&:hover .icon {
visibility: visible;
}
}
.butter-login-project-info {
position: relative;
}
.butter-login-project-info > ul {
position: absolute;
top: 48px;
left: 28px;
width: 175px;
border-top: 0;
border-radius: 0 0 2px 2px;
box-shadow: 0 10px 15px -5px rgba( 0, 0, 0, 0.6 );
padding: 0;
background: #FFF;
z-index: @MODAL_Z_INDEX;
li {
border-top: 1px solid @baseLight;
list-style: none;
font-size: 10px;
line-height: 35px;
margin: 0;
&:first-child {
border-top: none;
}
&:last-child {
border-radius: 0 0 2px 2px;
}
}
a {
text-decoration: none;
color: @baseText;
display: block;
padding: 0 20px;
height: 100%;
cursor: pointer;
&:hover {
color: @green;
}
}
}

Просмотреть файл

@ -0,0 +1,44 @@
#main {
min-width: 300px;
min-height: 200px;
}
.content-div {
width: 200px;
height: 200px;
border: 3px solid #000;
margin: 5px;
}
#main, .content-div {
float: left;
}
.container-div {
float: left;
text-align: center;
font-family: helvetica;
font-weight: bold;
}
video {
position: relative;
top: 30px;
}
#manual-test {
clear: both;
padding-top: 20px;
margin-left: 20px;
}
button {
margin: 5px;
padding: 0.25em 0.75em 0.2em;
display: inline-block;
color: #000000;
font-size: 1.142em;
border: 0 none;
border-radius: 0.2em 0.2em 0.2em 0.2em;
box-shadow: 1px 1px rgba(0, 0, 0, 0.25);
}

106
css/media-editor.less Normal file
Просмотреть файл

@ -0,0 +1,106 @@
/*********************************************************
* MEDIA EDITOR
*/
.media-editor {
.fl-right {
margin-top: 8px;
float: right;
}
.media-editor-inner-wrapper { // Textarea wrappers
position: relative;
overflow: hidden;
margin-bottom: 5px;
&:hover .delete-media-btn {
right: 2px;
}
textarea {
font-size: 12px;
font-family: monaco, monospace;
}
}
.alt-media-wrapper {
textarea {
height: 70px;
}
}
.alt-media-wrapper-inner {
position: relative;
}
.alternate-media-label { // When alternate media are displayed
cursor: pointer;
font-size: 14px;
margin-top: 20px;
&:hover {
color: #000;
}
>.icon-x {
float: right;
opacity: 0.5;
&:hover {
opacity: 1;
}
}
}
&.alternates-hidden { // When alternate media are hidden
.butter-btn-slide-out {
float: left;
}
.alternate-media-label {
font-size: 12px;
float: right;
margin: 0;
>.icon-x {
display: none;
}
&:after {
content: "...";
}
}
.alt-media-wrapper,
.add-alternate-media-source-btn,
.add-alternate-media-source-desc {
display: none;
}
}
.delete-media-btn {
position: absolute;
bottom: 10px;
right: -20px;
opacity: 0.5;
cursor: pointer;
.transition( right 0.2s ease );
}
.error {
border: 1px inset #ff0000;
}
.media-error-message {
color: #ff0000;
&.hidden {
display: none;
}
}
.media-loading-spinner {
position: relative;
top: 3px;
left: 10px;
height: 16px;
width: 16px;
display: inline-block;
background-image: url("../resources/spinny.gif");
&.hidden {
display: none;
}
}
}

272
css/normalize.less поставляемый Normal file
Просмотреть файл

@ -0,0 +1,272 @@
/*
* NORMALIZATION
* A lot of this is from normalize.css and HTML5 Boilerplate, docs for which can be found at h5bp.com/css
*/
/*********************************************************
* HTML5 display
*/
// Corrects html5 display definitions in IE9
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
nav,
section,
summary {
display: block;
}
audio,
canvas,
video {
display: inline-block;
}
// Forces controls to display in modern browsers
audio:not([controls]) {
display: none;
height: 0;
}
// IE9 doesn't support the new HTML5 hidden attribute
[hidden] {
display: none;
}
/*********************************************************
* Links
*/
// Addresses `outline` inconsistency between Chrome and other browsers.
a:focus {
outline: thin dotted;
}
// Improves readability when focused and also mouse hovered in all browsers.
// See people.opera.com/patrickl/experiments/keyboard/test
a:active,
a:hover {
outline: 0;
}
/*********************************************************
* Typography
*/
// Prevent iOS text size adjust on device orientation change, without disabling user zoom: h5bp.com/g
html {
font-size: 100%;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
// Normalize fonts
html,
button,
input,
select,
textarea {
font-family: "Open Sans", "Helvetica Neue", sans-serif;
}
abbr[title] {
border-bottom: 1px dotted;
}
b,
strong {
font-weight: bold;
}
blockquote {
margin: 1em 40px;
}
dfn {
font-style: italic;
}
hr {
display: block;
height: 1px;
border: 0;
border-top: 1px solid #CCC;
margin: 1em 0;
padding: 0;
}
// Redeclare monospace font family: h5bp.com/j
pre,
code,
kbd,
samp {
font-family: "menlo", monospace, serif;
font-size: 1em;
}
// Improve readability of pre-formatted text
pre {
white-space: pre;
white-space: pre-wrap;
word-wrap: break-word;
}
small {
font-size: 85%;
}
// Superscripts and subscripts: h5bp.com/k;
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sup {
top: -0.5em;
}
sub {
bottom: -0.25em;
}
/*********************************************************
* Selection
*/
::-moz-selection {
background: @COLOR_SELECTION;
text-shadow: none;
}
::selection {
background: @COLOR_SELECTION;
text-shadow: none;
}
/*********************************************************
* Images
*/
// Removes border when inside `a` element for IE9
img {
border: 0;
}
// Corrects overflow displayed oddly in IE9.
svg:not(:root) {
overflow: hidden;
}
// Margins not present in IE9
figure {
margin: 0;
}
/*********************************************************
* Forms
*/
// Normalize spacing
fieldset {
border: 1px solid #C0C0C0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
// Corrects color not being inherited in IE9
legend {
border: 0;
padding: 0;
}
// 1. Corrects font size not being inherited in all browsers.
// 2. Addresses margins set differently in Chrome
// 3. Improves appearance and consistency in all browsers.
button,
input,
select,
textarea {
font-size: 100%; //1
margin: 0; //2
vertical-align: baseline; //3
}
// 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` and `video` controls.
// 2. Corrects inability to style clickable `input` types in iOS.
// 3. Improves usability and consistency of cursor style between image-type `input` and others.
button,
html input[type="button"], //1
input[type="reset"],
input[type="submit"] {
-webkit-appearance: button; /* csslint-ignore: (2) */
cursor: pointer; //3
}
// Reset default cursor for disabled elements.
button[disabled],
input[disabled] {
cursor: default;
}
// 1. Addresses box sizing set to content-box in IE9.
// 2. Removes excess padding in IE9.
input[type="checkbox"],
input[type="radio"] {
box-sizing: border-box; //1
padding: 0; //2
}
// 1. Addresses `appearance` set to `searchfield` in S5, Chrome.
// 2. Addresses `box-sizing` set to `border-box` in S5, Chrome (include `-moz` to future-proof).
input[type="search"] {
-webkit-appearance: textfield; /* csslint-ignore: (1) */
-moz-box-sizing: content-box;
-webkit-box-sizing: content-box; //2
box-sizing: content-box;
}
// Removes inner padding and search cancel button in S5, Chrome on OS X.
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none; /* csslint-ignore */
}
// 1. Removes default vertical scrollbar in IE9.
// 2. Improves readability and alignment in all browsers.
textarea {
overflow: auto; //1
vertical-align: top; //2
}
/*********************************************************
* Tables
*/
// Remove most spacing between table cells.
table {
border-collapse: collapse;
border-spacing: 0;
}
td {
vertical-align: top;
}
/*********************************************************
* Iframes
*/
iframe {
border: 0;
}

41
css/plugin-module.less Normal file
Просмотреть файл

@ -0,0 +1,41 @@
//This is the list of plugins that appears after clickling the "Popcorn" Button
.plugin-list-editor .plugin-container {
padding: 20px;
}
.butter-plugin-tile {
text-transform: capitalize;
position: relative;
background: #FFF;
box-shadow: 1px 5px 2px -3px rgba(0, 0, 0, 0.1);
border: 1px solid #CCC;
border-radius: 2px;
border-top-color: #E3E3E3;
border-bottom-color: #BDBDBD;
color: #555;
font-size: 13px;
line-height: 24px;
cursor: move;
padding: 8px;
height: 30px;
margin-bottom: 10px;
display: block;
text-decoration: none;
&:hover {
left: -2px;
top: -2px;
box-shadow: 4px 4px 0 -2px rgba( 0, 0, 0, .3 );
}
}
.butter-plugin-icon {
background: url( "../resources/default-icon.png" ) center no-repeat;
display: block;
float: left;
width: 24px;
height: 24px;
margin-right: 10px;
}

95
css/share-editor.less Normal file
Просмотреть файл

@ -0,0 +1,95 @@
//This is the css for the editor that is opened when clicking the "Share" Button
.butter-editor-area {
.share-editor {
.sub-container {
>p {
padding-right: 10px;
display: inline-block;
}
}
.hide-container {
display: none;
}
.fade-container {
opacity: 0.3;
}
.butter-project-author {
width: 240px;
}
.butter-project-author-update {
width: 66px;
}
.container {
padding: 20px;
>p {
margin: 0 0 10px 5px;
font-size: 14px;
font-style: italic;
}
.big {
font-size: 16.25px;
}
>label {
font-size: 13px;
display: block;
padding: 10px 0;
}
>textarea {
min-height: 100px;
line-height: 1.25;
resize: none;
}
>div {
text-transform: capitalize;
position: relative;
background: #FFF;
box-shadow: inset 0 1px 0 0 white, inset 1px 0 0 0 white, inset -1px 0 0 0 white, inset 0 -1px 0 0 white, 0 1px 2px 0 rgba(0, 0, 0, 0.2);
border: 1px solid #CCC;
border-radius: 2px;
border-top-color: #E3E3E3;
border-bottom-color: #BDBDBD;
color: #555;
font-size: 14px;
height: 30px;
margin-bottom: 10px;
margin-right: 5px;
line-height: 25px;
cursor: move;
padding: 10px;
&:hover {
left: -2px;
top: -2px;
box-shadow: 4px 4px 0 -2px rgba( 0, 0, 0, .3 );
}
}
div {
>span {
&.icon {
display: block;
float: left;
width: 30px;
height: 30px;
background-size: 30px;
background-repeat: no-repeat;
margin-right: 10px;
}
}
}
}
}
}

205
css/super-scrollbar.less Executable file
Просмотреть файл

@ -0,0 +1,205 @@
@_superScrollbarHeight: 32px;
#butter-super-scrollbar-outer-container {
position: relative;
left: -@_trackHandleWidth;
.butter-super-scrollbar-zoom-slider {
position: absolute;
width: 94px;
height: 6px;
z-index: 2;
> .butter-super-scrollbar-zoom-handle {
top: -6px;
position: absolute;
height: 16px;
width: 6px;
left: 50%;
line-height: 100%;
cursor: pointer;
background: #EEE;
border: 1px solid #999;
border-radius: 6px;
}
}
.butter-super-scrollbar-zoom-slider-container {
position: absolute;
background: #EEE;
border: 1px solid #999;
width: 100px;
height: 6px;
border-radius: 6px;
left: 12px;
top: 13px;
z-index: 2;
> .tick {
height: 6px;
width: 1px;
float: left;
border-left: 1px solid #CCC;
}
> .tick:first-child {
margin-left: 1px;
}
> .tick:nth-child( 2 ) {
margin-left: 2px;
}
> .tick:nth-child( 3 ) {
margin-left: 4px;
}
> .tick:nth-child( 4 ) {
margin-left: 8px;
}
> .tick:nth-child( 5 ) {
margin-left: 16px;
}
> .tick:nth-child( 6 ) {
margin-left: 32px;
}
}
}
#butter-super-scrollbar-inner-container {
background: darken( @baseLight, 3% );
height: @_superScrollbarHeight;
position: absolute;
left: @_trackHandleWidth;
width: 100%;
right: -7px;
bottom: -@_superScrollbarHeight;
border: 1px solid @baseLightOutline;
margin-right: 5px;
top: 0;
box-shadow: inset 2px 0 5px -2px rgba( 0, 0, 0, 0.2 );
}
#butter-super-scrollbar-visuals {
margin-top: 7px;
height: 100%;
width: 100%;
position: absolute;
}
#butter-super-scrollbar-viewport {
.gradient( "vertical", fade( #FFF, 65% ), fade( #FFF, 20% ), true );
cursor: move;
height: 28px;
position: absolute;
top: 0;
min-width: 5px;
&.viewport-transition {
.transition( left 0.1s linear, right 0.1s linear );
}
&:before {
content: "";
width: 100%;
height: 100%;
position: absolute;
top: -3px;
bottom: -3px;
left: -3px;
display: block;
border: 4px solid #FFF;
z-index: 1;
box-shadow: 0 2px 2px 0 rgba( 0, 0, 0, 0.2 ) inset, 0 0 0 1px @baseLightOutline;
}
}
.butter-super-scrollbar-trackevent {
background: rgba( 0, 0, 0, 0.4 );
height: 3px;
position: absolute;
z-index: 1;
}
.butter-super-scrollbar-handle {
background: #41BB92;
display: block;
height: 18px;
margin-top: -9px;
position: absolute;
top: 50%;
width: 6px;
border: 1px solid @green;
box-shadow: 0 2px 3px -2px;
z-index: 1;
cursor: ew-resize;
&:before { // The little center grip on the handle
content: "";
position: absolute;
top: 2px;
bottom: 2px;
left: 2px;
width: 2px;
background: darken( @green, 10% );
}
.butter-super-arrow { // The green arrows
content: "";
opacity: 0;
position: absolute;
display: inline-block;
background-image: url( "../resources/glyphicons-halflings-green.png" );
width: 14px;
height: 14px;
top: 2px;
.transition( all 0.1s ease );
}
}
#butter-super-scrollbar-handle-left {
left: -8px;
.butter-super-arrow {
left: 0;
background-position: -240px -96px;
}
}
#butter-super-scrollbar-handle-right {
right: -10px;
.butter-super-arrow {
right: 0;
background-position: -264px -96px;
}
}
// Arrows - hover states
// This allows the arrows to animate.
#butter-super-scrollbar-viewport:hover {
.butter-super-arrow {
opacity: 0.5;
}
#butter-super-scrollbar-handle-left .butter-super-arrow {
left: 10px;
}
#butter-super-scrollbar-handle-right .butter-super-arrow {
right: 10px;
}
}
// Arrows - minimum size states
// At 50px wide, force the arrows to the outside
#butter-super-scrollbar-container.super-scrollbar-small:hover {
#butter-super-scrollbar-handle-left .butter-super-arrow {
left: -16px;
}
#butter-super-scrollbar-handle-right .butter-super-arrow {
right: -16px;
}
}
// The little red scrubber
#buter-super-scrollbar-scrubber {
background: none repeat scroll 0 0 @red;
bottom: 0;
cursor: move;
position: absolute;
top: 0;
width: 1px;
}

0
css/targets.less Normal file
Просмотреть файл

74
css/transitions.less Normal file
Просмотреть файл

@ -0,0 +1,74 @@
/* This Source Code Form is subject to the terms of the MIT license
* If a copy of the MIT license was not distributed with this file, you can
* obtain one at http://www.mozillapopcorn.org/butter-license.txt */
/*
*/
@import "globals";
/*********************************************************
* Base transition styles
*/
.transition-state( @property, @off, @on ) {
.transition( @property 0.3s ease, visibility 0.3s ease );
visibility: ~`"visible;\n @{property}: @{on}"`;
&.off{
visibility: ~`"hidden;\n @{property}: @{off} !important"`;
}
}
// Basic transitions
.popcorn-fade {
.transition-state( opacity, 0, 1 );
}
.popcorn-none {
display: block;
}
.popcorn-none.off {
display: none;
}
.popcorn-slide-up {
.transition-state( margin-top, 100%, 0 );
}
.popcorn-slide-down {
.transition-state( margin-top, -100%, 0 );
}
.popcorn-pop {
visibility: hidden;
.transform( scale( 0, 0 ) );
.transform-origin( center );
}
.popcorn-pop.on{
visibility: visible;
.transform( scale( 1, 1 ) );
.animation( pop .2s 1 ease-out );
}
/*********************************************************
* Keyframe Animations
*/
@-webkit-keyframes pop {
0% {-webkit-transform: scale( 0, 0 ); transform: scale( 0, 0 ); }
75% {-webkit-transform: scale( 1.6, 1.6 ); transform: scale( 1.6, 1.6 ); }
100% {-webkit-transform: scale( 1, 1 ); transform: scale( 1, 1 ); }
}
@-moz-keyframes pop {
0% {-moz-transform: scale( 0, 0 ); transform: scale( 0, 0 ); }
75% {-moz-transform: scale( 1.6, 1.6 ); transform: scale( 1.6, 1.6 ); }
100% {-moz-transform: scale( 1, 1); transform: scale( 1, 1 ); }
}
@-o-keyframes pop {
0% {-o-transform: scale( 0, 0 ); transform: scale( 0, 0 ); }
75% {-o-transform: scale( 1.6, 1.6 ); transform: scale( 1.6, 1.6 ); }
100% {-o-transform: scale( 1, 1 ); transform: scale( 1.0, 1.0 ); }
}
@keyframes pop {
0% {transform: scale( 0, 0 ); }
75% {transform: scale( 1.6, 1.6 ); }
100% {transform: scale( 1, 1 ); }
}

221
css/tray-status-bar.less Normal file
Просмотреть файл

@ -0,0 +1,221 @@
/*********************************************************
* TRAY STATUS
*/
@_butterStatusHeight: 45px;
@_mediaStatusContainerHeight: @_butterStatusHeight;
@_playButtonHeight: 26px;
@_scrubberBarHeight: 8px;
@_scrubberHandleHeight: 10px;
@_traySideWidth: 129px;
@_timebarRight: 27px;
/*********************************************************
* Butter Status Area - The top bar in the tray
*/
.butter-status-area {
position: absolute;
height: @_butterStatusHeight;
top: 0;
left: 0;
width: 100%;
.selectable( none );
}
.media-status-container {
position: relative;
height: @_mediaStatusContainerHeight;
background: #FFF;
border-top: 1px solid @baseLightOutline;
}
/*********************************************************
* Time bar - The horizontal line where the scrubber is
*/
.time-bar {
position: absolute;
left: @_traySideWidth;
top: ( @_mediaStatusContainerHeight - @_scrubberBarHeight ) / 2;
right: @_timebarRight;
height: ( @_mediaStatusContainerHeight / 2 ) + ( @_scrubberBarHeight / 2 );
}
// Time ticks
.time-bar-canvas-container {
position: absolute;
left: 0;
top: @_scrubberBarHeight;
height: ( @_mediaStatusContainerHeight / 2 ) - ( @_scrubberBarHeight / 2 );
overflow: hidden;
right: 0;
opacity: 0.5;
}
.time-bar-scrubber-container {
background: fade( @baseDark, 20% );
border-bottom: 1px solid #FFF;
z-index: @TRAY_Z_INDEX + 10;
border-radius: 15px;
position: absolute;
.selectable( none );
top: 0;
right: 0;
left: 0;
height: @_scrubberBarHeight;
.fill-bar {
background: @green;
border-radius: 15px;
height: @_scrubberBarHeight;
position: absolute;
top: 0;
}
}
.time-bar-scrubber-line {
position: absolute;
top: @_scrubberHandleHeight + 3px;
left: 4px;
width: 2px;
height: 150px;
cursor: pointer;
z-index: @TRAY_Z_INDEX + 9;
background: fade( @red, 50% );
&:after {
content: "";
position: absolute;
bottom: -3px;
left: -2px;
width: 6px;
height: 6px;
border-radius: 6px;
background-color: @red;
}
}
.time-bar-scrubber-node {
position: absolute;
top: 0;
left: 0;
border: 3px solid #FFF;
background: @green;
width: @_scrubberHandleHeight;
height: @_scrubberHandleHeight;
border-radius: @_scrubberHandleHeight;
margin-left: -@_scrubberHandleHeight / 2;
margin-top: -@_scrubberHandleHeight / 2;
box-shadow: 0 2px 3px rgba( 0, 0, 0, 0.8 );
cursor: pointer;
}
// Tooltip that show the time when you hover over/drag the scrubber
.butter-time-tooltip {
@_tooltipHeight: 30px;
@_tooltipWidth: 70px;
width: @_tooltipWidth;
margin-left: -@_tooltipWidth / 2;
padding: @_tooltipHeight / 3;
font-size: 11px;
line-height: @_tooltipHeight / 3;
top: -( @_tooltipHeight + 4px );
height: @_tooltipHeight;
&.tooltip-on {
.transition( all 0.1s ease 0 );
}
&:after, &:before{
top: 100%;
border-bottom: none;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 5px solid #FFF;
}
&:before {
top: 100%;
border-bottom: none;
border-top: 5px solid #CCC;
}
}
/*********************************************************
* Status container - Play button, time code at the top left of the tray.
*/
.status-container {
position: absolute;
width: @_traySideWidth;
height: @_butterStatusHeight;
line-height: @_butterStatusHeight;
top: 0;
left: 0;
}
.time-container {
float: left;
margin-left: 10px;
input {
background: transparent;
color: @baseText;
border: none;
width: 60px;
font-size: 10px;
font-family: menlo, monospace;
&:focus {
.selectable( text );
}
}
}
.status-button {
width: @_playButtonHeight;
height: @_playButtonHeight;
line-height: @_playButtonHeight;
text-align: center;
border-radius: 4px;
background: @green;
&:active {
background-color: lighten( @green, 10% );
}
}
.play-button-container {
position: relative;
height: 100%;
float: left;
width: @_playButtonHeight + 13px;
background: lighten( @baseLight, 5% );
border-right: 1px solid @baseLightOutline;
.triangle( "right", 4px, #FFF, 1px, @baseLightOutline );
.status-button {
position: absolute;
top: 50%;
left: 5px;
margin-top: -@_playButtonHeight / 2;
border-bottom: 1px solid #FFF;
}
.icon {
margin-top: 3px;
}
.status-button[data-state="true"] > .icon {
background-position: -288px -72px;
}
}
.mute-button-container {
position: absolute;
top: 13px;
left: @_traySideWidth - 23px;
.status-button {
width: 20px;
height: 20px;
background: none;
.icon {
background-image: url( "../resources/glyphicons-halflings.png" );
}
&[data-state="true"] .icon {
background-position: -360px -24px;
}
}
}

325
css/tray-timeline.less Normal file
Просмотреть файл

@ -0,0 +1,325 @@
/*********************************************************
* TRAY TIMELINE
*/
@_trackEventHeight: 27px;
@_trackHeight: @_trackEventHeight + 3;
@_mediaContainerHeight: 140px;
@_trackContainerSpacing: 0;
@_trackHandleWidth: 129px;
@_trackEventColor: #FFF;
// Main timeline section
.butter-timeline-area {
position: absolute;
left: 0;
border-top: 1px solid @baseLightOutline;
}
// butter-timeline-area-inner
.butter-timeline {
position: absolute;
top: 0;
left: 0;
right: @_timebarRight;
z-index: @TRAY_Z_INDEX + 1;
}
// inside .butter-timeline
.media-instance {
height: 100%;
padding-left: @_trackHandleWidth;
position: relative;
.selectable( none );
.transition( top 0.5s );
.butter-scroll-bar-v {
right: -18px;
width: 10px;
height: 100%;
top: 0;
}
}
//inside .media-instance
.media-container {
position: relative;
top: @_trackContainerSpacing;
height: @_mediaContainerHeight;
}
.tracks-container-wrapper {
background: @baseLight;
width: 100%;
height: 100%;
overflow: hidden;
border-left: 1px solid @baseLightOutline;
border-right: 1px solid @baseLightOutline;
box-shadow: inset 0 0 5px 0 rgba( 0, 0, 0, 0.2 );
}
.tracks-container {
padding-bottom: @_trackHeight; //must have padding to account for add popcorn button
position: relative;
}
/*********************************************************
* Track
*/
.butter-track {
width: 100%;
height: @_trackHeight;
position: relative;
border-bottom: 1px dashed @baseLightOutline;
.track-title {
display: inline-block;
position: absolute;
left: 5px;
top: 50%;
}
&.draggable-hover {
//box-shadow: inset 0 0 0 2px @green;
background: fade( #FFF, 80% );
}
&.butter-track-ghost {
opacity: 0.7;
}
}
/*********************************************************
* Track Event
*/
.butter-track-event {
.selectable( none );
background: @_trackEventColor;
box-shadow: 1px 5px 2px -3px rgba( 0, 0, 0, 0.1 );
border: 1px solid #CCC;
border-radius: 4px;
border-top-color: #E3E3E3;
border-bottom-color: #BDBDBD;
cursor: move;
display: block;
height: @_trackEventHeight;
padding: 1px 0;
position: absolute;
overflow: hidden;
&[selected="true"] {
border-color: @green;
box-shadow: 1px 5px 2px -3px rgba( 0, 0, 0, 0.2 );
}
.butter-track-event-info {
position: relative;
width: 100%;
overflow: hidden;
height: 100%;
}
.title {
font-size: 11px;
font-weight: 700;
text-align: left;
position: absolute;
left: 35px;
line-height: 25px;
color: @baseText;
}
.butter-track-event-icon {
position: absolute;
height: 25px;
width: 30px;
background-size: 20px;
background-image: url( "../resources/default-icon.png" );
background-position: center center;
background-repeat: no-repeat;
}
.handle {
position: absolute;
height: 18px;
width: 18px;
top: 5px;
opacity: 0;
background: @_trackEventColor;
.transition( all 0.1s ease );
&:after {
content: "";
position: absolute;
display: inline-block;
background-image: url( "../resources/glyphicons-halflings-green.png" );
width: 14px;
height: 14px;
top: 2px;
}
}
.left-handle {
left: 10px;
cursor: w-resize;
cursor: ew-resize;
&:after {
background-position: -240px -96px;
left: 2px;
}
}
.right-handle {
right: 10px;
cursor: e-resize;
cursor: ew-resize;
&:after {
background-position: -264px -96px;
right: 2px;
}
}
&:hover,
&.butter-resizable {
.handle {
opacity: 0.7;
}
.left-handle {
left: 0;
}
.right-handle {
right: 0;
}
}
&.trackevent-small {
overflow: visible;
.handle {
background: none;
}
.butter-track-event-info {
left: 0;
}
.left-handle {
left: -16px;
&:after {
left: -2px;
}
}
.right-handle {
right: -16px;
&:after {
right: -2px;
}
}
}
&.butter-track-event-ghost {
opacity: 0.7;
}
}
/*********************************************************
* Track Handle Container - Left-hand side panel
*/
.track-handle-container {
position: absolute;
width: @_trackHandleWidth;
left: 0;
top: @_trackContainerSpacing;
bottom: 1px;
border-bottom: 1px solid lighten( @baseLightOutline, 5% );
overflow: hidden;
background: @baseLight;
}
.handle-list {
position: relative;
padding-bottom: @_trackHeight;
}
.track-handle {
border: none;
border-bottom: 1px solid darken( @baseLight, 25% );
cursor: pointer;
width: 100%;
height: @_trackHeight;
background: @baseLight;
position: relative;
line-height: @_trackHeight;
overflow: hidden;
box-shadow: inset 0 1px #FFF;
.title {
padding-left: 22px;
font-size: 11px;
color: @baseText;
}
&:first-of-type {
box-shadow: none;
}
.menu {
position: absolute;
width: 0;
height: 100%;
top: 0;
left: 0;
.transition( width 0.1s );
opacity: 0.3;
&:hover {
opacity: 1;
}
.delete {
position: absolute;
right: 0;
top: 8px;
background-image: url( "../resources/glyphicons-halflings.png" );
background-position: -456px 0;
cursor: pointer;
width: 14px;
height: 14px;
}
}
&:hover .menu {
width: 15px;
}
&:hover .track-handle-icon {
border-color: #000;
&:after {
border-bottom-color: #000;
}
}
}
.track-handle-icon {
border: 0 solid darken( @baseLightOutline, 10% );
box-shadow: inset 0 1px 0 #FFF, 0 1px 0 #FFF;
position: absolute;
top: 9px;
right: 10px;
display: inline-block;
width: 14px;
height: 9px;
border-top-width: 1px;
border-bottom-width: 1px;
&:after {
box-shadow: 0 1px 0 #FFF;
border-bottom: 1px solid darken( @baseLightOutline, 10% );
content: " ";
position: absolute;
display: block;
top: 4px;
width: 100%;
}
}
.add-track {
padding: 0;
font-size: 11px;
position: absolute;
right: 9px;
width: 20px;
font-weight: 700;
margin-left: 10px;
margin-top: 14px; // This is a crappy work-around because the height of this is getting set in javascript
}
// Drag and dropping track handles
.placeholder {
width: 95px;
box-shadow: inset 0 1px 0 #999, 0 0 1px #AAA;
background: transparent;
}

68
css/tray.less Normal file
Просмотреть файл

@ -0,0 +1,68 @@
/*********************************************************
* TRAY
*/
// Adds room to bottom of template for tray
.butter-tray-spacing {
padding-bottom: @TRAY_SPACING;
.transition( padding-bottom 0.5s );
}
.butter-tray-spacing.tray-minimized {
padding-bottom: @TRAY_SPACING_MINIMIZED;
}
.butter-editor-spacing.editor-minimized .butter-tray {
right: 0;
}
.butter-tray {
background: #FFF;
position: fixed;
z-index: @TRAY_Z_INDEX;
bottom: 0;
left: 0;
right: @EDITOR_WIDTH;
height: @TRAY_HEIGHT;
.selectable( none );
.transition( bottom 0.35s, right 0.35s );
* {
.selectable( none );
}
&.minimized {
bottom: -@TRAY_HEIGHT + @_butterStatusHeight;
}
.butter-toggle-button {
top: 15px;
right: 6px;
z-index: @TRAY_Z_INDEX + 10;
}
// This is the list of plugins in "my events"
.butter-timeline-area {
position: relative;
height: 160px;
top: @_butterStatusHeight;
}
// This is the container for the spinning popcorn logo on the right side of the tray
.butter-loading-container {
position: absolute;
z-index: @TRAY_Z_INDEX + 1;
right: 10px;
top: 3px;
width: 50px;
height: 50px;
}
.fadable {
//TO-DO: This should be moved into a general UI kit
.transition( opacity 0.5s );
}
}

111
css/ui-states.less Normal file

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

11
css/utilities.less Normal file
Просмотреть файл

@ -0,0 +1,11 @@
/*********************************************************
* UTILITIES
*/
.butter-image-preload {
visibility: hidden;
position: absolute;
width: 1px;
height: 1px;
opacity: 0;
}

40
css/webfonts.less Normal file
Просмотреть файл

@ -0,0 +1,40 @@
/*********************************************************
* WEB FONTS
*/
@font-face {
font-family: 'Open Sans';
src: url('../resources/fonts/opensans-bold-webfont.woff') format('woff'),
url('../resources/fonts/opensans-bold-webfont.ttf') format('truetype');
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: 'Open Sans';
src: url('../resources/fonts/opensans-bolditalic-webfont.woff') format('woff'),
url('../resources/fonts/opensans-bolditalic-webfont.ttf') format('truetype');
font-weight: 700;
font-style: italic;
}
@font-face {
font-family: 'Open Sans';
src: url('../resources/fonts/opensans-regular-webfont.woff') format('woff'),
url('../resources/fonts/opensans-regular-webfont.ttf') format('truetype');
}
@font-face {
font-family: 'Open Sans';
src: url('../resources/fonts/opensans-italic-webfont.woff') format('woff'),
url('../resources/fonts/opensans-italic-webfont.ttf') format('truetype');
font-weight: normal;
font-style: italic;
}
@font-face {
font-family: 'Meta';
src: url('http://www.mozilla.org/img/fonts/MetaWebPro-Bold.eot');
src: local('?'), url('http://www.mozilla.org/img/fonts/MetaWebPro-Bold.woff') format('woff');
font-weight: bold;
}

Просмотреть файл

@ -0,0 +1,16 @@
<!-- This Source Code Form is subject to the terms of the MIT license
If a copy of the MIT license was not distributed with this file, you can
obtain one at http://www.mozillapopcorn.org/butter-license.txt -->
<div class="butter-editor default-editor allow-scrollbar">
<h1>Google Maps Editor</h1>
<div class="butter-editor-body scrollbar-container">
<div class="editor-options-wrapper scrollbar-outer">
<div class="editor-options scrollbar-inner">
<div class="error-message-container">
<div class="error-message"></div>
</div>
</div>
</div>
</div>
</div>

209
editors/googlemap-editor.js Normal file
Просмотреть файл

@ -0,0 +1,209 @@
/*global google*/
/* This Source Code Form is subject to the terms of the MIT license
* If a copy of the MIT license was not distributed with this file, you can
* obtain one at http://www.mozillapopcorn.org/butter-license.txt */
( function( Butter ) {
Butter.Editor.register( "googlemap", "load!{{baseDir}}editors/googlemap-editor.html",
function( rootElement, butter, compiledLayout ) {
var _this = this;
var _rootElement = rootElement,
_trackEvent,
_popcornEventMapReference,
_butter;
/**
* Member: getMapFromTrackEvent
*
* Retrieves a handle to the map associated with the trackevent.
*/
function getMapFromTrackEvent() {
if ( !_trackEvent.popcornTrackEvent._map ) {
_trackEvent.popcornTrackEvent.onmaploaded = function( options, map ){
_popcornEventMapReference = map;
};
}
else {
_popcornEventMapReference = _trackEvent.popcornTrackEvent._map;
}
}
/**
* Member: setup
*
* Sets up the content of this editor
*
* @param {TrackEvent} trackEvent: The TrackEvent being edited
*/
function setup( trackEvent ){
_trackEvent = trackEvent;
var pluginOptions = {},
ignoreKeys = [
"target",
"start",
"end"
],
optionsContainer = _rootElement.querySelector( ".editor-options" );
function callback( elementType, element, trackEvent, name ) {
pluginOptions[ name ] = {
element: element,
trackEvent: trackEvent,
elementType: elementType
};
}
function attachHandlers() {
var key,
option,
pitchObject,
headingObject,
currentMapType;
function toggleStreetView() {
pitchObject.element.parentNode.style.display = "block";
headingObject.element.parentNode.style.display = "block";
_this.scrollbar.update();
}
function toggleMaps() {
pitchObject.element.parentNode.style.display = "none";
headingObject.element.parentNode.style.display = "none";
_this.scrollbar.update();
}
function attachTypeHandler( option ) {
option.element.addEventListener( "change", function( e ) {
var elementVal = e.target.value,
updateOptions = {},
target;
if ( elementVal === "STREETVIEW" ) {
toggleStreetView();
} else {
toggleMaps();
}
updateOptions.type = elementVal;
option.trackEvent.update( updateOptions );
// Attempt to make the trackEvent's target blink
target = _butter.getTargetByType( "elementID", option.trackEvent.popcornOptions.target );
if ( target ) {
target.view.blink();
} else {
_butter.currentMedia.view.blink();
}
}, false );
}
function attachFullscreenHandler( option ) {
option.element.addEventListener( "click", function( e ) {
var srcElement = e.target,
updateOptions = {},
manifestOpts = trackEvent.manifest.options;
if ( srcElement.checked ) {
updateOptions = {
height: 100,
width: 100,
left: 0,
top: 0,
fullscreen: true
};
} else {
updateOptions = {
height: manifestOpts.height[ "default" ],
width: manifestOpts.width[ "default" ],
left: manifestOpts.left[ "default" ],
top: manifestOpts.top[ "default" ],
fullscreen: false
};
}
trackEvent.update( updateOptions );
}, false );
}
for ( key in pluginOptions ) {
if ( pluginOptions.hasOwnProperty( key ) ) {
option = pluginOptions[ key ];
if ( key === "type" ) {
pitchObject = pluginOptions.pitch;
headingObject = pluginOptions.heading;
currentMapType = option.trackEvent.popcornOptions.type;
if ( currentMapType === "STREETVIEW" ) {
toggleStreetView();
} else {
toggleMaps();
}
attachTypeHandler( option );
} else if ( option.elementType === "select" && key !== "type" ) {
_this.attachSelectChangeHandler( option.element, option.trackEvent, key, _this.updateTrackEventSafe );
} else if ( key === "fullscreen" ) {
attachFullscreenHandler( option );
} else if ( option.elementType === "input" ) {
_this.attachInputChangeHandler( option.element, option.trackEvent, key, _this.updateTrackEventSafe );
}
}
}
}
optionsContainer.appendChild( _this.createStartEndInputs( trackEvent, _this.updateTrackEventSafe ) );
_this.createPropertiesFromManifest({
trackEvent: trackEvent,
callback: callback,
basicContainer: optionsContainer,
ignoreManifestKeys: ignoreKeys
});
attachHandlers();
_this.updatePropertiesFromManifest( trackEvent );
_this.scrollbar.update();
_this.setTrackEventUpdateErrorCallback( _this.setErrorState );
}
// Extend this object to become a BaseEditor
Butter.Editor.TrackEventEditor.extend( _this, butter, rootElement, {
open: function( parentElement, trackEvent ) {
var popcorn = butter.currentMedia.popcorn.popcorn;
_butter = butter;
// Update properties when TrackEvent is updated
trackEvent.listen( "trackeventupdated", function ( e ) {
_this.updatePropertiesFromManifest( e.target );
_this.setErrorState( false );
// Now we REALLY know that we can try setting up listeners
popcorn.on( "googlemaps-loaded", function() {
popcorn.off( "googlemaps-loaded" );
getMapFromTrackEvent();
});
});
// Now we REALLY know that we can try setting up listeners
popcorn.on( "googlemaps-loaded", function() {
popcorn.off( "googlemaps-loaded" );
getMapFromTrackEvent();
});
setup( trackEvent );
},
close: function() {
}
});
});
}( window.Butter ));

1
external/html5-lint поставляемый Submodule

@ -0,0 +1 @@
Subproject commit 401f80981f905cd42f0317fdfb7472de614bc1ac

1
external/popcorn-js поставляемый Submodule

@ -0,0 +1 @@
Subproject commit fa07a4bb76ba92df075fd2e6c387c5ea425d3fd5

35
external/require/require.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,35 @@
/*
RequireJS 2.0.4 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
Available via the MIT or new BSD license.
see: http://github.com/jrburke/requirejs for details
*/
var requirejs,require,define;
(function(Y){function x(b){return J.call(b)==="[object Function]"}function G(b){return J.call(b)==="[object Array]"}function q(b,c){if(b){var e;for(e=0;e<b.length;e+=1)if(b[e]&&c(b[e],e,b))break}}function N(b,c){if(b){var e;for(e=b.length-1;e>-1;e-=1)if(b[e]&&c(b[e],e,b))break}}function y(b,c){for(var e in b)if(b.hasOwnProperty(e)&&c(b[e],e))break}function K(b,c,e,i){c&&y(c,function(c,j){if(e||!b.hasOwnProperty(j))i&&typeof c!=="string"?(b[j]||(b[j]={}),K(b[j],c,e,i)):b[j]=c});return b}function s(b,
c){return function(){return c.apply(b,arguments)}}function Z(b){if(!b)return b;var c=Y;q(b.split("."),function(b){c=c[b]});return c}function $(b,c,e){return function(){var i=fa.call(arguments,0),g;if(e&&x(g=i[i.length-1]))g.__requireJsBuild=!0;i.push(c);return b.apply(null,i)}}function aa(b,c,e){q([["toUrl"],["undef"],["defined","requireDefined"],["specified","requireSpecified"]],function(i){var g=i[1]||i[0];b[i[0]]=c?$(c[g],e):function(){var b=z[O];return b[g].apply(b,arguments)}})}function H(b,
c,e,i){c=Error(c+"\nhttp://requirejs.org/docs/errors.html#"+b);c.requireType=b;c.requireModules=i;if(e)c.originalError=e;return c}function ga(){if(I&&I.readyState==="interactive")return I;N(document.getElementsByTagName("script"),function(b){if(b.readyState==="interactive")return I=b});return I}var ha=/(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,ia=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,ba=/\.js$/,ja=/^\.\//,J=Object.prototype.toString,A=Array.prototype,fa=A.slice,ka=A.splice,w=!!(typeof window!==
"undefined"&&navigator&&document),ca=!w&&typeof importScripts!=="undefined",la=w&&navigator.platform==="PLAYSTATION 3"?/^complete$/:/^(complete|loaded)$/,O="_",S=typeof opera!=="undefined"&&opera.toString()==="[object Opera]",z={},p={},P=[],L=!1,j,t,C,u,D,I,E,da,ea;if(typeof define==="undefined"){if(typeof requirejs!=="undefined"){if(x(requirejs))return;p=requirejs;requirejs=void 0}typeof require!=="undefined"&&!x(require)&&(p=require,require=void 0);j=requirejs=function(b,c,e,i){var g=O,r;!G(b)&&
typeof b!=="string"&&(r=b,G(c)?(b=c,c=e,e=i):b=[]);if(r&&r.context)g=r.context;(i=z[g])||(i=z[g]=j.s.newContext(g));r&&i.configure(r);return i.require(b,c,e)};j.config=function(b){return j(b)};require||(require=j);j.version="2.0.4";j.jsExtRegExp=/^\/|:|\?|\.js$/;j.isBrowser=w;A=j.s={contexts:z,newContext:function(b){function c(a,d,o){var l=d&&d.split("/"),f=l,b=k.map,c=b&&b["*"],e,g,h;if(a&&a.charAt(0)===".")if(d){f=k.pkgs[d]?l=[d]:l.slice(0,l.length-1);d=a=f.concat(a.split("/"));for(f=0;d[f];f+=
1)if(e=d[f],e===".")d.splice(f,1),f-=1;else if(e==="..")if(f===1&&(d[2]===".."||d[0]===".."))break;else f>0&&(d.splice(f-1,2),f-=2);f=k.pkgs[d=a[0]];a=a.join("/");f&&a===d+"/"+f.main&&(a=d)}else a.indexOf("./")===0&&(a=a.substring(2));if(o&&(l||c)&&b){d=a.split("/");for(f=d.length;f>0;f-=1){g=d.slice(0,f).join("/");if(l)for(e=l.length;e>0;e-=1)if(o=b[l.slice(0,e).join("/")])if(o=o[g]){h=o;break}!h&&c&&c[g]&&(h=c[g]);if(h){d.splice(0,f,h);a=d.join("/");break}}}return a}function e(a){w&&q(document.getElementsByTagName("script"),
function(d){if(d.getAttribute("data-requiremodule")===a&&d.getAttribute("data-requirecontext")===h.contextName)return d.parentNode.removeChild(d),!0})}function i(a){var d=k.paths[a];if(d&&G(d)&&d.length>1)return e(a),d.shift(),h.undef(a),h.require([a]),!0}function g(a,d,o,b){var f=a?a.indexOf("!"):-1,v=null,e=d?d.name:null,g=a,i=!0,j="",k,m;a||(i=!1,a="_@r"+(N+=1));f!==-1&&(v=a.substring(0,f),a=a.substring(f+1,a.length));v&&(v=c(v,e,b),m=n[v]);a&&(v?j=m&&m.normalize?m.normalize(a,function(a){return c(a,
e,b)}):c(a,e,b):(j=c(a,e,b),k=h.nameToUrl(j)));a=v&&!m&&!o?"_unnormalized"+(O+=1):"";return{prefix:v,name:j,parentMap:d,unnormalized:!!a,url:k,originalName:g,isDefine:i,id:(v?v+"!"+j:j)+a}}function r(a){var d=a.id,o=m[d];o||(o=m[d]=new h.Module(a));return o}function p(a,d,o){var b=a.id,f=m[b];if(n.hasOwnProperty(b)&&(!f||f.defineEmitComplete))d==="defined"&&o(n[b]);else r(a).on(d,o)}function B(a,d){var b=a.requireModules,l=!1;if(d)d(a);else if(q(b,function(d){if(d=m[d])d.error=a,d.events.error&&(l=
!0,d.emit("error",a))}),!l)j.onError(a)}function u(){P.length&&(ka.apply(F,[F.length-1,0].concat(P)),P=[])}function t(a,d,b){a=a&&a.map;d=$(b||h.require,a,d);aa(d,h,a);d.isBrowser=w;return d}function z(a){delete m[a];q(M,function(d,b){if(d.map.id===a)return M.splice(b,1),d.defined||(h.waitCount-=1),!0})}function A(a,d){var b=a.map.id,l=a.depMaps,f;if(a.inited){if(d[b])return a;d[b]=!0;q(l,function(a){if(a=m[a.id])return!a.inited||!a.enabled?(f=null,delete d[b],!0):f=A(a,K({},d))});return f}}function C(a,
d,b){var l=a.map.id,f=a.depMaps;if(a.inited&&a.map.isDefine){if(d[l])return n[l];d[l]=a;q(f,function(f){var f=f.id,c=m[f];!Q[f]&&c&&(!c.inited||!c.enabled?b[l]=!0:(c=C(c,d,b),b[f]||a.defineDepById(f,c)))});a.check(!0);return n[l]}}function D(a){a.check()}function E(){var a=k.waitSeconds*1E3,d=a&&h.startTime+a<(new Date).getTime(),b=[],l=!1,f=!0,c,g,j;if(!T){T=!0;y(m,function(a){c=a.map;g=c.id;if(a.enabled&&!a.error)if(!a.inited&&d)i(g)?l=j=!0:(b.push(g),e(g));else if(!a.inited&&a.fetched&&c.isDefine&&
(l=!0,!c.prefix))return f=!1});if(d&&b.length)return a=H("timeout","Load timeout for modules: "+b,null,b),a.contextName=h.contextName,B(a);f&&(q(M,function(a){if(!a.defined){var a=A(a,{}),d={};a&&(C(a,d,{}),y(d,D))}}),y(m,D));if((!d||j)&&l)if((w||ca)&&!U)U=setTimeout(function(){U=0;E()},50);T=!1}}function V(a){r(g(a[0],null,!0)).init(a[1],a[2])}function J(a){var a=a.currentTarget||a.srcElement,d=h.onScriptLoad;a.detachEvent&&!S?a.detachEvent("onreadystatechange",d):a.removeEventListener("load",d,
!1);d=h.onScriptError;a.detachEvent&&!S||a.removeEventListener("error",d,!1);return{node:a,id:a&&a.getAttribute("data-requiremodule")}}var k={waitSeconds:7,baseUrl:"./",paths:{},pkgs:{},shim:{}},m={},W={},F=[],n={},R={},N=1,O=1,M=[],T,X,h,Q,U;Q={require:function(a){return t(a)},exports:function(a){a.usingExports=!0;if(a.map.isDefine)return a.exports=n[a.map.id]={}},module:function(a){return a.module={id:a.map.id,uri:a.map.url,config:function(){return k.config&&k.config[a.map.id]||{}},exports:n[a.map.id]}}};
X=function(a){this.events=W[a.id]||{};this.map=a;this.shim=k.shim[a.id];this.depExports=[];this.depMaps=[];this.depMatched=[];this.pluginMaps={};this.depCount=0};X.prototype={init:function(a,d,b,l){l=l||{};if(!this.inited){this.factory=d;if(b)this.on("error",b);else this.events.error&&(b=s(this,function(a){this.emit("error",a)}));this.depMaps=a&&a.slice(0);this.depMaps.rjsSkipMap=a.rjsSkipMap;this.errback=b;this.inited=!0;this.ignore=l.ignore;l.enabled||this.enabled?this.enable():this.check()}},defineDepById:function(a,
d){var b;q(this.depMaps,function(d,f){if(d.id===a)return b=f,!0});return this.defineDep(b,d)},defineDep:function(a,d){this.depMatched[a]||(this.depMatched[a]=!0,this.depCount-=1,this.depExports[a]=d)},fetch:function(){if(!this.fetched){this.fetched=!0;h.startTime=(new Date).getTime();var a=this.map;if(this.shim)t(this,!0)(this.shim.deps||[],s(this,function(){return a.prefix?this.callPlugin():this.load()}));else return a.prefix?this.callPlugin():this.load()}},load:function(){var a=this.map.url;R[a]||
(R[a]=!0,h.load(this.map.id,a))},check:function(a){if(this.enabled&&!this.enabling){var d=this.map.id,b=this.depExports,c=this.exports,f=this.factory,e;if(this.inited)if(this.error)this.emit("error",this.error);else{if(!this.defining){this.defining=!0;if(this.depCount<1&&!this.defined){if(x(f)){if(this.events.error)try{c=h.execCb(d,f,b,c)}catch(g){e=g}else c=h.execCb(d,f,b,c);if(this.map.isDefine)if((b=this.module)&&b.exports!==void 0&&b.exports!==this.exports)c=b.exports;else if(c===void 0&&this.usingExports)c=
this.exports;if(e)return e.requireMap=this.map,e.requireModules=[this.map.id],e.requireType="define",B(this.error=e)}else c=f;this.exports=c;if(this.map.isDefine&&!this.ignore&&(n[d]=c,j.onResourceLoad))j.onResourceLoad(h,this.map,this.depMaps);delete m[d];this.defined=!0;h.waitCount-=1;h.waitCount===0&&(M=[])}this.defining=!1;if(!a&&this.defined&&!this.defineEmitted)this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0}}else this.fetch()}},callPlugin:function(){var a=
this.map,d=a.id,b=g(a.prefix,null,!1,!0);p(b,"defined",s(this,function(b){var f=this.map.name,e=this.map.parentMap?this.map.parentMap.name:null;if(this.map.unnormalized){if(b.normalize&&(f=b.normalize(f,function(a){return c(a,e,!0)})||""),b=g(a.prefix+"!"+f,this.map.parentMap,!1,!0),p(b,"defined",s(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),b=m[b.id]){if(this.events.error)b.on("error",s(this,function(a){this.emit("error",a)}));b.enable()}}else f=s(this,function(a){this.init([],
function(){return a},null,{enabled:!0})}),f.error=s(this,function(a){this.inited=!0;this.error=a;a.requireModules=[d];y(m,function(a){a.map.id.indexOf(d+"_unnormalized")===0&&z(a.map.id)});B(a)}),f.fromText=function(a,d){var b=L;b&&(L=!1);r(g(a));j.exec(d);b&&(L=!0);h.completeLoad(a)},b.load(a.name,t(a.parentMap,!0,function(a,d){a.rjsSkipMap=!0;return h.require(a,d)}),f,k)}));h.enable(b,this);this.pluginMaps[b.id]=b},enable:function(){this.enabled=!0;if(!this.waitPushed)M.push(this),h.waitCount+=
1,this.waitPushed=!0;this.enabling=!0;q(this.depMaps,s(this,function(a,d){var b,c;if(typeof a==="string"){a=g(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.depMaps.rjsSkipMap);this.depMaps[d]=a;if(b=Q[a.id]){this.depExports[d]=b(this);return}this.depCount+=1;p(a,"defined",s(this,function(a){this.defineDep(d,a);this.check()}));this.errback&&p(a,"error",this.errback)}b=a.id;c=m[b];!Q[b]&&c&&!c.enabled&&h.enable(a,this)}));y(this.pluginMaps,s(this,function(a){var b=m[a.id];b&&!b.enabled&&
h.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){q(this.events[a],function(a){a(b)});a==="error"&&delete this.events[a]}};return h={config:k,contextName:b,registry:m,defined:n,urlFetched:R,waitCount:0,defQueue:F,Module:X,makeModuleMap:g,configure:function(a){a.baseUrl&&a.baseUrl.charAt(a.baseUrl.length-1)!=="/"&&(a.baseUrl+="/");var b=k.pkgs,c=k.shim,e=k.paths,f=k.map;K(k,a,!0);k.paths=K(e,a.paths,!0);if(a.map)k.map=
K(f||{},a.map,!0,!0);if(a.shim)y(a.shim,function(a,b){G(a)&&(a={deps:a});if(a.exports&&!a.exports.__buildReady)a.exports=h.makeShimExports(a.exports);c[b]=a}),k.shim=c;if(a.packages)q(a.packages,function(a){a=typeof a==="string"?{name:a}:a;b[a.name]={name:a.name,location:a.location||a.name,main:(a.main||"main").replace(ja,"").replace(ba,"")}}),k.pkgs=b;y(m,function(a,b){a.map=g(b)});if(a.deps||a.callback)h.require(a.deps||[],a.callback)},makeShimExports:function(a){var b;return typeof a==="string"?
(b=function(){return Z(a)},b.exports=a,b):function(){return a.apply(Y,arguments)}},requireDefined:function(a,b){var c=g(a,b,!1,!0).id;return n.hasOwnProperty(c)},requireSpecified:function(a,b){a=g(a,b,!1,!0).id;return n.hasOwnProperty(a)||m.hasOwnProperty(a)},require:function(a,d,c,e){var f;if(typeof a==="string"){if(x(d))return B(H("requireargs","Invalid require call"),c);if(j.get)return j.get(h,a,d);a=g(a,d,!1,!0);a=a.id;return!n.hasOwnProperty(a)?B(H("notloaded",'Module name "'+a+'" has not been loaded yet for context: '+
b)):n[a]}c&&!x(c)&&(e=c,c=void 0);d&&!x(d)&&(e=d,d=void 0);for(u();F.length;)if(f=F.shift(),f[0]===null)return B(H("mismatch","Mismatched anonymous define() module: "+f[f.length-1]));else V(f);r(g(null,e)).init(a,d,c,{enabled:!0});E();return h.require},undef:function(a){var b=g(a,null,!0),c=m[a];delete n[a];delete R[b.url];delete W[a];if(c){if(c.events.defined)W[a]=c.events;z(a)}},enable:function(a){m[a.id]&&r(a).enable()},completeLoad:function(a){var b=k.shim[a]||{},c=b.exports&&b.exports.exports,
e,f;for(u();F.length;){f=F.shift();if(f[0]===null){f[0]=a;if(e)break;e=!0}else f[0]===a&&(e=!0);V(f)}f=m[a];if(!e&&!n[a]&&f&&!f.inited)if(k.enforceDefine&&(!c||!Z(c)))if(i(a))return;else return B(H("nodefine","No define call for "+a,null,[a]));else V([a,b.deps||[],b.exports]);E()},toUrl:function(a,b){var e=a.lastIndexOf("."),g=null;e!==-1&&(g=a.substring(e,a.length),a=a.substring(0,e));return h.nameToUrl(c(a,b&&b.id,!0),g)},nameToUrl:function(a,b){var c,e,f,g,h,i;if(j.jsExtRegExp.test(a))g=a+(b||
"");else{c=k.paths;e=k.pkgs;g=a.split("/");for(h=g.length;h>0;h-=1)if(i=g.slice(0,h).join("/"),f=e[i],i=c[i]){G(i)&&(i=i[0]);g.splice(0,h,i);break}else if(f){c=a===f.name?f.location+"/"+f.main:f.location;g.splice(0,h,c);break}g=g.join("/")+(b||".js");g=(g.charAt(0)==="/"||g.match(/^[\w\+\.\-]+:/)?"":k.baseUrl)+g}return k.urlArgs?g+((g.indexOf("?")===-1?"?":"&")+k.urlArgs):g},load:function(a,b){j.load(h,a,b)},execCb:function(a,b,c,e){return b.apply(e,c)},onScriptLoad:function(a){if(a.type==="load"||
la.test((a.currentTarget||a.srcElement).readyState))I=null,a=J(a),h.completeLoad(a.id)},onScriptError:function(a){var b=J(a);if(!i(b.id))return B(H("scripterror","Script error",a,[b.id]))}}}};j({});aa(j);if(w&&(t=A.head=document.getElementsByTagName("head")[0],C=document.getElementsByTagName("base")[0]))t=A.head=C.parentNode;j.onError=function(b){throw b;};j.load=function(b,c,e){var i=b&&b.config||{},g;if(w)return g=i.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml","html:script"):document.createElement("script"),
g.type=i.scriptType||"text/javascript",g.charset="utf-8",g.async=!0,g.setAttribute("data-requirecontext",b.contextName),g.setAttribute("data-requiremodule",c),g.attachEvent&&!(g.attachEvent.toString&&g.attachEvent.toString().indexOf("[native code")<0)&&!S?(L=!0,g.attachEvent("onreadystatechange",b.onScriptLoad)):(g.addEventListener("load",b.onScriptLoad,!1),g.addEventListener("error",b.onScriptError,!1)),g.src=e,E=g,C?t.insertBefore(g,C):t.appendChild(g),E=null,g;else ca&&(importScripts(e),b.completeLoad(c))};
w&&N(document.getElementsByTagName("script"),function(b){if(!t)t=b.parentNode;if(u=b.getAttribute("data-main")){if(!p.baseUrl)D=u.split("/"),da=D.pop(),ea=D.length?D.join("/")+"/":"./",p.baseUrl=ea,u=da;u=u.replace(ba,"");p.deps=p.deps?p.deps.concat(u):[u];return!0}});define=function(b,c,e){var i,g;typeof b!=="string"&&(e=c,c=b,b=null);G(c)||(e=c,c=[]);!c.length&&x(e)&&e.length&&(e.toString().replace(ha,"").replace(ia,function(b,e){c.push(e)}),c=(e.length===1?["require"]:["require","exports","module"]).concat(c));
if(L&&(i=E||ga()))b||(b=i.getAttribute("data-requiremodule")),g=z[i.getAttribute("data-requirecontext")];(g?g.defQueue:P).push([b,c,e])};define.amd={jQuery:!0};j.exec=function(b){return eval(b)};j(p)}})(this);

6
external/require/text.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,6 @@
/*
RequireJS text 2.0.1 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
Available via the MIT or new BSD license.
see: http://github.com/requirejs/text for details
*/
define(["module"],function(e){"use strict";var t=["Msxml2.XMLHTTP","Microsoft.XMLHTTP","Msxml2.XMLHTTP.4.0"],n=/^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im,r=/<body[^>]*>\s*([\s\S]+)\s*<\/body>/im,i=typeof location!="undefined"&&location.href,s=i&&location.protocol&&location.protocol.replace(/\:/,""),o=i&&location.hostname,u=i&&(location.port||undefined),a=[],f=e.config&&e.config()||{},l,c;return l={version:"2.0.1",strip:function(e){if(e){e=e.replace(n,"");var t=e.match(r);t&&(e=t[1])}else e="";return e},jsEscape:function(e){return e.replace(/(['\\])/g,"\\$1").replace(/[\f]/g,"\\f").replace(/[\b]/g,"\\b").replace(/[\n]/g,"\\n").replace(/[\t]/g,"\\t").replace(/[\r]/g,"\\r").replace(/[\u2028]/g,"\\u2028").replace(/[\u2029]/g,"\\u2029")},createXhr:f.createXhr||function(){var e,n,r;if(typeof XMLHttpRequest!="undefined")return new XMLHttpRequest;if(typeof ActiveXObject!="undefined")for(n=0;n<3;n+=1){r=t[n];try{e=new ActiveXObject(r)}catch(i){}if(e){t=[r];break}}return e},parseName:function(e){var t=!1,n=e.indexOf("."),r=e.substring(0,n),i=e.substring(n+1,e.length);return n=i.indexOf("!"),n!==-1&&(t=i.substring(n+1,i.length),t=t==="strip",i=i.substring(0,n)),{moduleName:r,ext:i,strip:t}},xdRegExp:/^((\w+)\:)?\/\/([^\/\\]+)/,useXhr:function(e,t,n,r){var i=l.xdRegExp.exec(e),s,o,u;return i?(s=i[2],o=i[3],o=o.split(":"),u=o[1],o=o[0],(!s||s===t)&&(!o||o.toLowerCase()===n.toLowerCase())&&(!u&&!o||u===r)):!0},finishLoad:function(e,t,n,r){n=t?l.strip(n):n,f.isBuild&&(a[e]=n),r(n)},load:function(e,t,n,r){if(r.isBuild&&!r.inlineText){n();return}f.isBuild=r.isBuild;var a=l.parseName(e),c=a.moduleName+"."+a.ext,h=t.toUrl(c),p=f.useXhr||l.useXhr;!i||p(h,s,o,u)?l.get(h,function(t){l.finishLoad(e,a.strip,t,n)},function(e){n.error&&n.error(e)}):t([c],function(e){l.finishLoad(a.moduleName+"."+a.ext,a.strip,e,n)})},write:function(e,t,n,r){if(a.hasOwnProperty(t)){var i=l.jsEscape(a[t]);n.asModule(e+"!"+t,"define(function () { return '"+i+"';});\n")}},writeFile:function(e,t,n,r,i){var s=l.parseName(t),o=s.moduleName+"."+s.ext,u=n.toUrl(s.moduleName+"."+s.ext)+".js";l.load(o,n,function(t){var n=function(e){return r(u,e)};n.asModule=function(e,t){return r.asModule(e,u,t)},l.write(e,o,n,i)},i)}},typeof process!="undefined"&&process.versions&&!!process.versions.node?(c=require.nodeRequire("fs"),l.get=function(e,t){var n=c.readFileSync(e,"utf8");n.indexOf("\ufeff")===0&&(n=n.substring(1)),t(n)}):l.createXhr()?l.get=function(e,t,n){var r=l.createXhr();r.open("GET",e,!0),f.onXhr&&f.onXhr(r,e),r.onreadystatechange=function(i){var s,o;r.readyState===4&&(s=r.status,s>399&&s<600?(o=new Error(e+" HTTP status: "+s),o.xhr=r,n(o)):t(r.responseText))},r.send(null)}:typeof Packages!="undefined"&&(l.get=function(e,t){var n="utf-8",r=new java.io.File(e),i=java.lang.System.getProperty("line.separator"),s=new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(r),n)),o,u,a="";try{o=new java.lang.StringBuffer,u=s.readLine(),u&&u.length()&&u.charAt(0)===65279&&(u=u.substring(1)),o.append(u);while((u=s.readLine())!==null)o.append(i),o.append(u);a=String(o.toString())}finally{s.close()}t(a)}),l})

511
make.js Executable file
Просмотреть файл

@ -0,0 +1,511 @@
/*global cat,cd,cp,env,exec,echo,find,ls,mkdir,mv,pwd,rm,sed,target*/
var path = require( "path" ),
spawn = require('child_process').spawn,
normalize = function( p ){ return '"' + path.normalize( p ) + '"'; },
join = path.join,
// Make Windows happy, use `node <path>`
nodeExec = function( p ){ return 'node "' + p + '"'; },
pythonExec = function( p ){ return 'python "' + p + '"'; },
SLICE = Array.prototype.slice,
JSLINT = nodeExec( normalize( "./node_modules/jshint/bin/hint" ) ),
HTML5LINT = pythonExec( normalize( "./external/html5-lint/html5check.py" ) ),
CSSLINT = nodeExec( normalize( "./node_modules/csslint/cli.js" ) ),
UGLIFY = nodeExec( normalize( "./node_modules/uglify-js/bin/uglifyjs" ) ),
RJS = nodeExec( normalize( "./node_modules/requirejs/bin/r.js" ) ),
LESS = nodeExec( normalize( "./node_modules/less/bin/lessc" ) ),
SRC_DIR = 'src',
EDITORS_DIR = 'editors',
TEMPLATES_DIR = 'templates',
DIST_DIR = 'dist',
CSS_DIR = 'css',
CORNFIELD_DIR = 'cornfield',
PUBLIC_DIR = 'public',
DEFAULT_CONFIG = './src/default-config',
BUTTER_LESS_FILE = join( CSS_DIR, "butter.ui.less" ),
BUTTER_CSS_FILE_COMMENT = "/* THIS FILE WAS GENERATED BY A TOOL, DO NOT EDIT. SEE .less FILE IN css/ */",
BUTTER_CSS_FILE = join( CSS_DIR, "/butter.ui.css" ),
BUTTER_TRANSITIONS_LESS_FILE = join( CSS_DIR, "transitions.less" ),
BUTTER_TRANSITIONS_CSS_FILE = join( CSS_DIR, "/transitions.css" ),
BUTTERED_POPCORN = join( DIST_DIR, '/buttered-popcorn.js' ),
// We store version info about Popcorn and Butter when we deploy
VERSIONS_CONFIG = join( CORNFIELD_DIR, 'config', 'versions.json' );
require('shelljs/make');
// Get the git repo version info for a given repo root dir
function gitDescribe( repoRoot ) {
var cwd = pwd();
cd( repoRoot );
var version = exec( 'git describe',
{ silent: true } ).output.replace( /\r?\n/m, '' );
cd( cwd );
return version;
}
// Write a version.json file for cornfield to use when saving data
function publishVersionInfo( versionConfig ) {
var defaultConfig = require( DEFAULT_CONFIG ),
popcornDir = defaultConfig.dirs[ 'popcorn-js' ].replace( '{{baseDir}}', './' ),
butterDir = '.';
JSON.stringify({
date: (new Date()).toJSON(),
version: env.VERSION || 'development',
popcorn: gitDescribe( popcornDir ),
butter: gitDescribe( butterDir )
}, null, 2 ).to( versionConfig );
}
// To supress CSS warnings/errors for a particular line, end the line
// with a comment indicating you want CSS Lint to ignore this line's
// error(s). Here are some examples:
//
// -webkit-appearance: button; /* csslint-ignore */
// -webkit-appearance: button; /*csslint-ignore*/
// -webkit-appearance: button; /* csslint-ignore: This is being done because of iOS ... */
function checkCSSFile( filename, warnings, errors ) {
var fileLines = cat( filename ).split( /\r?\n/ ),
ignoreLines = "",
// Look for: "blah blah blah /* csslint-ignore */" or
// "blah blah /*csslint-ignore: this is my reason*/"
ignoreRegex = /\/\*\s*csslint-ignore[^*]*\*\/$/,
// Errors look like: "css/butter.ui.css: line 186, col 3, Error..."
lineRegex = /\: line (\d+),/;
// Build a map of lines to ignore: "|14||27|" means ignore lines 14 and 27
for( var i=0; i < fileLines.length; i++ ){
if( ignoreRegex.test( fileLines[ i ] ) ) {
ignoreLines += "|" + i + "|";
}
}
// Run CSSLint across the file, check for errors/warnings and ignore if
// they are ones we know about from above.
exec(CSSLINT +
' --warnings=' + warnings +
' --errors=' + errors +
' --quiet --format=compact' +
' ' + filename, { silent: true } ).output.split( /\r?\n/ )
.forEach( function( line ) {
if( !line ) {
return;
}
// Some warnings don't refer to a line, e.g.
// "css/butter.ui.css: Warning - Too many floats (10)..."
var matches = line.match( lineRegex ),
lineNumber = matches ? matches[ 1 ] : null;
if( !!lineNumber ) {
if( ignoreLines.indexOf( "|" + lineNumber + "|" ) === -1 ) {
echo( line );
}
} else {
echo( line );
}
});
}
function checkCSS( dir ) {
// see cli.js --list-rules.
var warnings = [
// "important",
// "adjoining-classes",
// "duplicate-background-images",
// "qualified-headings",
// "fallback-colors",
// "empty-rules",
// "shorthand",
// "overqualified-elements",
// "import",
// "regex-selectors",
// "rules-count",
// "font-sizes",
// "universal-selector",
// "unqualified-attributes",
"zero-units"
].join(",");
var errors = [
"known-properties",
"compatible-vendor-prefixes",
"display-property-grouping",
"duplicate-properties",
"errors",
"gradients",
"font-faces",
//"floats",
"vendor-prefix"
].join(",");
var files = ls( dir );
files.forEach( function( filename ) {
filename = join( dir, filename );
if( /\.css$/.test( filename ) ) {
checkCSSFile( filename, warnings, errors );
}
});
}
function checkJS(){
// Takes a string or an array of strings referring to directories.
var dirs = SLICE.call( arguments );
// Get all js and json files in dirs
var files = "";
[ /\.js$/, /\.json$/ ].forEach( function( regexp ){
files += find( dirs ).filter( function( file ) {
return file.match( regexp );
}).join(' ') + ' ';
});
// jshint with non-errors plus linting of json files
exec(JSLINT + ' ' + files + ' --show-non-errors --extra-ext json');
}
var desc = {
check: 'Lint CSS, HTML, and JS',
css: 'Build LESS files to CSS',
deploy: 'Build Butter suitable for production',
server: 'Run the development server'
};
target.all = function() {
echo('Please specify a target. Available targets:');
Object.keys(target).sort().filter(function(t) {
return t !== "all";
}).forEach(function(t) {
echo(' ' + t + ' - ' + desc[t]);
});
};
function clean() {
rm('-fr', DIST_DIR);
mkdir('-p', DIST_DIR);
}
function checkHTMLFile( filename, ignoreList ) {
var printedHeader = false,
printFooter = false;
var out = exec( HTML5LINT + " -h " + filename, { silent: true } ).output;
if ( out ) {
out = out.replace( "There were errors. (Tried in the text/html mode.)\n", "", "m" );
// Break the set of errors apart, and inspect each for
// matches in our ignoreList. If not something we should
// ignore, print each error.
out.split( "\n\n" ).forEach( function( error ) {
if ( !error.length ) {
return;
}
var i = ignoreList.length,
ignore;
while ( i-- ) {
ignore = ignoreList[ i ];
// If the error string matches the ignore string, make sure
// there isn't also a conditional when() function. If there is
// check that too.
if ( error.indexOf( ignore.text ) > -1 ) {
if ( ignore.when ) {
if ( ignore.when( filename ) ) {
return;
}
} else {
return;
}
}
}
if ( !printedHeader ) {
echo( "HTML5 Lint Issues for file: " + filename + "\n" );
printedHeader = true;
printFooter = true;
}
echo( error + "\n" );
});
if ( printFooter ) {
echo( "\n" );
}
}
}
function checkHTML() {
// Poor-man's HTML Doc vs. Fragment check
function isHTMLFragment( filename ) {
return !( /<html[^>]*\>/m ).test( cat( filename ) );
}
// List of errors/warnings to ignore, some with a conditional
// to only ignore when some condition is true.
var ignoreList = [
{
// Don't warn on valid docs
text: "The document is valid HTML5 + ARIA + SVG 1.1 + MathML 2.0 (subject to the utter previewness of this service)."
},
{
text: "Error: Start tag seen without seeing a doctype first. Expected “<!DOCTYPE html>”.",
when: isHTMLFragment
},
{
text: "Error: Element “head” is missing a required instance of child element “title”.",
when: isHTMLFragment
},
{
text: "Error: Bad value “X-UA-Compatible” for attribute “http-equiv” on element “meta”."
},
{
text: "Warning: The character encoding of the document was not declared."
},
{
// Let <style> be in fragments.
text: "Error: Element “style” not allowed as child of element “body” in this context. (Suppressing further errors from this subtree.)",
when: isHTMLFragment
}
];
find([
EDITORS_DIR,
PUBLIC_DIR,
join( SRC_DIR, "dialog", "dialogs" ),
join( SRC_DIR, "layouts" ),
join( SRC_DIR, "editor" ),
join( SRC_DIR, "ui", "webmakernav" ),
TEMPLATES_DIR ] ).filter( function( file ) {
return file.match( /\.html$/ );
}).forEach( function( filename ) {
checkHTMLFile( filename, ignoreList );
});
}
function lessToCSS( options ){
var compress = !!options.compress,
lessFile = options.lessFile,
cssFile = options.cssFile;
var args = compress ? " --yui-compress " : " ",
result = exec(LESS + args + lessFile, {silent:true});
if( result.code === 0 ){
var css = BUTTER_CSS_FILE_COMMENT + "\n\n" + result.output;
css.to( cssFile );
} else {
echo( result.output );
}
}
function buildCSS(compress) {
lessToCSS({
lessFile: BUTTER_LESS_FILE,
cssFile: BUTTER_CSS_FILE,
compress: compress
});
lessToCSS({
lessFile: BUTTER_TRANSITIONS_LESS_FILE,
cssFile: BUTTER_TRANSITIONS_CSS_FILE,
compress: compress
});
lessToCSS({
lessFile: "templates/assets/plugins/wikipedia/popcorn.wikipedia.less",
cssFile: "templates/assets/plugins/wikipedia/popcorn.wikipedia.css",
compress: compress
});
lessToCSS({
lessFile: "templates/assets/css/jquery-ui/jquery.ui.butter.less",
cssFile: "templates/assets/css/jquery-ui/jquery.ui.butter.css",
compress: compress
});
lessToCSS({
lessFile: "src/ui/webmakernav/webmakernav.less",
cssFile: "src/ui/webmakernav/webmakernav.css",
compress: compress
});
lessToCSS({
lessFile: "css/embed.less",
cssFile: "css/embed.css",
compress: compress
});
lessToCSS({
lessFile: "css/embed-shell.less",
cssFile: "css/embed-shell.css",
compress: compress
});
}
target.check = function() {
checkJS( 'make.js', SRC_DIR, EDITORS_DIR, CORNFIELD_DIR, TEMPLATES_DIR );
buildCSS();
checkCSS( CSS_DIR, TEMPLATES_DIR );
checkHTML();
};
function stampVersion( version, filename ){
// Stamp embed.version with supplied version, or git info
version = version || gitDescribe( "." );
sed( '-i', '@VERSION@', version, filename );
}
target.css = function() {
buildCSS();
};
function buildJS( version, compress ){
var doCompress = compress ? "" : "optimize=none";
var result = "";
result = exec(RJS + ' -o tools/build.js ' + doCompress, {silent: true});
if (!!result.code) {
echo(result.output);
}
stampVersion( version, 'dist/src/butter.js' );
result = exec(RJS + ' -o tools/embed.js ' + doCompress, {silent: true});
if (!!result.code) {
echo(result.output);
}
stampVersion( version, 'dist/src/embed.js' );
}
target.server = function() {
echo('### Serving butter');
// Write-out version info regarding Butter and Popcorn so cornfield knows what it's serving.
publishVersionInfo( VERSIONS_CONFIG );
cd( CORNFIELD_DIR );
// Use child_process.spawn here for a long-running server process
// (replaces `exec('node app.js', { async: true });`).
var server = spawn( 'node', [ 'app.js' ] );
// Mostly stolen from http://nodejs.org/docs/v0.3.5/api/child_processes.html#child_process.spawn
server.stdout.on( 'data', function( data ) {
process.stdout.write( data );
});
server.stderr.on( 'data', function( data ) {
process.stderr.write( "" + data );
});
server.on( 'exit', function( code ) {
console.log( 'server process exited with code ' + code );
});
};
function butteredPopcorn() {
var defaultConfig = require( DEFAULT_CONFIG ),
popcornDir = defaultConfig.dirs['popcorn-js'].replace( '{{baseDir}}', './' ),
popcornFiles = [];
// Popcorn License Header
popcornFiles.push( popcornDir + '/LICENSE_HEADER' );
// classList shim
popcornFiles.push( './tools/classlist-shim.js' );
// popcorn IE8 shim
popcornFiles.push( popcornDir + '/ie8/popcorn.ie8.js' );
// popcorn.js
popcornFiles.push( popcornDir + '/popcorn.js' );
// plugins
if ( defaultConfig.plugin && defaultConfig.plugin.plugins ) {
defaultConfig.plugin.plugins.forEach( function( plugin ){
popcornFiles.push( plugin.path.replace( '{{baseDir}}', './' ) );
});
}
// wrapper base prototype
popcornFiles.push( popcornDir + '/wrappers/common/popcorn._MediaElementProto.js' );
// wrappers
if ( defaultConfig.wrapper && defaultConfig.wrapper.wrappers ) {
defaultConfig.wrapper.wrappers.forEach( function( wrapper ){
popcornFiles.push( wrapper.path.replace( '{{baseDir}}', './' ) );
});
}
// module for baseplayer
popcornFiles.push( popcornDir + '/modules/player/popcorn.player.js' );
// players
if ( defaultConfig.player && defaultConfig.player.players ) {
defaultConfig.player.players.forEach( function( player ){
popcornFiles.push( player.path.replace( '{{baseDir}}', './' ) );
});
}
// Stamp Popcorn.version with the git commit sha we are using
var popcornVersion = gitDescribe( popcornDir );
// Write out dist/buttered-popcorn.js
cat( popcornFiles ).to( BUTTERED_POPCORN );
sed('-i', '@VERSION', popcornVersion, BUTTERED_POPCORN);
}
target.deploy = function(){
echo('### Making deployable versions of butter, embed, popcorn, etc. in dist/ (use UNMINIFIED=1 for unminified)');
// To get unminified butter.js, use the UNMINIFIED env variable:
// $ UNMINIFIED=1 node make deploy
var compress = env.UNMINIFIED !== "1",
version = env.VERSION;
clean();
buildJS( version, compress );
buildCSS( compress );
butteredPopcorn();
// We'll mirror src/butter.js and src/embed.js to mimic exploded install
mkdir('-p', './dist/src');
// Copy other assets over
mkdir( join( DIST_DIR, 'css' ) );
cp('css/*.css', join( DIST_DIR, 'css' ) );
cp('-R', 'editors', DIST_DIR);
cp('-R', 'resources', DIST_DIR);
cp('-R', 'templates', DIST_DIR);
cp('-R', 'cornfield', DIST_DIR);
cp('package.json', 'README.md', DIST_DIR);
// Export will need a version of popcorn.js where the templates expect it
// at dist/external/popcorn-js/popcorn.js
if ( compress ) {
exec( UGLIFY + ' --output ' + BUTTERED_POPCORN + ' ' + BUTTERED_POPCORN );
}
mkdir( '-p', 'dist/external/popcorn-js/' );
mv( BUTTERED_POPCORN, './dist/external/popcorn-js/popcorn.js' );
// Move everything into the public folder
cp( '-R', 'public', DIST_DIR );
mv([ 'dist/css', 'dist/editors', 'dist/external', 'dist/resources', 'dist/src', 'dist/templates' ], 'dist/public/' );
// Write-out version info regarding Butter and Popcorn so cornfield knows what it's serving.
publishVersionInfo( join( DIST_DIR, VERSIONS_CONFIG ) );
// Create a tar archive
var tarName = 'butter-' + gitDescribe( '.' ) + '.tar';
exec( 'tar -cyf "' + tarName + '" dist' );
mv( tarName, 'dist' );
// It's important to use the production config
echo( 'Run cornfield with `NODE_ENV=production node app.js`' );
};

265
npm-shrinkwrap.json сгенерированный Normal file
Просмотреть файл

@ -0,0 +1,265 @@
{
"name": "butter",
"version": "0.9.0",
"dependencies": {
"shelljs": {
"version": "0.0.6"
},
"jshint": {
"version": "0.6.2",
"dependencies": {
"argsparser": {
"version": "0.0.6"
},
"minimatch": {
"version": "0.0.5",
"dependencies": {
"lru-cache": {
"version": "1.0.6"
}
}
}
}
},
"requirejs": {
"version": "2.0.6"
},
"express": {
"version": "2.5.11",
"dependencies": {
"connect": {
"version": "1.9.2",
"dependencies": {
"qs": {
"version": "0.5.1"
},
"mime": {
"version": "1.2.7"
},
"formidable": {
"version": "1.0.11"
}
}
},
"mime": {
"version": "1.2.4"
},
"qs": {
"version": "0.4.2"
},
"mkdirp": {
"version": "0.3.0"
}
}
},
"jade": {
"version": "0.27.2",
"dependencies": {
"commander": {
"version": "0.6.1"
},
"mkdirp": {
"version": "0.3.0"
}
}
},
"express-persona": {
"version": "0.0.6"
},
"config": {
"version": "0.4.16",
"dependencies": {
"js-yaml": {
"version": "0.3.7"
},
"coffee-script": {
"version": "1.3.3"
},
"vows": {
"version": "0.6.4",
"dependencies": {
"eyes": {
"version": "0.1.8"
},
"diff": {
"version": "1.0.3"
}
}
}
}
},
"sqlite3": {
"version": "2.1.5"
},
"sequelize": {
"version": "1.5.0",
"dependencies": {
"mysql": {
"version": "0.9.6",
"dependencies": {
"hashish": {
"version": "0.0.4",
"dependencies": {
"traverse": {
"version": "0.6.3"
}
}
}
}
},
"underscore": {
"version": "1.2.4"
},
"underscore.string": {
"version": "2.0.0"
},
"lingo": {
"version": "0.0.5"
},
"validator": {
"version": "0.3.9"
},
"moment": {
"version": "1.1.1"
},
"commander": {
"version": "0.6.1"
},
"generic-pool": {
"version": "1.0.9"
}
}
},
"client-sessions": {
"version": "0.0.9",
"dependencies": {
"cookies": {
"version": "0.1.7",
"from": "https://github.com/benadida/cookies/tarball/d2f0f0b3"
},
"node-proxy": {
"version": "0.6.0"
}
}
},
"csslint": {
"version": "0.9.9"
},
"uglify-js": {
"version": "1.3.3"
},
"less": {
"version": "1.3.0"
},
"less-middleware": {
"version": "0.1.7",
"dependencies": {
"mkdirp": {
"version": "0.3.4"
}
}
},
"knox": {
"version": "0.3.1",
"dependencies": {
"mime": {
"version": "1.2.7"
}
}
},
"node-uuid": {
"version": "1.3.3"
},
"supertest": {
"version": "0.3.1",
"dependencies": {
"superagent": {
"version": "0.9.5",
"dependencies": {
"qs": {
"version": "0.4.2"
},
"formidable": {
"version": "1.0.9"
},
"mime": {
"version": "1.2.5"
},
"emitter-component": {
"version": "0.0.5"
},
"cookiejar": {
"version": "1.3.0"
}
}
},
"methods": {
"version": "0.0.1"
}
}
},
"tap": {
"version": "0.3.1",
"dependencies": {
"inherits": {
"version": "1.0.0"
},
"yamlish": {
"version": "0.0.5"
},
"slide": {
"version": "1.1.3"
},
"runforcover": {
"version": "0.0.2",
"dependencies": {
"bunker": {
"version": "0.1.2",
"dependencies": {
"burrito": {
"version": "0.2.12",
"dependencies": {
"traverse": {
"version": "0.5.2"
},
"uglify-js": {
"version": "1.1.1"
}
}
}
}
}
}
},
"nopt": {
"version": "2.0.0",
"dependencies": {
"abbrev": {
"version": "1.0.3"
}
}
},
"mkdirp": {
"version": "0.3.4"
},
"difflet": {
"version": "0.2.3",
"dependencies": {
"traverse": {
"version": "0.6.3"
},
"charm": {
"version": "0.0.8"
}
}
},
"deep-equal": {
"version": "0.0.0"
},
"buffer-equal": {
"version": "0.0.0"
}
}
}
}
}

113
package.json Normal file
Просмотреть файл

@ -0,0 +1,113 @@
{
"name": "butter",
"version": "0.9.0",
"author": "The Mozilla Popcorn Team <dev-popcorn@lists.mozilla.org>",
"description": "The Popcorn authoring tool",
"contributors": [
{
"name": "Brett Gaylor",
"email": "brett@mozillafoundation.org "
},
{
"name": "Bobby Richter",
"email": "secretrobotron@gmail.com"
},
{
"name": "David Seifried",
"email": "david.c.seifried@gmail.com"
},
{
"name": "Christopher De Cairos",
"email": "chris@chrisdecairos.ca"
},
{
"name": "Matthew Schranz",
"email": "schranz.m@gmail.com"
},
{
"name": "Jon Buckley",
"email": "jon@jbuckley.ca"
},
{
"name": "Scott Downe",
"email": "scott.downe@gmail.com"
},
{
"name": "Mohammed Buttu",
"email": "mohammedbuttu@gmail.com"
},
{
"name": "Kate Hudson",
"email": "katehudsondesign@gmail.com"
},
{
"name": "Ben Moskowitz",
"email": "benrito@gmail.com"
},
{
"name": "David Humphrey",
"email": "david.humphrey@senecacollege.ca"
},
{
"name": "Jeremy Banks",
"email": "jeremy@jeremybanks.ca"
},
{
"name": "Brian Chirls",
"email": "brian@chirls.com"
},
{
"name": "James Burke",
"email": "jrburke@gmail.com"
},
{
"name": "Robert Stanica",
"email": "robertstanica@gmail.com"
},
{
"name": "Chris Appleton",
"email": "christopher@mozillafoundation.org"
},
{
"name": "Cory Shaw",
"email": "c@ocupop.com"
},
{
"name": "Michael Nieling",
"email": "m@ocupop.com"
}
],
"repository": {
"type": "git",
"url": "https://github.com/mozilla/butter.git"
},
"dependencies": {
"shelljs": "0.0.6",
"jshint": "0.6.2",
"requirejs": "2.0.6",
"express": "2.5.11",
"jade": "0.27.2",
"express-persona": "~0.0.6",
"config": "0.4.16",
"sqlite3": "~2.1.5",
"sequelize": "~1.5.0",
"client-sessions": "0.0.9",
"csslint": "0.9.9",
"uglify-js": "1.3.3",
"less": "1.3.0",
"less-middleware": "~0.1.7",
"knox": "0.3.1",
"node-uuid": "1.3.3"
},
"devDependencies": {
"tap": "~0.3.1",
"supertest": "~0.3.1"
},
"scripts": {
"test": "./node_modules/.bin/tap cornfield/test/*.test.js"
},
"license": "MIT",
"engine": {
"node": ">=0.8"
}
}

Двоичные данные
public/favicon.ico Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.1 KiB

227
public/index.html Normal file
Просмотреть файл

@ -0,0 +1,227 @@
<!DOCTYPE html>
<html lang="en-US" dir="ltr" class="no-js">
<head>
<meta charset="utf-8">
<title>Mozilla Popcorn</title>
<link rel="stylesheet" href="media/css/sandstone.css">
<link rel="stylesheet" href="media/css/page.css">
<link href="//www.mozilla.org/tabzilla/media/css/tabzilla.css" rel="stylesheet">
<!--[if lt IE 9]>
<script src="/media/js/html5shiv.js"></script>
<![endif]-->
</head>
<body id="home">
<script type="text/javascript">
document.documentElement.className = document.documentElement.className.replace(/\bno-js\b/, '');
</script>
<div id="outer-wrapper">
<div id="wrapper">
<section id="header">
<header id="masthead">
<div class="row">
<div class="span10">
<div class="logo">
<a href="/en-US/">
<img src="/popcornlogo-small.png" alt="Mozilla Popcorn">
</a>
</div>
<a href="http://www.mozilla.org/" id="tabzilla">Mozilla</a>
<nav id="nav-main" role="navigation">
<ul>
<li class="home"><a href="/">Home</a></li>
</ul>
</nav>
</div>
</div>
</header>
</section>
<section>
<div class="container divider">
<div class="row">
<div class="hero-unit span6">
<hgroup class="large">
<h2>Video beyond the box</h2>
<h1>Popcorn Maker</h1>
</hgroup>
<p>
Popcorn Maker makes it easy to enhance, remix and share web video. Use your web browser to combine video and audio with content from the rest of the web — from text, links and maps to pictures and live feeds.
</p>
<p>
Use Popcorn Maker to create your own interactive newscasts, pop-up videos, multimedia reports, fan videos, guided web tours and more. Remix your favorite videos on YouTube or sounds on SoundCloud, add your own comments and links, or drag and drop in content from across the web.
</p>
<p>
The result is a whole new way to tell stories on the web, with videos that are dynamic, full of links, and unique each time you watch them. It's video beyond the box.
</p>
<p class="intro-bar">
<a class="button-blue start" href="/templates/basic">Start from scratch</a>
<em>or</em>
<a class="button start" href="#">Take a tutorial</a>
</p>
</div>
<div class="span4">
<video controls preload="none" width="344" height="232"
poster="media/img/poster.png">
<source src="http://videos.mozilla.org/serv/webmademovies/popcornsite.webm" type="video/webm">
<source src="http://videos.mozilla.org/serv/webmademovies/popcornsite.mp4" type="video/mp4">
</video>
</div>
</div>
<div class="row">
<div class="span10">
<h2>Remix and explore these great projects made with Popcorn Maker:</h2>
<div id="projects" class="carousel">
<a class="control left"><span>prev</span></a>
<a class="control right"><span>next</span></a>
<div class="carousel-items">
<ul>
<li>
<div class="thumbnail">
<a href="/templates/basic/?savedDataUrl=tour.json">
<img src="http://placehold.it/240x147" alt="">
</a>
<div class="caption">
<p>by Mozilla</p>
<h5 class="title"><a href="/templates/basic/?savedDataUrl=tour.json">Neighbourhood Tour</a></h5>
</div>
</div>
</li>
<li>
<div class="thumbnail">
<a href="/templates/basic/?savedDataUrl=tour.json">
<img src="http://placehold.it/240x147" alt="">
</a>
<div class="caption">
<p>by Mozilla</p>
<h5 class="title"><a href="/templates/basic/?savedDataUrl=soundscape.json">Soundscape</a></h5>
</div>
</div>
</li>
<li>
<div class="thumbnail">
<a href="/templates/basic/?savedDataUrl=meme.json">
<img src="http://placehold.it/240x147" alt="">
</a>
<div class="caption">
<p>by Mozilla</p>
<h5 class="title"><a href="/templates/basic/?savedDataUrl=meme.json">Video Meme</a></h5>
</div>
</div>
</li>
</ul>
</div>
</div>
<a class="more" href="#">See all projects...</a>
</div>
</div>
</div>
<div class="container">
<div class="row">
<div class="span10">
<ul class="thumbnails">
<li class="span3 threeColumn">
<div class="action">
<a href="http://popcornjs.org">
<img src="http://placehold.it/147x147" alt="">
</a>
<div class="caption">
<h5 class="title">
<a href="http://popcornjs.org">Create advanced experiences with Popcorn.js</a>
</h5>
</div>
</div>
</li>
<li class="span3 threeColumn">
<div class="action">
<a href="http://blog.mozilla.org/popcorn/">
<img src="http://placehold.it/147x147" alt="">
</a>
<div class="caption">
<h5 class="title">
<a href="http://blog.mozilla.org/popcorn/">Read the Popcorn Blog</a>
</h5>
</div>
</div>
</li>
<li class="span3 threeColumn">
<div class="action">
<a href="http://yeswecanhas.com/files/2008/02/bammacat.jpg">
<img src="http://placehold.it/147x147" alt="">
</a>
<div class="caption">
<h5 class="title">
<a href="http://yeswecanhas.com/files/2008/02/bammacat.jpg">Some sort of get involved link here</a>
</h5>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
</section>
<section id="page-bottom">
<footer id="colophon">
<div class="row">
<div class="span2">
<h2><img src="/media/img/footer-logo.png" alt="Mozilla Webmaker"></h2>
</div>
<nav class="span2">
<h3>About</h3>
<ul>
<li><a href="mailto:mozparty@mozilla.org">Contact Us</a></li>
<li><a href="//www.mozilla.org/en-US/privacy-policy">Privacy Policy</a></li>
<li><a href="//www.mozilla.org/en-US/about/legal.html">Legal Notices</a></li>
</ul>
</nav>
<nav class="span2">
<h3>Connect with us</h3>
<ul>
<li><a href="http://twitter.com/mozilla/">Twitter</a></li>
<li><a href="https://www.facebook.com/mozilla/">Facebook</a></li>
</ul>
</nav>
<nav class="span2">
<h3>Make something</h3>
<ul>
<li><a href="#">Projects</a></li>
<li><a href="https://webmaker.org/tools/">Tools</a></li>
<li><a href="https://webmaker.org/events/">Events</a></li>
</ul>
</nav>
<nav class="span2">
<h3>Support our work</h3>
<ul>
<li><a href="https://donate.mozilla.org/page/contribute/join-mozilla?source=join_link">Donate</a></li>
<li><a href="https://mozillalabs.com/en-US/">Contribute</a></li>
<li><a href="https://donate.mozilla.org/page/contribute/firefoxtshirt">Buy a t-shirt</a></li>
</ul>
</nav>
</div>
</footer>
</section>
</div>
</div>
<script src="//www.mozilla.org/media/js/libs/jquery-1.7.1.min.js"></script>
<script src="//www.mozilla.org/tabzilla/media/js/tabzilla.js"></script>
<script src="media/js/jcarousellite_1.0.1.min.js"></script>
<script>
$(function(){
$('#projects .carousel-items').jCarouselLite({
btnNext: '.right',
btnPrev: '.left',
circular: true,
});
});
</script>
</body>
</html>

Двоичные данные
public/logo-words.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 6.1 KiB

342
public/media/css/page.css Normal file
Просмотреть файл

@ -0,0 +1,342 @@
/*
Webpage Maker Styles
================================
Global styles and classes
*/
body {
color: #555;
}
.threeColumn {
margin-left: 47px;
}
h1 {
font-family: "Open Sans", sans-serif;
font-size: 36px;
color: rgb(109, 110, 113);
font-weight: 100;
line-height: 50px;
}
h2 {
font-family: "Open Sans", sans-serif;
font-size: 16px;
color: rgb(109, 110, 113);
font-weight: 500;
line-height: 23px
}
p {
font-family: "Open Sans",sans-serif;
font-size: 13px;
color: rgb(147, 149, 152);
line-height: 23px;
}
.hero-unit h2 {
text-transform: lowercase;
font-style: italic;
line-height: 50px;
font-family: "Open Sans Light", sans-serif;
}
.hero-unit h1 {
text-transform: uppercase;
font-weight: bold;
}
a.top,
a.top:link,
a.top:visited,
a.top:hover,
a.top:active {
color: #666;
position: absolute;
right: 5px;
bottom: 5px;
}
a.top:before {
content: "⇧ ";
}
.more {
float: right;
margin: 1em 0;
}
.large,
.large h1 {
font-size: 58px;
}
/*
Global styles and classes
================================
Layout styles
*/
#outer-wrapper {
border-top: 1px;
}
#wrapper {
background: #F9F9F9 url("/media/img/bg-stone.png") repeat-x 0 70px;
padding-bottom: 0;
display: table;
width: 100%;
}
#test-ribbon {
position: absolute;
top: 0;
left: 0;
z-index: 2;
}
#tabzilla-panel {
z-index: 2;
}
#header {
-webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .5);
-moz-box-shadow: 0 0 5px rgba(0,0,0,.5);
-ms-box-shadow: 0 0 5px rgba(0,0,0,.5);
-o-box-shadow: 0 0 5px rgba(0,0,0,.5);
box-shadow: 0 0 5px
rgba(0, 0, 0, .5);
background: #FFF;
background: rgba(255, 255, 255, 0.8);
position: relative;
z-index: 1;
}
#masthead {
margin-bottom: 90px;
}
#masthead .row {
clear: both;
}
.logo {
font-family: "Open Sans", sans-serif;
font-size: 2.2em;
font-weight: 200;
line-height: normal;
}
.logo a,
.logo a:link,
.logo a:visited,
.logo a:hover,
.logo a:active
{
color: #4d4d4d;
text-decoration: none;
text-transform: uppercase;
margin-right: 20px;
float: left;
}
.logo img {
display: block;
position: relative;
padding-top: 10px;
}
#masthead nav {
float: none;
}
#masthead nav ul li {
display: inline;
}
#nav-main a:hover,
#nav-main a:active {
text-decoration: none;
}
#nav-main ul {
height: 70px;
margin: 0;
padding: 0;
list-style: none;
font-weight: bold;
}
#masthead #nav-main li a {
-webkit-transition: all .25s linear 0s;
-moz-transition: all .25s linear 0s;
-ms-transition: all .25s linear 0s;
-o-transition: all .25s linear 0s;
transition: all .25s linear 0s;
display: block;
min-width: 70px;
height: 70px;
line-height: 70px;
margin: 0 0 0 -1px;
padding: 0 15px;
float: left;
text-align: center;
border: solid #E1E2E3;
border-width: 0 1px;
position: relative;
}
#nav-main li a:hover {
background: rgba(28,144,193,0.1);
}
#home #nav-main .home a {
background: #4199D6;
color: #FFF;
border: none;
margin-right: 1px;
text-shadow: -1px -1px #347DB0;
}
#home #nav-main .home a:after {
content: "";
position: absolute;
top: 100%;
left: 0;
border: solid transparent;
border-width: 25px 50px 0 50px;
border-top-color: #4199D6;
}
#page-bottom {
background-color: #fff;
border-top: solid 1px #E1E2E3;
margin-top: 50px;
}
#colophon {
line-height: 25px;
}
#colophon h2 {
font-family: "Open Sans", sans-serif;
font-weight: 100;
color: #999;
font-size: 2em;
}
#colophon h3 {
color: #999;
font-family: "Open Sans", sans-serif;
font-weight: 600;
font-size: 1em;
margin-top: 1em;
}
#colophon a,
#colophon a:link,
#colophon a:visited,
#colophon a:hover,
#colophon a:active {
color: #27AAE1;
text-decoration: none;
}
/*
Layout styles
================================
Home styles
*/
#home .caption p {
margin-bottom: 0;
}
.thumbnail img {
width: 240px;
height: 147px;
border: 6px solid white;
-webkit-box-shadow: 0 2px 2px 3px rgba(0, 0, 0, 0.1);
-moz-box-shadow: 0 2px 2px 3px rgba(0, 0, 0, 0.1);
box-shadow: 0 2px 2px 3px rgba(0, 0, 0, 0.1);
}
.thumbnails {
margin-left: -24px;
list-style: none;
}
.thumbnail {
margin-bottom: 30px;
}
.action {
text-align: center;
}
/*
Home styles
================================
Carousel styles
*/
.carousel {
position: relative;
}
.carousel li {
margin: 0 10px;
}
.carousel .control {
position: absolute;
top: 70px;
width: 40px;
height: 40px;
background: url(/media/img/slider-arrows.png) no-repeat;
overflow: hidden;
cursor: pointer;
}
.carousel .control span {
visibility: hidden;
}
.carousel .left {
left: 0;
background-position: 0 0;
}
.carousel .left:hover {
background-position: 0 -50px;
}
.carousel .left:active {
background-position: 0 -100px;
}
.carousel .right {
right: 0;
background-position: -50px 0;
}
.carousel .right:hover {
background-position: -50px -50px;
}
.carousel .right:active {
background-position: -50px -100px;
}
.carousel .carousel-items {
margin: 0 auto;
}
.no-js .carousel .control {
display: none;
}
.no-js .carousel ul {
list-style: none;
margin-left: -24px;
}
.no-js .carousel li {
float: left;
margin-left: 47px;
margin-right: 0;
}
/*
Carousel styles
================================
*/

Просмотреть файл

@ -0,0 +1,496 @@
/*
* Based on bootstrap, Copyright 2012 Twitter, Inc
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0
*/
html,
body,
div,
span,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
address,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
samp,
small,
strong,
sub,
sup,
var,
b,
i,
hr,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
figure,
figcaption,
hgroup,
menu,
footer,
header,
nav,
section,
summary,
time,
mark,
audio,
video {
margin: 0;
padding: 0;
border: 0;
}
article,
aside,
canvas,
figure,
figure img,
figcaption,
hgroup,
footer,
header,
nav,
section,
audio,
video {
display: block;
}
a img {
border: 0;
}
/* {{{ Basic Colors, Text, Links */
html {
background: #fff;
}
body {
font-size: 16px;
line-height: 24px;
font-family: "Open Sans", sans-serif;
color: #333333;
background: #fff;
}
.button, .button:link, .button:visited {
-moz-transition -webkit-transition -o-transition -ms-transition transition: all 0.25s linear 0s;
background-color: #659324;
background-image: -moz-linear-gradient(#81BC2E, #659324);
background-repeat: repeat-x;
border: 0 none;
border-radius: 0.25em 0.25em 0.25em 0.25em;
box-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.1), 0 -2px 0 0 rgba(0, 0, 0, 0.2) inset;
color: #FFFFFF;
display: inline-block;
font-family: 'OpenSansRegular',sans-serif;
font-size: 14px;
height: 48px;
line-height: 48px;
padding: 0 24px;
text-align: center;
text-decoration: none;
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
}
.button:hover, .button:link:hover, .button:visited:hover {
-moz-transition -webkit-transition -o-transition -ms-transition transition: all 0.25s linear 0s;
box-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.1), 0 -2px 0 0 rgba(0, 0, 0, 0.2) inset, 0 12px 24px 2px #83C822 inset;
color: #FFFFFF;
text-decoration: none;
}
.button:active, .button:link:active, .button:visited:active {
-moz-transition -webkit-transition -o-transition -ms-transition transition: all 0.25s linear 0s;
box-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.2) inset, 0 12px 24px 6px rgba(0, 0, 0, 0.2) inset, 0 0 2px 2px rgba(0, 0, 0, 0.2) inset;
color: #FFFFFF;
text-decoration: none;
}
.button-blue, .button-blue:link, .button-blue:visited {
-moz-transition -webkit-transition -o-transition -ms-transition transition: all 0.25s linear 0s;
background-color: #276195;
background-image: -moz-linear-gradient(#3C88CC, #276195);
background-repeat: repeat-x;
border: 0 none;
border-radius: 0.25em 0.25em 0.25em 0.25em;
box-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.1), 0 -2px 0 0 rgba(0, 0, 0, 0.2) inset;
color: #FFFFFF;
display: inline-block;
font-family: 'OpenSansRegular',sans-serif;
font-size: 14px;
height: 48px;
line-height: 48px;
padding: 0 24px;
text-align: center;
text-decoration: none;
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
}
.button-blue:hover, .button-blue:link:hover, .button-blue:visited:hover {
-moz-transition -webkit-transition -o-transition -ms-transition transition: all 0.25s linear 0s;
box-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.1), 0 -2px 0 0 rgba(0, 0, 0, 0.2) inset, 0 12px 24px 2px #83C822 inset;
color: #FFFFFF;
text-decoration: none;
}
.button-blue:active, .button-blue:link:active, .button-blue:visited:active {
-moz-transition -webkit-transition -o-transition -ms-transition transition: all 0.25s linear 0s;
box-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.2) inset, 0 12px 24px 6px rgba(0, 0, 0, 0.2) inset, 0 0 2px 2px rgba(0, 0, 0, 0.2) inset;
color: #FFFFFF;
text-decoration: none;
}
.button-blue small, .button-blue:link small, .button-blue:visited small {
display: block;
}
.button-blue:hover, .button-blue:link:hover, .button-blue:visited:hover {
box-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.1), 0 -2px 0 0 rgba(0, 0, 0, 0.2) inset, 0 12px 24px 2px #3089D8 inset;
}
#outer-wrapper {
border-top: 2px solid #fff;
}
#wrapper {
background: #f9f9f9 url(/media/img/bg-stone.png) 0 0 repeat-x;
padding-bottom: 48px;
}
a {
color: #2983c8;
text-decoration: none;
}
a:hover,
a:active {
color: #20679e;
text-decoration: underline;
}
h1,
h2,
h3,
h4,
h5,
h6,
.huge,
.large {
font-family: 'Open Sans', sans-serif;
font-weight: 100;
display: block;
margin: 0 0 12px 0;
line-height: 100%;
text-shadow: 0px 1px 0px rgba(255, 255, 255, 0.75);
color: #484848;
}
.huge,
.huge h1 {
font-size: 108px;
letter-spacing: -4px;
line-height: 100%;
}
.large,
.large h1 {
font-size: 72px;
letter-spacing: -3px;
line-height: 100%;
}
h1,
.huge h2,
.large h2,
.billboard h2 {
font-size: 48px;
letter-spacing: -2px;
}
h2 {
font-size: 32px;
letter-spacing: -1px;
}
h3 {
font-size: 28px;
letter-spacing: -0.5px;
}
h4 {
font-size: 24px;
letter-spacing: -0.25px;
}
h5 {
font-size: 16px;
}
h6 {
font-size: 14px;
}
hgroup h1,
hgroup h2,
hgroup h3,
hgroup h4,
hgroup h5,
hgroup h6 {
margin-bottom: 0;
}
p,
ul,
ol,
dl,
hgroup {
margin: 0 0 24px 0;
}
li {
margin-left: 24px;
}
/* }}} */
/* {{{ Layout */
.row {
margin-left: -24px;
zoom: 1;
}
.row:after {
display: block;
visibility: hidden;
height: 0;
clear: both;
content: ".";
}
[class*="span"] {
float: left;
margin-left: 24px;
}
.span1 {
width: 68px;
}
.span2 {
width: 160px;
}
.span3 {
width: 252px;
}
.span4 {
width: 344px;
}
.span5 {
width: 436px;
}
.span6 {
width: 528px;
}
.span7 {
width: 620px;
}
.span8 {
width: 712px;
}
.span9 {
width: 804px;
}
.span10 {
width: 896px;
}
.span11 {
width: 988px;
}
.span12,
.container {
width: 1080px;
}
.offset1 {
margin-left: 116px;
}
.offset2 {
margin-left: 208px;
}
.offset3 {
margin-left: 300px;
}
.offset4 {
margin-left: 392px;
}
.offset5 {
margin-left: 484px;
}
.offset6 {
margin-left: 576px;
}
.offset7 {
margin-left: 668px;
}
.offset8 {
margin-left: 760px;
}
.offset9 {
margin-left: 852px;
}
.offset10 {
margin-left: 944px;
}
.offset11 {
margin-left: 1036px;
}
#main-content,
#main-feature {
padding-bottom: 48px;
}
.main-column {
float: left;
margin-left: 24px;
width: 528px;
}
.sidebar {
float: left;
margin-left: 24px;
width: 160px;
margin-left: 208px;
}
.divider.container,
.divider {
border-bottom: 1px dotted #d6d6d6;
padding-bottom: 48px;
margin-bottom: 48px;
}
.divider-last.container,
.divider-last {
border-bottom: 0;
padding-bottom: 48px;
}
/* }}} */
/* {{{ Less Framework Grid */
/* Default Layout: 992px.
Gutters: 24px.
Outer margins: 48px.
Leftover space for scrollbars @1024px: 32px.
-------------------------------------------------------------------------------
cols 1 2 3 4 5 6 7 8 9 10
px 68 160 252 344 436 528 620 712 804 896 */
#masthead,
#main-feature,
#main-content,
#colophon,
.billboard,
.container {
display: block;
margin: 0 auto;
padding-left: 48px;
padding-right: 48px;
position: relative;
width: 896px;
zoom: 1;
}
#masthead:after,
#main-feature:after,
#main-content:after,
#colophon:after,
.billboard:after,
.container:after {
display: block;
visibility: hidden;
height: 0;
clear: both;
content: ".";
}
/* }}} */
/* {{{ Header Nav */
#masthead h2 {
padding: 48px 0 36px 0;
margin: 0;
}
#masthead nav {
float: right;
margin-right: 16px;
text-transform: uppercase;
font-size: 13px;
font-family: 'Open Sans', sans-serif;
}
#masthead nav ol li,
#masthead nav ul li {
display: inline-block;
*display: inline;
*zoom: 1;
list-style-type: none;
margin: 0;
}
#masthead nav ol li a,
#masthead nav ul li a,
#masthead nav ol li b,
#masthead nav ul li b {
display: inline-block;
padding: 12px;
font-weight: normal;
}
#masthead nav ol li a,
#masthead nav ul li a,
#masthead nav ol li a:link,
#masthead nav ul li a:link,
#masthead nav ol li a:visited,
#masthead nav ul li a:visited {
color: #484848;
}
/* }}} */
/* {{{ Header Breadcrumbs */
#masthead nav.breadcrumbs {
padding: 12px 0;
float: none;
padding: 12px 0;
}
#masthead nav.breadcrumbs a,
#masthead nav.breadcrumbs span {
margin-right: .5em;
margin-left: .5em;
}
#masthead nav.breadcrumbs a:first-child,
#masthead nav.breadcrumbs span:first-child {
margin-left: 0;
}
/* }}} */
/* {{{ Footer */
#colophon {
color: #bbbbbb;
padding: 48px 0;
font-size: 14px;
line-height: 18px;
}
#colophon a,
#colophon a:link,
#colophon a:visited {
color: #2983c8;
}
#colophon a:hover,
#colophon a:active {
color: #20679e;
}
#colophon nav {
font-family: 'Open Sans', sans-serif;
}
#colophon nav ol li,
#colophon nav ul li {
list-style-type: none;
margin: 0 0 2px 0;
}
/* }}} */

Двоичные данные
public/media/img/bg-stone.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 7.2 KiB

Двоичные данные
public/media/img/footer-logo.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 4.2 KiB

Двоичные данные
public/media/img/poster.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 115 KiB

Двоичные данные
public/media/img/slider-arrows.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.1 KiB

4
public/media/js/html5shiv.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,4 @@
(function(g,b){function k(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function l(a){var c={},f=a.createElement,b=a.createDocumentFragment,d=b();a.createElement=function(a){if(!e.shivMethods)return f(a);var b;b=c[a]?c[a].cloneNode():m.test(a)?(c[a]=f(a)).cloneNode():f(a);return b.canHaveChildren&&!n.test(a)?d.appendChild(b):b};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+k().join().replace(/\w+/g,function(a){f(a);
d.createElement(a);return'c("'+a+'")'})+");return n}")(e,d)}function h(a){var c;if(a.documentShived)return a;if(e.shivCSS&&!i){c=a.createElement("p");var b=a.getElementsByTagName("head")[0]||a.documentElement;c.innerHTML="x<style>article,aside,figcaption,figure,footer,header,hgroup,nav,section{display:block}mark{background:#FF0;color:#000}</style>";c=!!b.insertBefore(c.lastChild,b.firstChild)}j||(c=!l(a));if(c)a.documentShived=c;return a}var d=g.html5||{},n=/^<|^(?:button|form|map|select|textarea|object|iframe|option|optgroup)$/i,
m=/^<|^(?:a|b|button|code|div|fieldset|form|h1|h2|h3|h4|h5|h6|i|iframe|img|input|label|li|link|ol|option|p|param|q|script|select|span|strong|style|table|tbody|td|textarea|tfoot|th|thead|tr|ul)$/i,i,j;(function(){var a=b.createElement("a");a.innerHTML="<xyz></xyz>";i="hidden"in a;if(!(a=1==a.childNodes.length))a:{try{b.createElement("a")}catch(c){a=!0;break a}a=b.createDocumentFragment();a="undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}j=
a})();var e={elements:d.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:!1!==d.shivCSS,shivMethods:!1!==d.shivMethods,type:"default",shivDocument:h};g.html5=e;h(b)})(this,document);

1
public/media/js/jcarousellite_1.0.1.min.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1 @@
(function($){$.fn.jCarouselLite=function(o){o=$.extend({btnPrev:null,btnNext:null,btnGo:null,mouseWheel:false,auto:null,speed:200,easing:null,vertical:false,circular:true,visible:3,start:0,scroll:1,beforeStart:null,afterEnd:null},o||{});return this.each(function(){var b=false,animCss=o.vertical?"top":"left",sizeCss=o.vertical?"height":"width";var c=$(this),ul=$("ul",c),tLi=$("li",ul),tl=tLi.size(),v=o.visible;if(o.circular){ul.prepend(tLi.slice(tl-v-1+1).clone()).append(tLi.slice(0,v).clone());o.start+=v}var f=$("li",ul),itemLength=f.size(),curr=o.start;c.css("visibility","visible");f.css({overflow:"hidden",float:o.vertical?"none":"left"});ul.css({margin:"0",padding:"0",position:"relative","list-style-type":"none","z-index":"1"});c.css({overflow:"hidden",position:"relative","z-index":"2",left:"0px"});var g=o.vertical?height(f):width(f);var h=g*itemLength;var j=g*v;f.css({width:f.width(),height:f.height()});ul.css(sizeCss,h+"px").css(animCss,-(curr*g));c.css(sizeCss,j+"px");if(o.btnPrev)$(o.btnPrev).click(function(){return go(curr-o.scroll)});if(o.btnNext)$(o.btnNext).click(function(){return go(curr+o.scroll)});if(o.btnGo)$.each(o.btnGo,function(i,a){$(a).click(function(){return go(o.circular?o.visible+i:i)})});if(o.mouseWheel&&c.mousewheel)c.mousewheel(function(e,d){return d>0?go(curr-o.scroll):go(curr+o.scroll)});if(o.auto)setInterval(function(){go(curr+o.scroll)},o.auto+o.speed);function vis(){return f.slice(curr).slice(0,v)};function go(a){if(!b){if(o.beforeStart)o.beforeStart.call(this,vis());if(o.circular){if(a<=o.start-v-1){ul.css(animCss,-((itemLength-(v*2))*g)+"px");curr=a==o.start-v-1?itemLength-(v*2)-1:itemLength-(v*2)-o.scroll}else if(a>=itemLength-v+1){ul.css(animCss,-((v)*g)+"px");curr=a==itemLength-v+1?v+1:v+o.scroll}else curr=a}else{if(a<0||a>itemLength-v)return;else curr=a}b=true;ul.animate(animCss=="left"?{left:-(curr*g)}:{top:-(curr*g)},o.speed,o.easing,function(){if(o.afterEnd)o.afterEnd.call(this,vis());b=false});if(!o.circular){$(o.btnPrev+","+o.btnNext).removeClass("disabled");$((curr-o.scroll<0&&o.btnPrev)||(curr+o.scroll>itemLength-v&&o.btnNext)||[]).addClass("disabled")}}return false}})};function css(a,b){return parseInt($.css(a[0],b))||0};function width(a){return a[0].offsetWidth+css(a,'marginLeft')+css(a,'marginRight')};function height(a){return a[0].offsetHeight+css(a,'marginTop')+css(a,'marginBottom')}})(jQuery);

Двоичные данные
public/popcornlogo-small.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 7.0 KiB

Двоичные данные
public/struggling-dino.gif Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 749 KiB

Двоичные данные
resources/bg.jpg Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 297 B

Двоичные данные
resources/buttons.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 24 KiB

Двоичные данные
resources/controls/controls_icon_playPause.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1009 B

Двоичные данные
resources/controls/controls_icons.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 18 KiB

Двоичные данные
resources/controls/controls_time_arrowL.gif Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.2 KiB

Двоичные данные
resources/controls/controls_time_arrowR.gif Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.2 KiB

Двоичные данные
resources/controls/embed_size_sprite.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.4 KiB

Двоичные данные
resources/controls/icon_play.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.7 KiB

Двоичные данные
resources/controls/postroll_logo.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 14 KiB

Двоичные данные
resources/default-icon.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.4 KiB

Двоичные данные
resources/drop.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 4.2 KiB

Двоичные данные
resources/embed-icons-50x50.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.3 KiB

Двоичные данные
resources/feedback-icon.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 425 B

Двоичные данные
resources/fonts/BEBAS___-webfont.ttf Executable file

Двоичный файл не отображается.

Двоичные данные
resources/fonts/BEBAS___-webfont.woff Executable file

Двоичный файл не отображается.

Двоичные данные
resources/fonts/opensans-bold-webfont.ttf Executable file

Двоичный файл не отображается.

Двоичные данные
resources/fonts/opensans-bold-webfont.woff Executable file

Двоичный файл не отображается.

Двоичные данные
resources/fonts/opensans-bolditalic-webfont.ttf Executable file

Двоичный файл не отображается.

Двоичные данные
resources/fonts/opensans-bolditalic-webfont.woff Executable file

Двоичный файл не отображается.

Двоичные данные
resources/fonts/opensans-italic-webfont.ttf Executable file

Двоичный файл не отображается.

Двоичные данные
resources/fonts/opensans-italic-webfont.woff Executable file

Двоичный файл не отображается.

Двоичные данные
resources/fonts/opensans-regular-webfont.ttf Executable file

Двоичный файл не отображается.

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше