diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000..0fa2039
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,6 @@
+{
+ "extends": "./node_modules/eslint-config/.eslintrc",
+ "globals": {
+ $: true
+ }
+}
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 0000000..4e32c7b
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+5.10.1
diff --git a/.spelling b/.spelling
new file mode 100644
index 0000000..30b8942
--- /dev/null
+++ b/.spelling
@@ -0,0 +1,38 @@
+# markdown-spellcheck spelling configuration file
+# Format - lines beginning # are comments
+# global dictionary is at the start, file overrides afterwards
+# one word per line, to define a file override use ' - filename'
+# where filename is relative to this configuration file
+Django
+SQLite
+eg
+MyWindowsHosting
+nginx
+backends
+PyGame
+easter
+_Enabler
+Hipchat
+DabApps
+JakeSidSmith
+facepalm
+notsureif
+wat
+premis
+hipchat
+plugin
+firefox
+Jetpack
+Javascript
+facebook
+github
+morse
+wikipedia
+iframe
+querystring
+javascript
+jQuery
+gists
+Lenovo
+Collyer's
+til
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index 4acaa1b..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,22 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2015 Jake Howard
-
-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.
-
diff --git a/README.md b/README.md
index 02bcfd7..03e4a69 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,3 @@
-# My Website
+# My Website
+
+[![Circle CI](https://circleci.com/gh/RealOrangeOne/theorangeone.net.svg?style=svg)](https://circleci.com/gh/RealOrangeOne/theorangeone.net)
diff --git a/build b/build
index 7374d1a..6437486 100755
--- a/build
+++ b/build
@@ -1,11 +1,28 @@
#!/usr/bin/env bash
+if [ -z "$NVM_DIR" ]
+then
+ NVM_DIR="$HOME/.nvm"
+fi
+
+. $NVM_DIR/nvm.sh
+nvm install
+nvm use
+
set -e
-pyvenv-3.4 env
+pyvenv env
env/bin/pip install -r requirements.txt --upgrade
+scripts/get-private-data.sh
+
npm install
-npm run build
+npm run build $@
env/bin/python manage.py collectstatic --noinput
+
+if [[ $BUILD_PRODUCTION ]]
+then
+ echo ">> Running Migrations..."
+ env/bin/python manage.py migrate
+fi
diff --git a/circle.yml b/circle.yml
new file mode 100644
index 0000000..3beddab
--- /dev/null
+++ b/circle.yml
@@ -0,0 +1,20 @@
+machine:
+ python:
+ version: 3.4.2
+ node:
+ version: 5.10.1
+ environment:
+ DEBUG: true
+ DATABASE_URL: sqlite://~/database.db
+ EMAIL_BACKEND: django.core.mail.backends.console.EmailBackend
+ BUILD_PRODUCTION: true
+
+dependencies:
+ pre:
+ - ./build
+ cache_directories:
+ - env
+
+test:
+ override:
+ - ./runtests
diff --git a/data/context.yml b/data/context.yml
new file mode 100644
index 0000000..ef3fb5e
--- /dev/null
+++ b/data/context.yml
@@ -0,0 +1,6 @@
+links:
+ github: https://github.com/RealOrangeOne
+ twitter: https://twitter.com/RealOrangeOne
+ instagram: https://instagram.com/RealOrangeOne
+ youtube: https://www.youtube.com/user/TheOrangeOneOfficial
+ reddit: https://www.reddit.com/user/realorangeone
diff --git a/data/page_context.yml b/data/page_context.yml
new file mode 100644
index 0000000..4c9dcf1
--- /dev/null
+++ b/data/page_context.yml
@@ -0,0 +1,9 @@
+index:
+ body_class: index
+ html_title: Homepage
+
+projects/hipchat-emoticons-for-all:
+ header_image: https://hipchat-magnolia-cdn.atlassian.com/assets/img/hipchat/hipchat_og_image.jpg
+
+projects/yoga-pal:
+ header_image: http://www.lenovo.com/images/OneWebImages/SubSeries/gallery/laptops/IdeaPad-Yoga-13-Convertible-Laptop-PC-Clementine-Orange-Closed-Cover-View-gallery-940x529.jpg
diff --git a/data/path_switch.yml b/data/path_switch.yml
new file mode 100644
index 0000000..7a0d12c
--- /dev/null
+++ b/data/path_switch.yml
@@ -0,0 +1 @@
+college/attack-on-blocks: projects/attack-on-blocks
diff --git a/etc/environments/deployment/env b/etc/environments/deployment/env
index d79c86a..0c8ddc0 100644
--- a/etc/environments/deployment/env
+++ b/etc/environments/deployment/env
@@ -1,3 +1,3 @@
DEBUG=false
-DATABASE_URL=sqlite:///database.db
EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
+BUILD_PRODUCTION=true
diff --git a/etc/environments/development/env b/etc/environments/development/env
index e0bf99a..9204e2e 100644
--- a/etc/environments/development/env
+++ b/etc/environments/development/env
@@ -1,3 +1,2 @@
DEBUG=true
-DATABASE_URL=sqlite:///database.db
EMAIL_BACKEND=django.core.mail.backends.console.EmailBackend
diff --git a/etc/environments/development/procfile b/etc/environments/development/procfile
index dbc4b47..40349a7 100644
--- a/etc/environments/development/procfile
+++ b/etc/environments/development/procfile
@@ -1,2 +1,2 @@
web: manage.py runserver 0.0.0.0:$PORT
-watcher: npm run watch
\ No newline at end of file
+watcher: npm run watch
diff --git a/package.json b/package.json
index a7a407d..6580b23 100644
--- a/package.json
+++ b/package.json
@@ -1,43 +1,50 @@
{
"name": "TheOrangeOne-Site",
- "version": "0.0.1",
- "description": "TheOrangeOne.net site",
- "main": "app.js",
+ "version": "3.0.0",
+ "description": " Source code for TheOrangeOne.net",
"scripts": {
- "lint": "echo 'Nothing yet.';",
- "build-js": "bash scripts/build-js.sh",
- "create-build-dirs": "mkdir -p static/build/js static/build/fonts static/build/css static/build/img private",
+ "lint": "eslint 'static/src/js/'",
+ "build-js": "./scripts/build-js.sh",
+ "create-build-dirs": "mkdir -p static/build/js/lib static/build/fonts static/build/css static/build/img",
"build": "npm run create-build-dirs && npm run build-fonts && npm run build-images && npm run build-js && npm run build-less",
- "build-less": "lessc --silent static/src/less/style.less static/build/css/style.css && cleancss -d --s0 -o static/build/css/style.css static/build/css/style.css",
- "build-fonts": "cp -R node_modules/bootstrap/dist/fonts static/build/ && cp -R node_modules/ionicons-pre/fonts static/build/ ",
+ "build-less": "./scripts/build-less.sh",
+ "build-fonts": "cp -R node_modules/bootstrap/dist/fonts static/build/ && cp -R node_modules/ionicons/fonts static/build/ ",
"build-images": "cp -r static/src/img/* static/build/img/",
- "watch-images": "watch 'npm run build-images' static/src/img/",
"watch-less": "watch 'npm run build-less' static/src/less/",
- "watch": "npm run watch-less & npm run watch-images",
- "clean": "rm -rf static/build collected-static/ node_modules/ env/ private"
+ "watch": "npm run watch-less",
+ "clean": "./scripts/clean.js",
+ "test": "npm run lint",
+ "spellcheck": "mdspell --en-gb -ranx \"templates/**/*.*\""
},
"repository": {
"type": "git",
"url": "https://github.com/RealOrangeOne/theorangeone.net"
},
"dependencies": {
- "bootstrap": "^3.3.5",
- "jquery": "^2.1.4",
- "less": "^2.5.1",
- "markdown": "^0.5.0",
- "react": "^0.13.3",
- "react-bootstrap": "^0.25.1",
- "skrollr": "^0.6.26"
+ "animate.css": "=3.4.0",
+ "bootstrap": "=3.3.5",
+ "ionicons": "=2.0.1",
+ "jquery": "=2.1.4",
+ "react": "=0.13.3",
+ "react-bootstrap": "=0.25.1",
+ "whatwg-fetch": "=0.10.1"
},
"devDependencies": {
- "browserify": "^11.0.1",
- "clean-css": "^3.4.1",
- "eslint": "^1.5.0",
- "eslint-plugin-react": "^3.4.2",
- "ionicons-pre": "^1.0.0-pre",
- "react-tools": "^0.13.2",
- "reactify": "^1.1.1",
- "uglify-js": "^2.4.24",
- "watch": "^0.16.0"
+ "autoprefixer": "=6.3.3",
+ "babel-preset-es2015": "=6.1.18",
+ "babel-preset-react": "=6.1.18",
+ "babelify": "=7.2.0",
+ "browserify": "=11.0.1",
+ "clean-css": "=3.4.1",
+ "eslint": "=1.5.0",
+ "eslint-config": "git://github.com/dabapps/eslint-config.git",
+ "eslint-plugin-react": "=3.4.2",
+ "less": "=2.5.1",
+ "less-mixins": "git://github.com/RealOrangeOne/less-mixins.git",
+ "markdown-spellcheck": "=0.10.0",
+ "postcss-cli": "=2.5.1",
+ "react-tools": "=0.13.2",
+ "uglify-js": "=2.4.24",
+ "watch": "=0.16.0"
}
}
diff --git a/project/college/__init__.py b/project/blog/__init__.py
similarity index 100%
rename from project/college/__init__.py
rename to project/blog/__init__.py
diff --git a/project/blog/tests.py b/project/blog/tests.py
new file mode 100644
index 0000000..fc705e3
--- /dev/null
+++ b/project/blog/tests.py
@@ -0,0 +1,67 @@
+from django.test import TestCase
+import requests_mock, json
+from . import utils
+from django.core.urlresolvers import reverse
+
+
+@requests_mock.mock()
+class WordPressAPITestCase(TestCase):
+ def setUp(self):
+ self.test_blog_data = {
+ "title": "Test Blog Post",
+ "ID": 1,
+ "content": "
Test blog post content
",
+ "slug": "test-post"
+ }
+ self.invalid_blog_data = {
+ "title": "Invalid blog post",
+ "content": "",
+ "slug": "invalid"
+ }
+
+ def test_gets_correct_data(self, m):
+ payload = json.dumps(self.test_blog_data)
+ m.get(utils.build_url(self.test_blog_data['slug']), text=payload)
+ blog_data = utils.get_post(self.test_blog_data['slug'])
+ self.assertEqual(blog_data, self.test_blog_data)
+
+ def test_invalid_response(self, m):
+ payload = json.dumps(self.invalid_blog_data)
+ m.get(utils.build_url(self.invalid_blog_data['slug']), text=payload)
+ blog_data = utils.get_post(self.invalid_blog_data['slug'])
+ self.assertFalse(blog_data)
+
+ def test_invalid_status(self, m):
+ payload = json.dumps(self.test_blog_data)
+ m.get(utils.build_url(self.test_blog_data['slug']), text=payload, status_code=500)
+ blog_data = utils.get_post(self.test_blog_data['slug'])
+ self.assertFalse(blog_data)
+
+ def test_no_slug(self, m):
+ blog_data = utils.get_post('')
+ self.assertFalse(blog_data)
+
+
+@requests_mock.mock()
+class BlogViewTestCase(TestCase):
+ def setUp(self):
+ self.test_blog_data = {
+ "title": "Test Blog Post",
+ "ID": 1,
+ "content": "Test blog post content
",
+ "slug": "test-post",
+ "date": "2000-01-01T18:05:00+00:00"
+ }
+
+ def test_accessable(self, m):
+ payload = json.dumps(self.test_blog_data)
+ m.get(utils.build_url(self.test_blog_data['slug']), text=payload)
+ response = self.client.get(reverse('blog:blog-post', args=[self.test_blog_data['slug']]))
+ self.assertEqual(response.status_code, 200)
+
+ def test_correct_content(self, m):
+ payload = json.dumps(self.test_blog_data)
+ m.get(utils.build_url(self.test_blog_data['slug']), text=payload)
+ response = self.client.get(reverse('blog:blog-post', args=[self.test_blog_data['slug']]))
+ self.assertContains(response, self.test_blog_data['content'])
+ self.assertEqual(response.context['html_title'], self.test_blog_data['title'])
diff --git a/project/blog/urls.py b/project/blog/urls.py
new file mode 100644
index 0000000..51d5239
--- /dev/null
+++ b/project/blog/urls.py
@@ -0,0 +1,6 @@
+from django.conf.urls import url
+from . import views
+
+urlpatterns = [
+ url(r'^(?P.+)/?$', views.BlogView.as_view(), name='blog-post'),
+]
diff --git a/project/blog/utils.py b/project/blog/utils.py
new file mode 100644
index 0000000..a2af234
--- /dev/null
+++ b/project/blog/utils.py
@@ -0,0 +1,25 @@
+import requests, iso8601
+from django.conf import settings
+
+API_PATH = "https://public-api.wordpress.com/rest/v1.1/sites/{0}/posts/slug:{1}"
+
+
+def build_url(slug):
+ if not slug:
+ return
+ return API_PATH.format(settings.WORDPRESS_URL, slug)
+
+
+def get_post(slug):
+ if not slug:
+ return
+ response = requests.get(build_url(slug))
+
+ if response.status_code != 200:
+ return
+ data = response.json()
+ return data if "ID" in data else False
+
+
+def reformat_date(iso_date):
+ return iso8601.parse_date(iso_date).strftime("%x %I:%M")
diff --git a/project/blog/views.py b/project/blog/views.py
new file mode 100644
index 0000000..6ceed9d
--- /dev/null
+++ b/project/blog/views.py
@@ -0,0 +1,22 @@
+from django.views.generic import TemplateView
+from .utils import get_post, reformat_date
+from django.http import Http404
+
+
+class BlogView(TemplateView):
+ template_name = "blog/post.html"
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context['blog'] = self.blog_data
+ context['blog']['date'] = reformat_date(self.blog_data['date'])
+ context['html_title'] = self.blog_data['title']
+ if 'featured_image' in self.blog_data:
+ context['header_image'] = self.blog_data['featured_image']
+ return context
+
+ def dispatch(self, request, *args, **kwargs):
+ self.blog_data = get_post(kwargs['slug'])
+ if not self.blog_data:
+ raise Http404
+ return super().dispatch(request, *args, **kwargs)
diff --git a/project/college/admin.py b/project/college/admin.py
deleted file mode 100644
index 8c38f3f..0000000
--- a/project/college/admin.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from django.contrib import admin
-
-# Register your models here.
diff --git a/project/college/migrations/__init__.py b/project/college/migrations/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/project/college/models.py b/project/college/models.py
deleted file mode 100644
index 71a8362..0000000
--- a/project/college/models.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from django.db import models
-
-# Create your models here.
diff --git a/project/college/tests.py b/project/college/tests.py
deleted file mode 100644
index 7ce503c..0000000
--- a/project/college/tests.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from django.test import TestCase
-
-# Create your tests here.
diff --git a/project/college/urls.py b/project/college/urls.py
deleted file mode 100644
index e4a01a4..0000000
--- a/project/college/urls.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from django.conf.urls import url
-from .views import StudentServerView
-
-
-urlpatterns = [
- url(r'^student-server', StudentServerView.as_view(), name='student-server')
-]
diff --git a/project/college/views.py b/project/college/views.py
deleted file mode 100644
index 067dacc..0000000
--- a/project/college/views.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from django.views.generic import TemplateView
-from project.common.views import CustomHeaderBG
-
-class StudentServerView(CustomHeaderBG.Template):
- template_name = 'college/student-server.html'
\ No newline at end of file
diff --git a/project/common/data.py b/project/common/data.py
new file mode 100644
index 0000000..1794cd2
--- /dev/null
+++ b/project/common/data.py
@@ -0,0 +1,46 @@
+import os.path
+import yaml
+from glob import glob
+from project.pages.utils import get_title_from_markdown, parse_content
+
+
+def get_data_from_file(base_dir, filename):
+ with open(os.path.join(base_dir, 'data', filename)) as data_file:
+ return yaml.load(data_file) or {}
+
+
+def generate_config(base_dir):
+ default = get_data_from_file(base_dir, 'context.yml')
+ page = get_data_from_file(base_dir, 'page_context.yml')
+ switcher = get_data_from_file(base_dir, 'path_switch.yml')
+
+ # Add projects config
+ default['projects'] = generate_projects(base_dir)
+ # Join projects config with it's page context
+ for i in range(len(default['projects'])):
+ project = default['projects'][i]
+ if project['path'] in page: # If there's a custom config
+ default['projects'][i] = dict(project, **page[project['path']])
+
+ return default, page, switcher
+
+
+def generate_projects(base_dir):
+ projects_path = os.path.join(base_dir, 'templates/projects')
+ files = []
+ for path in glob(projects_path + '/*.*'):
+ filename = path.replace(projects_path, '')
+ if filename == '/index.html':
+ continue
+ with open(path) as f:
+ if filename.split('.')[1] == 'md':
+ parsed_content = parse_content(f.read(), filename.split('.')[1])
+ filename = get_title_from_markdown(parsed_content)
+ else:
+ filename = filename.split('.')[0]
+ files.append({
+ "name": filename,
+ "path": 'projects' + path.replace(projects_path, '').split('.')[0],
+ "url": '/projects' + path.replace(projects_path, '').split('.')[0],
+ })
+ return files
diff --git a/project/common/migrations/__init__.py b/project/common/migrations/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/project/common/models.py b/project/common/models.py
deleted file mode 100644
index 71a8362..0000000
--- a/project/common/models.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from django.db import models
-
-# Create your models here.
diff --git a/project/common/tests.py b/project/common/tests.py
deleted file mode 100644
index 7ce503c..0000000
--- a/project/common/tests.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from django.test import TestCase
-
-# Create your tests here.
diff --git a/project/common/views.py b/project/common/views.py
deleted file mode 100644
index be82893..0000000
--- a/project/common/views.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from django.views.generic import TemplateView
-
-
-class CustomHeaderBG():
- """Allow custom header background"""
-
- class Template(TemplateView):
- header_BG = ""
-
- def get_context_data(self, **kwargs):
- context = super().get_context_data(**kwargs)
- context['header_BG'] = self.header_BG
- return context
\ No newline at end of file
diff --git a/project/media/__init__.py b/project/media/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/project/media/admin.py b/project/media/admin.py
deleted file mode 100644
index 8c38f3f..0000000
--- a/project/media/admin.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from django.contrib import admin
-
-# Register your models here.
diff --git a/project/media/migrations/__init__.py b/project/media/migrations/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/project/media/tests.py b/project/media/tests.py
deleted file mode 100644
index 7ce503c..0000000
--- a/project/media/tests.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from django.test import TestCase
-
-# Create your tests here.
diff --git a/project/media/urls.py b/project/media/urls.py
deleted file mode 100644
index 0bacbc0..0000000
--- a/project/media/urls.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from django.conf.urls import url
-from .views import GalleryView, YoutubeView
-
-
-urlpatterns = [
- url(r'^gallery', GalleryView.as_view(), name='gallery'),
- url(r'^youtube', YoutubeView.as_view(), name='youtube')
-]
diff --git a/project/media/views.py b/project/media/views.py
deleted file mode 100644
index 516f356..0000000
--- a/project/media/views.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from django.views.generic import TemplateView
-from project.common.views import CustomHeaderBG
-
-
-class YoutubeView(CustomHeaderBG.Template):
- template_name = "media/youtube.html"
-
-
-class GalleryView(TemplateView):
- template_name = "media/gallery.html"
diff --git a/project/pages/tests.py b/project/pages/tests.py
index a79ca8b..7f2554e 100644
--- a/project/pages/tests.py
+++ b/project/pages/tests.py
@@ -1,3 +1,19 @@
-# from django.test import TestCase
+from django.test import TestCase
+from django.conf import settings
+import os.path
+from glob import glob
-# Create your tests here.
+
+class PagesTestCase(TestCase):
+ def setUp(self):
+ directories = glob(os.path.join(settings.BASE_DIR, 'templates') + '/**/*.*')
+ self.urls = []
+ for directory in directories:
+ if 'email' in directory or 'blog' in directory:
+ continue
+ self.urls.append(directory.replace(os.path.join(settings.BASE_DIR, 'templates'), '').split('.')[0].replace('index', ''))
+
+ def test_pages_accessable(self):
+ for path in self.urls:
+ response = self.client.get(path)
+ self.assertEqual(response.status_code, 200)
diff --git a/project/pages/urls.py b/project/pages/urls.py
index 6dbaf72..b597e68 100644
--- a/project/pages/urls.py
+++ b/project/pages/urls.py
@@ -1,11 +1,7 @@
from django.conf.urls import url
-from . import views
+from .views import page_view
urlpatterns = [
- url(r'^about/website/$', views.AboutWebsiteView.as_view(), name='about-website'),
- url(r'^about/$', views.AboutIndexView.as_view(), name='about'),
- url(r'^no-js/$', views.NoJavascriptView.as_view(), name='no-js'),
- url(r'^404/$', views.Custom404View.as_view(), name='404'),
- url(r'^$', views.IndexView.as_view(), name='index')
+ url(r'^(?P.*)', page_view, name='page'),
]
diff --git a/project/pages/utils.py b/project/pages/utils.py
new file mode 100644
index 0000000..c14cf37
--- /dev/null
+++ b/project/pages/utils.py
@@ -0,0 +1,29 @@
+from django.conf import settings
+from bs4 import BeautifulSoup
+import markdown2
+
+
+def get_context(path):
+ if path in settings.PAGE_CONTEXT:
+ context = dict(settings.DEFAULT_CONTEXT, **settings.PAGE_CONTEXT[path])
+ else:
+ context = dict(settings.DEFAULT_CONTEXT)
+ return context
+
+
+def get_title_from_markdown(md):
+ html_tree = BeautifulSoup(md, "html.parser")
+ tag = html_tree.find('h1')
+ return tag.contents[0]
+
+
+def parse_content(content, extension):
+ if extension == 'md':
+ return markdown2.markdown(content)
+ return content
+
+
+def swap_page(path):
+ if path in settings.PAGE_SWITCH:
+ return settings.PAGE_SWITCH[path]
+ return path
diff --git a/project/pages/views.py b/project/pages/views.py
index ca5457e..f5ce62a 100644
--- a/project/pages/views.py
+++ b/project/pages/views.py
@@ -1,34 +1,33 @@
-from django.views.generic import TemplateView
-from project.common.views import CustomHeaderBG
-from django.contrib.staticfiles.templatetags.staticfiles import static
+import os.path
+from django.conf import settings
+from django.http import HttpResponse, Http404
+from django.template.loader import get_template
+from .utils import get_context, parse_content, get_title_from_markdown, swap_page
-class IndexView(TemplateView):
- template_name = 'index.html'
+def page_view(request, path):
+ template = None
+ if path.endswith('/'): # Strip trailing slash
+ path = path[:-1]
+ path = swap_page(path)
-class NoJavascriptView(TemplateView):
- template_name = 'core/no-js.html'
-
-
-class Custom404View(CustomHeaderBG.Template):
- template_name = 'core/404.html'
- header_BG = static('img/ninjas.png')
-
- def get_context_data(self, **kwargs):
- context = super().get_context_data(**kwargs)
- context['no_footer'] = True
- return context
-
- def get(self, request, *args, **kwargs):
- context = self.get_context_data(**kwargs)
- return self.render_to_response(context, status=404)
-
-
-class AboutWebsiteView(CustomHeaderBG.Template):
- template_name = 'about/website.html'
- header_BG = ''
-
-
-class AboutIndexView(TemplateView):
- template_name = 'about/index.html'
\ No newline at end of file
+ if os.path.isdir(os.path.join(settings.BASE_DIR, 'templates', path)):
+ path = os.path.join(path, 'index')
+ for extension in ['md', 'html']:
+ try:
+ template = get_template("{}.{}".format(path, extension))
+ break
+ except:
+ pass
+ if not template:
+ raise Http404
+ context = get_context(path)
+ parsed_content = parse_content(template.render(context, request), extension)
+ if extension == 'md':
+ template = get_template('markdown_content.html')
+ context['markdown_content'] = parsed_content
+ context['page_title'] = get_title_from_markdown(parsed_content)
+ context['html_title'] = context['page_title']
+ parsed_content = template.render(context, request)
+ return HttpResponse(parsed_content)
diff --git a/project/projects/__init__.py b/project/projects/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/project/projects/migrations/__init__.py b/project/projects/migrations/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/project/projects/tests.py b/project/projects/tests.py
deleted file mode 100644
index 7ce503c..0000000
--- a/project/projects/tests.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from django.test import TestCase
-
-# Create your tests here.
diff --git a/project/projects/urls.py b/project/projects/urls.py
deleted file mode 100644
index 551c48f..0000000
--- a/project/projects/urls.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from django.conf.urls import url
-from . import views
-
-urlpatterns = [
- # Project URLs
- url(r'^attack-on-blocks', views.AttackOnBlocksView.as_view(), name="attack-on-blocks"),
- url(r'^bsod-enabler', views.BSODEnablerView.as_view(), name='bsod-enabler'),
- url(r'^hipchat-emoticons-for-all', views.HipChatEmoticonsForAllView.as_view(), name='hipchat-emoticons'),
- url(r'^pithos', views.PithosView.as_view(), name='pithos'),
-
- # Code URLs
- url(r'^morse-code-decoder', views.MorseCodeDecoderView.as_view(), name='morse-code-decoder'),
- url(r'^wiki-game-solver', views.WikiGameSolverView.as_view(), name='wiki-game-solver'),
-
- url(r'^', views.AllProjectsView.as_view(), name="index")
-]
diff --git a/project/projects/views.py b/project/projects/views.py
deleted file mode 100644
index 899ed06..0000000
--- a/project/projects/views.py
+++ /dev/null
@@ -1,35 +0,0 @@
-from django.views.generic import TemplateView
-from project.common.views import CustomHeaderBG
-
-
-# Project Views
-class AllProjectsView(TemplateView):
- template_name = "projects/all_projects.html"
-
-
-class AttackOnBlocksView(CustomHeaderBG.Template):
- template_name = "college/attack-on-blocks.html"
-
-
-class BSODEnablerView(CustomHeaderBG.Template):
- template_name = 'projects/BSOD-Enabler.html'
- header_BG = 'http://cdn9.howtogeek.com/wp-content/uploads/2013/05/xwindows-8-blue-screen-error.png.pagespeed.ic.yOWUS_rYGn.png'
-
-
-class HipChatEmoticonsForAllView(CustomHeaderBG.Template):
- template_name = 'projects/hipchat-emoticons-for-all.html'
- header_BG = "https://info.seibert-media.net/plugins/servlet/pptslide?attachment=HipChat+Server+Sales+Pitch+Final+PPT.pptx&attachmentId=7536966&attachmentVer=1&pageId=3179011&slide=0"
-
-
-class PithosView(CustomHeaderBG.Template):
- template_name = 'projects/pithos.html'
-
-
-# Code Views
-class MorseCodeDecoderView(CustomHeaderBG.Template):
- template_name = 'projects/morse-code-decoder.html'
-
-
-class WikiGameSolverView(CustomHeaderBG.Template):
- template_name = 'projects/wiki-game-solver.html'
- header_BG = "http://is1.mzstatic.com/image/pf/us/r30/Purple/v4/a3/3f/31/a33f31dd-3ace-ffe4-5531-89d3f3a1106f/mzl.ihqwkbih.png"
\ No newline at end of file
diff --git a/project/robotics/__init__.py b/project/robotics/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/project/robotics/migrations/__init__.py b/project/robotics/migrations/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/project/robotics/tests.py b/project/robotics/tests.py
deleted file mode 100644
index 7ce503c..0000000
--- a/project/robotics/tests.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from django.test import TestCase
-
-# Create your tests here.
diff --git a/project/robotics/urls.py b/project/robotics/urls.py
deleted file mode 100644
index 731879e..0000000
--- a/project/robotics/urls.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from django.conf.urls import url
-from .views import Index2015View, Index2014View, RoboticsIndexView, Robot2015View
-
-
-urlpatterns = [
- url(r'^$', RoboticsIndexView.as_view(), name='index' ),
- url(r'^2015', Index2015View.as_view(), name='2015-index'),
- url(r'^2015/robot', Robot2015View.as_view(), name='2015-robot'),
- url(r'^2014', Index2014View.as_view(), name='2014-index')
-]
diff --git a/project/robotics/views.py b/project/robotics/views.py
deleted file mode 100644
index 0a760f6..0000000
--- a/project/robotics/views.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from django.views.generic import TemplateView
-from project.common.views import CustomHeaderBG
-
-
-class RoboticsIndexView(TemplateView):
- template_name = 'robotics/index.html'
-
-# 2015
-class Index2015View(CustomHeaderBG.Template):
- template_name = 'robotics/2015-index.html'
- header_BG = "https://farm8.staticflickr.com/7711/17122633430_7d1bde923a_k.jpg"
-
-
-class Robot2015View(CustomHeaderBG.Template):
- template_name = 'robotics/2015-robot.html'
- header_BG = ""
-
-
-# 2014
-class Index2014View(CustomHeaderBG.Template):
- template_name = 'robotics/2014-index.html'
- header_BG = "nothing"
\ No newline at end of file
diff --git a/project/settings.py b/project/settings.py
index e4fdcd5..f7933f5 100644
--- a/project/settings.py
+++ b/project/settings.py
@@ -1,19 +1,6 @@
-"""
-Django settings for project.
-
-Generated by 'django-admin startproject' using Django 1.8.3.
-
-For more information on this file, see
-https://docs.djangoproject.com/en/1.8/topics/settings/
-
-For the full list of settings and their values, see
-https://docs.djangoproject.com/en/1.8/ref/settings/
-"""
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
-import dj_database_url
-# import logging
-import os
-import sys
+import dj_database_url, os
+from private import export
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -22,7 +9,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
-SECRET_KEY = 'q&^k#n_lkd5l9&g^k7$q1rhj_=%9yqy-)ln78ik414-bcowedy'
+SECRET_KEY = export('SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
@@ -31,23 +18,13 @@ ALLOWED_HOSTS = ['too.ctf.sh', 'theorangeone.net']
# Application definition
INSTALLED_APPS = (
- 'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.messages',
'django.contrib.staticfiles',
- 'rest_framework',
- 'bootstrapform',
-
'project.pages',
'project.common',
- 'project.projects',
- 'project.robotics',
- 'project.setup',
- 'project.media',
- 'project.college'
+ 'project.blog'
)
MIDDLEWARE_CLASSES = (
@@ -85,7 +62,7 @@ WSGI_APPLICATION = 'project.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.8/ref/settings/#databases
DATABASES = {
- 'default': dj_database_url.config(default=os.environ['DATABASE_URL'])
+ 'default': dj_database_url.config(default='sqlite://memory')
}
EMAIL_BACKEND = os.environ['EMAIL_BACKEND']
@@ -105,4 +82,9 @@ STATIC_ROOT = os.path.join(BASE_DIR, 'collected-static')
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static', 'build'),
)
-STATICFILES_STORAGE = 'whitenoise.django.GzipManifestStaticFilesStorage'
+
+WORDPRESS_URL = "realorangeone.wordpress.com"
+
+# Generate config data
+from project.common.data import generate_config
+DEFAULT_CONTEXT, PAGE_CONTEXT, PAGE_SWITCH = generate_config(BASE_DIR)
diff --git a/project/setup/__init__.py b/project/setup/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/project/setup/admin.py b/project/setup/admin.py
deleted file mode 100644
index 8c38f3f..0000000
--- a/project/setup/admin.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from django.contrib import admin
-
-# Register your models here.
diff --git a/project/setup/migrations/__init__.py b/project/setup/migrations/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/project/setup/models.py b/project/setup/models.py
deleted file mode 100644
index b2e2c23..0000000
--- a/project/setup/models.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from django.db import models
-import uuid
-
-
-class Device(models.Model):
- device_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
- device_name = models.CharField(max_length=50, blank=False, null=True)
- friendly_name = models.CharField(max_length=50, blank=False, null=True, unique=True)
- model_name = models.CharField(max_length=50, blank=False, null=True, unique=True)
- description = models.TextField(blank=True, null=True)
- computer = models.ForeignKey('setup.Computer')
-
-
-
-class Computer(models.Model):
- computer_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
- CPU = models.TextField(max_length=30, blank=False, null=True, verbose_name="Processor")
- GPU = models.TextField(max_length=40, blank=True, null=True, verbose_name="Graphics Card")
- RAM = models.IntegerField(blank=False, null=True)
-
-
-
-class Peripheral(models.Model):
- peripheral_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
- name = models.CharField(max_length=40, blank=False, null=True)
- model_name = models.CharField(max_length=100, blank=False, null=True)
- device = models.ForeignKey('setup.Device')
diff --git a/project/setup/tests.py b/project/setup/tests.py
deleted file mode 100644
index 7ce503c..0000000
--- a/project/setup/tests.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from django.test import TestCase
-
-# Create your tests here.
diff --git a/project/setup/urls.py b/project/setup/urls.py
deleted file mode 100644
index 234e3b2..0000000
--- a/project/setup/urls.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from django.conf.urls import url
-from . import views
-
-urlpatterns = [
- url(r'^desk', views.DeskView.as_view(), name="desk"),
- url(r'^', views.SetupIndexView.as_view(), name="index")
-]
diff --git a/project/setup/views.py b/project/setup/views.py
deleted file mode 100644
index bc04113..0000000
--- a/project/setup/views.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from django.views.generic import TemplateView
-from project.common.views import CustomHeaderBG
-
-
-class SetupIndexView(TemplateView):
- template_name = "setup/index.html"
-
-
-class DeskView(CustomHeaderBG.Template):
- template_name = "setup/my-rig.html"
- header_BG = "https://c1.staticflickr.com/1/557/18312934624_b51a541594_h.jpg"
diff --git a/project/urls.py b/project/urls.py
index 267eb33..47926c7 100644
--- a/project/urls.py
+++ b/project/urls.py
@@ -1,15 +1,7 @@
from django.conf.urls import include, url
-from django.contrib import admin
-from project.pages.views import Custom404View
+
urlpatterns = [
- url(r'^site-admin/', include(admin.site.urls)),
- url(r'^student-robotics/', include('project.robotics.urls', namespace='robotics')),
- url(r'^projects/', include('project.projects.urls', namespace='projects')),
- url(r'^media/', include('project.media.urls', namespace='media')),
- url(r'^setup/', include('project.setup.urls', namespace='setup')),
- url(r'^college/', include('project.college.urls', namespace='college')),
+ url(r'^blog/', include('project.blog.urls', namespace='blog')),
url(r'', include('project.pages.urls', namespace='pages'))
]
-
-handler404 = Custom404View
\ No newline at end of file
diff --git a/project/wsgi.py b/project/wsgi.py
index 927d764..81b9179 100644
--- a/project/wsgi.py
+++ b/project/wsgi.py
@@ -1,11 +1,3 @@
-"""
-WSGI config for twm project.
-
-It exposes the WSGI callable as a module-level variable named ``application``.
-
-For more information on this file, see
-https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/
-"""
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")
diff --git a/requirements.txt b/requirements.txt
index 0d89d27..09e7b03 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,12 +1,13 @@
-Django==1.8.3
+beautifulsoup4==4.4.1
+coverage==4.0.3
+colorama==0.3.6
+Django==1.8.11
dj-database-url==0.3.0
-django-bootstrap-form==3.2
-djangorestframework==3.2.0
-djorm-ext-pgfulltext==0.10
-flake8==2.4.1
-pep8==1.5.7
-pycrypto==2.6.1
-pyflakes==0.8.1
-six==1.9.0
-waitress==0.8.9
-whitenoise==2.0.2
+flake8==2.5.0
+iso8601==0.1.11
+markdown2==2.3.0
+PyYAML==3.11
+requests==2.9.1
+requests-mock==0.7.0
+whitenoise==2.0.6
+waitress==0.8.10
diff --git a/runtests b/runtests
index 5e6c7dd..ac34df1 100755
--- a/runtests
+++ b/runtests
@@ -1,7 +1,67 @@
-#!/usr/bin/env bash
+#!/usr/bin/env python3
+import coverage
+import os, sys
+import subprocess
+from colorama import Fore, init
-set -e
-export PATH=env/bin:${PATH}
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")
+from django.core.management import execute_from_command_line
+init(autoreset=True)
-flake8 project --ignore=E128,E501 --exclude="migrations,settings,*/wsgi.py"
+
+bin_dir = os.path.abspath(os.path.join(sys.executable, os.path.pardir))
+PERCENTAGE = 95
+EXIT_CODE = 0
+
+
+def check_if_exit_code():
+ if EXIT_CODE != 0:
+ print("\n{}Tests Failed. {}Please check messages above and then re-run the command.".format(Fore.RED, Fore.YELLOW))
+ exit(EXIT_CODE)
+
+
+cov = coverage.Coverage(
+ source=["project"],
+ omit=["*/wsgi.py", "*/settings.py", "*/migrations/*.py", "*/__init__*", "*/tests.py"]
+)
+cov.start()
+
+print(Fore.YELLOW + "Running Tests...")
+execute_from_command_line([sys.argv[0], 'test'])
+cov.stop()
+
+print("{}Tests Complete. {}Collecting Coverage...".format(Fore.GREEN, Fore.YELLOW))
+cov.save()
+cov.html_report()
+covered = cov.report()
+if covered < PERCENTAGE:
+ print("{}ERROR: Your coverage needs to be higher. Current coverage: {}%. Required: {}%.".format(Fore.RED, covered, PERCENTAGE))
+ EXIT_CODE = 1
+
+check_if_exit_code()
+print("{}Coverage Complete. {}Linting...".format(Fore.GREEN, Fore.YELLOW))
+
+
+FLAKE8_IGNORE = '--ignore=E128,E501,E401,E402'
+try:
+ subprocess.check_output([os.path.join(bin_dir, 'flake8'), 'project', FLAKE8_IGNORE, '--exclude=migrations,settings,wsgi.py,__init__.py'])
+ subprocess.check_output([os.path.join(bin_dir, 'flake8'), 'scripts', FLAKE8_IGNORE])
+ subprocess.check_output([os.path.join(bin_dir, 'flake8'), sys.argv[0], FLAKE8_IGNORE])
+except subprocess.CalledProcessError as e:
+ print(Fore.RED, e.output.decode())
+ EXIT_CODE = 1
+
+check_if_exit_code()
+print("{}All Python tests passed! {}Testing Static Files...".format(Fore.GREEN, Fore.YELLOW))
+
+
+EXIT_CODE = os.system('npm test')
+check_if_exit_code()
+
+print("{}All static tests passed! {}Running spell check...".format(Fore.GREEN, Fore.YELLOW))
+
+EXIT_CODE = os.system('')
+check_if_exit_code()
+
+print("{}All Tests Passed!".format(Fore.GREEN))
diff --git a/scripts/build-js.sh b/scripts/build-js.sh
old mode 100644
new mode 100755
index 6d6d64f..b4fdede
--- a/scripts/build-js.sh
+++ b/scripts/build-js.sh
@@ -1,18 +1,44 @@
-#!/usr/bin/bash
+#!/usr/bin/env bash
set -e
-cp node_modules/bootstrap/dist/js/bootstrap.js static/src/js/lib/
-cp node_modules/skrollr/src/skrollr.js static/src/js/lib/
+if [[ $BUILD_PRODUCTION ]]
+then
+ echo ">>> WARNING: Building in Production Mode!"
+fi
+
+mkdir -p static/build/js/lib
+
+if [[ $BUILD_PRODUCTION ]]
+then
+ echo ">> Compressing Libraries..."
+ uglifyjs node_modules/bootstrap/dist/js/bootstrap.js --compress --screw-ie8 --define --stats --keep-fnames -o static/build/js/lib/bootstrap.js
+ uglifyjs static/build/js/lib/* --compress --screw-ie8 --define --stats --keep-fnames -o static/build/js/libs.js
+else
+ echo ">> Building Libraries..."
+ cp node_modules/bootstrap/dist/js/bootstrap.js static/build/js/lib/bootstrap.js
+ uglifyjs static/build/js/lib/* --screw-ie8 --stats --keep-fnames -o static/build/js/libs.js
+fi
+
+rm -rf static/build/js/lib
+
+if [[ $BUILD_PRODUCTION ]]
+then
+ echo ">> Compressing jQuery..."
+ uglifyjs node_modules/jquery/dist/jquery.js --compress --screw-ie8 --define --stats --keep-fnames -o static/build/js/jquery.js
+else
+ echo ">> Building jQuery..."
+ uglifyjs node_modules/jquery/dist/jquery.js --screw-ie8 --stats --keep-fnames -o static/build/js/jquery.js
+fi
-echo ">> Building Libraries..."
-uglifyjs node_modules/jquery/dist/jquery.js --compress --screw-ie8 --define --stats --keep-fnames -o static/build/js/jquery.js
-uglifyjs node_modules/markdown/lib/markdown.js --compress --screw-ie8 --define --stats --keep-fnames -o static/build/js/markdown.js
-uglifyjs static/src/js/lib/* --compress --screw-ie8 --define --stats --keep-fnames -o static/build/js/libs.js
echo ">> Building Application JS..."
-browserify -t reactify static/src/js/app.js -o static/build/js/app.js
-# uglifyjs static/build/js/app.js --compress --screw-ie8 --define --stats --keep-fnames -o static/build/js/app.js
+browserify -t [ babelify --presets [ es2015 react ] ] static/src/js/app.js -o static/build/js/app.js
-echo ">> Building Global Utilities..."
-uglifyjs static/src/js/utils.js --compress --screw-ie8 --define --stats --keep-fnames -o static/build/js/utils.js
+if [[ $BUILD_PRODUCTION ]]
+then
+ echo ">> Compressing Application..."
+ uglifyjs static/build/js/app.js --compress --screw-ie8 --define --stats --keep-fnames -o static/build/js/app.js
+fi
+
+echo "> JS Built!"
diff --git a/scripts/build-less.sh b/scripts/build-less.sh
new file mode 100755
index 0000000..d9f27fa
--- /dev/null
+++ b/scripts/build-less.sh
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+set -e
+
+if [[ $BUILD_PRODUCTION ]]
+then
+ echo ">>> WARNING: Building in Production Mode!"
+fi
+
+echo ">> Building LESS..."
+lessc --silent static/src/less/style.less static/build/css/style.css
+
+echo ">> Post-Processing..."
+postcss -u autoprefixer -o static/build/css/style.css static/build/css/style.css
+
+if [[ $BUILD_PRODUCTION ]]
+then
+ echo ">> Compressing LESS..."
+ cleancss -d --s0 -o static/build/css/style.css static/build/css/style.css
+fi
+
+echo "> LESS Built!"
diff --git a/scripts/clean.sh b/scripts/clean.sh
new file mode 100755
index 0000000..0064077
--- /dev/null
+++ b/scripts/clean.sh
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+
+set -e
+
+
+echo ">> Removing VirtualEnv..."
+rm -rf env/
+
+echo ">> Removing Node Modules..."
+rm -rf node_modules/
+
+echo ">> Removing Static Build Directory..."
+rm -rf static/build/
+
+echo "> Cleaning Complete."
diff --git a/scripts/fresh.sh b/scripts/fresh.sh
new file mode 100755
index 0000000..4338659
--- /dev/null
+++ b/scripts/fresh.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+set -e
+
+
+echo ">> Removing VirtualEnv..."
+rm -rf env/
+
+echo ">> Removing Collected Static Files..."
+rm -rf collected-static/
+
+echo ">> Removing Private Data..."
+rm -rf private/
+
+echo ">> Removing Node Modules..."
+rm -rf node_modules/
+
+echo ">> Removing Static Build Directory..."
+rm -rf static/build/
+
+echo ">> Removing Stray Files and Folders..."
+rm -rf htmlcov/ .coverage
+
+echo "> Much Fresher!"
diff --git a/scripts/get-private-data.sh b/scripts/get-private-data.sh
new file mode 100755
index 0000000..c4b6d0e
--- /dev/null
+++ b/scripts/get-private-data.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+set -e
+
+echo ">> Removing old Private Data..."
+rm -rf private/
+
+echo ">> Getting Private Data..."
+git clone git@bitbucket.org:TheOrangeOne/theorangeone.net-site-private-data.git --branch master --single-branch private/
diff --git a/scripts/get_private_data.sh b/scripts/get_private_data.sh
deleted file mode 100644
index 92119cb..0000000
--- a/scripts/get_private_data.sh
+++ /dev/null
@@ -1,14 +0,0 @@
-#!/usr/bin/bash
-
-set -e
-
-echo ">> Removing old Private data..."
-rm -rf private/*
-mkdir private/
-cd private/
-
-echo ">> Cloning Private Repo..."
-git clone git@bitbucket.org:TheOrangeOne/theorangeone.net-site-private-data.git
-
-
-echo ">> That's it for now..."
diff --git a/static/src/img/SR-logo-banner.png b/static/src/img/SR-logo-banner.png
new file mode 100644
index 0000000..a4d9434
Binary files /dev/null and b/static/src/img/SR-logo-banner.png differ
diff --git a/static/src/img/logo-transparent.png b/static/src/img/logo-transparent.png
new file mode 100644
index 0000000..602aded
Binary files /dev/null and b/static/src/img/logo-transparent.png differ
diff --git a/static/src/img/BSOD_Enabler_Screenshot.png b/static/src/img/projects/BSOD_Enabler_Screenshot.png
similarity index 100%
rename from static/src/img/BSOD_Enabler_Screenshot.png
rename to static/src/img/projects/BSOD_Enabler_Screenshot.png
diff --git a/static/src/img/projects/collyers-logo.gif b/static/src/img/projects/collyers-logo.gif
new file mode 100644
index 0000000..ca69f3b
Binary files /dev/null and b/static/src/img/projects/collyers-logo.gif differ
diff --git a/static/src/img/projects/kermit-typing.gif b/static/src/img/projects/kermit-typing.gif
new file mode 100644
index 0000000..bb1b552
Binary files /dev/null and b/static/src/img/projects/kermit-typing.gif differ
diff --git a/static/src/js/app.js b/static/src/js/app.js
index d1ace96..47090a0 100644
--- a/static/src/js/app.js
+++ b/static/src/js/app.js
@@ -1,14 +1,24 @@
-var React = require('react');
-var indexCarousel = require('./components/indexCarousel.js');
+import './events.js';
+import './globals.js';
-require('./events.js');
+import 'whatwg-fetch';
+import React from 'react';
-if ($('#index-carousel-container').length == 1) {
- React.render(indexCarousel, $("#index-carousel-container")[0]);
+import NavBar from './components/navbar/navbar';
+import Breadcrumbs from './components/breadcrumbs';
+import renderImagePanels from './components/image-panel';
+
+renderImagePanels();
+
+if ($('navbar').length > 0) {
+ React.render(, $('navbar')[0]);
}
-$(window).load(function(){
- $(window).trigger('scroll').trigger('resize');
- var s = skrollr.init();
-});
\ No newline at end of file
+if ($('#breadcrumbs').length > 0) {
+ React.render(, $('#breadcrumbs')[0]);
+}
+
+if ($('.header h1').text()) {
+ $('.markdown-content h1').eq(0).hide();
+}
diff --git a/static/src/js/components/breadcrumbs.js b/static/src/js/components/breadcrumbs.js
new file mode 100644
index 0000000..bdbaa51
--- /dev/null
+++ b/static/src/js/components/breadcrumbs.js
@@ -0,0 +1,33 @@
+import React from 'react';
+
+export default class Breadcrumbs extends React.Component {
+ render() {
+ const loc = location.pathname.endsWith('/') ? location.pathname.slice(0, -1) : location.pathname;
+ const urlParts = Object.freeze(loc.split('/').slice(1));
+ if (urlParts.length < 2) {
+ return null;
+ }
+ var elements = [];
+ for (var i = 0; i < urlParts.length; i++) {
+ var dirs = [];
+ for (var j = 0; j <= i; j++) {
+ dirs.push(urlParts[j]);
+ }
+ if (i === (urlParts.length - 1)) {
+ elements.push(
+ {urlParts[i]}
+ );
+ } else {
+ var url = '/' + dirs.join('/') + '/';
+ elements.push(
+ {urlParts[i]}
+ );
+ }
+ }
+ return (
+
+ { elements }
+
+ );
+ }
+}
diff --git a/static/src/js/components/image-panel.js b/static/src/js/components/image-panel.js
new file mode 100644
index 0000000..085df6c
--- /dev/null
+++ b/static/src/js/components/image-panel.js
@@ -0,0 +1,24 @@
+export default function renderImagePanels() {
+ $('.image-panel').each(function () {
+ const element = $(this);
+ if (!element.data('image')) { // if it doesnt have an image, ignore it.
+ return;
+ }
+
+ element.css('background-image', 'url("' + element.data('image') + '")');
+
+ let height;
+ switch (element.data('size')) {
+ case 'small':
+ height = '30';
+ break;
+ default:
+ case 'medium':
+ height = '60';
+ break;
+ case 'large':
+ height = '100';
+ }
+ element.css('height', `${height}vh`);
+ });
+}
diff --git a/static/src/js/components/indexCarousel.js b/static/src/js/components/indexCarousel.js
deleted file mode 100644
index 0a56fa3..0000000
--- a/static/src/js/components/indexCarousel.js
+++ /dev/null
@@ -1,27 +0,0 @@
-var React = require('react');
-var Bootstrap = require('react-bootstrap');
-
-var Carousel = Bootstrap.Carousel;
-var CarouselItem = Bootstrap.CarouselItem;
-
-const indexCarousel = (
-
-
-
-
-
Setup
-
Nulla vitae elit libero, a pharetra augue mollis interdum.
-
-
-
-
-
-
-
Student Robotics
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
-
-
-
-);
-
-module.exports = indexCarousel;
\ No newline at end of file
diff --git a/static/src/js/components/navbar/dropdown.js b/static/src/js/components/navbar/dropdown.js
new file mode 100644
index 0000000..728a505
--- /dev/null
+++ b/static/src/js/components/navbar/dropdown.js
@@ -0,0 +1,20 @@
+import React from 'react';
+
+export default class Dropdown extends React.Component {
+ render() {
+ return (
+
+
+ { this.props.title }
+
+
+ { this.props.children }
+
+
+ );
+ }
+}
diff --git a/static/src/js/components/navbar/header.js b/static/src/js/components/navbar/header.js
new file mode 100644
index 0000000..5b2137c
--- /dev/null
+++ b/static/src/js/components/navbar/header.js
@@ -0,0 +1,28 @@
+import React from 'react';
+
+export default class Header extends React.Component {
+ render() {
+ const items = [0, 1, 2];
+ var iconBars = items.map(function (item) {
+ return (
+
+ );
+ });
+ return (
+
+ );
+ }
+}
diff --git a/static/src/js/components/navbar/item.js b/static/src/js/components/navbar/item.js
new file mode 100644
index 0000000..03e2984
--- /dev/null
+++ b/static/src/js/components/navbar/item.js
@@ -0,0 +1,23 @@
+import React from 'react';
+
+
+export default class Item extends React.Component {
+ render() {
+ const href = this.props.href.endsWith('/') ? this.props.href : this.props.href + '/';
+ let icon;
+ if (this.props.icon) {
+ if (this.props.icon.startsWith('ion')) {
+ icon = (
+
+ );
+ } else if (this.props.icon.startsWith('glyphicon')) {
+ icon = (
+
+ );
+ }
+ }
+ return (
+ {icon}{this.props.children}
+ );
+ }
+}
diff --git a/static/src/js/components/navbar/navbar.js b/static/src/js/components/navbar/navbar.js
new file mode 100644
index 0000000..ae0f79b
--- /dev/null
+++ b/static/src/js/components/navbar/navbar.js
@@ -0,0 +1,74 @@
+import React from 'react';
+import Header from './header';
+import Dropdown from './dropdown';
+import Item from './item';
+
+export default class NavBar extends React.Component {
+ render() {
+ return (
+
+
+
+
+
+
+ );
+ }
+}
diff --git a/static/src/js/events.js b/static/src/js/events.js
index cc9be5b..f16e9d9 100644
--- a/static/src/js/events.js
+++ b/static/src/js/events.js
@@ -1,16 +1,69 @@
-$(window).scroll(function() {
- position_navbar();
+/* global $ */
+var is_navbar_attached = false;
+
+$(window).load(function() {
+ $(window).trigger('scroll');
});
+function detach_navbar() {
+ $('#navbar-container').removeClass('stick-top').addClass('align');
+ $('.dropdown-menu').removeClass('dropdown').addClass('dropup');
+}
-$(window).resize(function() {
- set_page_sizes();
- space_navbar();
+function attach_navbar() {
+ $('#navbar-container').removeClass('align').addClass('stick-top');
+ $('.dropdown-menu').removeClass('dropup').addClass('dropdown');
+}
+
+function flip_dropdowns(obj, direction) {
+ if (obj.hasClass('drop' + direction)) { return; }
+ var reverse = ((direction === 'up') ? 'down' : 'up');
+ obj.removeClass('drop' + reverse).addClass('drop' + direction);
+ obj.prev().find('i').removeClass('ion-ios-arrow-' + reverse).addClass('ion-ios-arrow-' + direction);
+}
+
+function position_navbar() {
+ if ($(window).width() < 862) { // @screen-sm
+ $('.navbar-icon').removeClass('ion-ios-arrow-up').addClass('ion-ios-arrow-down');
+ return;
+ }
+ if ($(window).scrollTop() > $('#navbar-anchor').offset().top) {
+ if (!is_navbar_attached) {
+ attach_navbar();
+ is_navbar_attached = true;
+ }
+ } else if (is_navbar_attached) {
+ detach_navbar();
+ is_navbar_attached = false;
+ }
+ $('.dropdown-menu').each(function() {
+ var direction = ($(this).height() + 10 < $('nav').offset().top - $(window).scrollTop()) ? 'up' : 'down';
+ flip_dropdowns($(this), direction);
+ });
+}
+
+$(function() { // https://css-tricks.com/snippets/jquery/smooth-scrolling/
+ $('a[href*=#]:not([href=#])').click(function() {
+ if ($(this).data('toggle') === 'collapse') {
+ return true;
+ }
+ if (location.pathname.replace(/^\//, '') === this.pathname.replace(/^\//, '')
+ && location.hostname === this.hostname) {
+ var target = $(this.hash);
+ target = target.length ? target : $('[name=' + this.hash.slice(1) + ']');
+ if (target.length) {
+ $('html,body').animate({
+ scrollTop: target.offset().top
+ }, 1000);
+ return false;
+ }
+ }
+ });
});
-
-$("#page-down").click(function() {
- $('html, body').animate({
- scrollTop: $("#navbar-anchor").offset().top
- }, 750);
+$('a[href="soon"]').click(function (e) {
+ e.preventDefault();
+ alert('Content coming soon, stand by!');
});
+
+$(window).scroll(position_navbar);
diff --git a/static/src/js/globals.js b/static/src/js/globals.js
new file mode 100644
index 0000000..e168513
--- /dev/null
+++ b/static/src/js/globals.js
@@ -0,0 +1,7 @@
+window.updateTitle = function (value) {
+ document.title = value + ' | TheOrangeOne';
+};
+
+if ($('.header h1').length) {
+ window.updateTitle($('.header h1').text());
+}
diff --git a/static/src/js/lib/bootstrap.js b/static/src/js/lib/bootstrap.js
deleted file mode 100644
index 5debfd7..0000000
--- a/static/src/js/lib/bootstrap.js
+++ /dev/null
@@ -1,2363 +0,0 @@
-/*!
- * Bootstrap v3.3.5 (http://getbootstrap.com)
- * Copyright 2011-2015 Twitter, Inc.
- * Licensed under the MIT license
- */
-
-if (typeof jQuery === 'undefined') {
- throw new Error('Bootstrap\'s JavaScript requires jQuery')
-}
-
-+function ($) {
- 'use strict';
- var version = $.fn.jquery.split(' ')[0].split('.')
- if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1)) {
- throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher')
- }
-}(jQuery);
-
-/* ========================================================================
- * Bootstrap: transition.js v3.3.5
- * http://getbootstrap.com/javascript/#transitions
- * ========================================================================
- * Copyright 2011-2015 Twitter, Inc.
- * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
- * ======================================================================== */
-
-
-+function ($) {
- 'use strict';
-
- // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
- // ============================================================
-
- function transitionEnd() {
- var el = document.createElement('bootstrap')
-
- var transEndEventNames = {
- WebkitTransition : 'webkitTransitionEnd',
- MozTransition : 'transitionend',
- OTransition : 'oTransitionEnd otransitionend',
- transition : 'transitionend'
- }
-
- for (var name in transEndEventNames) {
- if (el.style[name] !== undefined) {
- return { end: transEndEventNames[name] }
- }
- }
-
- return false // explicit for ie8 ( ._.)
- }
-
- // http://blog.alexmaccaw.com/css-transitions
- $.fn.emulateTransitionEnd = function (duration) {
- var called = false
- var $el = this
- $(this).one('bsTransitionEnd', function () { called = true })
- var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
- setTimeout(callback, duration)
- return this
- }
-
- $(function () {
- $.support.transition = transitionEnd()
-
- if (!$.support.transition) return
-
- $.event.special.bsTransitionEnd = {
- bindType: $.support.transition.end,
- delegateType: $.support.transition.end,
- handle: function (e) {
- if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments)
- }
- }
- })
-
-}(jQuery);
-
-/* ========================================================================
- * Bootstrap: alert.js v3.3.5
- * http://getbootstrap.com/javascript/#alerts
- * ========================================================================
- * Copyright 2011-2015 Twitter, Inc.
- * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
- * ======================================================================== */
-
-
-+function ($) {
- 'use strict';
-
- // ALERT CLASS DEFINITION
- // ======================
-
- var dismiss = '[data-dismiss="alert"]'
- var Alert = function (el) {
- $(el).on('click', dismiss, this.close)
- }
-
- Alert.VERSION = '3.3.5'
-
- Alert.TRANSITION_DURATION = 150
-
- Alert.prototype.close = function (e) {
- var $this = $(this)
- var selector = $this.attr('data-target')
-
- if (!selector) {
- selector = $this.attr('href')
- selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
- }
-
- var $parent = $(selector)
-
- if (e) e.preventDefault()
-
- if (!$parent.length) {
- $parent = $this.closest('.alert')
- }
-
- $parent.trigger(e = $.Event('close.bs.alert'))
-
- if (e.isDefaultPrevented()) return
-
- $parent.removeClass('in')
-
- function removeElement() {
- // detach from parent, fire event then clean up data
- $parent.detach().trigger('closed.bs.alert').remove()
- }
-
- $.support.transition && $parent.hasClass('fade') ?
- $parent
- .one('bsTransitionEnd', removeElement)
- .emulateTransitionEnd(Alert.TRANSITION_DURATION) :
- removeElement()
- }
-
-
- // ALERT PLUGIN DEFINITION
- // =======================
-
- function Plugin(option) {
- return this.each(function () {
- var $this = $(this)
- var data = $this.data('bs.alert')
-
- if (!data) $this.data('bs.alert', (data = new Alert(this)))
- if (typeof option == 'string') data[option].call($this)
- })
- }
-
- var old = $.fn.alert
-
- $.fn.alert = Plugin
- $.fn.alert.Constructor = Alert
-
-
- // ALERT NO CONFLICT
- // =================
-
- $.fn.alert.noConflict = function () {
- $.fn.alert = old
- return this
- }
-
-
- // ALERT DATA-API
- // ==============
-
- $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)
-
-}(jQuery);
-
-/* ========================================================================
- * Bootstrap: button.js v3.3.5
- * http://getbootstrap.com/javascript/#buttons
- * ========================================================================
- * Copyright 2011-2015 Twitter, Inc.
- * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
- * ======================================================================== */
-
-
-+function ($) {
- 'use strict';
-
- // BUTTON PUBLIC CLASS DEFINITION
- // ==============================
-
- var Button = function (element, options) {
- this.$element = $(element)
- this.options = $.extend({}, Button.DEFAULTS, options)
- this.isLoading = false
- }
-
- Button.VERSION = '3.3.5'
-
- Button.DEFAULTS = {
- loadingText: 'loading...'
- }
-
- Button.prototype.setState = function (state) {
- var d = 'disabled'
- var $el = this.$element
- var val = $el.is('input') ? 'val' : 'html'
- var data = $el.data()
-
- state += 'Text'
-
- if (data.resetText == null) $el.data('resetText', $el[val]())
-
- // push to event loop to allow forms to submit
- setTimeout($.proxy(function () {
- $el[val](data[state] == null ? this.options[state] : data[state])
-
- if (state == 'loadingText') {
- this.isLoading = true
- $el.addClass(d).attr(d, d)
- } else if (this.isLoading) {
- this.isLoading = false
- $el.removeClass(d).removeAttr(d)
- }
- }, this), 0)
- }
-
- Button.prototype.toggle = function () {
- var changed = true
- var $parent = this.$element.closest('[data-toggle="buttons"]')
-
- if ($parent.length) {
- var $input = this.$element.find('input')
- if ($input.prop('type') == 'radio') {
- if ($input.prop('checked')) changed = false
- $parent.find('.active').removeClass('active')
- this.$element.addClass('active')
- } else if ($input.prop('type') == 'checkbox') {
- if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false
- this.$element.toggleClass('active')
- }
- $input.prop('checked', this.$element.hasClass('active'))
- if (changed) $input.trigger('change')
- } else {
- this.$element.attr('aria-pressed', !this.$element.hasClass('active'))
- this.$element.toggleClass('active')
- }
- }
-
-
- // BUTTON PLUGIN DEFINITION
- // ========================
-
- function Plugin(option) {
- return this.each(function () {
- var $this = $(this)
- var data = $this.data('bs.button')
- var options = typeof option == 'object' && option
-
- if (!data) $this.data('bs.button', (data = new Button(this, options)))
-
- if (option == 'toggle') data.toggle()
- else if (option) data.setState(option)
- })
- }
-
- var old = $.fn.button
-
- $.fn.button = Plugin
- $.fn.button.Constructor = Button
-
-
- // BUTTON NO CONFLICT
- // ==================
-
- $.fn.button.noConflict = function () {
- $.fn.button = old
- return this
- }
-
-
- // BUTTON DATA-API
- // ===============
-
- $(document)
- .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) {
- var $btn = $(e.target)
- if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
- Plugin.call($btn, 'toggle')
- if (!($(e.target).is('input[type="radio"]') || $(e.target).is('input[type="checkbox"]'))) e.preventDefault()
- })
- .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) {
- $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type))
- })
-
-}(jQuery);
-
-/* ========================================================================
- * Bootstrap: carousel.js v3.3.5
- * http://getbootstrap.com/javascript/#carousel
- * ========================================================================
- * Copyright 2011-2015 Twitter, Inc.
- * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
- * ======================================================================== */
-
-
-+function ($) {
- 'use strict';
-
- // CAROUSEL CLASS DEFINITION
- // =========================
-
- var Carousel = function (element, options) {
- this.$element = $(element)
- this.$indicators = this.$element.find('.carousel-indicators')
- this.options = options
- this.paused = null
- this.sliding = null
- this.interval = null
- this.$active = null
- this.$items = null
-
- this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this))
-
- this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element
- .on('mouseenter.bs.carousel', $.proxy(this.pause, this))
- .on('mouseleave.bs.carousel', $.proxy(this.cycle, this))
- }
-
- Carousel.VERSION = '3.3.5'
-
- Carousel.TRANSITION_DURATION = 600
-
- Carousel.DEFAULTS = {
- interval: 5000,
- pause: 'hover',
- wrap: true,
- keyboard: true
- }
-
- Carousel.prototype.keydown = function (e) {
- if (/input|textarea/i.test(e.target.tagName)) return
- switch (e.which) {
- case 37: this.prev(); break
- case 39: this.next(); break
- default: return
- }
-
- e.preventDefault()
- }
-
- Carousel.prototype.cycle = function (e) {
- e || (this.paused = false)
-
- this.interval && clearInterval(this.interval)
-
- this.options.interval
- && !this.paused
- && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
-
- return this
- }
-
- Carousel.prototype.getItemIndex = function (item) {
- this.$items = item.parent().children('.item')
- return this.$items.index(item || this.$active)
- }
-
- Carousel.prototype.getItemForDirection = function (direction, active) {
- var activeIndex = this.getItemIndex(active)
- var willWrap = (direction == 'prev' && activeIndex === 0)
- || (direction == 'next' && activeIndex == (this.$items.length - 1))
- if (willWrap && !this.options.wrap) return active
- var delta = direction == 'prev' ? -1 : 1
- var itemIndex = (activeIndex + delta) % this.$items.length
- return this.$items.eq(itemIndex)
- }
-
- Carousel.prototype.to = function (pos) {
- var that = this
- var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active'))
-
- if (pos > (this.$items.length - 1) || pos < 0) return
-
- if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid"
- if (activeIndex == pos) return this.pause().cycle()
-
- return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos))
- }
-
- Carousel.prototype.pause = function (e) {
- e || (this.paused = true)
-
- if (this.$element.find('.next, .prev').length && $.support.transition) {
- this.$element.trigger($.support.transition.end)
- this.cycle(true)
- }
-
- this.interval = clearInterval(this.interval)
-
- return this
- }
-
- Carousel.prototype.next = function () {
- if (this.sliding) return
- return this.slide('next')
- }
-
- Carousel.prototype.prev = function () {
- if (this.sliding) return
- return this.slide('prev')
- }
-
- Carousel.prototype.slide = function (type, next) {
- var $active = this.$element.find('.item.active')
- var $next = next || this.getItemForDirection(type, $active)
- var isCycling = this.interval
- var direction = type == 'next' ? 'left' : 'right'
- var that = this
-
- if ($next.hasClass('active')) return (this.sliding = false)
-
- var relatedTarget = $next[0]
- var slideEvent = $.Event('slide.bs.carousel', {
- relatedTarget: relatedTarget,
- direction: direction
- })
- this.$element.trigger(slideEvent)
- if (slideEvent.isDefaultPrevented()) return
-
- this.sliding = true
-
- isCycling && this.pause()
-
- if (this.$indicators.length) {
- this.$indicators.find('.active').removeClass('active')
- var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)])
- $nextIndicator && $nextIndicator.addClass('active')
- }
-
- var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid"
- if ($.support.transition && this.$element.hasClass('slide')) {
- $next.addClass(type)
- $next[0].offsetWidth // force reflow
- $active.addClass(direction)
- $next.addClass(direction)
- $active
- .one('bsTransitionEnd', function () {
- $next.removeClass([type, direction].join(' ')).addClass('active')
- $active.removeClass(['active', direction].join(' '))
- that.sliding = false
- setTimeout(function () {
- that.$element.trigger(slidEvent)
- }, 0)
- })
- .emulateTransitionEnd(Carousel.TRANSITION_DURATION)
- } else {
- $active.removeClass('active')
- $next.addClass('active')
- this.sliding = false
- this.$element.trigger(slidEvent)
- }
-
- isCycling && this.cycle()
-
- return this
- }
-
-
- // CAROUSEL PLUGIN DEFINITION
- // ==========================
-
- function Plugin(option) {
- return this.each(function () {
- var $this = $(this)
- var data = $this.data('bs.carousel')
- var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)
- var action = typeof option == 'string' ? option : options.slide
-
- if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))
- if (typeof option == 'number') data.to(option)
- else if (action) data[action]()
- else if (options.interval) data.pause().cycle()
- })
- }
-
- var old = $.fn.carousel
-
- $.fn.carousel = Plugin
- $.fn.carousel.Constructor = Carousel
-
-
- // CAROUSEL NO CONFLICT
- // ====================
-
- $.fn.carousel.noConflict = function () {
- $.fn.carousel = old
- return this
- }
-
-
- // CAROUSEL DATA-API
- // =================
-
- var clickHandler = function (e) {
- var href
- var $this = $(this)
- var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7
- if (!$target.hasClass('carousel')) return
- var options = $.extend({}, $target.data(), $this.data())
- var slideIndex = $this.attr('data-slide-to')
- if (slideIndex) options.interval = false
-
- Plugin.call($target, options)
-
- if (slideIndex) {
- $target.data('bs.carousel').to(slideIndex)
- }
-
- e.preventDefault()
- }
-
- $(document)
- .on('click.bs.carousel.data-api', '[data-slide]', clickHandler)
- .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler)
-
- $(window).on('load', function () {
- $('[data-ride="carousel"]').each(function () {
- var $carousel = $(this)
- Plugin.call($carousel, $carousel.data())
- })
- })
-
-}(jQuery);
-
-/* ========================================================================
- * Bootstrap: collapse.js v3.3.5
- * http://getbootstrap.com/javascript/#collapse
- * ========================================================================
- * Copyright 2011-2015 Twitter, Inc.
- * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
- * ======================================================================== */
-
-
-+function ($) {
- 'use strict';
-
- // COLLAPSE PUBLIC CLASS DEFINITION
- // ================================
-
- var Collapse = function (element, options) {
- this.$element = $(element)
- this.options = $.extend({}, Collapse.DEFAULTS, options)
- this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' +
- '[data-toggle="collapse"][data-target="#' + element.id + '"]')
- this.transitioning = null
-
- if (this.options.parent) {
- this.$parent = this.getParent()
- } else {
- this.addAriaAndCollapsedClass(this.$element, this.$trigger)
- }
-
- if (this.options.toggle) this.toggle()
- }
-
- Collapse.VERSION = '3.3.5'
-
- Collapse.TRANSITION_DURATION = 350
-
- Collapse.DEFAULTS = {
- toggle: true
- }
-
- Collapse.prototype.dimension = function () {
- var hasWidth = this.$element.hasClass('width')
- return hasWidth ? 'width' : 'height'
- }
-
- Collapse.prototype.show = function () {
- if (this.transitioning || this.$element.hasClass('in')) return
-
- var activesData
- var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing')
-
- if (actives && actives.length) {
- activesData = actives.data('bs.collapse')
- if (activesData && activesData.transitioning) return
- }
-
- var startEvent = $.Event('show.bs.collapse')
- this.$element.trigger(startEvent)
- if (startEvent.isDefaultPrevented()) return
-
- if (actives && actives.length) {
- Plugin.call(actives, 'hide')
- activesData || actives.data('bs.collapse', null)
- }
-
- var dimension = this.dimension()
-
- this.$element
- .removeClass('collapse')
- .addClass('collapsing')[dimension](0)
- .attr('aria-expanded', true)
-
- this.$trigger
- .removeClass('collapsed')
- .attr('aria-expanded', true)
-
- this.transitioning = 1
-
- var complete = function () {
- this.$element
- .removeClass('collapsing')
- .addClass('collapse in')[dimension]('')
- this.transitioning = 0
- this.$element
- .trigger('shown.bs.collapse')
- }
-
- if (!$.support.transition) return complete.call(this)
-
- var scrollSize = $.camelCase(['scroll', dimension].join('-'))
-
- this.$element
- .one('bsTransitionEnd', $.proxy(complete, this))
- .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize])
- }
-
- Collapse.prototype.hide = function () {
- if (this.transitioning || !this.$element.hasClass('in')) return
-
- var startEvent = $.Event('hide.bs.collapse')
- this.$element.trigger(startEvent)
- if (startEvent.isDefaultPrevented()) return
-
- var dimension = this.dimension()
-
- this.$element[dimension](this.$element[dimension]())[0].offsetHeight
-
- this.$element
- .addClass('collapsing')
- .removeClass('collapse in')
- .attr('aria-expanded', false)
-
- this.$trigger
- .addClass('collapsed')
- .attr('aria-expanded', false)
-
- this.transitioning = 1
-
- var complete = function () {
- this.transitioning = 0
- this.$element
- .removeClass('collapsing')
- .addClass('collapse')
- .trigger('hidden.bs.collapse')
- }
-
- if (!$.support.transition) return complete.call(this)
-
- this.$element
- [dimension](0)
- .one('bsTransitionEnd', $.proxy(complete, this))
- .emulateTransitionEnd(Collapse.TRANSITION_DURATION)
- }
-
- Collapse.prototype.toggle = function () {
- this[this.$element.hasClass('in') ? 'hide' : 'show']()
- }
-
- Collapse.prototype.getParent = function () {
- return $(this.options.parent)
- .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]')
- .each($.proxy(function (i, element) {
- var $element = $(element)
- this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element)
- }, this))
- .end()
- }
-
- Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) {
- var isOpen = $element.hasClass('in')
-
- $element.attr('aria-expanded', isOpen)
- $trigger
- .toggleClass('collapsed', !isOpen)
- .attr('aria-expanded', isOpen)
- }
-
- function getTargetFromTrigger($trigger) {
- var href
- var target = $trigger.attr('data-target')
- || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7
-
- return $(target)
- }
-
-
- // COLLAPSE PLUGIN DEFINITION
- // ==========================
-
- function Plugin(option) {
- return this.each(function () {
- var $this = $(this)
- var data = $this.data('bs.collapse')
- var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
-
- if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false
- if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
- if (typeof option == 'string') data[option]()
- })
- }
-
- var old = $.fn.collapse
-
- $.fn.collapse = Plugin
- $.fn.collapse.Constructor = Collapse
-
-
- // COLLAPSE NO CONFLICT
- // ====================
-
- $.fn.collapse.noConflict = function () {
- $.fn.collapse = old
- return this
- }
-
-
- // COLLAPSE DATA-API
- // =================
-
- $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) {
- var $this = $(this)
-
- if (!$this.attr('data-target')) e.preventDefault()
-
- var $target = getTargetFromTrigger($this)
- var data = $target.data('bs.collapse')
- var option = data ? 'toggle' : $this.data()
-
- Plugin.call($target, option)
- })
-
-}(jQuery);
-
-/* ========================================================================
- * Bootstrap: dropdown.js v3.3.5
- * http://getbootstrap.com/javascript/#dropdowns
- * ========================================================================
- * Copyright 2011-2015 Twitter, Inc.
- * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
- * ======================================================================== */
-
-
-+function ($) {
- 'use strict';
-
- // DROPDOWN CLASS DEFINITION
- // =========================
-
- var backdrop = '.dropdown-backdrop'
- var toggle = '[data-toggle="dropdown"]'
- var Dropdown = function (element) {
- $(element).on('click.bs.dropdown', this.toggle)
- }
-
- Dropdown.VERSION = '3.3.5'
-
- function getParent($this) {
- var selector = $this.attr('data-target')
-
- if (!selector) {
- selector = $this.attr('href')
- selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
- }
-
- var $parent = selector && $(selector)
-
- return $parent && $parent.length ? $parent : $this.parent()
- }
-
- function clearMenus(e) {
- if (e && e.which === 3) return
- $(backdrop).remove()
- $(toggle).each(function () {
- var $this = $(this)
- var $parent = getParent($this)
- var relatedTarget = { relatedTarget: this }
-
- if (!$parent.hasClass('open')) return
-
- if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return
-
- $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
-
- if (e.isDefaultPrevented()) return
-
- $this.attr('aria-expanded', 'false')
- $parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget)
- })
- }
-
- Dropdown.prototype.toggle = function (e) {
- var $this = $(this)
-
- if ($this.is('.disabled, :disabled')) return
-
- var $parent = getParent($this)
- var isActive = $parent.hasClass('open')
-
- clearMenus()
-
- if (!isActive) {
- if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
- // if mobile we use a backdrop because click events don't delegate
- $(document.createElement('div'))
- .addClass('dropdown-backdrop')
- .insertAfter($(this))
- .on('click', clearMenus)
- }
-
- var relatedTarget = { relatedTarget: this }
- $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))
-
- if (e.isDefaultPrevented()) return
-
- $this
- .trigger('focus')
- .attr('aria-expanded', 'true')
-
- $parent
- .toggleClass('open')
- .trigger('shown.bs.dropdown', relatedTarget)
- }
-
- return false
- }
-
- Dropdown.prototype.keydown = function (e) {
- if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return
-
- var $this = $(this)
-
- e.preventDefault()
- e.stopPropagation()
-
- if ($this.is('.disabled, :disabled')) return
-
- var $parent = getParent($this)
- var isActive = $parent.hasClass('open')
-
- if (!isActive && e.which != 27 || isActive && e.which == 27) {
- if (e.which == 27) $parent.find(toggle).trigger('focus')
- return $this.trigger('click')
- }
-
- var desc = ' li:not(.disabled):visible a'
- var $items = $parent.find('.dropdown-menu' + desc)
-
- if (!$items.length) return
-
- var index = $items.index(e.target)
-
- if (e.which == 38 && index > 0) index-- // up
- if (e.which == 40 && index < $items.length - 1) index++ // down
- if (!~index) index = 0
-
- $items.eq(index).trigger('focus')
- }
-
-
- // DROPDOWN PLUGIN DEFINITION
- // ==========================
-
- function Plugin(option) {
- return this.each(function () {
- var $this = $(this)
- var data = $this.data('bs.dropdown')
-
- if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))
- if (typeof option == 'string') data[option].call($this)
- })
- }
-
- var old = $.fn.dropdown
-
- $.fn.dropdown = Plugin
- $.fn.dropdown.Constructor = Dropdown
-
-
- // DROPDOWN NO CONFLICT
- // ====================
-
- $.fn.dropdown.noConflict = function () {
- $.fn.dropdown = old
- return this
- }
-
-
- // APPLY TO STANDARD DROPDOWN ELEMENTS
- // ===================================
-
- $(document)
- .on('click.bs.dropdown.data-api', clearMenus)
- .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
- .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
- .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
- .on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown)
-
-}(jQuery);
-
-/* ========================================================================
- * Bootstrap: modal.js v3.3.5
- * http://getbootstrap.com/javascript/#modals
- * ========================================================================
- * Copyright 2011-2015 Twitter, Inc.
- * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
- * ======================================================================== */
-
-
-+function ($) {
- 'use strict';
-
- // MODAL CLASS DEFINITION
- // ======================
-
- var Modal = function (element, options) {
- this.options = options
- this.$body = $(document.body)
- this.$element = $(element)
- this.$dialog = this.$element.find('.modal-dialog')
- this.$backdrop = null
- this.isShown = null
- this.originalBodyPad = null
- this.scrollbarWidth = 0
- this.ignoreBackdropClick = false
-
- if (this.options.remote) {
- this.$element
- .find('.modal-content')
- .load(this.options.remote, $.proxy(function () {
- this.$element.trigger('loaded.bs.modal')
- }, this))
- }
- }
-
- Modal.VERSION = '3.3.5'
-
- Modal.TRANSITION_DURATION = 300
- Modal.BACKDROP_TRANSITION_DURATION = 150
-
- Modal.DEFAULTS = {
- backdrop: true,
- keyboard: true,
- show: true
- }
-
- Modal.prototype.toggle = function (_relatedTarget) {
- return this.isShown ? this.hide() : this.show(_relatedTarget)
- }
-
- Modal.prototype.show = function (_relatedTarget) {
- var that = this
- var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
-
- this.$element.trigger(e)
-
- if (this.isShown || e.isDefaultPrevented()) return
-
- this.isShown = true
-
- this.checkScrollbar()
- this.setScrollbar()
- this.$body.addClass('modal-open')
-
- this.escape()
- this.resize()
-
- this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
-
- this.$dialog.on('mousedown.dismiss.bs.modal', function () {
- that.$element.one('mouseup.dismiss.bs.modal', function (e) {
- if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true
- })
- })
-
- this.backdrop(function () {
- var transition = $.support.transition && that.$element.hasClass('fade')
-
- if (!that.$element.parent().length) {
- that.$element.appendTo(that.$body) // don't move modals dom position
- }
-
- that.$element
- .show()
- .scrollTop(0)
-
- that.adjustDialog()
-
- if (transition) {
- that.$element[0].offsetWidth // force reflow
- }
-
- that.$element.addClass('in')
-
- that.enforceFocus()
-
- var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })
-
- transition ?
- that.$dialog // wait for modal to slide in
- .one('bsTransitionEnd', function () {
- that.$element.trigger('focus').trigger(e)
- })
- .emulateTransitionEnd(Modal.TRANSITION_DURATION) :
- that.$element.trigger('focus').trigger(e)
- })
- }
-
- Modal.prototype.hide = function (e) {
- if (e) e.preventDefault()
-
- e = $.Event('hide.bs.modal')
-
- this.$element.trigger(e)
-
- if (!this.isShown || e.isDefaultPrevented()) return
-
- this.isShown = false
-
- this.escape()
- this.resize()
-
- $(document).off('focusin.bs.modal')
-
- this.$element
- .removeClass('in')
- .off('click.dismiss.bs.modal')
- .off('mouseup.dismiss.bs.modal')
-
- this.$dialog.off('mousedown.dismiss.bs.modal')
-
- $.support.transition && this.$element.hasClass('fade') ?
- this.$element
- .one('bsTransitionEnd', $.proxy(this.hideModal, this))
- .emulateTransitionEnd(Modal.TRANSITION_DURATION) :
- this.hideModal()
- }
-
- Modal.prototype.enforceFocus = function () {
- $(document)
- .off('focusin.bs.modal') // guard against infinite focus loop
- .on('focusin.bs.modal', $.proxy(function (e) {
- if (this.$element[0] !== e.target && !this.$element.has(e.target).length) {
- this.$element.trigger('focus')
- }
- }, this))
- }
-
- Modal.prototype.escape = function () {
- if (this.isShown && this.options.keyboard) {
- this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) {
- e.which == 27 && this.hide()
- }, this))
- } else if (!this.isShown) {
- this.$element.off('keydown.dismiss.bs.modal')
- }
- }
-
- Modal.prototype.resize = function () {
- if (this.isShown) {
- $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this))
- } else {
- $(window).off('resize.bs.modal')
- }
- }
-
- Modal.prototype.hideModal = function () {
- var that = this
- this.$element.hide()
- this.backdrop(function () {
- that.$body.removeClass('modal-open')
- that.resetAdjustments()
- that.resetScrollbar()
- that.$element.trigger('hidden.bs.modal')
- })
- }
-
- Modal.prototype.removeBackdrop = function () {
- this.$backdrop && this.$backdrop.remove()
- this.$backdrop = null
- }
-
- Modal.prototype.backdrop = function (callback) {
- var that = this
- var animate = this.$element.hasClass('fade') ? 'fade' : ''
-
- if (this.isShown && this.options.backdrop) {
- var doAnimate = $.support.transition && animate
-
- this.$backdrop = $(document.createElement('div'))
- .addClass('modal-backdrop ' + animate)
- .appendTo(this.$body)
-
- this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) {
- if (this.ignoreBackdropClick) {
- this.ignoreBackdropClick = false
- return
- }
- if (e.target !== e.currentTarget) return
- this.options.backdrop == 'static'
- ? this.$element[0].focus()
- : this.hide()
- }, this))
-
- if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
-
- this.$backdrop.addClass('in')
-
- if (!callback) return
-
- doAnimate ?
- this.$backdrop
- .one('bsTransitionEnd', callback)
- .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
- callback()
-
- } else if (!this.isShown && this.$backdrop) {
- this.$backdrop.removeClass('in')
-
- var callbackRemove = function () {
- that.removeBackdrop()
- callback && callback()
- }
- $.support.transition && this.$element.hasClass('fade') ?
- this.$backdrop
- .one('bsTransitionEnd', callbackRemove)
- .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
- callbackRemove()
-
- } else if (callback) {
- callback()
- }
- }
-
- // these following methods are used to handle overflowing modals
-
- Modal.prototype.handleUpdate = function () {
- this.adjustDialog()
- }
-
- Modal.prototype.adjustDialog = function () {
- var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight
-
- this.$element.css({
- paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',
- paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''
- })
- }
-
- Modal.prototype.resetAdjustments = function () {
- this.$element.css({
- paddingLeft: '',
- paddingRight: ''
- })
- }
-
- Modal.prototype.checkScrollbar = function () {
- var fullWindowWidth = window.innerWidth
- if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8
- var documentElementRect = document.documentElement.getBoundingClientRect()
- fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left)
- }
- this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth
- this.scrollbarWidth = this.measureScrollbar()
- }
-
- Modal.prototype.setScrollbar = function () {
- var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)
- this.originalBodyPad = document.body.style.paddingRight || ''
- if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)
- }
-
- Modal.prototype.resetScrollbar = function () {
- this.$body.css('padding-right', this.originalBodyPad)
- }
-
- Modal.prototype.measureScrollbar = function () { // thx walsh
- var scrollDiv = document.createElement('div')
- scrollDiv.className = 'modal-scrollbar-measure'
- this.$body.append(scrollDiv)
- var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth
- this.$body[0].removeChild(scrollDiv)
- return scrollbarWidth
- }
-
-
- // MODAL PLUGIN DEFINITION
- // =======================
-
- function Plugin(option, _relatedTarget) {
- return this.each(function () {
- var $this = $(this)
- var data = $this.data('bs.modal')
- var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
-
- if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
- if (typeof option == 'string') data[option](_relatedTarget)
- else if (options.show) data.show(_relatedTarget)
- })
- }
-
- var old = $.fn.modal
-
- $.fn.modal = Plugin
- $.fn.modal.Constructor = Modal
-
-
- // MODAL NO CONFLICT
- // =================
-
- $.fn.modal.noConflict = function () {
- $.fn.modal = old
- return this
- }
-
-
- // MODAL DATA-API
- // ==============
-
- $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
- var $this = $(this)
- var href = $this.attr('href')
- var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7
- var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
-
- if ($this.is('a')) e.preventDefault()
-
- $target.one('show.bs.modal', function (showEvent) {
- if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown
- $target.one('hidden.bs.modal', function () {
- $this.is(':visible') && $this.trigger('focus')
- })
- })
- Plugin.call($target, option, this)
- })
-
-}(jQuery);
-
-/* ========================================================================
- * Bootstrap: tooltip.js v3.3.5
- * http://getbootstrap.com/javascript/#tooltip
- * Inspired by the original jQuery.tipsy by Jason Frame
- * ========================================================================
- * Copyright 2011-2015 Twitter, Inc.
- * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
- * ======================================================================== */
-
-
-+function ($) {
- 'use strict';
-
- // TOOLTIP PUBLIC CLASS DEFINITION
- // ===============================
-
- var Tooltip = function (element, options) {
- this.type = null
- this.options = null
- this.enabled = null
- this.timeout = null
- this.hoverState = null
- this.$element = null
- this.inState = null
-
- this.init('tooltip', element, options)
- }
-
- Tooltip.VERSION = '3.3.5'
-
- Tooltip.TRANSITION_DURATION = 150
-
- Tooltip.DEFAULTS = {
- animation: true,
- placement: 'top',
- selector: false,
- template: '',
- trigger: 'hover focus',
- title: '',
- delay: 0,
- html: false,
- container: false,
- viewport: {
- selector: 'body',
- padding: 0
- }
- }
-
- Tooltip.prototype.init = function (type, element, options) {
- this.enabled = true
- this.type = type
- this.$element = $(element)
- this.options = this.getOptions(options)
- this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport))
- this.inState = { click: false, hover: false, focus: false }
-
- if (this.$element[0] instanceof document.constructor && !this.options.selector) {
- throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!')
- }
-
- var triggers = this.options.trigger.split(' ')
-
- for (var i = triggers.length; i--;) {
- var trigger = triggers[i]
-
- if (trigger == 'click') {
- this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
- } else if (trigger != 'manual') {
- var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin'
- var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'
-
- this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
- this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
- }
- }
-
- this.options.selector ?
- (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
- this.fixTitle()
- }
-
- Tooltip.prototype.getDefaults = function () {
- return Tooltip.DEFAULTS
- }
-
- Tooltip.prototype.getOptions = function (options) {
- options = $.extend({}, this.getDefaults(), this.$element.data(), options)
-
- if (options.delay && typeof options.delay == 'number') {
- options.delay = {
- show: options.delay,
- hide: options.delay
- }
- }
-
- return options
- }
-
- Tooltip.prototype.getDelegateOptions = function () {
- var options = {}
- var defaults = this.getDefaults()
-
- this._options && $.each(this._options, function (key, value) {
- if (defaults[key] != value) options[key] = value
- })
-
- return options
- }
-
- Tooltip.prototype.enter = function (obj) {
- var self = obj instanceof this.constructor ?
- obj : $(obj.currentTarget).data('bs.' + this.type)
-
- if (!self) {
- self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
- $(obj.currentTarget).data('bs.' + this.type, self)
- }
-
- if (obj instanceof $.Event) {
- self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true
- }
-
- if (self.tip().hasClass('in') || self.hoverState == 'in') {
- self.hoverState = 'in'
- return
- }
-
- clearTimeout(self.timeout)
-
- self.hoverState = 'in'
-
- if (!self.options.delay || !self.options.delay.show) return self.show()
-
- self.timeout = setTimeout(function () {
- if (self.hoverState == 'in') self.show()
- }, self.options.delay.show)
- }
-
- Tooltip.prototype.isInStateTrue = function () {
- for (var key in this.inState) {
- if (this.inState[key]) return true
- }
-
- return false
- }
-
- Tooltip.prototype.leave = function (obj) {
- var self = obj instanceof this.constructor ?
- obj : $(obj.currentTarget).data('bs.' + this.type)
-
- if (!self) {
- self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
- $(obj.currentTarget).data('bs.' + this.type, self)
- }
-
- if (obj instanceof $.Event) {
- self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false
- }
-
- if (self.isInStateTrue()) return
-
- clearTimeout(self.timeout)
-
- self.hoverState = 'out'
-
- if (!self.options.delay || !self.options.delay.hide) return self.hide()
-
- self.timeout = setTimeout(function () {
- if (self.hoverState == 'out') self.hide()
- }, self.options.delay.hide)
- }
-
- Tooltip.prototype.show = function () {
- var e = $.Event('show.bs.' + this.type)
-
- if (this.hasContent() && this.enabled) {
- this.$element.trigger(e)
-
- var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])
- if (e.isDefaultPrevented() || !inDom) return
- var that = this
-
- var $tip = this.tip()
-
- var tipId = this.getUID(this.type)
-
- this.setContent()
- $tip.attr('id', tipId)
- this.$element.attr('aria-describedby', tipId)
-
- if (this.options.animation) $tip.addClass('fade')
-
- var placement = typeof this.options.placement == 'function' ?
- this.options.placement.call(this, $tip[0], this.$element[0]) :
- this.options.placement
-
- var autoToken = /\s?auto?\s?/i
- var autoPlace = autoToken.test(placement)
- if (autoPlace) placement = placement.replace(autoToken, '') || 'top'
-
- $tip
- .detach()
- .css({ top: 0, left: 0, display: 'block' })
- .addClass(placement)
- .data('bs.' + this.type, this)
-
- this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
- this.$element.trigger('inserted.bs.' + this.type)
-
- var pos = this.getPosition()
- var actualWidth = $tip[0].offsetWidth
- var actualHeight = $tip[0].offsetHeight
-
- if (autoPlace) {
- var orgPlacement = placement
- var viewportDim = this.getPosition(this.$viewport)
-
- placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' :
- placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' :
- placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' :
- placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' :
- placement
-
- $tip
- .removeClass(orgPlacement)
- .addClass(placement)
- }
-
- var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
-
- this.applyPlacement(calculatedOffset, placement)
-
- var complete = function () {
- var prevHoverState = that.hoverState
- that.$element.trigger('shown.bs.' + that.type)
- that.hoverState = null
-
- if (prevHoverState == 'out') that.leave(that)
- }
-
- $.support.transition && this.$tip.hasClass('fade') ?
- $tip
- .one('bsTransitionEnd', complete)
- .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
- complete()
- }
- }
-
- Tooltip.prototype.applyPlacement = function (offset, placement) {
- var $tip = this.tip()
- var width = $tip[0].offsetWidth
- var height = $tip[0].offsetHeight
-
- // manually read margins because getBoundingClientRect includes difference
- var marginTop = parseInt($tip.css('margin-top'), 10)
- var marginLeft = parseInt($tip.css('margin-left'), 10)
-
- // we must check for NaN for ie 8/9
- if (isNaN(marginTop)) marginTop = 0
- if (isNaN(marginLeft)) marginLeft = 0
-
- offset.top += marginTop
- offset.left += marginLeft
-
- // $.fn.offset doesn't round pixel values
- // so we use setOffset directly with our own function B-0
- $.offset.setOffset($tip[0], $.extend({
- using: function (props) {
- $tip.css({
- top: Math.round(props.top),
- left: Math.round(props.left)
- })
- }
- }, offset), 0)
-
- $tip.addClass('in')
-
- // check to see if placing tip in new offset caused the tip to resize itself
- var actualWidth = $tip[0].offsetWidth
- var actualHeight = $tip[0].offsetHeight
-
- if (placement == 'top' && actualHeight != height) {
- offset.top = offset.top + height - actualHeight
- }
-
- var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)
-
- if (delta.left) offset.left += delta.left
- else offset.top += delta.top
-
- var isVertical = /top|bottom/.test(placement)
- var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight
- var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'
-
- $tip.offset(offset)
- this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)
- }
-
- Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) {
- this.arrow()
- .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')
- .css(isVertical ? 'top' : 'left', '')
- }
-
- Tooltip.prototype.setContent = function () {
- var $tip = this.tip()
- var title = this.getTitle()
-
- $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
- $tip.removeClass('fade in top bottom left right')
- }
-
- Tooltip.prototype.hide = function (callback) {
- var that = this
- var $tip = $(this.$tip)
- var e = $.Event('hide.bs.' + this.type)
-
- function complete() {
- if (that.hoverState != 'in') $tip.detach()
- that.$element
- .removeAttr('aria-describedby')
- .trigger('hidden.bs.' + that.type)
- callback && callback()
- }
-
- this.$element.trigger(e)
-
- if (e.isDefaultPrevented()) return
-
- $tip.removeClass('in')
-
- $.support.transition && $tip.hasClass('fade') ?
- $tip
- .one('bsTransitionEnd', complete)
- .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
- complete()
-
- this.hoverState = null
-
- return this
- }
-
- Tooltip.prototype.fixTitle = function () {
- var $e = this.$element
- if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') {
- $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
- }
- }
-
- Tooltip.prototype.hasContent = function () {
- return this.getTitle()
- }
-
- Tooltip.prototype.getPosition = function ($element) {
- $element = $element || this.$element
-
- var el = $element[0]
- var isBody = el.tagName == 'BODY'
-
- var elRect = el.getBoundingClientRect()
- if (elRect.width == null) {
- // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
- elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })
- }
- var elOffset = isBody ? { top: 0, left: 0 } : $element.offset()
- var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }
- var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null
-
- return $.extend({}, elRect, scroll, outerDims, elOffset)
- }
-
- Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
- return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } :
- placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
- placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
- /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }
-
- }
-
- Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
- var delta = { top: 0, left: 0 }
- if (!this.$viewport) return delta
-
- var viewportPadding = this.options.viewport && this.options.viewport.padding || 0
- var viewportDimensions = this.getPosition(this.$viewport)
-
- if (/right|left/.test(placement)) {
- var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll
- var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight
- if (topEdgeOffset < viewportDimensions.top) { // top overflow
- delta.top = viewportDimensions.top - topEdgeOffset
- } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow
- delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset
- }
- } else {
- var leftEdgeOffset = pos.left - viewportPadding
- var rightEdgeOffset = pos.left + viewportPadding + actualWidth
- if (leftEdgeOffset < viewportDimensions.left) { // left overflow
- delta.left = viewportDimensions.left - leftEdgeOffset
- } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow
- delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
- }
- }
-
- return delta
- }
-
- Tooltip.prototype.getTitle = function () {
- var title
- var $e = this.$element
- var o = this.options
-
- title = $e.attr('data-original-title')
- || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
-
- return title
- }
-
- Tooltip.prototype.getUID = function (prefix) {
- do prefix += ~~(Math.random() * 1000000)
- while (document.getElementById(prefix))
- return prefix
- }
-
- Tooltip.prototype.tip = function () {
- if (!this.$tip) {
- this.$tip = $(this.options.template)
- if (this.$tip.length != 1) {
- throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!')
- }
- }
- return this.$tip
- }
-
- Tooltip.prototype.arrow = function () {
- return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))
- }
-
- Tooltip.prototype.enable = function () {
- this.enabled = true
- }
-
- Tooltip.prototype.disable = function () {
- this.enabled = false
- }
-
- Tooltip.prototype.toggleEnabled = function () {
- this.enabled = !this.enabled
- }
-
- Tooltip.prototype.toggle = function (e) {
- var self = this
- if (e) {
- self = $(e.currentTarget).data('bs.' + this.type)
- if (!self) {
- self = new this.constructor(e.currentTarget, this.getDelegateOptions())
- $(e.currentTarget).data('bs.' + this.type, self)
- }
- }
-
- if (e) {
- self.inState.click = !self.inState.click
- if (self.isInStateTrue()) self.enter(self)
- else self.leave(self)
- } else {
- self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
- }
- }
-
- Tooltip.prototype.destroy = function () {
- var that = this
- clearTimeout(this.timeout)
- this.hide(function () {
- that.$element.off('.' + that.type).removeData('bs.' + that.type)
- if (that.$tip) {
- that.$tip.detach()
- }
- that.$tip = null
- that.$arrow = null
- that.$viewport = null
- })
- }
-
-
- // TOOLTIP PLUGIN DEFINITION
- // =========================
-
- function Plugin(option) {
- return this.each(function () {
- var $this = $(this)
- var data = $this.data('bs.tooltip')
- var options = typeof option == 'object' && option
-
- if (!data && /destroy|hide/.test(option)) return
- if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
- if (typeof option == 'string') data[option]()
- })
- }
-
- var old = $.fn.tooltip
-
- $.fn.tooltip = Plugin
- $.fn.tooltip.Constructor = Tooltip
-
-
- // TOOLTIP NO CONFLICT
- // ===================
-
- $.fn.tooltip.noConflict = function () {
- $.fn.tooltip = old
- return this
- }
-
-}(jQuery);
-
-/* ========================================================================
- * Bootstrap: popover.js v3.3.5
- * http://getbootstrap.com/javascript/#popovers
- * ========================================================================
- * Copyright 2011-2015 Twitter, Inc.
- * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
- * ======================================================================== */
-
-
-+function ($) {
- 'use strict';
-
- // POPOVER PUBLIC CLASS DEFINITION
- // ===============================
-
- var Popover = function (element, options) {
- this.init('popover', element, options)
- }
-
- if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
-
- Popover.VERSION = '3.3.5'
-
- Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {
- placement: 'right',
- trigger: 'click',
- content: '',
- template: ''
- })
-
-
- // NOTE: POPOVER EXTENDS tooltip.js
- // ================================
-
- Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)
-
- Popover.prototype.constructor = Popover
-
- Popover.prototype.getDefaults = function () {
- return Popover.DEFAULTS
- }
-
- Popover.prototype.setContent = function () {
- var $tip = this.tip()
- var title = this.getTitle()
- var content = this.getContent()
-
- $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
- $tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events
- this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text'
- ](content)
-
- $tip.removeClass('fade top bottom left right in')
-
- // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
- // this manually by checking the contents.
- if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
- }
-
- Popover.prototype.hasContent = function () {
- return this.getTitle() || this.getContent()
- }
-
- Popover.prototype.getContent = function () {
- var $e = this.$element
- var o = this.options
-
- return $e.attr('data-content')
- || (typeof o.content == 'function' ?
- o.content.call($e[0]) :
- o.content)
- }
-
- Popover.prototype.arrow = function () {
- return (this.$arrow = this.$arrow || this.tip().find('.arrow'))
- }
-
-
- // POPOVER PLUGIN DEFINITION
- // =========================
-
- function Plugin(option) {
- return this.each(function () {
- var $this = $(this)
- var data = $this.data('bs.popover')
- var options = typeof option == 'object' && option
-
- if (!data && /destroy|hide/.test(option)) return
- if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
- if (typeof option == 'string') data[option]()
- })
- }
-
- var old = $.fn.popover
-
- $.fn.popover = Plugin
- $.fn.popover.Constructor = Popover
-
-
- // POPOVER NO CONFLICT
- // ===================
-
- $.fn.popover.noConflict = function () {
- $.fn.popover = old
- return this
- }
-
-}(jQuery);
-
-/* ========================================================================
- * Bootstrap: scrollspy.js v3.3.5
- * http://getbootstrap.com/javascript/#scrollspy
- * ========================================================================
- * Copyright 2011-2015 Twitter, Inc.
- * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
- * ======================================================================== */
-
-
-+function ($) {
- 'use strict';
-
- // SCROLLSPY CLASS DEFINITION
- // ==========================
-
- function ScrollSpy(element, options) {
- this.$body = $(document.body)
- this.$scrollElement = $(element).is(document.body) ? $(window) : $(element)
- this.options = $.extend({}, ScrollSpy.DEFAULTS, options)
- this.selector = (this.options.target || '') + ' .nav li > a'
- this.offsets = []
- this.targets = []
- this.activeTarget = null
- this.scrollHeight = 0
-
- this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this))
- this.refresh()
- this.process()
- }
-
- ScrollSpy.VERSION = '3.3.5'
-
- ScrollSpy.DEFAULTS = {
- offset: 10
- }
-
- ScrollSpy.prototype.getScrollHeight = function () {
- return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)
- }
-
- ScrollSpy.prototype.refresh = function () {
- var that = this
- var offsetMethod = 'offset'
- var offsetBase = 0
-
- this.offsets = []
- this.targets = []
- this.scrollHeight = this.getScrollHeight()
-
- if (!$.isWindow(this.$scrollElement[0])) {
- offsetMethod = 'position'
- offsetBase = this.$scrollElement.scrollTop()
- }
-
- this.$body
- .find(this.selector)
- .map(function () {
- var $el = $(this)
- var href = $el.data('target') || $el.attr('href')
- var $href = /^#./.test(href) && $(href)
-
- return ($href
- && $href.length
- && $href.is(':visible')
- && [[$href[offsetMethod]().top + offsetBase, href]]) || null
- })
- .sort(function (a, b) { return a[0] - b[0] })
- .each(function () {
- that.offsets.push(this[0])
- that.targets.push(this[1])
- })
- }
-
- ScrollSpy.prototype.process = function () {
- var scrollTop = this.$scrollElement.scrollTop() + this.options.offset
- var scrollHeight = this.getScrollHeight()
- var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height()
- var offsets = this.offsets
- var targets = this.targets
- var activeTarget = this.activeTarget
- var i
-
- if (this.scrollHeight != scrollHeight) {
- this.refresh()
- }
-
- if (scrollTop >= maxScroll) {
- return activeTarget != (i = targets[targets.length - 1]) && this.activate(i)
- }
-
- if (activeTarget && scrollTop < offsets[0]) {
- this.activeTarget = null
- return this.clear()
- }
-
- for (i = offsets.length; i--;) {
- activeTarget != targets[i]
- && scrollTop >= offsets[i]
- && (offsets[i + 1] === undefined || scrollTop < offsets[i + 1])
- && this.activate(targets[i])
- }
- }
-
- ScrollSpy.prototype.activate = function (target) {
- this.activeTarget = target
-
- this.clear()
-
- var selector = this.selector +
- '[data-target="' + target + '"],' +
- this.selector + '[href="' + target + '"]'
-
- var active = $(selector)
- .parents('li')
- .addClass('active')
-
- if (active.parent('.dropdown-menu').length) {
- active = active
- .closest('li.dropdown')
- .addClass('active')
- }
-
- active.trigger('activate.bs.scrollspy')
- }
-
- ScrollSpy.prototype.clear = function () {
- $(this.selector)
- .parentsUntil(this.options.target, '.active')
- .removeClass('active')
- }
-
-
- // SCROLLSPY PLUGIN DEFINITION
- // ===========================
-
- function Plugin(option) {
- return this.each(function () {
- var $this = $(this)
- var data = $this.data('bs.scrollspy')
- var options = typeof option == 'object' && option
-
- if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))
- if (typeof option == 'string') data[option]()
- })
- }
-
- var old = $.fn.scrollspy
-
- $.fn.scrollspy = Plugin
- $.fn.scrollspy.Constructor = ScrollSpy
-
-
- // SCROLLSPY NO CONFLICT
- // =====================
-
- $.fn.scrollspy.noConflict = function () {
- $.fn.scrollspy = old
- return this
- }
-
-
- // SCROLLSPY DATA-API
- // ==================
-
- $(window).on('load.bs.scrollspy.data-api', function () {
- $('[data-spy="scroll"]').each(function () {
- var $spy = $(this)
- Plugin.call($spy, $spy.data())
- })
- })
-
-}(jQuery);
-
-/* ========================================================================
- * Bootstrap: tab.js v3.3.5
- * http://getbootstrap.com/javascript/#tabs
- * ========================================================================
- * Copyright 2011-2015 Twitter, Inc.
- * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
- * ======================================================================== */
-
-
-+function ($) {
- 'use strict';
-
- // TAB CLASS DEFINITION
- // ====================
-
- var Tab = function (element) {
- // jscs:disable requireDollarBeforejQueryAssignment
- this.element = $(element)
- // jscs:enable requireDollarBeforejQueryAssignment
- }
-
- Tab.VERSION = '3.3.5'
-
- Tab.TRANSITION_DURATION = 150
-
- Tab.prototype.show = function () {
- var $this = this.element
- var $ul = $this.closest('ul:not(.dropdown-menu)')
- var selector = $this.data('target')
-
- if (!selector) {
- selector = $this.attr('href')
- selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
- }
-
- if ($this.parent('li').hasClass('active')) return
-
- var $previous = $ul.find('.active:last a')
- var hideEvent = $.Event('hide.bs.tab', {
- relatedTarget: $this[0]
- })
- var showEvent = $.Event('show.bs.tab', {
- relatedTarget: $previous[0]
- })
-
- $previous.trigger(hideEvent)
- $this.trigger(showEvent)
-
- if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return
-
- var $target = $(selector)
-
- this.activate($this.closest('li'), $ul)
- this.activate($target, $target.parent(), function () {
- $previous.trigger({
- type: 'hidden.bs.tab',
- relatedTarget: $this[0]
- })
- $this.trigger({
- type: 'shown.bs.tab',
- relatedTarget: $previous[0]
- })
- })
- }
-
- Tab.prototype.activate = function (element, container, callback) {
- var $active = container.find('> .active')
- var transition = callback
- && $.support.transition
- && ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length)
-
- function next() {
- $active
- .removeClass('active')
- .find('> .dropdown-menu > .active')
- .removeClass('active')
- .end()
- .find('[data-toggle="tab"]')
- .attr('aria-expanded', false)
-
- element
- .addClass('active')
- .find('[data-toggle="tab"]')
- .attr('aria-expanded', true)
-
- if (transition) {
- element[0].offsetWidth // reflow for transition
- element.addClass('in')
- } else {
- element.removeClass('fade')
- }
-
- if (element.parent('.dropdown-menu').length) {
- element
- .closest('li.dropdown')
- .addClass('active')
- .end()
- .find('[data-toggle="tab"]')
- .attr('aria-expanded', true)
- }
-
- callback && callback()
- }
-
- $active.length && transition ?
- $active
- .one('bsTransitionEnd', next)
- .emulateTransitionEnd(Tab.TRANSITION_DURATION) :
- next()
-
- $active.removeClass('in')
- }
-
-
- // TAB PLUGIN DEFINITION
- // =====================
-
- function Plugin(option) {
- return this.each(function () {
- var $this = $(this)
- var data = $this.data('bs.tab')
-
- if (!data) $this.data('bs.tab', (data = new Tab(this)))
- if (typeof option == 'string') data[option]()
- })
- }
-
- var old = $.fn.tab
-
- $.fn.tab = Plugin
- $.fn.tab.Constructor = Tab
-
-
- // TAB NO CONFLICT
- // ===============
-
- $.fn.tab.noConflict = function () {
- $.fn.tab = old
- return this
- }
-
-
- // TAB DATA-API
- // ============
-
- var clickHandler = function (e) {
- e.preventDefault()
- Plugin.call($(this), 'show')
- }
-
- $(document)
- .on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler)
- .on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler)
-
-}(jQuery);
-
-/* ========================================================================
- * Bootstrap: affix.js v3.3.5
- * http://getbootstrap.com/javascript/#affix
- * ========================================================================
- * Copyright 2011-2015 Twitter, Inc.
- * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
- * ======================================================================== */
-
-
-+function ($) {
- 'use strict';
-
- // AFFIX CLASS DEFINITION
- // ======================
-
- var Affix = function (element, options) {
- this.options = $.extend({}, Affix.DEFAULTS, options)
-
- this.$target = $(this.options.target)
- .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))
- .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this))
-
- this.$element = $(element)
- this.affixed = null
- this.unpin = null
- this.pinnedOffset = null
-
- this.checkPosition()
- }
-
- Affix.VERSION = '3.3.5'
-
- Affix.RESET = 'affix affix-top affix-bottom'
-
- Affix.DEFAULTS = {
- offset: 0,
- target: window
- }
-
- Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) {
- var scrollTop = this.$target.scrollTop()
- var position = this.$element.offset()
- var targetHeight = this.$target.height()
-
- if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false
-
- if (this.affixed == 'bottom') {
- if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom'
- return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom'
- }
-
- var initializing = this.affixed == null
- var colliderTop = initializing ? scrollTop : position.top
- var colliderHeight = initializing ? targetHeight : height
-
- if (offsetTop != null && scrollTop <= offsetTop) return 'top'
- if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom'
-
- return false
- }
-
- Affix.prototype.getPinnedOffset = function () {
- if (this.pinnedOffset) return this.pinnedOffset
- this.$element.removeClass(Affix.RESET).addClass('affix')
- var scrollTop = this.$target.scrollTop()
- var position = this.$element.offset()
- return (this.pinnedOffset = position.top - scrollTop)
- }
-
- Affix.prototype.checkPositionWithEventLoop = function () {
- setTimeout($.proxy(this.checkPosition, this), 1)
- }
-
- Affix.prototype.checkPosition = function () {
- if (!this.$element.is(':visible')) return
-
- var height = this.$element.height()
- var offset = this.options.offset
- var offsetTop = offset.top
- var offsetBottom = offset.bottom
- var scrollHeight = Math.max($(document).height(), $(document.body).height())
-
- if (typeof offset != 'object') offsetBottom = offsetTop = offset
- if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element)
- if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element)
-
- var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom)
-
- if (this.affixed != affix) {
- if (this.unpin != null) this.$element.css('top', '')
-
- var affixType = 'affix' + (affix ? '-' + affix : '')
- var e = $.Event(affixType + '.bs.affix')
-
- this.$element.trigger(e)
-
- if (e.isDefaultPrevented()) return
-
- this.affixed = affix
- this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null
-
- this.$element
- .removeClass(Affix.RESET)
- .addClass(affixType)
- .trigger(affixType.replace('affix', 'affixed') + '.bs.affix')
- }
-
- if (affix == 'bottom') {
- this.$element.offset({
- top: scrollHeight - height - offsetBottom
- })
- }
- }
-
-
- // AFFIX PLUGIN DEFINITION
- // =======================
-
- function Plugin(option) {
- return this.each(function () {
- var $this = $(this)
- var data = $this.data('bs.affix')
- var options = typeof option == 'object' && option
-
- if (!data) $this.data('bs.affix', (data = new Affix(this, options)))
- if (typeof option == 'string') data[option]()
- })
- }
-
- var old = $.fn.affix
-
- $.fn.affix = Plugin
- $.fn.affix.Constructor = Affix
-
-
- // AFFIX NO CONFLICT
- // =================
-
- $.fn.affix.noConflict = function () {
- $.fn.affix = old
- return this
- }
-
-
- // AFFIX DATA-API
- // ==============
-
- $(window).on('load', function () {
- $('[data-spy="affix"]').each(function () {
- var $spy = $(this)
- var data = $spy.data()
-
- data.offset = data.offset || {}
-
- if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom
- if (data.offsetTop != null) data.offset.top = data.offsetTop
-
- Plugin.call($spy, data)
- })
- })
-
-}(jQuery);
diff --git a/static/src/js/lib/skrollr.js b/static/src/js/lib/skrollr.js
deleted file mode 100644
index 2ff15f3..0000000
--- a/static/src/js/lib/skrollr.js
+++ /dev/null
@@ -1,1771 +0,0 @@
-/*!
- * skrollr core
- *
- * Alexander Prinzhorn - https://github.com/Prinzhorn/skrollr
- *
- * Free to use under terms of MIT license
- */
-(function(window, document, undefined) {
- 'use strict';
-
- /*
- * Global api.
- */
- var skrollr = {
- get: function() {
- return _instance;
- },
- //Main entry point.
- init: function(options) {
- return _instance || new Skrollr(options);
- },
- VERSION: '0.6.26'
- };
-
- //Minify optimization.
- var hasProp = Object.prototype.hasOwnProperty;
- var Math = window.Math;
- var getStyle = window.getComputedStyle;
-
- //They will be filled when skrollr gets initialized.
- var documentElement;
- var body;
-
- var EVENT_TOUCHSTART = 'touchstart';
- var EVENT_TOUCHMOVE = 'touchmove';
- var EVENT_TOUCHCANCEL = 'touchcancel';
- var EVENT_TOUCHEND = 'touchend';
-
- var SKROLLABLE_CLASS = 'skrollable';
- var SKROLLABLE_BEFORE_CLASS = SKROLLABLE_CLASS + '-before';
- var SKROLLABLE_BETWEEN_CLASS = SKROLLABLE_CLASS + '-between';
- var SKROLLABLE_AFTER_CLASS = SKROLLABLE_CLASS + '-after';
-
- var SKROLLR_CLASS = 'skrollr';
- var NO_SKROLLR_CLASS = 'no-' + SKROLLR_CLASS;
- var SKROLLR_DESKTOP_CLASS = SKROLLR_CLASS + '-desktop';
- var SKROLLR_MOBILE_CLASS = SKROLLR_CLASS + '-mobile';
-
- var DEFAULT_EASING = 'linear';
- var DEFAULT_DURATION = 1000;//ms
- var DEFAULT_MOBILE_DECELERATION = 0.004;//pixel/ms²
-
- var DEFAULT_SMOOTH_SCROLLING_DURATION = 200;//ms
-
- var ANCHOR_START = 'start';
- var ANCHOR_END = 'end';
- var ANCHOR_CENTER = 'center';
- var ANCHOR_BOTTOM = 'bottom';
-
- //The property which will be added to the DOM element to hold the ID of the skrollable.
- var SKROLLABLE_ID_DOM_PROPERTY = '___skrollable_id';
-
- var rxTouchIgnoreTags = /^(?:input|textarea|button|select)$/i;
-
- var rxTrim = /^\s+|\s+$/g;
-
- //Find all data-attributes. data-[_constant]-[offset]-[anchor]-[anchor].
- var rxKeyframeAttribute = /^data(?:-(_\w+))?(?:-?(-?\d*\.?\d+p?))?(?:-?(start|end|top|center|bottom))?(?:-?(top|center|bottom))?$/;
-
- var rxPropValue = /\s*(@?[\w\-\[\]]+)\s*:\s*(.+?)\s*(?:;|$)/gi;
-
- //Easing function names follow the property in square brackets.
- var rxPropEasing = /^(@?[a-z\-]+)\[(\w+)\]$/;
-
- var rxCamelCase = /-([a-z0-9_])/g;
- var rxCamelCaseFn = function(str, letter) {
- return letter.toUpperCase();
- };
-
- //Numeric values with optional sign.
- var rxNumericValue = /[\-+]?[\d]*\.?[\d]+/g;
-
- //Used to replace occurences of {?} with a number.
- var rxInterpolateString = /\{\?\}/g;
-
- //Finds rgb(a) colors, which don't use the percentage notation.
- var rxRGBAIntegerColor = /rgba?\(\s*-?\d+\s*,\s*-?\d+\s*,\s*-?\d+/g;
-
- //Finds all gradients.
- var rxGradient = /[a-z\-]+-gradient/g;
-
- //Vendor prefix. Will be set once skrollr gets initialized.
- var theCSSPrefix = '';
- var theDashedCSSPrefix = '';
-
- //Will be called once (when skrollr gets initialized).
- var detectCSSPrefix = function() {
- //Only relevant prefixes. May be extended.
- //Could be dangerous if there will ever be a CSS property which actually starts with "ms". Don't hope so.
- var rxPrefixes = /^(?:O|Moz|webkit|ms)|(?:-(?:o|moz|webkit|ms)-)/;
-
- //Detect prefix for current browser by finding the first property using a prefix.
- if(!getStyle) {
- return;
- }
-
- var style = getStyle(body, null);
-
- for(var k in style) {
- //We check the key and if the key is a number, we check the value as well, because safari's getComputedStyle returns some weird array-like thingy.
- theCSSPrefix = (k.match(rxPrefixes) || (+k == k && style[k].match(rxPrefixes)));
-
- if(theCSSPrefix) {
- break;
- }
- }
-
- //Did we even detect a prefix?
- if(!theCSSPrefix) {
- theCSSPrefix = theDashedCSSPrefix = '';
-
- return;
- }
-
- theCSSPrefix = theCSSPrefix[0];
-
- //We could have detected either a dashed prefix or this camelCaseish-inconsistent stuff.
- if(theCSSPrefix.slice(0,1) === '-') {
- theDashedCSSPrefix = theCSSPrefix;
-
- //There's no logic behind these. Need a look up.
- theCSSPrefix = ({
- '-webkit-': 'webkit',
- '-moz-': 'Moz',
- '-ms-': 'ms',
- '-o-': 'O'
- })[theCSSPrefix];
- } else {
- theDashedCSSPrefix = '-' + theCSSPrefix.toLowerCase() + '-';
- }
- };
-
- var polyfillRAF = function() {
- var requestAnimFrame = window.requestAnimationFrame || window[theCSSPrefix.toLowerCase() + 'RequestAnimationFrame'];
-
- var lastTime = _now();
-
- if(_isMobile || !requestAnimFrame) {
- requestAnimFrame = function(callback) {
- //How long did it take to render?
- var deltaTime = _now() - lastTime;
- var delay = Math.max(0, 1000 / 60 - deltaTime);
-
- return window.setTimeout(function() {
- lastTime = _now();
- callback();
- }, delay);
- };
- }
-
- return requestAnimFrame;
- };
-
- var polyfillCAF = function() {
- var cancelAnimFrame = window.cancelAnimationFrame || window[theCSSPrefix.toLowerCase() + 'CancelAnimationFrame'];
-
- if(_isMobile || !cancelAnimFrame) {
- cancelAnimFrame = function(timeout) {
- return window.clearTimeout(timeout);
- };
- }
-
- return cancelAnimFrame;
- };
-
- //Built-in easing functions.
- var easings = {
- begin: function() {
- return 0;
- },
- end: function() {
- return 1;
- },
- linear: function(p) {
- return p;
- },
- quadratic: function(p) {
- return p * p;
- },
- cubic: function(p) {
- return p * p * p;
- },
- swing: function(p) {
- return (-Math.cos(p * Math.PI) / 2) + 0.5;
- },
- sqrt: function(p) {
- return Math.sqrt(p);
- },
- outCubic: function(p) {
- return (Math.pow((p - 1), 3) + 1);
- },
- //see https://www.desmos.com/calculator/tbr20s8vd2 for how I did this
- bounce: function(p) {
- var a;
-
- if(p <= 0.5083) {
- a = 3;
- } else if(p <= 0.8489) {
- a = 9;
- } else if(p <= 0.96208) {
- a = 27;
- } else if(p <= 0.99981) {
- a = 91;
- } else {
- return 1;
- }
-
- return 1 - Math.abs(3 * Math.cos(p * a * 1.028) / a);
- }
- };
-
- /**
- * Constructor.
- */
- function Skrollr(options) {
- documentElement = document.documentElement;
- body = document.body;
-
- detectCSSPrefix();
-
- _instance = this;
-
- options = options || {};
-
- _constants = options.constants || {};
-
- //We allow defining custom easings or overwrite existing.
- if(options.easing) {
- for(var e in options.easing) {
- easings[e] = options.easing[e];
- }
- }
-
- _edgeStrategy = options.edgeStrategy || 'set';
-
- _listeners = {
- //Function to be called right before rendering.
- beforerender: options.beforerender,
-
- //Function to be called right after finishing rendering.
- render: options.render,
-
- //Function to be called whenever an element with the `data-emit-events` attribute passes a keyframe.
- keyframe: options.keyframe
- };
-
- //forceHeight is true by default
- _forceHeight = options.forceHeight !== false;
-
- if(_forceHeight) {
- _scale = options.scale || 1;
- }
-
- _mobileDeceleration = options.mobileDeceleration || DEFAULT_MOBILE_DECELERATION;
-
- _smoothScrollingEnabled = options.smoothScrolling !== false;
- _smoothScrollingDuration = options.smoothScrollingDuration || DEFAULT_SMOOTH_SCROLLING_DURATION;
-
- //Dummy object. Will be overwritten in the _render method when smooth scrolling is calculated.
- _smoothScrolling = {
- targetTop: _instance.getScrollTop()
- };
-
- //A custom check function may be passed.
- _isMobile = ((options.mobileCheck || function() {
- return (/Android|iPhone|iPad|iPod|BlackBerry/i).test(navigator.userAgent || navigator.vendor || window.opera);
- })());
-
- if(_isMobile) {
- _skrollrBody = document.getElementById('skrollr-body');
-
- //Detect 3d transform if there's a skrollr-body (only needed for #skrollr-body).
- if(_skrollrBody) {
- _detect3DTransforms();
- }
-
- _initMobile();
- _updateClass(documentElement, [SKROLLR_CLASS, SKROLLR_MOBILE_CLASS], [NO_SKROLLR_CLASS]);
- } else {
- _updateClass(documentElement, [SKROLLR_CLASS, SKROLLR_DESKTOP_CLASS], [NO_SKROLLR_CLASS]);
- }
-
- //Triggers parsing of elements and a first reflow.
- _instance.refresh();
-
- _addEvent(window, 'resize orientationchange', function() {
- var width = documentElement.clientWidth;
- var height = documentElement.clientHeight;
-
- //Only reflow if the size actually changed (#271).
- if(height !== _lastViewportHeight || width !== _lastViewportWidth) {
- _lastViewportHeight = height;
- _lastViewportWidth = width;
-
- _requestReflow = true;
- }
- });
-
- var requestAnimFrame = polyfillRAF();
-
- //Let's go.
- (function animloop(){
- _render();
- _animFrame = requestAnimFrame(animloop);
- }());
-
- return _instance;
- }
-
- /**
- * (Re)parses some or all elements.
- */
- Skrollr.prototype.refresh = function(elements) {
- var elementIndex;
- var elementsLength;
- var ignoreID = false;
-
- //Completely reparse anything without argument.
- if(elements === undefined) {
- //Ignore that some elements may already have a skrollable ID.
- ignoreID = true;
-
- _skrollables = [];
- _skrollableIdCounter = 0;
-
- elements = document.getElementsByTagName('*');
- } else if(elements.length === undefined) {
- //We also accept a single element as parameter.
- elements = [elements];
- }
-
- elementIndex = 0;
- elementsLength = elements.length;
-
- for(; elementIndex < elementsLength; elementIndex++) {
- var el = elements[elementIndex];
- var anchorTarget = el;
- var keyFrames = [];
-
- //If this particular element should be smooth scrolled.
- var smoothScrollThis = _smoothScrollingEnabled;
-
- //The edge strategy for this particular element.
- var edgeStrategy = _edgeStrategy;
-
- //If this particular element should emit keyframe events.
- var emitEvents = false;
-
- //If we're reseting the counter, remove any old element ids that may be hanging around.
- if(ignoreID && SKROLLABLE_ID_DOM_PROPERTY in el) {
- delete el[SKROLLABLE_ID_DOM_PROPERTY];
- }
-
- if(!el.attributes) {
- continue;
- }
-
- //Iterate over all attributes and search for key frame attributes.
- var attributeIndex = 0;
- var attributesLength = el.attributes.length;
-
- for (; attributeIndex < attributesLength; attributeIndex++) {
- var attr = el.attributes[attributeIndex];
-
- if(attr.name === 'data-anchor-target') {
- anchorTarget = document.querySelector(attr.value);
-
- if(anchorTarget === null) {
- throw 'Unable to find anchor target "' + attr.value + '"';
- }
-
- continue;
- }
-
- //Global smooth scrolling can be overridden by the element attribute.
- if(attr.name === 'data-smooth-scrolling') {
- smoothScrollThis = attr.value !== 'off';
-
- continue;
- }
-
- //Global edge strategy can be overridden by the element attribute.
- if(attr.name === 'data-edge-strategy') {
- edgeStrategy = attr.value;
-
- continue;
- }
-
- //Is this element tagged with the `data-emit-events` attribute?
- if(attr.name === 'data-emit-events') {
- emitEvents = true;
-
- continue;
- }
-
- var match = attr.name.match(rxKeyframeAttribute);
-
- if(match === null) {
- continue;
- }
-
- var kf = {
- props: attr.value,
- //Point back to the element as well.
- element: el,
- //The name of the event which this keyframe will fire, if emitEvents is
- eventType: attr.name.replace(rxCamelCase, rxCamelCaseFn)
- };
-
- keyFrames.push(kf);
-
- var constant = match[1];
-
- if(constant) {
- //Strip the underscore prefix.
- kf.constant = constant.substr(1);
- }
-
- //Get the key frame offset.
- var offset = match[2];
-
- //Is it a percentage offset?
- if(/p$/.test(offset)) {
- kf.isPercentage = true;
- kf.offset = (offset.slice(0, -1) | 0) / 100;
- } else {
- kf.offset = (offset | 0);
- }
-
- var anchor1 = match[3];
-
- //If second anchor is not set, the first will be taken for both.
- var anchor2 = match[4] || anchor1;
-
- //"absolute" (or "classic") mode, where numbers mean absolute scroll offset.
- if(!anchor1 || anchor1 === ANCHOR_START || anchor1 === ANCHOR_END) {
- kf.mode = 'absolute';
-
- //data-end needs to be calculated after all key frames are known.
- if(anchor1 === ANCHOR_END) {
- kf.isEnd = true;
- } else if(!kf.isPercentage) {
- //For data-start we can already set the key frame w/o calculations.
- //#59: "scale" options should only affect absolute mode.
- kf.offset = kf.offset * _scale;
- }
- }
- //"relative" mode, where numbers are relative to anchors.
- else {
- kf.mode = 'relative';
- kf.anchors = [anchor1, anchor2];
- }
- }
-
- //Does this element have key frames?
- if(!keyFrames.length) {
- continue;
- }
-
- //Will hold the original style and class attributes before we controlled the element (see #80).
- var styleAttr, classAttr;
-
- var id;
-
- if(!ignoreID && SKROLLABLE_ID_DOM_PROPERTY in el) {
- //We already have this element under control. Grab the corresponding skrollable id.
- id = el[SKROLLABLE_ID_DOM_PROPERTY];
- styleAttr = _skrollables[id].styleAttr;
- classAttr = _skrollables[id].classAttr;
- } else {
- //It's an unknown element. Asign it a new skrollable id.
- id = (el[SKROLLABLE_ID_DOM_PROPERTY] = _skrollableIdCounter++);
- styleAttr = el.style.cssText;
- classAttr = _getClass(el);
- }
-
- _skrollables[id] = {
- element: el,
- styleAttr: styleAttr,
- classAttr: classAttr,
- anchorTarget: anchorTarget,
- keyFrames: keyFrames,
- smoothScrolling: smoothScrollThis,
- edgeStrategy: edgeStrategy,
- emitEvents: emitEvents,
- lastFrameIndex: -1
- };
-
- _updateClass(el, [SKROLLABLE_CLASS], []);
- }
-
- //Reflow for the first time.
- _reflow();
-
- //Now that we got all key frame numbers right, actually parse the properties.
- elementIndex = 0;
- elementsLength = elements.length;
-
- for(; elementIndex < elementsLength; elementIndex++) {
- var sk = _skrollables[elements[elementIndex][SKROLLABLE_ID_DOM_PROPERTY]];
-
- if(sk === undefined) {
- continue;
- }
-
- //Parse the property string to objects
- _parseProps(sk);
-
- //Fill key frames with missing properties from left and right
- _fillProps(sk);
- }
-
- return _instance;
- };
-
- /**
- * Transform "relative" mode to "absolute" mode.
- * That is, calculate anchor position and offset of element.
- */
- Skrollr.prototype.relativeToAbsolute = function(element, viewportAnchor, elementAnchor) {
- var viewportHeight = documentElement.clientHeight;
- var box = element.getBoundingClientRect();
- var absolute = box.top;
-
- //#100: IE doesn't supply "height" with getBoundingClientRect.
- var boxHeight = box.bottom - box.top;
-
- if(viewportAnchor === ANCHOR_BOTTOM) {
- absolute -= viewportHeight;
- } else if(viewportAnchor === ANCHOR_CENTER) {
- absolute -= viewportHeight / 2;
- }
-
- if(elementAnchor === ANCHOR_BOTTOM) {
- absolute += boxHeight;
- } else if(elementAnchor === ANCHOR_CENTER) {
- absolute += boxHeight / 2;
- }
-
- //Compensate scrolling since getBoundingClientRect is relative to viewport.
- absolute += _instance.getScrollTop();
-
- return (absolute + 0.5) | 0;
- };
-
- /**
- * Animates scroll top to new position.
- */
- Skrollr.prototype.animateTo = function(top, options) {
- options = options || {};
-
- var now = _now();
- var scrollTop = _instance.getScrollTop();
-
- //Setting this to a new value will automatically cause the current animation to stop, if any.
- _scrollAnimation = {
- startTop: scrollTop,
- topDiff: top - scrollTop,
- targetTop: top,
- duration: options.duration || DEFAULT_DURATION,
- startTime: now,
- endTime: now + (options.duration || DEFAULT_DURATION),
- easing: easings[options.easing || DEFAULT_EASING],
- done: options.done
- };
-
- //Don't queue the animation if there's nothing to animate.
- if(!_scrollAnimation.topDiff) {
- if(_scrollAnimation.done) {
- _scrollAnimation.done.call(_instance, false);
- }
-
- _scrollAnimation = undefined;
- }
-
- return _instance;
- };
-
- /**
- * Stops animateTo animation.
- */
- Skrollr.prototype.stopAnimateTo = function() {
- if(_scrollAnimation && _scrollAnimation.done) {
- _scrollAnimation.done.call(_instance, true);
- }
-
- _scrollAnimation = undefined;
- };
-
- /**
- * Returns if an animation caused by animateTo is currently running.
- */
- Skrollr.prototype.isAnimatingTo = function() {
- return !!_scrollAnimation;
- };
-
- Skrollr.prototype.isMobile = function() {
- return _isMobile;
- };
-
- Skrollr.prototype.setScrollTop = function(top, force) {
- _forceRender = (force === true);
-
- if(_isMobile) {
- _mobileOffset = Math.min(Math.max(top, 0), _maxKeyFrame);
- } else {
- window.scrollTo(0, top);
- }
-
- return _instance;
- };
-
- Skrollr.prototype.getScrollTop = function() {
- if(_isMobile) {
- return _mobileOffset;
- } else {
- return window.pageYOffset || documentElement.scrollTop || body.scrollTop || 0;
- }
- };
-
- Skrollr.prototype.getMaxScrollTop = function() {
- return _maxKeyFrame;
- };
-
- Skrollr.prototype.on = function(name, fn) {
- _listeners[name] = fn;
-
- return _instance;
- };
-
- Skrollr.prototype.off = function(name) {
- delete _listeners[name];
-
- return _instance;
- };
-
- Skrollr.prototype.destroy = function() {
- var cancelAnimFrame = polyfillCAF();
- cancelAnimFrame(_animFrame);
- _removeAllEvents();
-
- _updateClass(documentElement, [NO_SKROLLR_CLASS], [SKROLLR_CLASS, SKROLLR_DESKTOP_CLASS, SKROLLR_MOBILE_CLASS]);
-
- var skrollableIndex = 0;
- var skrollablesLength = _skrollables.length;
-
- for(; skrollableIndex < skrollablesLength; skrollableIndex++) {
- _reset(_skrollables[skrollableIndex].element);
- }
-
- documentElement.style.overflow = body.style.overflow = '';
- documentElement.style.height = body.style.height = '';
-
- if(_skrollrBody) {
- skrollr.setStyle(_skrollrBody, 'transform', 'none');
- }
-
- _instance = undefined;
- _skrollrBody = undefined;
- _listeners = undefined;
- _forceHeight = undefined;
- _maxKeyFrame = 0;
- _scale = 1;
- _constants = undefined;
- _mobileDeceleration = undefined;
- _direction = 'down';
- _lastTop = -1;
- _lastViewportWidth = 0;
- _lastViewportHeight = 0;
- _requestReflow = false;
- _scrollAnimation = undefined;
- _smoothScrollingEnabled = undefined;
- _smoothScrollingDuration = undefined;
- _smoothScrolling = undefined;
- _forceRender = undefined;
- _skrollableIdCounter = 0;
- _edgeStrategy = undefined;
- _isMobile = false;
- _mobileOffset = 0;
- _translateZ = undefined;
- };
-
- /*
- Private methods.
- */
-
- var _initMobile = function() {
- var initialElement;
- var initialTouchY;
- var initialTouchX;
- var currentElement;
- var currentTouchY;
- var currentTouchX;
- var lastTouchY;
- var deltaY;
-
- var initialTouchTime;
- var currentTouchTime;
- var lastTouchTime;
- var deltaTime;
-
- _addEvent(documentElement, [EVENT_TOUCHSTART, EVENT_TOUCHMOVE, EVENT_TOUCHCANCEL, EVENT_TOUCHEND].join(' '), function(e) {
- var touch = e.changedTouches[0];
-
- currentElement = e.target;
-
- //We don't want text nodes.
- while(currentElement.nodeType === 3) {
- currentElement = currentElement.parentNode;
- }
-
- currentTouchY = touch.clientY;
- currentTouchX = touch.clientX;
- currentTouchTime = e.timeStamp;
-
- if(!rxTouchIgnoreTags.test(currentElement.tagName)) {
- e.preventDefault();
- }
-
- switch(e.type) {
- case EVENT_TOUCHSTART:
- //The last element we tapped on.
- if(initialElement) {
- initialElement.blur();
- }
-
- _instance.stopAnimateTo();
-
- initialElement = currentElement;
-
- initialTouchY = lastTouchY = currentTouchY;
- initialTouchX = currentTouchX;
- initialTouchTime = currentTouchTime;
-
- break;
- case EVENT_TOUCHMOVE:
- //Prevent default event on touchIgnore elements in case they don't have focus yet.
- if(rxTouchIgnoreTags.test(currentElement.tagName) && document.activeElement !== currentElement) {
- e.preventDefault();
- }
-
- deltaY = currentTouchY - lastTouchY;
- deltaTime = currentTouchTime - lastTouchTime;
-
- _instance.setScrollTop(_mobileOffset - deltaY, true);
-
- lastTouchY = currentTouchY;
- lastTouchTime = currentTouchTime;
- break;
- default:
- case EVENT_TOUCHCANCEL:
- case EVENT_TOUCHEND:
- var distanceY = initialTouchY - currentTouchY;
- var distanceX = initialTouchX - currentTouchX;
- var distance2 = distanceX * distanceX + distanceY * distanceY;
-
- //Check if it was more like a tap (moved less than 7px).
- if(distance2 < 49) {
- if(!rxTouchIgnoreTags.test(initialElement.tagName)) {
- initialElement.focus();
-
- //It was a tap, click the element.
- var clickEvent = document.createEvent('MouseEvents');
- clickEvent.initMouseEvent('click', true, true, e.view, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, 0, null);
- initialElement.dispatchEvent(clickEvent);
- }
-
- return;
- }
-
- initialElement = undefined;
-
- var speed = deltaY / deltaTime;
-
- //Cap speed at 3 pixel/ms.
- speed = Math.max(Math.min(speed, 3), -3);
-
- var duration = Math.abs(speed / _mobileDeceleration);
- var targetOffset = speed * duration + 0.5 * _mobileDeceleration * duration * duration;
- var targetTop = _instance.getScrollTop() - targetOffset;
-
- //Relative duration change for when scrolling above bounds.
- var targetRatio = 0;
-
- //Change duration proportionally when scrolling would leave bounds.
- if(targetTop > _maxKeyFrame) {
- targetRatio = (_maxKeyFrame - targetTop) / targetOffset;
-
- targetTop = _maxKeyFrame;
- } else if(targetTop < 0) {
- targetRatio = -targetTop / targetOffset;
-
- targetTop = 0;
- }
-
- duration = duration * (1 - targetRatio);
-
- _instance.animateTo((targetTop + 0.5) | 0, {easing: 'outCubic', duration: duration});
- break;
- }
- });
-
- //Just in case there has already been some native scrolling, reset it.
- window.scrollTo(0, 0);
- documentElement.style.overflow = body.style.overflow = 'hidden';
- };
-
- /**
- * Updates key frames which depend on others / need to be updated on resize.
- * That is "end" in "absolute" mode and all key frames in "relative" mode.
- * Also handles constants, because they may change on resize.
- */
- var _updateDependentKeyFrames = function() {
- var viewportHeight = documentElement.clientHeight;
- var processedConstants = _processConstants();
- var skrollable;
- var element;
- var anchorTarget;
- var keyFrames;
- var keyFrameIndex;
- var keyFramesLength;
- var kf;
- var skrollableIndex;
- var skrollablesLength;
- var offset;
- var constantValue;
-
- //First process all relative-mode elements and find the max key frame.
- skrollableIndex = 0;
- skrollablesLength = _skrollables.length;
-
- for(; skrollableIndex < skrollablesLength; skrollableIndex++) {
- skrollable = _skrollables[skrollableIndex];
- element = skrollable.element;
- anchorTarget = skrollable.anchorTarget;
- keyFrames = skrollable.keyFrames;
-
- keyFrameIndex = 0;
- keyFramesLength = keyFrames.length;
-
- for(; keyFrameIndex < keyFramesLength; keyFrameIndex++) {
- kf = keyFrames[keyFrameIndex];
-
- offset = kf.offset;
- constantValue = processedConstants[kf.constant] || 0;
-
- kf.frame = offset;
-
- if(kf.isPercentage) {
- //Convert the offset to percentage of the viewport height.
- offset = offset * viewportHeight;
-
- //Absolute + percentage mode.
- kf.frame = offset;
- }
-
- if(kf.mode === 'relative') {
- _reset(element);
-
- kf.frame = _instance.relativeToAbsolute(anchorTarget, kf.anchors[0], kf.anchors[1]) - offset;
-
- _reset(element, true);
- }
-
- kf.frame += constantValue;
-
- //Only search for max key frame when forceHeight is enabled.
- if(_forceHeight) {
- //Find the max key frame, but don't use one of the data-end ones for comparison.
- if(!kf.isEnd && kf.frame > _maxKeyFrame) {
- _maxKeyFrame = kf.frame;
- }
- }
- }
- }
-
- //#133: The document can be larger than the maxKeyFrame we found.
- _maxKeyFrame = Math.max(_maxKeyFrame, _getDocumentHeight());
-
- //Now process all data-end keyframes.
- skrollableIndex = 0;
- skrollablesLength = _skrollables.length;
-
- for(; skrollableIndex < skrollablesLength; skrollableIndex++) {
- skrollable = _skrollables[skrollableIndex];
- keyFrames = skrollable.keyFrames;
-
- keyFrameIndex = 0;
- keyFramesLength = keyFrames.length;
-
- for(; keyFrameIndex < keyFramesLength; keyFrameIndex++) {
- kf = keyFrames[keyFrameIndex];
-
- constantValue = processedConstants[kf.constant] || 0;
-
- if(kf.isEnd) {
- kf.frame = _maxKeyFrame - kf.offset + constantValue;
- }
- }
-
- skrollable.keyFrames.sort(_keyFrameComparator);
- }
- };
-
- /**
- * Calculates and sets the style properties for the element at the given frame.
- * @param fakeFrame The frame to render at when smooth scrolling is enabled.
- * @param actualFrame The actual frame we are at.
- */
- var _calcSteps = function(fakeFrame, actualFrame) {
- //Iterate over all skrollables.
- var skrollableIndex = 0;
- var skrollablesLength = _skrollables.length;
-
- for(; skrollableIndex < skrollablesLength; skrollableIndex++) {
- var skrollable = _skrollables[skrollableIndex];
- var element = skrollable.element;
- var frame = skrollable.smoothScrolling ? fakeFrame : actualFrame;
- var frames = skrollable.keyFrames;
- var framesLength = frames.length;
- var firstFrame = frames[0];
- var lastFrame = frames[frames.length - 1];
- var beforeFirst = frame < firstFrame.frame;
- var afterLast = frame > lastFrame.frame;
- var firstOrLastFrame = beforeFirst ? firstFrame : lastFrame;
- var emitEvents = skrollable.emitEvents;
- var lastFrameIndex = skrollable.lastFrameIndex;
- var key;
- var value;
-
- //If we are before/after the first/last frame, set the styles according to the given edge strategy.
- if(beforeFirst || afterLast) {
- //Check if we already handled this edge case last time.
- //Note: using setScrollTop it's possible that we jumped from one edge to the other.
- if(beforeFirst && skrollable.edge === -1 || afterLast && skrollable.edge === 1) {
- continue;
- }
-
- //Add the skrollr-before or -after class.
- if(beforeFirst) {
- _updateClass(element, [SKROLLABLE_BEFORE_CLASS], [SKROLLABLE_AFTER_CLASS, SKROLLABLE_BETWEEN_CLASS]);
-
- //This handles the special case where we exit the first keyframe.
- if(emitEvents && lastFrameIndex > -1) {
- _emitEvent(element, firstFrame.eventType, _direction);
- skrollable.lastFrameIndex = -1;
- }
- } else {
- _updateClass(element, [SKROLLABLE_AFTER_CLASS], [SKROLLABLE_BEFORE_CLASS, SKROLLABLE_BETWEEN_CLASS]);
-
- //This handles the special case where we exit the last keyframe.
- if(emitEvents && lastFrameIndex < framesLength) {
- _emitEvent(element, lastFrame.eventType, _direction);
- skrollable.lastFrameIndex = framesLength;
- }
- }
-
- //Remember that we handled the edge case (before/after the first/last keyframe).
- skrollable.edge = beforeFirst ? -1 : 1;
-
- switch(skrollable.edgeStrategy) {
- case 'reset':
- _reset(element);
- continue;
- case 'ease':
- //Handle this case like it would be exactly at first/last keyframe and just pass it on.
- frame = firstOrLastFrame.frame;
- break;
- default:
- case 'set':
- var props = firstOrLastFrame.props;
-
- for(key in props) {
- if(hasProp.call(props, key)) {
- value = _interpolateString(props[key].value);
-
- //Set style or attribute.
- if(key.indexOf('@') === 0) {
- element.setAttribute(key.substr(1), value);
- } else {
- skrollr.setStyle(element, key, value);
- }
- }
- }
-
- continue;
- }
- } else {
- //Did we handle an edge last time?
- if(skrollable.edge !== 0) {
- _updateClass(element, [SKROLLABLE_CLASS, SKROLLABLE_BETWEEN_CLASS], [SKROLLABLE_BEFORE_CLASS, SKROLLABLE_AFTER_CLASS]);
- skrollable.edge = 0;
- }
- }
-
- //Find out between which two key frames we are right now.
- var keyFrameIndex = 0;
-
- for(; keyFrameIndex < framesLength - 1; keyFrameIndex++) {
- if(frame >= frames[keyFrameIndex].frame && frame <= frames[keyFrameIndex + 1].frame) {
- var left = frames[keyFrameIndex];
- var right = frames[keyFrameIndex + 1];
-
- for(key in left.props) {
- if(hasProp.call(left.props, key)) {
- var progress = (frame - left.frame) / (right.frame - left.frame);
-
- //Transform the current progress using the given easing function.
- progress = left.props[key].easing(progress);
-
- //Interpolate between the two values
- value = _calcInterpolation(left.props[key].value, right.props[key].value, progress);
-
- value = _interpolateString(value);
-
- //Set style or attribute.
- if(key.indexOf('@') === 0) {
- element.setAttribute(key.substr(1), value);
- } else {
- skrollr.setStyle(element, key, value);
- }
- }
- }
-
- //Are events enabled on this element?
- //This code handles the usual cases of scrolling through different keyframes.
- //The special cases of before first and after last keyframe are handled above.
- if(emitEvents) {
- //Did we pass a new keyframe?
- if(lastFrameIndex !== keyFrameIndex) {
- if(_direction === 'down') {
- _emitEvent(element, left.eventType, _direction);
- } else {
- _emitEvent(element, right.eventType, _direction);
- }
-
- skrollable.lastFrameIndex = keyFrameIndex;
- }
- }
-
- break;
- }
- }
- }
- };
-
- /**
- * Renders all elements.
- */
- var _render = function() {
- if(_requestReflow) {
- _requestReflow = false;
- _reflow();
- }
-
- //We may render something else than the actual scrollbar position.
- var renderTop = _instance.getScrollTop();
-
- //If there's an animation, which ends in current render call, call the callback after rendering.
- var afterAnimationCallback;
- var now = _now();
- var progress;
-
- //Before actually rendering handle the scroll animation, if any.
- if(_scrollAnimation) {
- //It's over
- if(now >= _scrollAnimation.endTime) {
- renderTop = _scrollAnimation.targetTop;
- afterAnimationCallback = _scrollAnimation.done;
- _scrollAnimation = undefined;
- } else {
- //Map the current progress to the new progress using given easing function.
- progress = _scrollAnimation.easing((now - _scrollAnimation.startTime) / _scrollAnimation.duration);
-
- renderTop = (_scrollAnimation.startTop + progress * _scrollAnimation.topDiff) | 0;
- }
-
- _instance.setScrollTop(renderTop, true);
- }
- //Smooth scrolling only if there's no animation running and if we're not forcing the rendering.
- else if(!_forceRender) {
- var smoothScrollingDiff = _smoothScrolling.targetTop - renderTop;
-
- //The user scrolled, start new smooth scrolling.
- if(smoothScrollingDiff) {
- _smoothScrolling = {
- startTop: _lastTop,
- topDiff: renderTop - _lastTop,
- targetTop: renderTop,
- startTime: _lastRenderCall,
- endTime: _lastRenderCall + _smoothScrollingDuration
- };
- }
-
- //Interpolate the internal scroll position (not the actual scrollbar).
- if(now <= _smoothScrolling.endTime) {
- //Map the current progress to the new progress using easing function.
- progress = easings.sqrt((now - _smoothScrolling.startTime) / _smoothScrollingDuration);
-
- renderTop = (_smoothScrolling.startTop + progress * _smoothScrolling.topDiff) | 0;
- }
- }
-
- //That's were we actually "scroll" on mobile.
- if(_isMobile && _skrollrBody) {
- //Set the transform ("scroll it").
- skrollr.setStyle(_skrollrBody, 'transform', 'translate(0, ' + -(_mobileOffset) + 'px) ' + _translateZ);
- }
-
- //Did the scroll position even change?
- if(_forceRender || _lastTop !== renderTop) {
- //Remember in which direction are we scrolling?
- _direction = (renderTop > _lastTop) ? 'down' : (renderTop < _lastTop ? 'up' : _direction);
-
- _forceRender = false;
-
- var listenerParams = {
- curTop: renderTop,
- lastTop: _lastTop,
- maxTop: _maxKeyFrame,
- direction: _direction
- };
-
- //Tell the listener we are about to render.
- var continueRendering = _listeners.beforerender && _listeners.beforerender.call(_instance, listenerParams);
-
- //The beforerender listener function is able the cancel rendering.
- if(continueRendering !== false) {
- //Now actually interpolate all the styles.
- _calcSteps(renderTop, _instance.getScrollTop());
-
- //Remember when we last rendered.
- _lastTop = renderTop;
-
- if(_listeners.render) {
- _listeners.render.call(_instance, listenerParams);
- }
- }
-
- if(afterAnimationCallback) {
- afterAnimationCallback.call(_instance, false);
- }
- }
-
- _lastRenderCall = now;
- };
-
- /**
- * Parses the properties for each key frame of the given skrollable.
- */
- var _parseProps = function(skrollable) {
- //Iterate over all key frames
- var keyFrameIndex = 0;
- var keyFramesLength = skrollable.keyFrames.length;
-
- for(; keyFrameIndex < keyFramesLength; keyFrameIndex++) {
- var frame = skrollable.keyFrames[keyFrameIndex];
- var easing;
- var value;
- var prop;
- var props = {};
-
- var match;
-
- while((match = rxPropValue.exec(frame.props)) !== null) {
- prop = match[1];
- value = match[2];
-
- easing = prop.match(rxPropEasing);
-
- //Is there an easing specified for this prop?
- if(easing !== null) {
- prop = easing[1];
- easing = easing[2];
- } else {
- easing = DEFAULT_EASING;
- }
-
- //Exclamation point at first position forces the value to be taken literal.
- value = value.indexOf('!') ? _parseProp(value) : [value.slice(1)];
-
- //Save the prop for this key frame with his value and easing function
- props[prop] = {
- value: value,
- easing: easings[easing]
- };
- }
-
- frame.props = props;
- }
- };
-
- /**
- * Parses a value extracting numeric values and generating a format string
- * for later interpolation of the new values in old string.
- *
- * @param val The CSS value to be parsed.
- * @return Something like ["rgba(?%,?%, ?%,?)", 100, 50, 0, .7]
- * where the first element is the format string later used
- * and all following elements are the numeric value.
- */
- var _parseProp = function(val) {
- var numbers = [];
-
- //One special case, where floats don't work.
- //We replace all occurences of rgba colors
- //which don't use percentage notation with the percentage notation.
- rxRGBAIntegerColor.lastIndex = 0;
- val = val.replace(rxRGBAIntegerColor, function(rgba) {
- return rgba.replace(rxNumericValue, function(n) {
- return n / 255 * 100 + '%';
- });
- });
-
- //Handle prefixing of "gradient" values.
- //For now only the prefixed value will be set. Unprefixed isn't supported anyway.
- if(theDashedCSSPrefix) {
- rxGradient.lastIndex = 0;
- val = val.replace(rxGradient, function(s) {
- return theDashedCSSPrefix + s;
- });
- }
-
- //Now parse ANY number inside this string and create a format string.
- val = val.replace(rxNumericValue, function(n) {
- numbers.push(+n);
- return '{?}';
- });
-
- //Add the formatstring as first value.
- numbers.unshift(val);
-
- return numbers;
- };
-
- /**
- * Fills the key frames with missing left and right hand properties.
- * If key frame 1 has property X and key frame 2 is missing X,
- * but key frame 3 has X again, then we need to assign X to key frame 2 too.
- *
- * @param sk A skrollable.
- */
- var _fillProps = function(sk) {
- //Will collect the properties key frame by key frame
- var propList = {};
- var keyFrameIndex;
- var keyFramesLength;
-
- //Iterate over all key frames from left to right
- keyFrameIndex = 0;
- keyFramesLength = sk.keyFrames.length;
-
- for(; keyFrameIndex < keyFramesLength; keyFrameIndex++) {
- _fillPropForFrame(sk.keyFrames[keyFrameIndex], propList);
- }
-
- //Now do the same from right to fill the last gaps
-
- propList = {};
-
- //Iterate over all key frames from right to left
- keyFrameIndex = sk.keyFrames.length - 1;
-
- for(; keyFrameIndex >= 0; keyFrameIndex--) {
- _fillPropForFrame(sk.keyFrames[keyFrameIndex], propList);
- }
- };
-
- var _fillPropForFrame = function(frame, propList) {
- var key;
-
- //For each key frame iterate over all right hand properties and assign them,
- //but only if the current key frame doesn't have the property by itself
- for(key in propList) {
- //The current frame misses this property, so assign it.
- if(!hasProp.call(frame.props, key)) {
- frame.props[key] = propList[key];
- }
- }
-
- //Iterate over all props of the current frame and collect them
- for(key in frame.props) {
- propList[key] = frame.props[key];
- }
- };
-
- /**
- * Calculates the new values for two given values array.
- */
- var _calcInterpolation = function(val1, val2, progress) {
- var valueIndex;
- var val1Length = val1.length;
-
- //They both need to have the same length
- if(val1Length !== val2.length) {
- throw 'Can\'t interpolate between "' + val1[0] + '" and "' + val2[0] + '"';
- }
-
- //Add the format string as first element.
- var interpolated = [val1[0]];
-
- valueIndex = 1;
-
- for(; valueIndex < val1Length; valueIndex++) {
- //That's the line where the two numbers are actually interpolated.
- interpolated[valueIndex] = val1[valueIndex] + ((val2[valueIndex] - val1[valueIndex]) * progress);
- }
-
- return interpolated;
- };
-
- /**
- * Interpolates the numeric values into the format string.
- */
- var _interpolateString = function(val) {
- var valueIndex = 1;
-
- rxInterpolateString.lastIndex = 0;
-
- return val[0].replace(rxInterpolateString, function() {
- return val[valueIndex++];
- });
- };
-
- /**
- * Resets the class and style attribute to what it was before skrollr manipulated the element.
- * Also remembers the values it had before reseting, in order to undo the reset.
- */
- var _reset = function(elements, undo) {
- //We accept a single element or an array of elements.
- elements = [].concat(elements);
-
- var skrollable;
- var element;
- var elementsIndex = 0;
- var elementsLength = elements.length;
-
- for(; elementsIndex < elementsLength; elementsIndex++) {
- element = elements[elementsIndex];
- skrollable = _skrollables[element[SKROLLABLE_ID_DOM_PROPERTY]];
-
- //Couldn't find the skrollable for this DOM element.
- if(!skrollable) {
- continue;
- }
-
- if(undo) {
- //Reset class and style to the "dirty" (set by skrollr) values.
- element.style.cssText = skrollable.dirtyStyleAttr;
- _updateClass(element, skrollable.dirtyClassAttr);
- } else {
- //Remember the "dirty" (set by skrollr) class and style.
- skrollable.dirtyStyleAttr = element.style.cssText;
- skrollable.dirtyClassAttr = _getClass(element);
-
- //Reset class and style to what it originally was.
- element.style.cssText = skrollable.styleAttr;
- _updateClass(element, skrollable.classAttr);
- }
- }
- };
-
- /**
- * Detects support for 3d transforms by applying it to the skrollr-body.
- */
- var _detect3DTransforms = function() {
- _translateZ = 'translateZ(0)';
- skrollr.setStyle(_skrollrBody, 'transform', _translateZ);
-
- var computedStyle = getStyle(_skrollrBody);
- var computedTransform = computedStyle.getPropertyValue('transform');
- var computedTransformWithPrefix = computedStyle.getPropertyValue(theDashedCSSPrefix + 'transform');
- var has3D = (computedTransform && computedTransform !== 'none') || (computedTransformWithPrefix && computedTransformWithPrefix !== 'none');
-
- if(!has3D) {
- _translateZ = '';
- }
- };
-
- /**
- * Set the CSS property on the given element. Sets prefixed properties as well.
- */
- skrollr.setStyle = function(el, prop, val) {
- var style = el.style;
-
- //Camel case.
- prop = prop.replace(rxCamelCase, rxCamelCaseFn).replace('-', '');
-
- //Make sure z-index gets a .
- //This is the only case we need to handle.
- if(prop === 'zIndex') {
- if(isNaN(val)) {
- //If it's not a number, don't touch it.
- //It could for example be "auto" (#351).
- style[prop] = val;
- } else {
- //Floor the number.
- style[prop] = '' + (val | 0);
- }
- }
- //#64: "float" can't be set across browsers. Needs to use "cssFloat" for all except IE.
- else if(prop === 'float') {
- style.styleFloat = style.cssFloat = val;
- }
- else {
- //Need try-catch for old IE.
- try {
- //Set prefixed property if there's a prefix.
- if(theCSSPrefix) {
- style[theCSSPrefix + prop.slice(0,1).toUpperCase() + prop.slice(1)] = val;
- }
-
- //Set unprefixed.
- style[prop] = val;
- } catch(ignore) {}
- }
- };
-
- /**
- * Cross browser event handling.
- */
- var _addEvent = skrollr.addEvent = function(element, names, callback) {
- var intermediate = function(e) {
- //Normalize IE event stuff.
- e = e || window.event;
-
- if(!e.target) {
- e.target = e.srcElement;
- }
-
- if(!e.preventDefault) {
- e.preventDefault = function() {
- e.returnValue = false;
- e.defaultPrevented = true;
- };
- }
-
- return callback.call(this, e);
- };
-
- names = names.split(' ');
-
- var name;
- var nameCounter = 0;
- var namesLength = names.length;
-
- for(; nameCounter < namesLength; nameCounter++) {
- name = names[nameCounter];
-
- if(element.addEventListener) {
- element.addEventListener(name, callback, false);
- } else {
- element.attachEvent('on' + name, intermediate);
- }
-
- //Remember the events to be able to flush them later.
- _registeredEvents.push({
- element: element,
- name: name,
- listener: callback
- });
- }
- };
-
- var _removeEvent = skrollr.removeEvent = function(element, names, callback) {
- names = names.split(' ');
-
- var nameCounter = 0;
- var namesLength = names.length;
-
- for(; nameCounter < namesLength; nameCounter++) {
- if(element.removeEventListener) {
- element.removeEventListener(names[nameCounter], callback, false);
- } else {
- element.detachEvent('on' + names[nameCounter], callback);
- }
- }
- };
-
- var _removeAllEvents = function() {
- var eventData;
- var eventCounter = 0;
- var eventsLength = _registeredEvents.length;
-
- for(; eventCounter < eventsLength; eventCounter++) {
- eventData = _registeredEvents[eventCounter];
-
- _removeEvent(eventData.element, eventData.name, eventData.listener);
- }
-
- _registeredEvents = [];
- };
-
- var _emitEvent = function(element, name, direction) {
- if(_listeners.keyframe) {
- _listeners.keyframe.call(_instance, element, name, direction);
- }
- };
-
- var _reflow = function() {
- var pos = _instance.getScrollTop();
-
- //Will be recalculated by _updateDependentKeyFrames.
- _maxKeyFrame = 0;
-
- if(_forceHeight && !_isMobile) {
- //un-"force" the height to not mess with the calculations in _updateDependentKeyFrames (#216).
- body.style.height = '';
- }
-
- _updateDependentKeyFrames();
-
- if(_forceHeight && !_isMobile) {
- //"force" the height.
- body.style.height = (_maxKeyFrame + documentElement.clientHeight) + 'px';
- }
-
- //The scroll offset may now be larger than needed (on desktop the browser/os prevents scrolling farther than the bottom).
- if(_isMobile) {
- _instance.setScrollTop(Math.min(_instance.getScrollTop(), _maxKeyFrame));
- } else {
- //Remember and reset the scroll pos (#217).
- _instance.setScrollTop(pos, true);
- }
-
- _forceRender = true;
- };
-
- /*
- * Returns a copy of the constants object where all functions and strings have been evaluated.
- */
- var _processConstants = function() {
- var viewportHeight = documentElement.clientHeight;
- var copy = {};
- var prop;
- var value;
-
- for(prop in _constants) {
- value = _constants[prop];
-
- if(typeof value === 'function') {
- value = value.call(_instance);
- }
- //Percentage offset.
- else if((/p$/).test(value)) {
- value = (value.slice(0, -1) / 100) * viewportHeight;
- }
-
- copy[prop] = value;
- }
-
- return copy;
- };
-
- /*
- * Returns the height of the document.
- */
- var _getDocumentHeight = function() {
- var skrollrBodyHeight = (_skrollrBody && _skrollrBody.offsetHeight || 0);
- var bodyHeight = Math.max(skrollrBodyHeight, body.scrollHeight, body.offsetHeight, documentElement.scrollHeight, documentElement.offsetHeight, documentElement.clientHeight);
-
- return bodyHeight - documentElement.clientHeight;
- };
-
- /**
- * Returns a string of space separated classnames for the current element.
- * Works with SVG as well.
- */
- var _getClass = function(element) {
- var prop = 'className';
-
- //SVG support by using className.baseVal instead of just className.
- if(window.SVGElement && element instanceof window.SVGElement) {
- element = element[prop];
- prop = 'baseVal';
- }
-
- return element[prop];
- };
-
- /**
- * Adds and removes a CSS classes.
- * Works with SVG as well.
- * add and remove are arrays of strings,
- * or if remove is ommited add is a string and overwrites all classes.
- */
- var _updateClass = function(element, add, remove) {
- var prop = 'className';
-
- //SVG support by using className.baseVal instead of just className.
- if(window.SVGElement && element instanceof window.SVGElement) {
- element = element[prop];
- prop = 'baseVal';
- }
-
- //When remove is ommited, we want to overwrite/set the classes.
- if(remove === undefined) {
- element[prop] = add;
- return;
- }
-
- //Cache current classes. We will work on a string before passing back to DOM.
- var val = element[prop];
-
- //All classes to be removed.
- var classRemoveIndex = 0;
- var removeLength = remove.length;
-
- for(; classRemoveIndex < removeLength; classRemoveIndex++) {
- val = _untrim(val).replace(_untrim(remove[classRemoveIndex]), ' ');
- }
-
- val = _trim(val);
-
- //All classes to be added.
- var classAddIndex = 0;
- var addLength = add.length;
-
- for(; classAddIndex < addLength; classAddIndex++) {
- //Only add if el not already has class.
- if(_untrim(val).indexOf(_untrim(add[classAddIndex])) === -1) {
- val += ' ' + add[classAddIndex];
- }
- }
-
- element[prop] = _trim(val);
- };
-
- var _trim = function(a) {
- return a.replace(rxTrim, '');
- };
-
- /**
- * Adds a space before and after the string.
- */
- var _untrim = function(a) {
- return ' ' + a + ' ';
- };
-
- var _now = Date.now || function() {
- return +new Date();
- };
-
- var _keyFrameComparator = function(a, b) {
- return a.frame - b.frame;
- };
-
- /*
- * Private variables.
- */
-
- //Singleton
- var _instance;
-
- /*
- A list of all elements which should be animated associated with their the metadata.
- Exmaple skrollable with two key frames animating from 100px width to 20px:
-
- skrollable = {
- element: ,
- styleAttr:
+
+
{% endif %}
-
-
-
-