+
diff --git a/apps/customercare/urls.py b/apps/customercare/urls.py
index 25f27cf3a46..4acd2e50aad 100644
--- a/apps/customercare/urls.py
+++ b/apps/customercare/urls.py
@@ -1,5 +1,6 @@
from django.conf.urls.defaults import patterns, url
urlpatterns = patterns('customercare.views',
- url(r'^$', 'landing', name='customercare.landing'),
+ url(r'/twitter_auth', 'twitter_auth', name="customercare.twitter_auth"),
+ url(r'', 'landing', name='customercare.landing'),
)
diff --git a/apps/customercare/views.py b/apps/customercare/views.py
index 94edcd5d660..175a7956780 100644
--- a/apps/customercare/views.py
+++ b/apps/customercare/views.py
@@ -1,6 +1,124 @@
+from datetime import datetime
+from email.Utils import parsedate
+import json
+import logging
+from uuid import uuid4
+
from django import http
+from django.conf import settings
+from django.core.cache import cache
+
+import jingo
+import tweepy
+
+from .models import CannedCategory, Tweet
+
+
+log = logging.getLogger('custcare')
+
+token_cache_prefix = 'custcare_token_'
+key_prefix = token_cache_prefix + 'key_'
+secret_prefix = token_cache_prefix + 'secret_'
+
+# cookie names are duplicated in js/cusomtercare.js
+access_cookie_name = 'custcare_twitter_access_id'
+redirect_cookie_name = 'custcare_twitter_redirect_flag'
+
+
+def auth_factory(request):
+ return tweepy.OAuthHandler(settings.TWITTER_CONSUMER_KEY,
+ settings.TWITTER_CONSUMER_SECRET,
+ 'https://{0}/{1}/customercare/'.format(
+ request.get_host(), request.locale))
+
+
+def set_access_cookie(resp, id):
+ resp.set_cookie(redirect_cookie_name, '1', httponly=True)
+ resp.set_cookie(access_cookie_name, id, secure=True)
+
+
+def set_tokens(id, key, secret):
+ cache.set(key_prefix + id, key)
+ cache.set(secret_prefix + id, secret)
+
+
+def get_tokens(id):
+ key = cache.get(key_prefix + id)
+ secret = cache.get(secret_prefix + id)
+ return key, secret
def landing(request):
"""Customer Care Landing page."""
- return http.HttpResponse(landing.__doc__)
+
+ canned_responses = CannedCategory.objects.all()
+ tweets = []
+ for tweet in Tweet.objects.filter(locale='en')[:10]:
+ data = json.loads(tweet.raw_json)
+ parsed_date = parsedate(data['created_at'])
+ date = datetime(*parsed_date[0:6])
+ tweets.append({
+ 'profile_img': data['profile_image_url'],
+ 'user': data['from_user'],
+ 'text': tweet,
+ 'date': date,
+ })
+
+ resp = jingo.render(request, 'customercare/landing.html', {
+ 'canned_responses': canned_responses,
+ 'tweets': tweets,
+ 'now': datetime.utcnow(),
+ })
+
+ # TODO HTTP redirect flag checking?
+ if request.COOKIES.get(redirect_cookie_name):
+ return http.HttpResponseRedirect('https://{0}/{1}'.format(
+ request.get_host(), request.get_full_path()))
+
+ # if GET[oauth_verifier] exists, we're handling an OAuth login
+ verifier = request.GET.get('oauth_verifier')
+ if verifier:
+ auth = auth_factory(request)
+ request_key = request.COOKIES.get('request_token_key')
+ request_secret = request.COOKIES.get('request_token_secret')
+ if request_key and request_secret:
+ resp.delete_cookie('request_token_key')
+ resp.delete_cookie('request_token_secret')
+ auth.set_request_token(request_key, request_secret)
+
+ try:
+ auth.get_access_token(verifier)
+ except tweepy.TweepError:
+ log.warning('Tweepy Error with verifier token')
+ pass
+ else:
+ access_id = uuid4().hex
+ set_access_cookie(resp, access_id)
+ set_tokens(access_id, auth.access_token.key, auth.access_token.secret)
+
+ return resp
+
+
+def twitter_post(request):
+ # access_id = request.COOKIES.get(access_cookie_name)
+ # if access_id:
+ # key, secret = get_tokens(access_id)
+ # authed = True
+ # resp.write('key: %s sec: %s' % (key, secret))
+ # set_access_cookie(resp, access_id)
+ pass
+
+
+def twitter_auth(request):
+ auth = auth_factory(request)
+
+ try:
+ redirect_url = auth.get_authorization_url()
+ except tweepy.TweepError:
+ log.warning('Tweepy error while getting authorization url')
+ return http.HttpReponseServerError()
+
+ resp = http.HttpResponseRedirect(redirect_url)
+ resp.set_cookie('request_token_key', auth.request_token.key, max_age=3600, secure=True)
+ resp.set_cookie('request_token_secret', auth.request_token.secret, max_age=3600, secure=True)
+ return resp
diff --git a/media/css/customercare.css b/media/css/customercare.css
new file mode 100644
index 00000000000..4e4b6f54200
--- /dev/null
+++ b/media/css/customercare.css
@@ -0,0 +1,351 @@
+body {
+ background-position: 50% 0;
+}
+
+.feature-contents {
+ width: 640px;
+ background: url("../img/customercare/nurse.png") no-repeat scroll right 20px transparent;
+ padding: 30px 0 0 25px;
+}
+
+.feature-contents h2, .feature-contents h3 {
+ line-height: 1em;
+ margin-right: 250px;
+}
+
+.feature-contents h2 {
+ color: #FFF;
+ font-size: 46px;
+}
+
+.feature-contents h3 {
+ color: #68645b;
+ line-height: 1.5em;
+ font-size: 150%;
+ margin-top: 15px;
+}
+#speach-bubbles {
+ padding: 60px 0 15px 25px;
+}
+#speach-bubbles li {
+ list-style-type: none;
+ float: left;
+ text-indent: -99999px;
+ width: 177px;
+ height: 97px;
+}
+#speach-bubbles li.choose {
+ background: url("../img/customercare/bubble-choose.png") no-repeat scroll top left transparent;
+}
+#speach-bubbles li.signin {
+ background: url("../img/customercare/bubble-signin.png") no-repeat scroll top left transparent;
+}
+#speach-bubbles li.respond {
+ background: url("../img/customercare/bubble-respond.png") no-repeat scroll top left transparent;
+}
+#tweetcontainer {
+ border: 1px solid #f5f5f5;
+ padding: 2px;
+ clear: both;
+}
+#tweetcontainer h2 {
+ margin: 0px;
+ padding: 0px;
+ float: left;
+ line-height: 3em;
+ font-weight: bold;
+ font-size: 20px;
+ background: none;
+}
+#tweetcontainer img {
+ float: left;
+ padding: 4px;
+}
+.tweets-header {
+ padding-left: 15px;
+}
+#tweets {
+ margin: 0 0 15px 15px;
+}
+#tweets ul {
+ margin: 0px;
+}
+#tweets li {
+ width: 630px;
+ border-top: 1px solid #ccc;
+ overflow: auto;
+ margin-bottom: 0px;
+ padding: 10px;
+ cursor: pointer;
+}
+#tweets li.alt {
+ background: -moz-linear-gradient(top, #fff, #f5f5f5);
+}
+#tweets li:hover {
+ border-top: 1px solid #fef1ad;
+ background: -moz-linear-gradient(top, #fffffd, #fdefa6);
+}
+#tweets li img {
+ float: left;
+ margin-right: 1em;
+ border: solid 1px #cacccb;
+ padding: 0px;
+ width: 48px;
+ height: 48px;
+}
+#tweets li span {
+ display: block;
+}
+#tweets li span {
+ color: #307fc1;
+ text-decoration: none;
+ font-weight: bold;
+ font-size: 13px;
+ line-height: 1.4em;
+}
+#tweets li span .time {
+ float: right;
+ font-weight: normal;
+ font-style: italic;
+ font-size: 12px;
+ color: #afaba3;
+}
+#tweets li span .text {
+ font-weight: normal;
+ font-size: 14px;
+ color: #69645b;
+}
+.tweets-buttons {
+ float: right;
+ line-height: 4em;
+ padding-right: 15px;
+}
+.tweet a, .tweet a:hover {
+ color: #69645b;
+ text-decoration: none;
+}
+.hidden {
+ display: none;
+}
+
+/* reply page styling */
+#reply-modal { display: none; }
+
+#reply-container {
+ background-color: #e6f2f6;
+ width: 444px;
+ margin: 0 auto;
+ padding: 30px 24px;
+ top: 86px;
+ font-family: Verdana, sans-serif;
+ text-align: left;
+
+ -moz-box-shadow: 0 0 10px #aaa;
+ box-shadow: 0 0 10px #aaa;
+}
+
+#reply-container,
+#reply-container #replies {
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+
+#initial-tweet img {
+ border: 1px solid #bbb;
+ float: left;
+}
+
+#initial-tweet .box {
+ border: 1px solid #aaa;
+ display: block;
+ margin-left: 70px;
+ text-align: left;
+ padding: 6px 9px 10px 9px;
+ color: #69645b;
+ font-size: 80%;
+ background: #fff;
+}
+
+#initial-tweet a {
+ color: #2276bb;
+ display: block;
+ text-decoration: none;
+ font-weight: bold;
+}
+
+#initial-tweet .box a {
+ padding-bottom: 4px;
+}
+
+#initial-tweet #arrow {
+ border: 0;
+ position: absolute;
+ left: 88px;
+ top: 65px;
+}
+
+#reply-container h4 {
+ font-family: Verdana, sans-serif;
+ color: #4b4740;
+ font-size: 125%;
+ font-weight: normal;
+ margin-bottom: 15px;
+}
+
+#reply-container #replies {
+ background: #8fc1da;
+ background: -moz-linear-gradient(center top , #8fc1da, #e5f1f7) repeat;
+ margin-top: 20px;
+ padding: 15px 20px;
+}
+
+#reply-container h3.ui-accordion-header {
+ background: #f0f0f0 url('../img/customercare/expander-closed.png') 8px 50% no-repeat;
+ border: 1px solid #b2b2b2;
+ font-family: Verdana, sans-serif;
+ font-size: 12px;
+ padding: 4px 0 5px 24px;
+ margin: 0 0 9px 0;
+
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+
+#reply-container h3.ui-accordion-header a {
+ color: #afaba3;
+ display: block;
+}
+
+#reply-container h3.ui-accordion-header a:hover {
+ text-decoration: none;
+}
+
+#reply-container #replies ul {
+ margin: 0 0 10px 24px;
+ list-style-type: none;
+}
+
+#reply-container #replies li a {
+ display: block;
+ color: #0489b7;
+ padding-bottom: 2px;
+}
+
+#reply-container h3.ui-accordion-header.ui-state-default {
+ background: #f0f0f0 url('../img/customercare/expander-closed.png') 8px 50% no-repeat;
+}
+
+#reply-container h3.ui-accordion-header.ui-state-active {
+ background: #f0f0f0 url('../img/customercare/expander-open.png') 8px 50% no-repeat;
+}
+
+#reply-container h3.ui-accordion-header.ui-state-default a {
+ font-weight: normal;
+ color: #afaba3;
+}
+
+#reply-container h3.ui-accordion-header.ui-state-active a {
+ font-weight: bold;
+ color: #69645b;
+}
+
+#accordion .ui-widget-content {
+ background: none;
+ border: none;
+}
+
+.topics .snippet {
+ display: none;
+}
+
+#reply {
+ padding-bottom: 34px;
+}
+
+#reply .reply-message {
+ width: 310px;
+ height: 65px;
+ border: 1px solid #aaa;
+ padding: 10px 15px;
+ color: #afaba3;
+ background: #fff;
+ font-size: .9em;
+}
+
+#reply #reply-arrow {
+ position: relative;
+ left: 30px;
+ top: 4px;
+ display: inline;
+}
+
+#reply .submit-message {
+ display: none;
+ background: url('../img/customercare/reply-check.png') right top no-repeat;
+ font-weight: bold;
+ color: #4b4740;
+ height: 21px;
+ padding-right: 23px;
+ padding-top: 3px;
+ float: left;
+ margin-top: 14px;
+}
+
+#reply .container {
+ position: relative;
+ top: -12px;
+}
+
+#reply .submitButton {
+ background: -moz-linear-gradient(center top, #eed052 15px, #ebb04f 18px, #e79f30, #e0752b);
+ border-left: 1px solid #d4661a;
+ border-right: 1px solid #d4661a;
+ border-top: 1px solid #d19f7c;
+ border-bottom: 1px solid #ba5c1c;
+ color: #b01a04;
+ width: 140px;
+ height: 36px;
+ margin-top: 10px;
+ float: right;
+
+ text-shadow: 1px 1px 0 rgba(255, 255, 255, 0.6);
+ -moz-box-shadow: 0 0 5px #777;
+ box-shadow: 0 0 5px #777;
+
+ -webkit-border-radius: 6px;
+ -moz-border-radius: 6px;
+ border-radius: 6px;
+
+ cursor: pointer;
+}
+
+#reply .character-counter {
+ width: 50px;
+ color: #fff;
+ font-weight: bold;
+ font-size: 24px;
+ float: right;
+ position: relative;
+ top: 33px;
+}
+
+.hrbreak {
+ background: url('../img/customercare/hr-line.png') center 20px no-repeat;
+ height: 50px;
+}
+/* end of reply page styling */
+
+.notice {
+ background-color:#FFFFCC;
+ border:2px solid orange;
+ margin:1em 0;
+ padding:1em;
+ text-align:center;
+ float: left;
+}
+
+#twitter-modal {
+ display: none;
+}
diff --git a/media/img/customercare/bubble-choose.png b/media/img/customercare/bubble-choose.png
new file mode 100644
index 00000000000..651109e989a
Binary files /dev/null and b/media/img/customercare/bubble-choose.png differ
diff --git a/media/img/customercare/bubble-respond.png b/media/img/customercare/bubble-respond.png
new file mode 100644
index 00000000000..c1d4721f760
Binary files /dev/null and b/media/img/customercare/bubble-respond.png differ
diff --git a/media/img/customercare/bubble-signin.png b/media/img/customercare/bubble-signin.png
new file mode 100644
index 00000000000..54121f9e358
Binary files /dev/null and b/media/img/customercare/bubble-signin.png differ
diff --git a/media/img/customercare/expander-closed.png b/media/img/customercare/expander-closed.png
new file mode 100755
index 00000000000..e6a96f0eda1
Binary files /dev/null and b/media/img/customercare/expander-closed.png differ
diff --git a/media/img/customercare/expander-open.png b/media/img/customercare/expander-open.png
new file mode 100755
index 00000000000..8aa3ee101dd
Binary files /dev/null and b/media/img/customercare/expander-open.png differ
diff --git a/media/img/customercare/hr-line.png b/media/img/customercare/hr-line.png
new file mode 100755
index 00000000000..e402051aaa5
Binary files /dev/null and b/media/img/customercare/hr-line.png differ
diff --git a/media/img/customercare/initial-tweet-arrow.png b/media/img/customercare/initial-tweet-arrow.png
new file mode 100755
index 00000000000..e001e9844de
Binary files /dev/null and b/media/img/customercare/initial-tweet-arrow.png differ
diff --git a/media/img/customercare/nurse.png b/media/img/customercare/nurse.png
new file mode 100644
index 00000000000..29fc8323c6f
Binary files /dev/null and b/media/img/customercare/nurse.png differ
diff --git a/media/img/customercare/popup-background.png b/media/img/customercare/popup-background.png
new file mode 100755
index 00000000000..81abc796c6a
Binary files /dev/null and b/media/img/customercare/popup-background.png differ
diff --git a/media/img/customercare/reply-arrow.png b/media/img/customercare/reply-arrow.png
new file mode 100755
index 00000000000..0b8d990455c
Binary files /dev/null and b/media/img/customercare/reply-arrow.png differ
diff --git a/media/img/customercare/reply-check.png b/media/img/customercare/reply-check.png
new file mode 100755
index 00000000000..18258ead16f
Binary files /dev/null and b/media/img/customercare/reply-check.png differ
diff --git a/media/img/customercare/sumo-logo.png b/media/img/customercare/sumo-logo.png
new file mode 100644
index 00000000000..c6a3880f54a
Binary files /dev/null and b/media/img/customercare/sumo-logo.png differ
diff --git a/media/img/customercare/twitter-icon.png b/media/img/customercare/twitter-icon.png
new file mode 100644
index 00000000000..ab47769c9cf
Binary files /dev/null and b/media/img/customercare/twitter-icon.png differ
diff --git a/media/img/customercare/wantgetinvolved-back.png b/media/img/customercare/wantgetinvolved-back.png
new file mode 100644
index 00000000000..47517d31760
Binary files /dev/null and b/media/img/customercare/wantgetinvolved-back.png differ
diff --git a/media/js/customercare.js b/media/js/customercare.js
new file mode 100644
index 00000000000..1815e18af56
--- /dev/null
+++ b/media/js/customercare.js
@@ -0,0 +1,56 @@
+var has_twitter_access = false;
+// cookie names are duplicated in apps/customercare/views.py
+if ($.cookie('custcare_twitter_access_id'))
+ has_twitter_access = true;
+
+
+$(document).ready(function() {
+ $('.reply-message').NobleCount('.character-counter');
+
+ $('.reply-message').autoPlaceholderText();
+
+ $('#accordion').accordion({
+ 'icons': false,
+ 'autoHeight': false,
+ });
+
+ $('.tweet').click(function() {
+ if (!has_twitter_access) {
+ $('#twitter-modal').dialog({
+ 'modal': 'true',
+ 'position': 'top',
+ });
+ $('#twitter-modal .cancel').click(function(e) {
+ $('#twitter-modal').dialog('close');
+ e.preventDefault();
+ return false;
+ });
+ return;
+ }
+
+ var avatar_href = $(this).find('.avatar').attr('href');
+ var avatar_img = $(this).find('.avatar img').attr('src');
+ var twittername = $(this).find('.twittername').text();
+ var text = $(this).find('.text').text();
+
+ var modal = $('#reply-modal');
+ modal.find('.avatar').attr('href', avatar_href);
+ modal.find('.avatar img').attr('src', avatar_img);
+ modal.find('.twittername').text(twittername);
+ modal.find('.text').text(text);
+ modal.dialog({
+ 'modal': true,
+ 'position': 'top',
+ 'width': 500,
+ });
+ });
+
+ $('.reply-topic').click(function(e) {
+ snippet = $(this).next('.snippet').text();
+ $('.reply-message').val(snippet);
+ $('.reply-message').trigger('keydown');
+
+ e.preventDefault();
+ return false;
+ });
+});
diff --git a/media/js/libs/jquery.NobleCount.js b/media/js/libs/jquery.NobleCount.js
new file mode 100644
index 00000000000..5ccecb802a2
--- /dev/null
+++ b/media/js/libs/jquery.NobleCount.js
@@ -0,0 +1,480 @@
+/******************************************************************************************************
+
+ jQuery.NobleCount
+
+ Author Jeremy Horn
+ Version 1.0
+ Date: 3/21/2010
+
+ Copyright (c) 2010 Jeremy Horn- jeremydhorn(at)gmail(dot)c0m | http://tpgblog.com
+ Dual licensed under MIT and GPL.
+
+ DESCRIPTION
+ NobleCount... for a more 'proper' count of the characters remaining.
+
+ NobleCount is a customizable jQuery plugin for a more the improved counting of the remaining
+ characters, and resulting behaviors, of a text entry object, e.g. input textfield, textarea.
+
+ As text is entered into the target text area an object for the purposes of tracking
+ the total number of characters remaining, defined as the maximum number of characters
+ minus the current total number of characters within the text entry object, and storing
+ that information visually and/or within the DOM as an HTML 5 compliant data-* attribute.
+
+ Events and CSS Class alterations, if defined, are triggered based on current user
+ interaction with the target text entry object as well as the current state (positive or
+ negative) of the character remaining value.
+
+ NobleCount supports pre-existing text within the text object.
+ NobleCount supports jQuery chaining.
+
+ Within NobleCount context...
+ NEGATIVE is defined as Integers < 0
+ POSITIVE is defined as Integers >= 0 [on_positive will fire when char_rem == 0]
+
+ BY DEFAULT
+ - maximum characters EQUAL 140 characters
+ - no events defined
+ - no class changes defined
+ - no DOM attributes are created/altered
+ - user permitted to type past the maximum number of characters limit, resulting in
+ negative number of characters remaining
+
+ IMPLEMENTATION
+
+ $('#textarea1').NobleCount('#characters_remaining1');
+ $('#textfield2').NobleCount('#characters_remaining2', { / * OPTIONS * / });
+
+ COMPATIBILITY
+
+ Tested in FF3.5, IE7
+ With jQuery 1.3.x, 1.4.x
+
+ METHOD(S)
+ To properly intialize, both the text entry object and the object that will store the
+ total number of characters remaining must exist and be passed to NobleCount.
+
+ $(TEXT_ENTRY_OBJECT).NobleCount(CHARACTERS_REMAINING_OBJECT);
+
+ Any callback functions assigned to any of the availale events are passed the following
+ parameters: t_obj, char_area, c_settings, char_rem
+
+ t_obj text entry object
+
+ char_area selection of the characters remaining object
+
+ c_settings result of the options passed into NobleCount at time of
+ initialization merged with the default options
+
+ ** this is a GREAT way to pass in and remember other state
+ information that will be needed upon the triggering of
+ NobleCount events **
+
+ char_rem integer representation of the total number of characters
+ remaining resulting from the calculated difference between
+ the target maximum number of characters and the current
+ number of characters currently within t_obj
+
+ Both TEXT_ENTRY_OBJECT and CHARACTERS_REMAINING_OBJECT must be specified and valid.
+
+ Upon successful initialization, all appropriate events and classes are applied to
+ the CHARACTERS_REMAINING_OBJECT, including the storage (if not disabled) visually
+ or only in the DOM (if enabled) of the integer representing the number of characters
+ remaining.
+
+ The target maximum number of characters (max_chars) are determined by the following
+ precedence rules....
+
+ if max_chars passed via constructor
+ max_chars = max_chars passed
+ else if number exists within characters_remaining object and number > 0
+ max_chars = number within the text() of characters_remaining object
+ else use the NobleCount's default max_chars
+
+ CUSTOMIZATION
+
+ NobleCount(c_obj, )
+ e.g. $(t_obj).NobleCount(c_obj, {max_chars:100px});
+
+
+ on_negative class (STRING) or FUNCTION that is applied/called
+ when characters remaining is negative IF DEFINED
+
+ on_positive class (STRING) or FUNCTION that is applied/called
+ when characters remaining is positive IF DEFINED
+
+ on_update FUNCTION that is called when characters remaining changes
+
+ max_chars target maximum number of characters
+
+ block_negative if TRUE, then all attempts are made to block entering
+ more than max_characters; not effective against user
+ pasting in blocks of text that exceed the max_chars value
+ otherwise, text area will let individual entering the text
+ to exceed max_chars limit (characters remaining becomes
+ negative)
+
+ cloak: false, if TRUE, then no visual updates of characters remaining
+ object (c_obj) will occur; this does not have any effect
+ on the char_rem value returned via any event callbacks
+ otherwise, the text within c_obj is constantly updated to
+ represent the total number of characters remaining until
+ the max_chars limit has been reached
+
+ in_dom: false if TRUE and cloak is ALSO TRUE, then the number of characters
+ remaining are stored as the attribute of c_obj
+ named 'data-noblecount'
+
+ !NOTE: if enabled, due to constant updating of a DOM element
+ attribute user experience can appear sluggish while
+ the individual is modifying the text entry object (t_obj)
+
+
+ EXAMPLE OPTIONS =
+ {
+ on_negative: 'go_red',
+ on_positive: 'go_green',
+ max_chars: 25,
+ on_update: function(t_obj, char_area, c_settings, char_rem){
+ if ((char_rem % 10) == 0) {
+ char_area.css('font-weight', 'bold');
+ char_area.css('font-size', '300%');
+ } else {
+ char_area.css('font-weight', 'normal');
+ char_area.css('font-size', '100%');
+ }
+ }
+ };
+
+ MORE
+
+ For more details about NobleCount, its implementation, usage, and examples, go to:
+ http://tpgblog.com/noblecount/
+
+******************************************************************************************************/
+
+(function($) {
+
+ /**********************************************************************************
+
+ FUNCTION
+ NobleCount
+
+ DESCRIPTION
+ NobleCount method constructor
+
+ allows for customization of maximum length and related update/length
+ behaviors
+
+ e.g. $(text_obj).NobleCount(characters_remaining_obj);
+
+ REQUIRED: c_obj
+ OPTIONAL: options
+
+ **********************************************************************************/
+
+ $.fn.NobleCount = function(c_obj, options) {
+ var c_settings;
+ var mc_passed = false;
+
+ // if c_obj is not specified, then nothing to do here
+ if (typeof c_obj == 'string') {
+ // check for new & valid options
+ c_settings = $.extend({}, $.fn.NobleCount.settings, options);
+
+ // was max_chars passed via options parameter?
+ if (typeof options != 'undefined') {
+ mc_passed = ((typeof options.max_chars == 'number') ? true : false);
+ }
+
+ // process all provided objects
+ return this.each(function(){
+ var $this = $(this);
+
+ // attach events to c_obj
+ attach_nobility($this, c_obj, c_settings, mc_passed);
+ });
+ }
+
+ return this;
+ };
+
+
+ /**********************************************************************************
+
+ FUNCTION
+ NobleCount.settings
+
+ DESCRIPTION
+ publically accessible data stucture containing the max_chars and
+ event handling specifications for NobleCount
+
+ can be directly accessed by '$.fn.NobleCount.settings = ... ;'
+
+ **********************************************************************************/
+ $.fn.NobleCount.settings = {
+
+ on_negative: null, // class (STRING) or FUNCTION that is applied/called
+ // when characters remaining is negative
+ on_positive: null, // class (STRING) or FUNCTION that is applied/called
+ // when characters remaining is positive
+ on_update: null, // FUNCTION that is called when characters remaining
+ // changes
+ max_chars: 140, // maximum number of characters
+ block_negative: false, // if true, then all attempts are made to block entering
+ // more than max_characters
+ cloak: false, // if true, then no visual updates of characters
+ // remaining (c_obj) occur
+ in_dom: false // if true and cloak == true, then number of characters
+ // remaining are stored as the attribute
+ // 'data-noblecount' of c_obj
+
+ };
+
+
+ //////////////////////////////////////////////////////////////////////////////////
+
+ // private functions and settings
+
+ /**********************************************************************************
+
+ FUNCTION
+ attach_nobility
+
+ DESCRIPTION
+ performs all initialization routines and display initiation
+
+ assigns both the keyup and keydown events to the target text entry
+ object; both keyup and keydown are used to provide the smoothest
+ user experience
+
+ if max_chars_passed via constructor
+ max_chars = max_chars_passed
+ else if number exists within counting_object (and number > 0)
+ max_chars = counting_object.number
+ else use default max_chars
+
+ PRE
+ t_obj and c_obj EXIST
+ c_settings and mc_passed initialized
+
+ POST
+ maximum number of characters for t_obj calculated and stored in max_char
+ key events attached to t_obj
+
+ **********************************************************************************/
+
+ function attach_nobility(t_obj, c_obj, c_settings, mc_passed){
+ var max_char = c_settings.max_chars;
+ var char_area = $(c_obj);
+
+ // first determine if max_char needs adjustment
+ if (!mc_passed) {
+ var tmp_num = char_area.text();
+ var isPosNumber = (/^[1-9]\d*$/).test(tmp_num);
+
+ if (isPosNumber) {
+ max_char = tmp_num;
+ }
+ }
+
+ // initialize display of characters remaining
+ // * note: initializing should not trigger on_update
+ event_internals(t_obj, char_area, c_settings, max_char, true);
+
+ // then attach the events -- seem to work better than keypress
+ $(t_obj).keydown(function(e) {
+ event_internals(t_obj, char_area, c_settings, max_char, false);
+
+ // to block text entry, return false
+ if (check_block_negative(e, t_obj, c_settings, max_char) == false) {
+ return false;
+ }
+ });
+
+ $(t_obj).keyup(function(e) {
+ event_internals(t_obj, char_area, c_settings, max_char, false);
+
+ // to block text entry, return false
+ if (check_block_negative(e, t_obj, c_settings, max_char) == false) {
+ return false;
+ }
+ });
+ }
+
+
+ /**********************************************************************************
+
+ FUNCTION
+ check_block_negative
+
+ DESCRIPTION
+ determines whether or not text entry within t_obj should be prevented
+
+ PRE
+ e EXISTS
+ t_obj VALID
+ c_settings and max_char initialized / calculated
+
+ POST
+ if t_obj text entry should be prevented FALSE is returned
+ otherwise TRUE returned
+
+ TODO
+ improve selection detection and permissible behaviors experience
+ ALSO
+ doesnt CURRENTLY block from the pasting of large chunks of text that
+ exceed max_char
+
+ **********************************************************************************/
+
+ function check_block_negative(e, t_obj, c_settings, max_char){
+ if (c_settings.block_negative) {
+ var char_code = e.which;
+ var selected;
+
+ // goofy handling required to work in both IE and FF
+ if (typeof document.selection != 'undefined') {
+ selected = (document.selection.createRange().text.length > 0);
+ } else {
+ selected = (t_obj[0].selectionStart != t_obj[0].selectionEnd);
+ }
+
+ //return false if can't write more
+ if ((!((find_remaining(t_obj, max_char) < 1) &&
+ (char_code > 47 || char_code == 32 || char_code == 0 || char_code == 13) &&
+ !e.ctrlKey &&
+ !e.altKey &&
+ !selected)) == false) {
+
+ // block text entry
+ return false;
+ }
+ }
+
+ // allow text entry
+ return true;
+ }
+
+
+ /**********************************************************************************
+
+ FUNCTION
+ find_remaining
+
+ DESCRIPTION
+ determines of the number of characters permitted (max_char), the number of
+ characters remaining until that limit has been reached
+
+ PRE
+ t_obj and max_char EXIST and are VALID
+
+ POST
+ returns integer of the difference between max_char and total number of
+ characters within the text entry object (t_obj)
+
+ **********************************************************************************/
+
+ function find_remaining(t_obj, max_char){
+ return max_char - ($(t_obj).val()).length;
+ }
+
+
+ /**********************************************************************************
+
+ FUNCTION
+ event_internals
+
+ DESCRIPTION
+ primarily used for the calculation of appropriate behavior resulting from
+ any event attached to the text entry object (t_obj)
+
+ whenever the char_rem and related display and/or DOM information needs
+ updating this function is called
+
+ if cloaking is being used, then no visual representation of the characters
+ remaining, nor attempt by this plugin to change any of its visual
+ characteristics will occur
+
+ if cloaking and in_dom are both TRUE, then the number of characters
+ remaining are stored within the HTML 5 compliant attribute of the
+ character count remaining object (c_obj) labeled 'data-noblecount'
+
+ PRE
+ c_settings, init_disp initialized
+
+ POST
+ performs all updates to the DOM visual and otherwise required
+ performs all relevant function calls
+
+ **********************************************************************************/
+
+ function event_internals(t_obj, char_area, c_settings, max_char, init_disp) {
+ var char_rem = find_remaining(t_obj, max_char);
+
+ // is chararacters remaining positive or negative
+ if (char_rem < 0) {
+ toggle_states(c_settings.on_negative, c_settings.on_positive, t_obj, char_area, c_settings, char_rem);
+ } else {
+ toggle_states(c_settings.on_positive, c_settings.on_negative, t_obj, char_area, c_settings, char_rem);
+ }
+
+ // determine whether or not to update the text of the char_area (or c_obj)
+ if (c_settings.cloak) {
+ // this slows stuff down quite a bit; TODO: implement better method of publically accessible data storage
+ if (c_settings.in_dom) {
+ char_area.attr('data-noblecount', char_rem);
+ }
+ } else {
+ // show the numbers of characters remaining
+ char_area.text(char_rem);
+ }
+
+ // if event_internals isn't being called for initialization purposes and
+ // on_update is a properly defined function then call it on this update
+ if (!init_disp && jQuery.isFunction(c_settings.on_update)) {
+ c_settings.on_update(t_obj, char_area, c_settings, char_rem);
+ }
+ }
+
+
+ /**********************************************************************************
+
+ FUNCTION
+ toggle_states
+
+ DESCRIPTION
+ performs the toggling operations between the watched positive and negative
+ characteristics
+
+ first, enables/triggers/executes the toggle_on behavior/class
+ second, disables the trigger_off class
+
+ PRE
+ toggle_on, toggle_off
+ IF DEFINED,
+ must be a string representation of a VALID class
+ OR
+ must be a VALID function
+
+ POST
+ toggle_on objects have been applied/executed
+ toggle_off class has been removed (if it is a class)
+
+ **********************************************************************************/
+
+ function toggle_states(toggle_on, toggle_off, t_obj, char_area, c_settings, char_rem){
+ if (toggle_on != null) {
+ if (typeof toggle_on == 'string') {
+ char_area.addClass(toggle_on);
+ } else if (jQuery.isFunction(toggle_on)) {
+ toggle_on(t_obj, char_area, c_settings, char_rem);
+ }
+ }
+
+ if (toggle_off != null) {
+ if (typeof toggle_off == 'string') {
+ char_area.removeClass(toggle_off);
+ }
+ }
+ }
+})(jQuery);
diff --git a/media/js/libs/jqueryui.min.js b/media/js/libs/jqueryui.min.js
index c85da15b282..f604e06f0a4 100644
--- a/media/js/libs/jqueryui.min.js
+++ b/media/js/libs/jqueryui.min.js
@@ -230,4 +230,34 @@ c=this._daylightSavingAdjust(new Date(c,e+(b<0?b:f[0]*f[1]),1));b<0&&c.setDate(t
"dayNamesShort"),dayNames:this._get(a,"dayNames"),monthNamesShort:this._get(a,"monthNamesShort"),monthNames:this._get(a,"monthNames")}},_formatDate:function(a,b,c,e){if(!b){a.currentDay=a.selectedDay;a.currentMonth=a.selectedMonth;a.currentYear=a.selectedYear}b=b?typeof b=="object"?b:this._daylightSavingAdjust(new Date(e,c,b)):this._daylightSavingAdjust(new Date(a.currentYear,a.currentMonth,a.currentDay));return this.formatDate(this._get(a,"dateFormat"),b,this._getFormatConfig(a))}});d.fn.datepicker=
function(a){if(!d.datepicker.initialized){d(document).mousedown(d.datepicker._checkExternalClick).find("body").append(d.datepicker.dpDiv);d.datepicker.initialized=true}var b=Array.prototype.slice.call(arguments,1);if(typeof a=="string"&&(a=="isDisabled"||a=="getDate"||a=="widget"))return d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this[0]].concat(b));if(a=="option"&&arguments.length==2&&typeof arguments[1]=="string")return d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this[0]].concat(b));
return this.each(function(){typeof a=="string"?d.datepicker["_"+a+"Datepicker"].apply(d.datepicker,[this].concat(b)):d.datepicker._attachDatepicker(this,a)})};d.datepicker=new L;d.datepicker.initialized=false;d.datepicker.uuid=(new Date).getTime();d.datepicker.version="1.8.4";window["DP_jQuery_"+y]=d})(jQuery);
-;
\ No newline at end of file
+;
+/*
+ * jQuery UI Accordion 1.8.5
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Accordion
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
+ */
+(function(c){c.widget("ui.accordion",{options:{active:0,animated:"slide",autoHeight:true,clearStyle:false,collapsible:false,event:"click",fillSpace:false,header:"> li > :first-child,> :not(li):even",icons:{header:"ui-icon-triangle-1-e",headerSelected:"ui-icon-triangle-1-s"},navigation:false,navigationFilter:function(){return this.href.toLowerCase()===location.href.toLowerCase()}},_create:function(){var a=this,b=a.options;a.running=0;a.element.addClass("ui-accordion ui-widget ui-helper-reset").children("li").addClass("ui-accordion-li-fix");
+a.headers=a.element.find(b.header).addClass("ui-accordion-header ui-helper-reset ui-state-default ui-corner-all").bind("mouseenter.accordion",function(){b.disabled||c(this).addClass("ui-state-hover")}).bind("mouseleave.accordion",function(){b.disabled||c(this).removeClass("ui-state-hover")}).bind("focus.accordion",function(){b.disabled||c(this).addClass("ui-state-focus")}).bind("blur.accordion",function(){b.disabled||c(this).removeClass("ui-state-focus")});a.headers.next().addClass("ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom");
+if(b.navigation){var d=a.element.find("a").filter(b.navigationFilter).eq(0);if(d.length){var f=d.closest(".ui-accordion-header");a.active=f.length?f:d.closest(".ui-accordion-content").prev()}}a.active=a._findActive(a.active||b.active).addClass("ui-state-default ui-state-active").toggleClass("ui-corner-all ui-corner-top");a.active.next().addClass("ui-accordion-content-active");a._createIcons();a.resize();a.element.attr("role","tablist");a.headers.attr("role","tab").bind("keydown.accordion",function(g){return a._keydown(g)}).next().attr("role",
+"tabpanel");a.headers.not(a.active||"").attr({"aria-expanded":"false",tabIndex:-1}).next().hide();a.active.length?a.active.attr({"aria-expanded":"true",tabIndex:0}):a.headers.eq(0).attr("tabIndex",0);c.browser.safari||a.headers.find("a").attr("tabIndex",-1);b.event&&a.headers.bind(b.event.split(" ").join(".accordion ")+".accordion",function(g){a._clickHandler.call(a,g,this);g.preventDefault()})},_createIcons:function(){var a=this.options;if(a.icons){c("").addClass("ui-icon "+a.icons.header).prependTo(this.headers);
+this.active.children(".ui-icon").toggleClass(a.icons.header).toggleClass(a.icons.headerSelected);this.element.addClass("ui-accordion-icons")}},_destroyIcons:function(){this.headers.children(".ui-icon").remove();this.element.removeClass("ui-accordion-icons")},destroy:function(){var a=this.options;this.element.removeClass("ui-accordion ui-widget ui-helper-reset").removeAttr("role");this.headers.unbind(".accordion").removeClass("ui-accordion-header ui-accordion-disabled ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top").removeAttr("role").removeAttr("aria-expanded").removeAttr("tabIndex");
+this.headers.find("a").removeAttr("tabIndex");this._destroyIcons();var b=this.headers.next().css("display","").removeAttr("role").removeClass("ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-accordion-disabled ui-state-disabled");if(a.autoHeight||a.fillHeight)b.css("height","");return c.Widget.prototype.destroy.call(this)},_setOption:function(a,b){c.Widget.prototype._setOption.apply(this,arguments);a=="active"&&this.activate(b);if(a=="icons"){this._destroyIcons();
+b&&this._createIcons()}if(a=="disabled")this.headers.add(this.headers.next())[b?"addClass":"removeClass"]("ui-accordion-disabled ui-state-disabled")},_keydown:function(a){if(!(this.options.disabled||a.altKey||a.ctrlKey)){var b=c.ui.keyCode,d=this.headers.length,f=this.headers.index(a.target),g=false;switch(a.keyCode){case b.RIGHT:case b.DOWN:g=this.headers[(f+1)%d];break;case b.LEFT:case b.UP:g=this.headers[(f-1+d)%d];break;case b.SPACE:case b.ENTER:this._clickHandler({target:a.target},a.target);
+a.preventDefault()}if(g){c(a.target).attr("tabIndex",-1);c(g).attr("tabIndex",0);g.focus();return false}return true}},resize:function(){var a=this.options,b;if(a.fillSpace){if(c.browser.msie){var d=this.element.parent().css("overflow");this.element.parent().css("overflow","hidden")}b=this.element.parent().height();c.browser.msie&&this.element.parent().css("overflow",d);this.headers.each(function(){b-=c(this).outerHeight(true)});this.headers.next().each(function(){c(this).height(Math.max(0,b-c(this).innerHeight()+
+c(this).height()))}).css("overflow","auto")}else if(a.autoHeight){b=0;this.headers.next().each(function(){b=Math.max(b,c(this).height("").height())}).height(b)}return this},activate:function(a){this.options.active=a;a=this._findActive(a)[0];this._clickHandler({target:a},a);return this},_findActive:function(a){return a?typeof a==="number"?this.headers.filter(":eq("+a+")"):this.headers.not(this.headers.not(a)):a===false?c([]):this.headers.filter(":eq(0)")},_clickHandler:function(a,b){var d=this.options;
+if(!d.disabled)if(a.target){a=c(a.currentTarget||b);b=a[0]===this.active[0];d.active=d.collapsible&&b?false:this.headers.index(a);if(!(this.running||!d.collapsible&&b)){this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header);if(!b){a.removeClass("ui-state-default ui-corner-all").addClass("ui-state-active ui-corner-top").children(".ui-icon").removeClass(d.icons.header).addClass(d.icons.headerSelected);
+a.next().addClass("ui-accordion-content-active")}h=a.next();f=this.active.next();g={options:d,newHeader:b&&d.collapsible?c([]):a,oldHeader:this.active,newContent:b&&d.collapsible?c([]):h,oldContent:f};d=this.headers.index(this.active[0])>this.headers.index(a[0]);this.active=b?c([]):a;this._toggle(h,f,g,b,d)}}else if(d.collapsible){this.active.removeClass("ui-state-active ui-corner-top").addClass("ui-state-default ui-corner-all").children(".ui-icon").removeClass(d.icons.headerSelected).addClass(d.icons.header);
+this.active.next().addClass("ui-accordion-content-active");var f=this.active.next(),g={options:d,newHeader:c([]),oldHeader:d.active,newContent:c([]),oldContent:f},h=this.active=c([]);this._toggle(h,f,g)}},_toggle:function(a,b,d,f,g){var h=this,e=h.options;h.toShow=a;h.toHide=b;h.data=d;var j=function(){if(h)return h._completed.apply(h,arguments)};h._trigger("changestart",null,h.data);h.running=b.size()===0?a.size():b.size();if(e.animated){d={};d=e.collapsible&&f?{toShow:c([]),toHide:b,complete:j,
+down:g,autoHeight:e.autoHeight||e.fillSpace}:{toShow:a,toHide:b,complete:j,down:g,autoHeight:e.autoHeight||e.fillSpace};if(!e.proxied)e.proxied=e.animated;if(!e.proxiedDuration)e.proxiedDuration=e.duration;e.animated=c.isFunction(e.proxied)?e.proxied(d):e.proxied;e.duration=c.isFunction(e.proxiedDuration)?e.proxiedDuration(d):e.proxiedDuration;f=c.ui.accordion.animations;var i=e.duration,k=e.animated;if(k&&!f[k]&&!c.easing[k])k="slide";f[k]||(f[k]=function(l){this.slide(l,{easing:k,duration:i||700})});
+f[k](d)}else{if(e.collapsible&&f)a.toggle();else{b.hide();a.show()}j(true)}b.prev().attr({"aria-expanded":"false",tabIndex:-1}).blur();a.prev().attr({"aria-expanded":"true",tabIndex:0}).focus()},_completed:function(a){this.running=a?0:--this.running;if(!this.running){this.options.clearStyle&&this.toShow.add(this.toHide).css({height:"",overflow:""});this.toHide.removeClass("ui-accordion-content-active");this._trigger("change",null,this.data)}}});c.extend(c.ui.accordion,{version:"1.8.5",animations:{slide:function(a,
+b){a=c.extend({easing:"swing",duration:300},a,b);if(a.toHide.size())if(a.toShow.size()){var d=a.toShow.css("overflow"),f=0,g={},h={},e;b=a.toShow;e=b[0].style.width;b.width(parseInt(b.parent().width(),10)-parseInt(b.css("paddingLeft"),10)-parseInt(b.css("paddingRight"),10)-(parseInt(b.css("borderLeftWidth"),10)||0)-(parseInt(b.css("borderRightWidth"),10)||0));c.each(["height","paddingTop","paddingBottom"],function(j,i){h[i]="hide";j=(""+c.css(a.toShow[0],i)).match(/^([\d+-.]+)(.*)$/);g[i]={value:j[1],
+unit:j[2]||"px"}});a.toShow.css({height:0,overflow:"hidden"}).show();a.toHide.filter(":hidden").each(a.complete).end().filter(":visible").animate(h,{step:function(j,i){if(i.prop=="height")f=i.end-i.start===0?0:(i.now-i.start)/(i.end-i.start);a.toShow[0].style[i.prop]=f*g[i.prop].value+g[i.prop].unit},duration:a.duration,easing:a.easing,complete:function(){a.autoHeight||a.toShow.css("height","");a.toShow.css({width:e,overflow:d});a.complete()}})}else a.toHide.animate({height:"hide",paddingTop:"hide",
+paddingBottom:"hide"},a);else a.toShow.animate({height:"show",paddingTop:"show",paddingBottom:"show"},a)},bounceslide:function(a){this.slide(a,{easing:a.down?"easeOutBounce":"swing",duration:a.down?1E3:200})}}})})(jQuery);
diff --git a/migrations/44-cannedresponses-initialdata.sql b/migrations/44-cannedresponses-initialdata.sql
index b1af0f4eaab..6c2f807fc2a 100644
--- a/migrations/44-cannedresponses-initialdata.sql
+++ b/migrations/44-cannedresponses-initialdata.sql
@@ -1,31 +1,31 @@
INSERT INTO `customercare_cannedcategory` (`id`, `title`, `weight`) VALUES
-(1, 'Welcome and Thanks', 0),
-(2, 'Using Firefox', 1),
-(3, 'Firefox Beta', 2),
-(4, 'Support', 3),
-(5, 'Get Involved', 4);
+(1, "Welcome and Thanks", 0),
+(2, "Using Firefox", 1),
+(3, "Firefox Beta", 2),
+(4, "Support", 3),
+(5, "Get Involved", 4);
INSERT INTO `customercare_cannedresponse` (`id`, `title`, `response`) VALUES
-(1, 'Welcome to our community', 'Thanks for joining Mozilla! You're now part of our global community. We're here if you need help: http://mzl.la/bMDof6'),
-(2, 'Thanks for using Firefox', 'Thanks for using Firefox! You’re not just a user to us, but part of a community that''s 400M strong: http://mzl.la/9whtWo'),
-(3, 'We’re a non-profit organization', 'Hey, I’m a Mozilla volunteer. Did you know Mozilla is made up of 1000s of us worldwide? More here: http://mzl.la/cvlwvd'),
-(4, 'Tip & tricks', 'Need help getting started with Firefox? Here are some tips & tricks for getting the most out of it: http://mzl.la/c0B9P2'),
-(5, 'Customize Firefox with add-ons', 'Have you tried add-ons? Cool apps for shopping, music, news, whatever you do online. Start here: http://mzl.la/blOuoD'),
-(6, 'Add-on reviews', 'Just getting started with Firefox? Add-ons personalize it with cool features & function. User reviews: http://mzl.la/cGypVI'),
-(7, 'Upgrade Firefox', 'Hey, maybe you need to upgrade Firefox? New version is speedier with a lot more going on. Download: http://mzl.la/9wJe30'),
-(8, 'Update plugins and add-ons', 'Have you updated your plug-ins and add-ons? Should work out the kinks. Here’s the place to refresh: http://mzl.la/cGCg12'),
-(9, 'Try the Beta', 'Try the future of Firefox! We need your help testing the new beta. Hop on board: http://mzl.la/d23n7a'),
-(10, 'Firefox Sync', 'Tried Firefox Sync? It’s awesome! Switch computers & it saves open tabs, pwords, history. Try it: http://mzl.la/aHHUYA'),
-(11, 'Firefox Panorama', 'Heard about Firefox Panorama? It groups and displays your tabs, eliminating clutter. Give it a whirl! http://mzl.la/d21MyY'),
-(12, 'Fix crashes', 'Sorry your Firefox is crashing. Here’s a list of quick fixes to prevent that from happening again: http://mzl.la/atSsFt'),
-(13, 'Slow Firefox startup', 'Slow start could mean your Firefox just needs a refresh. Here are tips to make Firefox load faster: http://mzl.la/9bB1FY'),
-(14, 'Quick Firefox fixes', 'Have you tried Firefox support? If their quick fixes don’t solve it, volunteers are ready to help out: http://mzl.la/9V9uWd'),
-(15, 'Ask SUMO', 'Maybe ask SUMO about this issue? Firefox’s community support team. They’ll know what’s up: http://mzl.la/bMDof6'),
-(16, 'Get involved with Mozilla', 'Want a better web? Join the Mozilla movement. There is something to do for everyone. Get started: http://mzl.la/cufJmX'),
-(17, 'Join Drumbeat', 'Want to spark a movement? Mozilla Drumbeat is your chance to keep the web open and free. More info: http://mzl.la/aIXCLA'),
-(18, 'Become a beta tester', 'Become a beta tester! Help develop the next Firefox. You don’t have to be a techie to contribute: http://mzl.la/d23n7a'),
-(19, 'Mozilla Developer Network', 'Open up the web & make it better! Build web pages, apps and add-ons here: Mozilla Developer Network http://mzl.la/9gQfrn'),
-(20, 'Report a bug', 'Thanks for finding a bug. It makes everyone’s Firefox experience better if you report it. It''s easy: http://mzl.la/bcujVc');
+(1, "Welcome to our community", "Thanks for joining Mozilla! You're now part of our global community. We're here if you need help: http://mzl.la/bMDof6r"),
+(2, "Thanks for using Firefox", "Thanks for using Firefox! You're not just a user to us, but part of a community that's 400M strong: http://mzl.la/9whtWor"),
+(3, "We're a non-profit organization", "Hey, I'm a Mozilla volunteer. Did you know Mozilla is made up of 1000s of us worldwide? More here: http://mzl.la/cvlwvd"),
+(4, "Tip & tricks", "Need help getting started with Firefox? Here are some tips & tricks for getting the most out of it: http://mzl.la/c0B9P2"),
+(5, "Customize Firefox with add-ons", "Have you tried add-ons? Cool apps for shopping, music, news, whatever you do online. Start here: http://mzl.la/blOuoD"),
+(6, "Add-on reviews", "Just getting started with Firefox? Add-ons personalize it with cool features & function. User reviews: http://mzl.la/cGypVI"),
+(7, "Upgrade Firefox", "Hey, maybe you need to upgrade Firefox? New version is speedier with a lot more going on. Download: http://mzl.la/9wJe30"),
+(8, "Update plugins and add-ons", "Have you updated your plug-ins and add-ons? Should work out the kinks. Here's the place to refresh: http://mzl.la/cGCg12"),
+(9, "Try the Beta", "Try the future of Firefox! We need your help testing the new beta. Hop on board: http://mzl.la/d23n7a"),
+(10, "Firefox Sync", "Tried Firefox Sync? It's awesome! Switch computers & it saves open tabs, pwords, history. Try it: http://mzl.la/aHHUYA"),
+(11, "Firefox Panorama", "Heard about Firefox Panorama? It groups and displays your tabs, eliminating clutter. Give it a whirl! http://mzl.la/d21MyY"),
+(12, "Fix crashes", "Sorry your Firefox is crashing. Here's a list of quick fixes to prevent that from happening again: http://mzl.la/atSsFt"),
+(13, "Slow Firefox startup", "Slow start could mean your Firefox just needs a refresh. Here are tips to make Firefox load faster: http://mzl.la/9bB1FY"),
+(14, "Quick Firefox fixes", "Have you tried Firefox support? If their quick fixes don't solve it, volunteers are ready to help out: http://mzl.la/9V9uWd"),
+(15, "Ask SUMO", "Maybe ask SUMO about this issue? Firefox's community support team. They'll know what's up: http://mzl.la/bMDof6"),
+(16, "Get involved with Mozilla", "Want a better web? Join the Mozilla movement. There is something to do for everyone. Get started: http://mzl.la/cufJmX"),
+(17, "Join Drumbeat", "Want to spark a movement? Mozilla Drumbeat is your chance to keep the web open and free. More info: http://mzl.la/aIXCLA"),
+(18, "Become a beta tester", "Become a beta tester! Help develop the next Firefox. You don't have to be a techie to contribute: http://mzl.la/d23n7a"),
+(19, "Mozilla Developer Network", "Open up the web & make it better! Build web pages, apps and add-ons here: Mozilla Developer Network http://mzl.la/9gQfrn"),
+(20, "Report a bug", "Thanks for finding a bug. It makes everyone's Firefox experience better if you report it. It''s easy: http://mzl.la/bcujVc");
INSERT INTO `customercare_categorymembership` (`id`, `category_id`, `response_id`, `weight`) VALUES
(1, 1, 1, 0),
diff --git a/requirements/prod.txt b/requirements/prod.txt
index f42d2a403c1..a4f5676294f 100644
--- a/requirements/prod.txt
+++ b/requirements/prod.txt
@@ -22,3 +22,5 @@ pytz
-e git://github.com/jsocol/commonware.git#egg=commonware
-e git://github.com/pcraciunoiu/py-wikimarkup.git#egg=py-wikimarkup
-e hg+http://bitbucket.org/danfairs/django-authority#egg=django_authority
+
+-e git://github.com/joshthecoder/tweepy.git#egg=tweepy
diff --git a/settings.py b/settings.py
index 04c301ca502..3ad289be6db 100644
--- a/settings.py
+++ b/settings.py
@@ -291,6 +291,11 @@ def JINJA_CONFIG():
'ie': (
'css/ie.css',
),
+ 'customercare': (
+ 'css/jqueryui/jquery.ui.core.css',
+ 'css/jqueryui/jquery.ui.theme.css',
+ 'css/customercare.css',
+ ),
},
'js': {
'common': (
@@ -322,6 +327,12 @@ def JINJA_CONFIG():
),
'gallery': (
),
+ 'customercare': (
+ 'js/libs/jqueryui.min.js',
+ 'js/libs/jquery.NobleCount.js',
+ 'js/libs/jquery.cookie.js',
+ 'js/customercare.js',
+ ),
},
}
@@ -426,3 +437,6 @@ def JINJA_CONFIG():
# Customare care tweet collection settings
CC_MAX_TWEETS = 500 # Max. no. of tweets in DB
CC_TWEETS_PERPAGE = 100 # How many tweets to collect in one go. Max: 100.
+
+TWITTER_CONSUMER_KEY = ''
+TWITTER_CONSUMER_SECRET = ''
diff --git a/urls.py b/urls.py
index 406ec5228c0..0872a4e6cc1 100644
--- a/urls.py
+++ b/urls.py
@@ -19,7 +19,7 @@
(r'^upload', include('upload.urls')),
(r'^kb', include('wiki.urls')),
(r'^gallery', include('gallery.urls')),
- (r'^customercare', include('customercare.urls')),
+ (r'^army-of-awesome', include('customercare.urls')),
# Kitsune admin (not Django admin).
(r'^admin/', include('kadmin.urls')),