Permalink
Browse files

Initial commit.

  • Loading branch information...
0 parents commit b0d3b1dc03d1e7cfdbc674175a28437fad68e258 @Osmose Osmose committed Apr 12, 2012
9 README.md
@@ -0,0 +1,9 @@
+# about:home Snippets
+
+This repo contains code for non-standard and interactive snippets that we share
+on the about:home page in Firefox.
+
+## License
+
+Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/
6 blank-snippet/.gitignore
@@ -0,0 +1,6 @@
+build/*
+!build/.gitignore
+*~
+*.pyc
+.snippetconfig
+*.pyc
9 blank-snippet/README.md
@@ -0,0 +1,9 @@
+# Blank Snippet
+
+This snippet, when displayed, blanks out the snippet box so that it is not
+shown to the user. This apparently increases conversion rates for other
+snippets.
+
+See the
+[Snippet Development Template](https://github.com/Osmose/snippet-dev-template)
+for info on how to work on this.
0 blank-snippet/build/.gitignore
No changes.
0 blank-snippet/content.html
No changes.
0 blank-snippet/css/.gitignore
No changes.
290 blank-snippet/fabfile.py
@@ -0,0 +1,290 @@
+# TODO: Determine how to organize code
+# TODO: Figure out a better way to edit the DB
+
+import base64
+import ConfigParser
+import os
+import pystache
+import select
+import sqlite3
+
+from fabric.decorators import task
+from glob import glob
+from os.path import isfile
+
+BUILD_DIR = 'build'
+BUILD_JS_FILE = BUILD_DIR + '/compiled.min.js'
+BUILD_CSS_FILE = BUILD_DIR + '/compiled.css'
+BUILD_CONTENT_FILE = BUILD_DIR + '/compiled.html'
+BUILD_OUT_FILE = BUILD_DIR + '/snippet.html'
+
+SCRIPTS_DIR = 'scripts'
+CSS_DIR = 'css'
+IMAGES_DIR = 'images'
+
+SNIPPET_TEMPLATE_FILE = 'snippet_template.html'
+SNIPPET_CONTENT_FILE = 'content.html'
+
+config = ConfigParser.ConfigParser()
+config.read('.snippetconfig')
+database_present = config.has_section('Database')
+
+
+@task
+def monitor_build_push():
+ """
+ Monitors the current directory for changes and pushes when they happen
+ """
+
+ monitor = DirectoryMonitor('./')
+ print "Monitoring for changes..."
+
+ while True:
+ if monitor.is_directory_changed():
+ print "Change detected, pushing..."
+ build_all_push()
+
+
+class DirectoryMonitor:
+ def __init__(self, path):
+ directories = os.walk(path)
+
+ self.kitems = []
+ self.kq = select.kqueue()
+ for d in directories:
+ # Ignore .git and the like
+ # TODO: Handle embedded dot directories
+ if d[0].startswith('./.'):
+ continue
+
+ # Setup kqueue for monitoring
+ directory = os.open(d[0], os.O_RDONLY)
+ ke = select.kevent(directory, filter=select.KQ_FILTER_VNODE,
+ flags=select.KQ_EV_ADD | select.KQ_EV_ENABLE |
+ select.KQ_EV_CLEAR,
+ fflags=select.KQ_NOTE_DELETE |
+ select.KQ_NOTE_WRITE)
+ self.kq.control([ke], 0, None)
+ self.kitems.append((ke, directory))
+
+ def __del__(self):
+ for (ke, directory) in self.kitems:
+ directory.close()
+
+ def is_directory_changed(self):
+ for (ke, directory) in self.kitems:
+ raised_events = self.kq.control([ke], 1, None)
+ for event in raised_events:
+ if event.fflags & (select.KQ_NOTE_DELETE |
+ select.KQ_NOTE_WRITE):
+ return True
+
+ return False
+
+
+@task
+def build_all_push():
+ build_all()
+ push_to_db()
+
+
+@task
+def push_to_db():
+ """
+ Checks for database configuration info and pushes the last built snippet
+ to the specified database
+ """
+
+ if (database_present):
+ with open(BUILD_OUT_FILE, 'r') as snippet:
+ conn = sqlite3.connect(config.get('Database', 'db_path'))
+ conn.execute("""
+ UPDATE homesnippets_snippet
+ SET body=?
+ WHERE id=?
+ """, (snippet.read(), config.get('Database', 'snippet_id')))
+ conn.commit()
+ conn.close()
+
+
+@task
+def db_setup():
+ """Setup database details and create snippet to push updates to."""
+
+ if not config.has_section('Database'):
+ config.add_section('Database')
+
+ db_path = raw_input('Enter the absolute path to the sqlite '
+ 'database file: ')
+
+ while not _test_sqlite3_db(db_path):
+ db_path = raw_input('Error validating database. Enter absolute path'
+ ' to database file (blank to quit setup): ')
+ if not db_path:
+ return
+
+ config.set('Database', 'db_path', db_path)
+
+ # Set up snippet to push to
+ conn = sqlite3.connect(db_path)
+
+ # Create Snippet
+ conn.execute("""
+ INSERT INTO homesnippets_clientmatchrule (description, exclude,
+ created, modified)
+ VALUES ('Matches Anything', 0, datetime('now'), datetime('now'))
+ """)
+ client_rule_id = _get_last_insert_rowid(conn)
+
+ # Create client rule
+ conn.execute("""
+ INSERT INTO homesnippets_snippet (name, body, disabled, preview,
+ created, modified)
+ VALUES (?, '', 0, 0, datetime('now'), datetime('now'))
+ """, ('Autogenerated Snippet',))
+ snippet_id = _get_last_insert_rowid(conn)
+
+ # Associate snippet with rule
+ conn.execute("""
+ INSERT INTO homesnippets_snippet_client_match_rules
+ (snippet_id, clientmatchrule_id)
+ VALUES (?, ?)
+ """, (snippet_id, client_rule_id))
+
+ conn.commit()
+ conn.close()
+
+ config.set('Database', 'snippet_id', snippet_id)
+ config.set('Database', 'client_rule_id', client_rule_id)
+
+ # TODO: Handle failure better
+ with open('.snippetconfig', 'w') as f:
+ config.write(f)
+
+
+def _get_last_insert_rowid(conn):
+ cursor = conn.execute('SELECT last_insert_rowid()')
+ result = cursor.fetchone()
+ if result is not None:
+ return result[0]
+ else:
+ return None
+
+
+def _test_sqlite3_db(db_path):
+ """Very basic test for validity of database."""
+
+ # Check for file existance and if it's a sqlite3 db
+ if isfile(db_path):
+ try:
+ conn = sqlite3.connect(db_path)
+
+ # TODO: If we really care, do a more thorough check
+ # If it has the homesnippets_snippet table we're content
+ tables = [r[0] for r in conn.execute("""
+ SELECT name FROM sqlite_master
+ WHERE type='table'
+ """).fetchall()]
+ conn.close()
+
+ if 'homesnippets_snippet' in tables:
+ return True
+ except sqlite3.DatabaseError:
+ pass
+
+ return False
+
+
+@task
+def build_all():
+ combine_js()
+ combine_css()
+ build_content()
+ build_snippet()
+
+
+@task
+def combine_js():
+ """Combines every .js file in the SCRIPTS_DIR into one script."""
+
+ _combine_files(SCRIPTS_DIR + '/*.js', BUILD_JS_FILE)
+
+
+@task
+def combine_css():
+ """Combines every .css file in the CSS_DIR into one file."""
+
+ _combine_files(CSS_DIR + '/*.css', BUILD_CSS_FILE)
+
+
+def _combine_files(glob_mask, combined_file_name):
+ """Combines all files that match glob_mask into one file."""
+
+ files = []
+ for file in glob(glob_mask):
+ with open(file, 'r') as f:
+ files.append(f.read())
+
+ with open(combined_file_name, 'w') as f:
+ f.write('\n'.join(files))
+
+
+@task
+def build_content():
+ with open(BUILD_CONTENT_FILE, 'w') as f:
+ f.write(ContentView().render())
+
+
+@task
+def build_snippet():
+ """
+ Combines the compiled JS, CSS, and content into the template and outputs
+ the result.
+ """
+ template = SnippetView({
+ 'css': BUILD_CSS_FILE,
+ 'js': BUILD_JS_FILE,
+ 'content': BUILD_CONTENT_FILE
+ })
+
+ with open(BUILD_OUT_FILE, 'w') as output:
+ output.write(template.render())
+
+
+class SnippetView(pystache.View):
+ template_file = SNIPPET_TEMPLATE_FILE
+
+ def __init__(self, filenames):
+ super(SnippetView, self).__init__()
+ self._filenames = filenames
+
+ def __getattr__(self, item):
+ try:
+ return self._loadfile(item)
+ except KeyError:
+ raise AttributeError
+
+ def _loadfile(self, key):
+ with open(self._filenames[key], 'r') as f:
+ return f.read()
+
+
+class ContentView(pystache.View):
+ template_file = SNIPPET_CONTENT_FILE
+
+ def base64img(self, text=None):
+ return self._base64img
+
+ def _base64img(self, text=''):
+ filename = text.strip()
+ if filename.endswith('png'):
+ mimetype = 'image/png'
+ elif filename.endswith(('jpg', 'jpeg')):
+ mimetype = 'image/jpeg'
+ else:
+ return ''
+
+ with open(IMAGES_DIR + '/' + filename, 'r') as f:
+ data = base64.encodestring(f.read())
+
+ return 'data:%s;base64,%s' % (mimetype, data)
0 blank-snippet/images/.gitignore
No changes.
2 blank-snippet/requirements.txt
@@ -0,0 +1,2 @@
+Fabric==1.1.1
+pystache==0.3.1
0 blank-snippet/scripts/.gitignore
No changes.
7 blank-snippet/scripts/blank.js
@@ -0,0 +1,7 @@
+(function() {
+ var snippet = document.getElementById('blankSnippet');
+ snippet.addEventListener('show_snippet', function(e) {
+ document.getElementById('snippets').style.display = 'none';
+ document.getElementById('contentContainer').style.backgroundImage = 'none';
+ });
+})();
6 blank-snippet/snippet_template.html
@@ -0,0 +1,6 @@
+<script>
+ //<![CDATA[
+ {{& js}}
+ //]]>
+</script>
+<div id="blankSnippet" class="snippet"></div>
7 sopa/.gitignore
@@ -0,0 +1,7 @@
+build/*
+!build/.gitignore
+*~
+*.pyc
+.snippetconfig
+*.pyc
+.DS_Store
8 sopa/README.md
@@ -0,0 +1,8 @@
+# SOPA Blackout Snippet
+
+This is the blackout snippet we used to black out the about:home page for
+every Firefox user on January 18th.
+
+See the
+[Snippet Development Template](https://github.com/Osmose/snippet-dev-template)
+for info on how to work on this.
0 sopa/build/.gitignore
No changes.
7 sopa/content.html
@@ -0,0 +1,7 @@
+<p>Mozilla is joining the virtual strike against Internet censorship. Help us fight SOPA/PIPA - <a href="https://donate.mozilla.org/SOPA-snippet">take action today!</a></p>
+
+<div id="doom_container">
+ <a id="black_bar_of_doom" href="http://www.mozilla.org/sopa/">
+ <img src="{{#base64img}}stop_censorship.png{{/base64img}}" />
+ </a>
+</div>
0 sopa/css/.gitignore
No changes.
42 sopa/css/styles.css
@@ -0,0 +1,42 @@
+#doom_container {
+ display: none;
+}
+
+#brandStart {
+ position: relative;
+}
+
+#black_bar_of_doom {
+ position: absolute;
+ left: -moz-calc(50% - 113px);
+ top: -moz-calc(47% - 16px);
+ -moz-transform: rotate(-5deg);
+}
+
+body {
+ -moz-transition: 1s linear;
+}
+
+body.dark {
+ background: #181818;
+}
+
+body.dark #searchText {
+ background: #181818;
+ color: #BBB;
+}
+
+body.dark #contentContainer {
+ background: none;
+}
+
+body.dark a {
+ color: #990000;
+}
+
+body.dark #snippets,
+body.dark #snippets:hover,
+body.dark #snippets:hover:active {
+ color: #BBB;
+ background: none;
+}
290 sopa/fabfile.py
@@ -0,0 +1,290 @@
+# TODO: Determine how to organize code
+# TODO: Figure out a better way to edit the DB
+
+import base64
+import ConfigParser
+import os
+import pystache
+import select
+import sqlite3
+
+from fabric.decorators import task
+from glob import glob
+from os.path import isfile
+
+BUILD_DIR = 'build'
+BUILD_JS_FILE = BUILD_DIR + '/compiled.min.js'
+BUILD_CSS_FILE = BUILD_DIR + '/compiled.css'
+BUILD_CONTENT_FILE = BUILD_DIR + '/compiled.html'
+BUILD_OUT_FILE = BUILD_DIR + '/snippet.html'
+
+SCRIPTS_DIR = 'scripts'
+CSS_DIR = 'css'
+IMAGES_DIR = 'images'
+
+SNIPPET_TEMPLATE_FILE = 'snippet_template.html'
+SNIPPET_CONTENT_FILE = 'content.html'
+
+config = ConfigParser.ConfigParser()
+config.read('.snippetconfig')
+database_present = config.has_section('Database')
+
+
+@task
+def monitor_build_push():
+ """
+ Monitors the current directory for changes and pushes when they happen
+ """
+
+ monitor = DirectoryMonitor('./')
+ print "Monitoring for changes..."
+
+ while True:
+ if monitor.is_directory_changed():
+ print "Change detected, pushing..."
+ build_all_push()
+
+
+class DirectoryMonitor:
+ def __init__(self, path):
+ directories = os.walk(path)
+
+ self.kitems = []
+ self.kq = select.kqueue()
+ for d in directories:
+ # Ignore .git and the like
+ # TODO: Handle embedded dot directories
+ if d[0].startswith('./.'):
+ continue
+
+ # Setup kqueue for monitoring
+ directory = os.open(d[0], os.O_RDONLY)
+ ke = select.kevent(directory, filter=select.KQ_FILTER_VNODE,
+ flags=select.KQ_EV_ADD | select.KQ_EV_ENABLE |
+ select.KQ_EV_CLEAR,
+ fflags=select.KQ_NOTE_DELETE |
+ select.KQ_NOTE_WRITE)
+ self.kq.control([ke], 0, None)
+ self.kitems.append((ke, directory))
+
+ def __del__(self):
+ for (ke, directory) in self.kitems:
+ directory.close()
+
+ def is_directory_changed(self):
+ for (ke, directory) in self.kitems:
+ raised_events = self.kq.control([ke], 1, None)
+ for event in raised_events:
+ if event.fflags & (select.KQ_NOTE_DELETE |
+ select.KQ_NOTE_WRITE):
+ return True
+
+ return False
+
+
+@task
+def build_all_push():
+ build_all()
+ push_to_db()
+
+
+@task
+def push_to_db():
+ """
+ Checks for database configuration info and pushes the last built snippet
+ to the specified database
+ """
+
+ if (database_present):
+ with open(BUILD_OUT_FILE, 'r') as snippet:
+ conn = sqlite3.connect(config.get('Database', 'db_path'))
+ conn.execute("""
+ UPDATE homesnippets_snippet
+ SET body=?
+ WHERE id=?
+ """, (snippet.read(), config.get('Database', 'snippet_id')))
+ conn.commit()
+ conn.close()
+
+
+@task
+def db_setup():
+ """Setup database details and create snippet to push updates to."""
+
+ if not config.has_section('Database'):
+ config.add_section('Database')
+
+ db_path = raw_input('Enter the absolute path to the sqlite '
+ 'database file: ')
+
+ while not _test_sqlite3_db(db_path):
+ db_path = raw_input('Error validating database. Enter absolute path'
+ ' to database file (blank to quit setup): ')
+ if not db_path:
+ return
+
+ config.set('Database', 'db_path', db_path)
+
+ # Set up snippet to push to
+ conn = sqlite3.connect(db_path)
+
+ # Create Snippet
+ conn.execute("""
+ INSERT INTO homesnippets_clientmatchrule (description, exclude,
+ created, modified)
+ VALUES ('Matches Anything', 0, datetime('now'), datetime('now'))
+ """)
+ client_rule_id = _get_last_insert_rowid(conn)
+
+ # Create client rule
+ conn.execute("""
+ INSERT INTO homesnippets_snippet (name, body, disabled, preview,
+ created, modified)
+ VALUES (?, '', 0, 0, datetime('now'), datetime('now'))
+ """, ('Autogenerated Snippet',))
+ snippet_id = _get_last_insert_rowid(conn)
+
+ # Associate snippet with rule
+ conn.execute("""
+ INSERT INTO homesnippets_snippet_client_match_rules
+ (snippet_id, clientmatchrule_id)
+ VALUES (?, ?)
+ """, (snippet_id, client_rule_id))
+
+ conn.commit()
+ conn.close()
+
+ config.set('Database', 'snippet_id', snippet_id)
+ config.set('Database', 'client_rule_id', client_rule_id)
+
+ # TODO: Handle failure better
+ with open('.snippetconfig', 'w') as f:
+ config.write(f)
+
+
+def _get_last_insert_rowid(conn):
+ cursor = conn.execute('SELECT last_insert_rowid()')
+ result = cursor.fetchone()
+ if result is not None:
+ return result[0]
+ else:
+ return None
+
+
+def _test_sqlite3_db(db_path):
+ """Very basic test for validity of database."""
+
+ # Check for file existance and if it's a sqlite3 db
+ if isfile(db_path):
+ try:
+ conn = sqlite3.connect(db_path)
+
+ # TODO: If we really care, do a more thorough check
+ # If it has the homesnippets_snippet table we're content
+ tables = [r[0] for r in conn.execute("""
+ SELECT name FROM sqlite_master
+ WHERE type='table'
+ """).fetchall()]
+ conn.close()
+
+ if 'homesnippets_snippet' in tables:
+ return True
+ except sqlite3.DatabaseError:
+ pass
+
+ return False
+
+
+@task
+def build_all():
+ combine_js()
+ combine_css()
+ build_content()
+ build_snippet()
+
+
+@task
+def combine_js():
+ """Combines every .js file in the SCRIPTS_DIR into one script."""
+
+ _combine_files(SCRIPTS_DIR + '/*.js', BUILD_JS_FILE)
+
+
+@task
+def combine_css():
+ """Combines every .css file in the CSS_DIR into one file."""
+
+ _combine_files(CSS_DIR + '/*.css', BUILD_CSS_FILE)
+
+
+def _combine_files(glob_mask, combined_file_name):
+ """Combines all files that match glob_mask into one file."""
+
+ files = []
+ for file in glob(glob_mask):
+ with open(file, 'r') as f:
+ files.append(f.read())
+
+ with open(combined_file_name, 'w') as f:
+ f.write('\n'.join(files))
+
+
+@task
+def build_content():
+ with open(BUILD_CONTENT_FILE, 'w') as f:
+ f.write(ContentView().render())
+
+
+@task
+def build_snippet():
+ """
+ Combines the compiled JS, CSS, and content into the template and outputs
+ the result.
+ """
+ template = SnippetView({
+ 'css': BUILD_CSS_FILE,
+ 'js': BUILD_JS_FILE,
+ 'content': BUILD_CONTENT_FILE
+ })
+
+ with open(BUILD_OUT_FILE, 'w') as output:
+ output.write(template.render())
+
+
+class SnippetView(pystache.View):
+ template_file = SNIPPET_TEMPLATE_FILE
+
+ def __init__(self, filenames):
+ super(SnippetView, self).__init__()
+ self._filenames = filenames
+
+ def __getattr__(self, item):
+ try:
+ return self._loadfile(item)
+ except KeyError:
+ raise AttributeError
+
+ def _loadfile(self, key):
+ with open(self._filenames[key], 'r') as f:
+ return f.read()
+
+
+class ContentView(pystache.View):
+ template_file = SNIPPET_CONTENT_FILE
+
+ def base64img(self, text=None):
+ return self._base64img
+
+ def _base64img(self, text=''):
+ filename = text.strip()
+ if filename.endswith('png'):
+ mimetype = 'image/png'
+ elif filename.endswith(('jpg', 'jpeg')):
+ mimetype = 'image/jpeg'
+ else:
+ return ''
+
+ with open(IMAGES_DIR + '/' + filename, 'r') as f:
+ data = base64.encodestring(f.read())
+
+ return 'data:%s;base64,%s' % (mimetype, data)
0 sopa/images/.gitignore
No changes.
BIN sopa/images/bg.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN sopa/images/icon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN sopa/images/stop_censorship.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 sopa/requirements.txt
@@ -0,0 +1,2 @@
+Fabric==1.1.1
+pystache==0.3.1
0 sopa/scripts/.gitignore
No changes.
17 sopa/scripts/code.js
@@ -0,0 +1,17 @@
+(function() {
+ var brand_start = document.getElementById('brandStart'),
+ doom_container = document.getElementById('doom_container'),
+ bar = document.getElementById('black_bar_of_doom');
+
+
+var date = new Date(),
+ gmt = date.getTime() + (date.getTimezoneOffset() * 60),
+ est = gmt - (5 * 60 * 60),
+ end = new Date('Wed, 18 Jan 2012 20:00:00 EST').getTime();
+
+ if (est < end) {
+ doom_container.removeChild(bar);
+ brand_start.appendChild(bar);
+ document.body.className = 'dark';
+ }
+})();
11 sopa/snippet_template.html
@@ -0,0 +1,11 @@
+<div class="snippet">
+ <style>
+{{& css}}
+ </style>
+{{& content}}
+ <script>
+ //<![CDATA[
+{{& js}}
+ //]]>
+ </script>
+</div>
4 standard_js/README.md
@@ -0,0 +1,4 @@
+# Standard Snippet JS
+
+This is the Javascript that is always sent by the snippets service. It's main
+purpose is to randomly choose a snippet and display it to the user.
41 standard_js/script.js
@@ -0,0 +1,41 @@
+//<![CDATA[
+var snippet_container = document.getElementById('snippetContainer');
+var snippets = snippet_container.getElementsByClassName('snippet');
+if (snippets.length > 0) {
+ var show_snippet = snippets[Math.floor(Math.random()*snippets.length)];
+ show_snippet.style.display = 'block';
+ try {
+ activateSnippetsButtonClick(show_snippet);
+ } catch (err) {
+ // Do nothing, most likely a newer version w/o
+ // activateSnippetsButtonClick
+ }
+
+ // Send impression to the snippets stats server.
+ var snippet_id = show_snippet.parentNode.dataset.snippetId;
+ send_impression(snippet_id);
+
+ // Trigger show_snippet event on snippet node.
+ var evt = document.createEvent('Event');
+ evt.initEvent('show_snippet', true, true);
+ show_snippet.dispatchEvent(evt);
+} else {
+ localStorage['snippets'] = '';
+ showSnippets();
+}
+
+// Notifies stats server that the given snippet ID
+// was shown. No personally-identifiable information
+// is sent.
+function send_impression(id) {
+ var sample_rate = 0.01;
+ var url = 'https://snippets-stats.mozilla.org/foo.html';
+
+ if (Math.random() <= sample_rate) {
+ var locale = navigator.language;
+ var r = XMLHttpRequest();
+ r.open('POST', url + '?locale=' + locale + '&snippet_name=' + id);
+ r.send();
+ }
+}
+//]]>

0 comments on commit b0d3b1d

Please sign in to comment.