diff --git a/.gitignore b/.gitignore index 40cc63e..921016e 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,7 @@ myconfig.json db.sqlite node_modules comments.xml +stacosys/bin/ +stacosys/pyvenv.cfg +stacosys/lib64 +.vscode/ \ No newline at end of file diff --git a/README.md b/README.md index db998c1..b8dffd5 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Stacosys (aka STAtic blog COmment SYStem) is a fork of [Pecosys](http://github.com/kianby/pecosys) trying to fix Pecosys design drawbacks and to provide an alternative to hosting services like Disqus. Stacosys protects your readers's privacy. Stacosys works with any static blog or even a simple HTML page. It privilegiates e-mails to communicate with the blog administrator. It doesn't sound *hype* but I'm an old-school guy ;-) E-mail is reliable and an -universal way to discuss. You can answer from any device using an e-mail client or a Webmail. +universal way to discuss. You can answer from any device using an e-mail client. ### Features overview @@ -16,25 +16,17 @@ Here is the workflow: comment is submitted - Blog administrator can approve or drop the comment by replying to e-mail - Stacosys stores approved comment in its database. - -Moreover Stacosys has an additional feature: readers can subscribe to further -comments for a post if they have provided an email. Stacosys is localized (english and french). ### Technically speaking, how does it work? -Stacosys can be hosted on the same server or on a different server than the blog. Stacosys offers a REST API to retrieve and post comments. Static blog is HTML-based and a piece of JavaScript code interacts with Stacosys using [CORS](http://enable-cors.org) requests. Each blog has a unique ID. Thus Stacosys can serve multiple blogs. Each page has a unique id and a simple request allows to retrieve comments for a given page. Similarly a POST request allows to send a comment from reader browser to Stacosy server. The comment post is relayed to the administrator by e-mail. for this purpose a dedicated email is assigned to Stacosys to communicate with blog administrator and blog subscribers. +Stacosys can be hosted on the same server or on a different server than the blog. Stacosys offers a REST API to retrieve and post comments. Static blog is HTML-based and a piece of JavaScript code interacts with Stacosys using HTTP requests. Each page has a unique id and a simple request allows to retrieve comments for a given page. Similarly a POST request allows to send a comment from reader browser to Stacosy server. The comment post is relayed to the administrator by e-mail. for this purpose a dedicated email is assigned to Stacosys to communicate with blog administrator and blog subscribers. ### FAQ -*So the blog needs a server-side language?* -- It depends on your hosting configuration. Stacosys can run on a different host than the blog and it can serve several blogs. You have to change JS code embedded in blog pages to point the right Stacosys API URL. - *How do you block spammers?* -- That's a huge topic. Current comment form is basic: no captcha support but a honey - pot. Nothing prevents from improving the template with JavaScript libs to do more - complex things. +- Current comment form is basic: no captcha support but a honey pot. Second defense barrier: admin can tag comment as SPAM and, for example, link stacosys log to fail2ban tool. *Which database is used?* - Thanks to Peewee ORM a wide range of databases is supported. I personnaly uses SQLite. @@ -48,4 +40,4 @@ Stacosys can be hosted on the same server or on a different server than the blog ### Ways of improvement -Current version of Stacosys fits my needs and it serves comments on [my blog](http://blogduyax.madyanne.fr) for 6 months. However Stacosys has been designed to serve several blogs and e-mail can be a constraint for some people. So an area of improvement would be to add an administration UI to configure sites, approve or reject comments, keep track of usage statistics and to make e-mail communication optional. I encourage you to fork the project and create such improvements if you need them. I'll be happy to see the project evolving and growing according to users needs. +Current version of Stacosys fits my needs and it serves comments on [my blog](https://blogduyax.madyanne.fr). However Stacosys has been designed to serve several blogs and e-mail can be a constraint for some people. So an area of improvement would be to add an administration UI to configure sites, approve or reject comments, keep track of usage statistics and get rid of e-mails. I encourage you to fork the project and create such improvements if you need them. diff --git a/app/conf/schema.py b/app/conf/schema.py index 8cca55f..f35e594 100644 --- a/app/conf/schema.py +++ b/app/conf/schema.py @@ -6,9 +6,10 @@ json_schema = """ { - "$ref": "#/definitions/Stacosys", + "$schema": "http://json-schema.org/draft-06/schema#", + "$ref": "#/definitions/Welcome", "definitions": { - "Stacosys": { + "Welcome": { "type": "object", "additionalProperties": false, "properties": { @@ -23,19 +24,15 @@ json_schema = """ }, "rss": { "$ref": "#/definitions/RSS" - }, - "rabbitmq": { - "$ref": "#/definitions/Rabbitmq" } }, "required": [ "general", "http", - "rabbitmq", "rss", "security" ], - "title": "stacosys" + "title": "Welcome" }, "General": { "type": "object", @@ -56,7 +53,7 @@ json_schema = """ "debug", "lang" ], - "title": "general" + "title": "General" }, "HTTP": { "type": "object", @@ -77,44 +74,7 @@ json_schema = """ "port", "root_url" ], - "title": "http" - }, - "Rabbitmq": { - "type": "object", - "additionalProperties": false, - "properties": { - "active": { - "type": "boolean" - }, - "host": { - "type": "string" - }, - "port": { - "type": "integer" - }, - "username": { - "type": "string" - }, - "password": { - "type": "string" - }, - "vhost": { - "type": "string" - }, - "exchange": { - "type": "string" - } - }, - "required": [ - "active", - "exchange", - "host", - "password", - "port", - "username", - "vhost" - ], - "title": "rabbitmq" + "title": "HTTP" }, "RSS": { "type": "object", @@ -131,7 +91,7 @@ json_schema = """ "file", "proto" ], - "title": "rss" + "title": "RSS" }, "Security": { "type": "object", @@ -142,17 +102,13 @@ json_schema = """ }, "secret": { "type": "string" - }, - "private": { - "type": "boolean" } }, "required": [ - "private", "salt", "secret" ], - "title": "security" + "title": "Security" } } } diff --git a/app/core/__init__.py b/app/core/__init__.py index 8a6cccb..48cee40 100644 --- a/app/core/__init__.py +++ b/app/core/__init__.py @@ -5,9 +5,9 @@ import os import sys import logging from flask import Flask -from flask.ext.cors import CORS from conf import config from jsonschema import validate +from flask_apscheduler import APScheduler app = Flask(__name__) @@ -24,8 +24,6 @@ import database import processor from interface import api from interface import form -from interface import report -from interface import rmqclient # configure logging def configure_logging(level): @@ -46,26 +44,37 @@ configure_logging(logging_level) logger = logging.getLogger(__name__) +class Config(object): + JOBS = [ + { + 'id': 'fetch_mail', + 'func': 'core.cron:fetch_mail_answers', + 'trigger': 'interval', + 'seconds': 120 + }, + { + 'id': 'submit_new_comment', + 'func': 'core.cron:submit_new_comment', + 'trigger': 'interval', + 'seconds': 60 + }, + ] + # initialize database database.setup() -# start broker client -rmqclient.start() - # start processor template_path = os.path.abspath(os.path.join(current_path, '../templates')) processor.start(template_path) -# less feature in private mode -if not config.security['private']: - # enable CORS - cors = CORS(app, resources={r"/comments/*": {"origins": "*"}}) - from app.controllers import reader - logger.debug('imported: %s ' % reader.__name__) +# cron +app.config.from_object(Config()) +scheduler = APScheduler() +scheduler.init_app(app) +scheduler.start() # tune logging level if not config.general['debug']: - logging.getLogger('app.cors').level = logging.WARNING logging.getLogger('werkzeug').level = logging.WARNING logger.info("Start Stacosys application") diff --git a/app/core/cron.py b/app/core/cron.py new file mode 100644 index 0000000..77f4077 --- /dev/null +++ b/app/core/cron.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import logging +import time +from core import app +from core import processor +from models.comment import Comment + +logger = logging.getLogger(__name__) + +def fetch_mail_answers(): + + logger.info('DEBUT POP MAIL') + time.sleep(80) + logger.info('FIN POP MAIL') + #data = request.get_json() + #logger.debug(data) + + #processor.enqueue({'request': 'new_mail', 'data': data}) + +def submit_new_comment(): + + for comment in Comment.select().where(Comment.notified.is_null()): + # render email body template + comment_list = ( + "author: %s" % comment.author_name, + "site: %s" % comment.author_site, + "date: %s" % comment.create, + "url: %s" % comment.url, + "", + "%s" % comment.message, + "", + ) + comment_text = "\n".join(comment_list) + email_body = get_template("new_comment").render(url=url, comment=comment_text) + + if clientip: + client_ips[comment.id] = clientip + + # send email + subject = "STACOSYS %s: [%d:%s]" % (site.name, comment.id, token) + mailer.send_mail(site.admin_email, subject, email_body) + logger.debug("new comment processed ") + +def get_template(name): + return env.get_template(config.general["lang"] + "/" + name + ".tpl") diff --git a/app/core/database.py b/app/core/database.py index 7f96f5d..f6797d8 100644 --- a/app/core/database.py +++ b/app/core/database.py @@ -23,7 +23,5 @@ def provide_db(func): def setup(db): from models.site import Site from models.comment import Comment - from models.reader import Reader - from models.report import Report - db.create_tables([Site, Comment, Reader, Report], safe=True) + db.create_tables([Site, Comment], safe=True) diff --git a/app/core/mailer.py b/app/core/mailer.py new file mode 100644 index 0000000..3a1213c --- /dev/null +++ b/app/core/mailer.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + diff --git a/app/core/processor.py b/app/core/processor.py index e089f8b..b94636c 100644 --- a/app/core/processor.py +++ b/app/core/processor.py @@ -4,142 +4,64 @@ import os import logging import re +import PyRSS2Gen +import markdown +import json from datetime import datetime from threading import Thread from queue import Queue from jinja2 import Environment from jinja2 import FileSystemLoader from models.site import Site -from models.reader import Reader -from models.report import Report from models.comment import Comment from helpers.hashing import md5 -import json from conf import config -from util import rabbit -import PyRSS2Gen -import markdown -import pika +from core import mailer + logger = logging.getLogger(__name__) queue = Queue() proc = None env = None -# store client IP in memory until classification +# keep client IP in memory until classified client_ips = {} class Processor(Thread): - def stop(self): logger.info("stop requested") self.is_running = False def run(self): - logger.info('processor thread started') + logger.info("processor thread started") self.is_running = True while self.is_running: try: msg = queue.get() - if msg['request'] == 'new_comment': - new_comment(msg['data'], msg.get('clientip', '')) - elif msg['request'] == 'new_mail': - reply_comment_email(msg['data']) - send_delete_command(msg['data']) - elif msg['request'] == 'unsubscribe': - unsubscribe_reader(msg['data']) - elif msg['request'] == 'report': - report(msg['data']) - elif msg['request'] == 'late_accept': - late_accept_comment(msg['data']) - elif msg['request'] == 'late_reject': - late_reject_comment(msg['data']) + if msg["request"] == "new_mail": + reply_comment_email(msg["data"]) + send_delete_command(msg["data"]) else: logger.info("throw unknown request " + str(msg)) except: logger.exception("processing failure") -def new_comment(data, clientip): - - logger.info('new comment received: %s' % data) - - token = data.get('token', '') - url = data.get('url', '') - author_name = data.get('author', '').strip() - author_email = data.get('email', '').strip() - author_site = data.get('site', '').strip() - message = data.get('message', '') - subscribe = data.get('subscribe', '') - - # private mode: email contains gravar md5 hash - if config.security['private']: - author_gravatar = author_email - author_email = '' - else: - author_gravatar = md5(author_email.lower()) - - # create a new comment row - site = Site.select().where(Site.token == token).get() - - if author_site and author_site[:4] != 'http': - author_site = 'http://' + author_site - - created = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - # add a row to Comment table - comment = Comment(site=site, url=url, author_name=author_name, - author_site=author_site, author_email=author_email, - author_gravatar=author_gravatar, - content=message, created=created, published=None) - comment.save() - - article_url = "http://" + site.url + url - - # render email body template - comment_list = ( - 'author: %s' % author_name, - 'email: %s' % author_email, - 'site: %s' % author_site, - 'date: %s' % created, - 'url: %s' % url, - '', - '%s' % message, - '' - ) - comment_text = '\n'.join(comment_list) - email_body = get_template('new_comment').render( - url=article_url, comment=comment_text) - - if clientip: - client_ips[comment.id] = clientip - - # send email - subject = 'STACOSYS %s: [%d:%s]' % (site.name, comment.id, token) - mail(site.admin_email, subject, email_body) - - # Reader subscribes to further comments - if not config.security['private'] and subscribe and author_email: - subscribe_reader(author_email, token, url) - - logger.debug("new comment processed ") - - def reply_comment_email(data): - from_email = data['from'] - subject = data['subject'] - message = '' - for part in data['parts']: - if part['content-type'] == 'text/plain': - message = part['content'] + from_email = data["from"] + subject = data["subject"] + message = "" + for part in data["parts"]: + if part["content-type"] == "text/plain": + message = part["content"] break - m = re.search('\[(\d+)\:(\w+)\]', subject) + m = re.search("\[(\d+)\:(\w+)\]", subject) if not m: - logger.warn('ignore corrupted email. No token %s' % subject) + logger.warn("ignore corrupted email. No token %s" % subject) return comment_id = int(m.group(1)) token = m.group(2) @@ -148,317 +70,110 @@ def reply_comment_email(data): try: comment = Comment.select().where(Comment.id == comment_id).get() except: - logger.warn('unknown comment %d' % comment_id) + logger.warn("unknown comment %d" % comment_id) return if comment.published: - logger.warn('ignore already published email. token %d' % comment_id) + logger.warn("ignore already published email. token %d" % comment_id) return if comment.site.token != token: - logger.warn('ignore corrupted email. Unknown token %d' % comment_id) + logger.warn("ignore corrupted email. Unknown token %d" % comment_id) return if not message: - logger.warn('ignore empty email') + logger.warn("ignore empty email") return # safe logic: no answer or unknown answer is a go for publishing - if message[:2].upper() in ('NO', 'SP'): + if message[:2].upper() in ("NO", "SP"): # put a log to help fail2ban - if message[:2].upper() == 'SP': # SPAM + if message[:2].upper() == "SP": # SPAM if comment_id in client_ips: - logger.info('SPAM comment from %s: %d' % - (client_ips[comment_id], comment_id)) + logger.info( + "SPAM comment from %s: %d" % (client_ips[comment_id], comment_id) + ) else: - logger.info('cannot identify SPAM source: %d' % comment_id) + logger.info("cannot identify SPAM source: %d" % comment_id) # forget client IP if comment_id in client_ips: del client_ips[comment_id] - # report event - report_rejected(comment) - - logger.info('discard comment: %d' % comment_id) + logger.info("discard comment: %d" % comment_id) comment.delete_instance() - email_body = get_template('drop_comment').render(original=message) - mail(from_email, 'Re: ' + subject, email_body) + email_body = get_template("drop_comment").render(original=message) + mail(from_email, "Re: " + subject, email_body) else: - # report event - report_published(comment) - # update Comment row comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") comment.save() - logger.info('commit comment: %d' % comment_id) + logger.info("commit comment: %d" % comment_id) # rebuild RSS rss(token) # send approval confirmation email to admin - email_body = get_template('approve_comment').render(original=message) - mail(from_email, 'Re: ' + subject, email_body) - - # notify reader once comment is published - if not config.security['private']: - reader_email = get_email_metadata(message) - if reader_email: - notify_reader(from_email, reader_email, comment.site.token, - comment.site.url, comment.url) - - # notify subscribers every time a new comment is published - notify_subscribed_readers( - comment.site.token, comment.site.url, comment.url) - - -def late_reject_comment(id): - - # retrieve site and comment rows - comment = Comment.select().where(Comment.id == id).get() - - # report event - report_rejected(comment) - - # delete Comment row - comment.delete_instance() - - logger.info('late reject comment: %s' % id) - - -def late_accept_comment(id): - - # retrieve site and comment rows - comment = Comment.select().where(Comment.id == id).get() - - # report event - report_published(comment) - - # update Comment row - comment.published = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - comment.save() - - logger.info('late accept comment: %s' % id) + email_body = get_template("approve_comment").render(original=message) + mail(from_email, "Re: " + subject, email_body) def get_email_metadata(message): # retrieve metadata reader email from email body sent by admin email = "" - m = re.search('email:\s(.+@.+\..+)', message) + m = re.search(r"email:\s(.+@.+\..+)", message) if m: email = m.group(1) return email -def subscribe_reader(email, token, url): - logger.info('subscribe reader %s to %s [%s]' % (email, url, token)) - recorded = Reader.select().join(Site).where(Site.token == token, - Reader.email == email, - Reader.url == url).count() - if recorded: - logger.debug('reader %s is already recorded' % email) - else: - site = Site.select().where(Site.token == token).get() - reader = Reader(site=site, email=email, url=url) - reader.save() - - # report event - report_subscribed(reader) - - -def unsubscribe_reader(data): - token = data.get('token', '') - url = data.get('url', '') - email = data.get('email', '') - logger.info('unsubscribe reader %s from %s (%s)' % (email, url, token)) - for reader in Reader.select().join(Site).where(Site.token == token, - Reader.email == email, - Reader.url == url): - # report event - report_unsubscribed(reader) - - reader.delete_instance() - - -def notify_subscribed_readers(token, site_url, url): - logger.info('notify subscribers for %s (%s)' % (url, token)) - article_url = "http://" + site_url + url - for reader in Reader.select().join(Site).where(Site.token == token, - Reader.url == url): - to_email = reader.email - logger.info('notify reader %s' % to_email) - unsubscribe_url = '%s/unsubscribe?email=%s&token=%s&url=%s' % ( - config.http['root_url'], to_email, token, reader.url) - email_body = get_template( - 'notify_subscriber').render(article_url=article_url, - unsubscribe_url=unsubscribe_url) - subject = get_template('notify_message').render() - mail(to_email, subject, email_body) - - -def notify_reader(from_email, to_email, token, site_url, url): - logger.info('notify reader: email %s about URL %s' % (to_email, url)) - article_url = "http://" + site_url + url - email_body = get_template('notify_reader').render(article_url=article_url) - subject = get_template('notify_message').render() - mail(to_email, subject, email_body) - - -def report_rejected(comment): - report = Report(site=comment.site, url=comment.url, - name=comment.author_name, email=comment.author_email, - rejected=True) - report.save() - - -def report_published(comment): - report = Report(site=comment.site, url=comment.url, - name=comment.author_name, email=comment.author_email, - published=True) - report.save() - - -def report_subscribed(reader): - report = Report(site=reader.site, url=reader.url, - name='', email=reader.email, - subscribed=True) - report.save() - - -def report_unsubscribed(reader): - report = Report(site=reader.site, url=reader.url, - name='', email=reader.email, - unsubscribed=True) - report.save() - - -def report(token): - site = Site.select().where(Site.token == token).get() - - standbys = [] - for row in Comment.select().join(Site).where( - Site.token == token, Comment.published.is_null(True)): - standbys.append({'url': "http://" + site.url + row.url, - 'created': row.created.strftime('%d/%m/%y %H:%M'), - 'name': row.author_name, 'content': row.content, - 'id': row.id}) - - published = [] - for row in Report.select().join(Site).where( - Site.token == token, Report.published): - published.append({'url': "http://" + site.url + row.url, - 'name': row.name, 'email': row.email}) - - rejected = [] - for row in Report.select().join(Site).where( - Site.token == token, Report.rejected): - rejected.append({'url': "http://" + site.url + row.url, - 'name': row.name, 'email': row.email}) - - subscribed = [] - for row in Report.select().join(Site).where( - Site.token == token, Report.subscribed): - subscribed.append({'url': "http://" + site.url + row.url, - 'name': row.name, 'email': row.email}) - - unsubscribed = [] - for row in Report.select().join(Site).where( - Site.token == token, Report.subscribed): - unsubscribed.append({'url': "http://" + site.url + row.url, - 'name': row.name, 'email': row.email}) - - email_body = get_template('report').render(secret=config.security['secret'], - root_url=config.http[ - 'root_url'], - standbys=standbys, - published=published, - rejected=rejected, - subscribed=subscribed, - unsubscribed=unsubscribed) - subject = get_template('report_message').render(site=site.name) - - mail(site.admin_email, subject, email_body) - - # delete report table - Report.delete().execute() - - def rss(token, onstart=False): - if onstart and os.path.isfile(config.rss['file']): + if onstart and os.path.isfile(config.rss["file"]): return site = Site.select().where(Site.token == token).get() - rss_title = get_template('rss_title_message').render(site=site.name) + rss_title = get_template("rss_title_message").render(site=site.name) md = markdown.Markdown() items = [] - for row in Comment.select().join(Site).where( - Site.token == token, Comment.published).order_by( - -Comment.published).limit(10): - item_link = "%s://%s%s" % (config.rss['proto'], site.url, row.url) - items.append(PyRSS2Gen.RSSItem( - title='%s - %s://%s%s' % (config.rss['proto'], - row.author_name, site.url, row.url), - link=item_link, - description=md.convert(row.content), - guid=PyRSS2Gen.Guid('%s/%d' % (item_link, row.id)), - pubDate=row.published - )) + for row in ( + Comment.select() + .join(Site) + .where(Site.token == token, Comment.published) + .order_by(-Comment.published) + .limit(10) + ): + item_link = "%s://%s%s" % (config.rss["proto"], site.url, row.url) + items.append( + PyRSS2Gen.RSSItem( + title="%s - %s://%s%s" + % (config.rss["proto"], row.author_name, site.url, row.url), + link=item_link, + description=md.convert(row.content), + guid=PyRSS2Gen.Guid("%s/%d" % (item_link, row.id)), + pubDate=row.published, + ) + ) rss = PyRSS2Gen.RSS2( title=rss_title, - link='%s://%s' % (config.rss['proto'], site.url), + link="%s://%s" % (config.rss["proto"], site.url), description="Commentaires du site '%s'" % site.name, lastBuildDate=datetime.now(), - items=items) - rss.write_xml(open(config.rss['file'], 'w'), encoding='utf-8') - - -def get_rabbitmq_connection(): - - credentials = pika.PlainCredentials( - config.rabbitmq['username'], config.rabbitmq['password']) - parameters = pika.ConnectionParameters( - host=config.rabbitmq['host'], - port=config.rabbitmq['port'], - credentials=credentials, - virtual_host=config.rabbitmq['vhost'] + items=items, ) - return rabbit.Connection(parameters) - -def mail(to_email, subject, message): - - body = { - 'to': to_email, - 'subject': subject, - 'content': message - } - connector = get_rabbitmq_connection() - connection = connector.open() - channel = connection.channel() - channel.basic_publish(exchange=config.rabbitmq['exchange'], - routing_key='mail.command.send', - body=json.dumps(body, indent=False, sort_keys=False)) - connector.close() - logger.debug('Email for %s posted' % to_email) + rss.write_xml(open(config.rss["file"], "w"), encoding="utf-8") def send_delete_command(content): - - connector = get_rabbitmq_connection() - connection = connector.open() - channel = connection.channel() - channel.basic_publish(exchange=config.rabbitmq['exchange'], - routing_key='mail.command.delete', - body=json.dumps(content, indent=False, sort_keys=False)) - connector.close() - logger.debug('Email accepted. Delete request sent for %s' % content) + # TODO delete mail + pass def get_template(name): - return env.get_template(config.general['lang'] + '/' + name + '.tpl') + return env.get_template(config.general["lang"] + "/" + name + ".tpl") def enqueue(something): diff --git a/app/interface/form.py b/app/interface/form.py index 42d6f2c..e1e67ec 100644 --- a/app/interface/form.py +++ b/app/interface/form.py @@ -2,46 +2,70 @@ # -*- coding: utf-8 -*- import logging -from flask import request, jsonify, abort, redirect +from datetime import datetime +from flask import request, abort, redirect from core import app from models.site import Site from models.comment import Comment from helpers.hashing import md5 -from core import processor logger = logging.getLogger(__name__) -@app.route("/newcomment", methods=['POST']) + +@app.route("/newcomment", methods=["POST"]) def new_form_comment(): try: data = request.form # add client IP if provided by HTTP proxy - clientip = '' - if 'X-Forwarded-For' in request.headers: - clientip = request.headers['X-Forwarded-For'] - - # log + ip = "" + if "X-Forwarded-For" in request.headers: + ip = request.headers["X-Forwarded-For"] + + # log logger.info(data) # validate token: retrieve site entity - token = data.get('token', '') + token = data.get("token", "") site = Site.select().where(Site.token == token).get() if site is None: - logger.warn('Unknown site %s' % token) + logger.warn("Unknown site %s" % token) abort(400) # honeypot for spammers - captcha = data.get('captcha', '') + captcha = data.get("captcha", "") if captcha: - logger.warn('discard spam: data %s' % data) + logger.warn("discard spam: data %s" % data) abort(400) - processor.enqueue({'request': 'new_comment', 'data': data, 'clientip': clientip}) + url = data.get("url", "") + author_name = data.get("author", "").strip() + author_gravatar = data.get("email", "").strip() + author_site = data.get("site", "").to_lower().strip() + if author_site and author_site[:4] != "http": + author_site = "http://" + author_site + message = data.get("message", "") + + created = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + # add a row to Comment table + comment = Comment( + site=site, + url=url, + author_name=author_name, + author_site=author_site, + author_gravatar=author_gravatar, + content=message, + created=created, + notified=None, + published=None, + ip=ip, + ) + comment.save() except: logger.exception("new comment failure") abort(400) - return redirect('/redirect/', code=302) \ No newline at end of file + return redirect("/redirect/", code=302) diff --git a/app/interface/mail.py b/app/interface/mail.py deleted file mode 100644 index ccebbb9..0000000 --- a/app/interface/mail.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import logging -from flask import request, abort -from core import app -from core import processor - -logger = logging.getLogger(__name__) - - -@app.route("/inbox", methods=['POST']) -def new_mail(): - - try: - data = request.get_json() - logger.debug(data) - - processor.enqueue({'request': 'new_mail', 'data': data}) - - except: - logger.exception("new mail failure") - abort(400) - - return "OK" diff --git a/app/interface/reader.py b/app/interface/reader.py deleted file mode 100644 index fb6149b..0000000 --- a/app/interface/reader.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import logging -from flask import request, abort -from core import app -from core import processor - -logger = logging.getLogger(__name__) - - -@app.route("/unsubscribe", methods=['GET']) -def unsubscribe(): - - try: - data = { - 'token': request.args.get('token', ''), - 'url': request.args.get('url', ''), - 'email': request.args.get('email', '') - } - logger.debug(data) - - processor.enqueue({'request': 'unsubscribe', 'data': data}) - - except: - logger.exception("unsubscribe failure") - abort(400) - - return "OK" diff --git a/app/interface/report.py b/app/interface/report.py deleted file mode 100644 index 297b76c..0000000 --- a/app/interface/report.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import logging -from conf import config -from flask import request, jsonify, abort -from core import app -from models.site import Site -from models.comment import Comment -from helpers.hashing import md5 -from core import processor - -logger = logging.getLogger(__name__) - -@app.route("/report", methods=['GET']) -def report(): - - try: - token = request.args.get('token', '') - secret = request.args.get('secret', '') - - if secret != config.security['secret']: - logger.warn('Unauthorized request') - abort(401) - - site = Site.select().where(Site.token == token).get() - if site is None: - logger.warn('Unknown site %s' % token) - abort(404) - - processor.enqueue({'request': 'report', 'data': token}) - - - except: - logger.exception("report failure") - abort(500) - - return "OK" - - -@app.route("/accept", methods=['GET']) -def accept_comment(): - - try: - id = request.args.get('comment', '') - secret = request.args.get('secret', '') - - if secret != config.security['secret']: - logger.warn('Unauthorized request') - abort(401) - - processor.enqueue({'request': 'late_accept', 'data': id}) - - except: - logger.exception("accept failure") - abort(500) - - return "PUBLISHED" - - -@app.route("/reject", methods=['GET']) -def reject_comment(): - - try: - id = request.args.get('comment', '') - secret = request.args.get('secret', '') - - if secret != config.security['secret']: - logger.warn('Unauthorized request') - abort(401) - - processor.enqueue({'request': 'late_reject', 'data': id}) - - except: - logger.exception("reject failure") - abort(500) - - return "REJECTED" diff --git a/app/interface/rmqclient.py b/app/interface/rmqclient.py deleted file mode 100644 index 51830fb..0000000 --- a/app/interface/rmqclient.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import pika -from conf import config -from threading import Thread -import logging -import json -from core import processor -from util import rabbit - -logger = logging.getLogger(__name__) - - -class MailConsumer(rabbit.Consumer): - - def process(self, channel, method, properties, body): - try: - topic = method.routing_key - data = json.loads(body) - - if topic == 'mail.message': - if "STACOSYS" in data['subject']: - logger.info('new message => {}'.format(data)) - processor.enqueue({'request': 'new_mail', 'data': data}) - else: - logger.info('ignore message => {}'.format(data)) - else: - logger.warn('unsupported message [topic={}]'.format(topic)) - except: - logger.exception('cannot process message') - - -def start(): - - logger.info('start rmqclient') - - credentials = pika.PlainCredentials( - config.rabbitmq['username'], config.rabbitmq['password']) - parameters = pika.ConnectionParameters( - host=config.rabbitmq['host'], - port=config.rabbitmq['port'], - credentials=credentials, - virtual_host=config.rabbitmq['vhost'] - ) - - connection = rabbit.Connection(parameters) - c = MailConsumer(connection, config.rabbitmq['exchange'], 'mail.message') - c.start() diff --git a/app/models/comment.py b/app/models/comment.py index 502f0bf..10f7301 100644 --- a/app/models/comment.py +++ b/app/models/comment.py @@ -13,11 +13,12 @@ from core.database import get_db class Comment(Model): url = CharField() created = DateTimeField() + notified = DateTimeField(null=True,default=None) published = DateTimeField(null=True, default=None) author_name = CharField() - author_email = CharField(default='') author_site = CharField(default='') author_gravatar = CharField(default='') + ip = CharField(default='') content = TextField() site = ForeignKeyField(Site, related_name='site') diff --git a/app/models/reader.py b/app/models/reader.py deleted file mode 100644 index 212c74b..0000000 --- a/app/models/reader.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/python -# -*- coding: UTF-8 -*- - -from peewee import Model -from peewee import CharField -from peewee import ForeignKeyField -from core.database import get_db -from models.site import Site - - -class Reader(Model): - url = CharField() - email = CharField(default='') - site = ForeignKeyField(Site, related_name='reader_site') - - class Meta: - database = get_db() diff --git a/app/models/report.py b/app/models/report.py deleted file mode 100644 index bda6e68..0000000 --- a/app/models/report.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/python -# -*- coding: UTF-8 -*- - -from peewee import Model -from peewee import CharField -from peewee import BooleanField -from peewee import ForeignKeyField -from core.database import get_db -from models.site import Site - -class Report(Model): - name = CharField() - email = CharField() - url = CharField() - published = BooleanField(default=False) - rejected = BooleanField(default=False) - subscribed = BooleanField(default=False) - unsubscribed = BooleanField(default=False) - site = ForeignKeyField(Site, related_name='report_site') - - class Meta: - database = get_db() diff --git a/app/run.py b/app/run.py index d1cfec0..a46b640 100644 --- a/app/run.py +++ b/app/run.py @@ -3,7 +3,7 @@ import logging import json -from clize import clize, run +from clize import Clize, run from jsonschema import validate from conf import config, schema @@ -15,20 +15,19 @@ def load_json(filename): return jsondoc -@clize +@Clize def stacosys_server(config_pathname): # load and validate startup config conf = load_json(config_pathname) json_schema = json.loads(schema.json_schema) - v = validate(conf, json_schema) + validate(conf, json_schema) # set configuration config.general = conf['general'] config.http = conf['http'] config.security = conf['security'] config.rss = conf['rss'] - config.rabbitmq = conf['rabbitmq'] # start application from core import app diff --git a/app/templates/en/notify_reader.tpl b/app/templates/en/notify_reader.tpl deleted file mode 100644 index 701b55f..0000000 --- a/app/templates/en/notify_reader.tpl +++ /dev/null @@ -1,9 +0,0 @@ -Hi, - -Your comment has been approved. It should be published in few minutes. - - {{ article_url }} - --- -Stacosys - diff --git a/app/templates/en/notify_subscriber.tpl b/app/templates/en/notify_subscriber.tpl deleted file mode 100644 index 5a81758..0000000 --- a/app/templates/en/notify_subscriber.tpl +++ /dev/null @@ -1,13 +0,0 @@ -Hi, - -A new comment has been published for an article you have subscribed to. - - {{ article_url }} - -You can unsubscribe at any time using this link: - - {{ unsubscribe_url }} - --- -Stacosys - diff --git a/app/templates/en/report.tpl b/app/templates/en/report.tpl deleted file mode 100644 index 0960555..0000000 --- a/app/templates/en/report.tpl +++ /dev/null @@ -1,42 +0,0 @@ -{% if subscribed %} -{% if subscribed|length > 1 %}NEW SUBSCRIPTIONS{% else %}NEW SUBSCRIPTION{% endif %} : -{% for c in subscribed %} -- {{ c.name }} ({{ c.email }}) => {{ c.url }} -{% endfor %} - -{% endif %} -{% if unsubscribed %} -{% if unsubscribed|length > 1 %}CANCELLED SUBSCRIPTIONS{% else %}CANCELLED SUBSCRIPTION{% endif %} : -{% for c in unsubscribed %} -- {{ c.name }} ({{ c.email }}) => {{ c.url }} -{% endfor %} - -{% endif %} -{% if published %} -{% if published|length > 1 %}PUBLISHED COMMENTS{% else %}PUBLISHED COMMENT{% endif %} : -{% for c in published %} -- {{ c.name }} ({{ c.email }}) => {{ c.url }} -{% endfor %} - -{% endif %} -{% if rejected %} -{% if rejected|length > 1 %}REJECTED COMMENTS{% else %}REJECTED COMMENT{% endif %} : -{% for c in rejected %} -- {{ c.name }} ({{ c.email }}) => {{ c.url }} -{% endfor %} - -{% endif %} -{% if standbys %} -{% if standbys|length > 1 %}STANDBY COMMENTS{% else %}STANDBY COMMENT{% endif %} : -{% for c in standbys %} -- {{ c.name }} ({{ c.created }}) => {{ c.url }} -{{ c.content }} - - Accepter : {{ root_url}}/accept?secret={{ secret}}&comment={{ c.id }} - Rejeter : {{ root_url}}/reject?secret={{ secret}}&comment={{ c.id }} - -{% endfor %} -{% endif %} --- -Stacosys - diff --git a/app/templates/en/report_message.tpl b/app/templates/en/report_message.tpl deleted file mode 100644 index c1b5056..0000000 --- a/app/templates/en/report_message.tpl +++ /dev/null @@ -1 +0,0 @@ -Status report : {{ site }} \ No newline at end of file diff --git a/app/templates/en/unsubscribe_page.tpl b/app/templates/en/unsubscribe_page.tpl deleted file mode 100644 index a52afd7..0000000 --- a/app/templates/en/unsubscribe_page.tpl +++ /dev/null @@ -1,2 +0,0 @@ -Your request has been sent. In case of issue please contact site -administrator. diff --git a/app/templates/fr/notify_reader.tpl b/app/templates/fr/notify_reader.tpl deleted file mode 100644 index 9464cb0..0000000 --- a/app/templates/fr/notify_reader.tpl +++ /dev/null @@ -1,9 +0,0 @@ -Bonjour, - -Votre commentaire a été approuvé. Il sera publié dans quelques minutes. - - {{ article_url }} - --- -Stacosys - diff --git a/app/templates/fr/notify_subscriber.tpl b/app/templates/fr/notify_subscriber.tpl deleted file mode 100644 index 1d79f8a..0000000 --- a/app/templates/fr/notify_subscriber.tpl +++ /dev/null @@ -1,13 +0,0 @@ -Bonjour, - -Un nouveau commentaire a été publié pour un article auquel vous êtes abonné. - - {{ article_url }} - -Vous pouvez vous désinscrire à tout moment en suivant ce lien : - - {{ unsubscribe_url }} - --- -Stacosys - diff --git a/app/templates/fr/report.tpl b/app/templates/fr/report.tpl deleted file mode 100644 index 9c8f52b..0000000 --- a/app/templates/fr/report.tpl +++ /dev/null @@ -1,42 +0,0 @@ -{% if subscribed %} -{% if subscribed|length > 1 %}NOUVEAUX ABONNEMENTS{% else %}NOUVEL ABONNEMENT{% endif %} : -{% for c in subscribed %} -- {{ c.name }} ({{ c.email }}) => {{ c.url }} -{% endfor %} - -{% endif %} -{% if unsubscribed %} -{% if unsubscribed|length > 1 %}ABONNEMENTS RESILIES{% else %}ABONNEMENT RESILIE{% endif %} : -{% for c in unsubscribed %} -- {{ c.name }} ({{ c.email }}) => {{ c.url }} -{% endfor %} - -{% endif %} -{% if published %} -{% if published|length > 1 %}COMMENTAIRES PUBLIES{% else %}COMMENTAIRE PUBLIE{% endif %} : -{% for c in published %} -- {{ c.name }} ({{ c.email }}) => {{ c.url }} -{% endfor %} - -{% endif %} -{% if rejected %} -{% if rejected|length > 1 %}COMMENTAIRES REJETES{% else %}COMMENTAIRE REJETE{% endif %} : -{% for c in rejected %} -- {{ c.name }} ({{ c.email }}) => {{ c.url }} -{% endfor %} - -{% endif %} -{% if standbys %} -{% if standbys|length > 1 %}COMMENTAIRES EN ATTENTE{% else %}COMMENTAIRE EN ATTENTE{% endif %} : -{% for c in standbys %} -- {{ c.name }} ({{ c.created }}) => {{ c.url }} -{{ c.content }} - - Accepter : {{ root_url}}/accept?secret={{ secret}}&comment={{ c.id }} - Rejeter : {{ root_url}}/reject?secret={{ secret}}&comment={{ c.id }} - -{% endfor %} -{% endif %} --- -Stacosys - diff --git a/app/templates/fr/report_message.tpl b/app/templates/fr/report_message.tpl deleted file mode 100644 index 45f08e7..0000000 --- a/app/templates/fr/report_message.tpl +++ /dev/null @@ -1 +0,0 @@ -Rapport d'activité : {{ site }} \ No newline at end of file diff --git a/app/templates/fr/unsubscribe_page.tpl b/app/templates/fr/unsubscribe_page.tpl deleted file mode 100644 index 3cd63e8..0000000 --- a/app/templates/fr/unsubscribe_page.tpl +++ /dev/null @@ -1,2 +0,0 @@ -Votre requête a été envoyée. En cas de problème, contactez l'administrateur du -site. diff --git a/app/util/rabbit.py b/app/util/rabbit.py deleted file mode 100644 index 042e272..0000000 --- a/app/util/rabbit.py +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 - *- - -import logging -import pika -import time -from threading import Thread - -logger = logging.getLogger(__name__) - -EXCHANGE_TYPE = "topic" -CONNECT_DELAY = 3 - -class Connection: - - def __init__(self, connection_parameters): - self._connection_parameters = connection_parameters - - def open(self): - - self._connection = None - while True: - try: - self._connection = pika.BlockingConnection( - self._connection_parameters) - break - except: - time.sleep(CONNECT_DELAY) - logger.exception('rabbitmq connection failure. try again...') - return self._connection - - def close(self): - self._connection.close() - self._connection = None - - def get(self): - return self._connection - - -class Consumer(Thread): - - _connector = None - _channel = None - _queue_name = None - - def __init__(self, connector, exchange_name, routing_key): - Thread.__init__(self) - self._connector = connector - self._exchange_name = exchange_name - self._routing_key = routing_key - - def configure(self, connection): - - self._channel = None - while True: - try: - - self._channel = connection.channel() - self._channel.exchange_declare( - exchange=self._exchange_name, exchange_type=EXCHANGE_TYPE - ) - - result = self._channel.queue_declare(exclusive=True) - self._queue_name = result.method.queue - self._channel.queue_bind( - exchange=self._exchange_name, - queue=self._queue_name, - routing_key=self._routing_key, - ) - break - except: - logger.exception('configuration failure. try again...') - time.sleep(CONNECT_DELAY) - - def run(self): - - self._connector.open() - self.configure(self._connector.get()) - self._channel.basic_consume( - self.process, queue=self._queue_name, no_ack=True) - self._channel.start_consuming() - - def process(self, channel, method, properties, body): - raise NotImplemented diff --git a/config.json b/config.json index 1681d40..5abcdfc 100755 --- a/config.json +++ b/config.json @@ -11,20 +11,10 @@ }, "security": { "salt": "BRRJRqXgGpXWrgTidBPcixIThHpDuKc0", - "secret": "Uqca5Kc8xuU6THz9", - "private": true + "secret": "Uqca5Kc8xuU6THz9" }, "rss": { "proto": "http", "file": "comments.xml" - }, - "rabbitmq": { - "active": true, - "host": "rabbit", - "port": 5672, - "username": "techuser", - "password": "tech", - "vhost": "devhub", - "exchange": "hub.topic" } } diff --git a/demo/app.js b/demo/app.js deleted file mode 100644 index aade06b..0000000 --- a/demo/app.js +++ /dev/null @@ -1,14 +0,0 @@ -var http = require('http'); -var finalhandler = require('finalhandler'); -var serveStatic = require('serve-static'); - -var serve = serveStatic("./public"), - port = 9000;; - -var server = http.createServer(function(req, res){ - var done = finalhandler(req, res) - serve(req, res, done) -}); - -server.listen(port); -console.log('serve static resources at http://localhost:%d', port); diff --git a/demo/package.json b/demo/package.json deleted file mode 100644 index 5876a71..0000000 --- a/demo/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "website", - "version": "0.1.0", - "description": "Web site", - "main": "app.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "Yax", - "license": "BSD", - "dependencies": { - "finalhandler": "~0.3.3", - "serve-static": "~1.9.1" - } -} diff --git a/demo/public/css/font-awesome.css b/demo/public/css/font-awesome.css deleted file mode 100644 index c0a5993..0000000 --- a/demo/public/css/font-awesome.css +++ /dev/null @@ -1,4 +0,0 @@ -/*! - * Font Awesome 4.1.0 by @davegandy - http://fontawesome.io - @fontawesome - * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:'FontAwesome';src:url('../fonts/awesome/fontawesome-webfont.eot?v=4.1.0');src:url('../fonts/awesome/fontawesome-webfont.eot?#iefix&v=4.1.0') format('embedded-opentype'),url('../fonts/awesome/fontawesome-webfont.woff?v=4.1.0') format('woff'),url('../fonts/awesome/fontawesome-webfont.ttf?v=4.1.0') format('truetype'),url('../fonts/awesome/fontawesome-webfont.svg?v=4.1.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:spin 2s infinite linear;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(359deg)}}@keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-moz-transform:scale(-1, 1);-ms-transform:scale(-1, 1);-o-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-moz-transform:scale(1, -1);-ms-transform:scale(1, -1);-o-transform:scale(1, -1);transform:scale(1, -1)}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-square:before,.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"} diff --git a/demo/public/css/grids-responsive-min.css b/demo/public/css/grids-responsive-min.css deleted file mode 100644 index 7bcfd32..0000000 --- a/demo/public/css/grids-responsive-min.css +++ /dev/null @@ -1,7 +0,0 @@ -/*! -Pure v0.5.0 -Copyright 2014 Yahoo! Inc. All rights reserved. -Licensed under the BSD License. -https://github.com/yui/pure/blob/master/LICENSE.md -*/ -@media screen and (min-width:35.5em){.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-1-2,.pure-u-sm-1-3,.pure-u-sm-2-3,.pure-u-sm-1-4,.pure-u-sm-3-4,.pure-u-sm-1-5,.pure-u-sm-2-5,.pure-u-sm-3-5,.pure-u-sm-4-5,.pure-u-sm-5-5,.pure-u-sm-1-6,.pure-u-sm-5-6,.pure-u-sm-1-8,.pure-u-sm-3-8,.pure-u-sm-5-8,.pure-u-sm-7-8,.pure-u-sm-1-12,.pure-u-sm-5-12,.pure-u-sm-7-12,.pure-u-sm-11-12,.pure-u-sm-1-24,.pure-u-sm-2-24,.pure-u-sm-3-24,.pure-u-sm-4-24,.pure-u-sm-5-24,.pure-u-sm-6-24,.pure-u-sm-7-24,.pure-u-sm-8-24,.pure-u-sm-9-24,.pure-u-sm-10-24,.pure-u-sm-11-24,.pure-u-sm-12-24,.pure-u-sm-13-24,.pure-u-sm-14-24,.pure-u-sm-15-24,.pure-u-sm-16-24,.pure-u-sm-17-24,.pure-u-sm-18-24,.pure-u-sm-19-24,.pure-u-sm-20-24,.pure-u-sm-21-24,.pure-u-sm-22-24,.pure-u-sm-23-24,.pure-u-sm-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-sm-1-24{width:4.1667%;*width:4.1357%}.pure-u-sm-1-12,.pure-u-sm-2-24{width:8.3333%;*width:8.3023%}.pure-u-sm-1-8,.pure-u-sm-3-24{width:12.5%;*width:12.469%}.pure-u-sm-1-6,.pure-u-sm-4-24{width:16.6667%;*width:16.6357%}.pure-u-sm-1-5{width:20%;*width:19.969%}.pure-u-sm-5-24{width:20.8333%;*width:20.8023%}.pure-u-sm-1-4,.pure-u-sm-6-24{width:25%;*width:24.969%}.pure-u-sm-7-24{width:29.1667%;*width:29.1357%}.pure-u-sm-1-3,.pure-u-sm-8-24{width:33.3333%;*width:33.3023%}.pure-u-sm-3-8,.pure-u-sm-9-24{width:37.5%;*width:37.469%}.pure-u-sm-2-5{width:40%;*width:39.969%}.pure-u-sm-5-12,.pure-u-sm-10-24{width:41.6667%;*width:41.6357%}.pure-u-sm-11-24{width:45.8333%;*width:45.8023%}.pure-u-sm-1-2,.pure-u-sm-12-24{width:50%;*width:49.969%}.pure-u-sm-13-24{width:54.1667%;*width:54.1357%}.pure-u-sm-7-12,.pure-u-sm-14-24{width:58.3333%;*width:58.3023%}.pure-u-sm-3-5{width:60%;*width:59.969%}.pure-u-sm-5-8,.pure-u-sm-15-24{width:62.5%;*width:62.469%}.pure-u-sm-2-3,.pure-u-sm-16-24{width:66.6667%;*width:66.6357%}.pure-u-sm-17-24{width:70.8333%;*width:70.8023%}.pure-u-sm-3-4,.pure-u-sm-18-24{width:75%;*width:74.969%}.pure-u-sm-19-24{width:79.1667%;*width:79.1357%}.pure-u-sm-4-5{width:80%;*width:79.969%}.pure-u-sm-5-6,.pure-u-sm-20-24{width:83.3333%;*width:83.3023%}.pure-u-sm-7-8,.pure-u-sm-21-24{width:87.5%;*width:87.469%}.pure-u-sm-11-12,.pure-u-sm-22-24{width:91.6667%;*width:91.6357%}.pure-u-sm-23-24{width:95.8333%;*width:95.8023%}.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-5-5,.pure-u-sm-24-24{width:100%}}@media screen and (min-width:48em){.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-1-2,.pure-u-md-1-3,.pure-u-md-2-3,.pure-u-md-1-4,.pure-u-md-3-4,.pure-u-md-1-5,.pure-u-md-2-5,.pure-u-md-3-5,.pure-u-md-4-5,.pure-u-md-5-5,.pure-u-md-1-6,.pure-u-md-5-6,.pure-u-md-1-8,.pure-u-md-3-8,.pure-u-md-5-8,.pure-u-md-7-8,.pure-u-md-1-12,.pure-u-md-5-12,.pure-u-md-7-12,.pure-u-md-11-12,.pure-u-md-1-24,.pure-u-md-2-24,.pure-u-md-3-24,.pure-u-md-4-24,.pure-u-md-5-24,.pure-u-md-6-24,.pure-u-md-7-24,.pure-u-md-8-24,.pure-u-md-9-24,.pure-u-md-10-24,.pure-u-md-11-24,.pure-u-md-12-24,.pure-u-md-13-24,.pure-u-md-14-24,.pure-u-md-15-24,.pure-u-md-16-24,.pure-u-md-17-24,.pure-u-md-18-24,.pure-u-md-19-24,.pure-u-md-20-24,.pure-u-md-21-24,.pure-u-md-22-24,.pure-u-md-23-24,.pure-u-md-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-md-1-24{width:4.1667%;*width:4.1357%}.pure-u-md-1-12,.pure-u-md-2-24{width:8.3333%;*width:8.3023%}.pure-u-md-1-8,.pure-u-md-3-24{width:12.5%;*width:12.469%}.pure-u-md-1-6,.pure-u-md-4-24{width:16.6667%;*width:16.6357%}.pure-u-md-1-5{width:20%;*width:19.969%}.pure-u-md-5-24{width:20.8333%;*width:20.8023%}.pure-u-md-1-4,.pure-u-md-6-24{width:25%;*width:24.969%}.pure-u-md-7-24{width:29.1667%;*width:29.1357%}.pure-u-md-1-3,.pure-u-md-8-24{width:33.3333%;*width:33.3023%}.pure-u-md-3-8,.pure-u-md-9-24{width:37.5%;*width:37.469%}.pure-u-md-2-5{width:40%;*width:39.969%}.pure-u-md-5-12,.pure-u-md-10-24{width:41.6667%;*width:41.6357%}.pure-u-md-11-24{width:45.8333%;*width:45.8023%}.pure-u-md-1-2,.pure-u-md-12-24{width:50%;*width:49.969%}.pure-u-md-13-24{width:54.1667%;*width:54.1357%}.pure-u-md-7-12,.pure-u-md-14-24{width:58.3333%;*width:58.3023%}.pure-u-md-3-5{width:60%;*width:59.969%}.pure-u-md-5-8,.pure-u-md-15-24{width:62.5%;*width:62.469%}.pure-u-md-2-3,.pure-u-md-16-24{width:66.6667%;*width:66.6357%}.pure-u-md-17-24{width:70.8333%;*width:70.8023%}.pure-u-md-3-4,.pure-u-md-18-24{width:75%;*width:74.969%}.pure-u-md-19-24{width:79.1667%;*width:79.1357%}.pure-u-md-4-5{width:80%;*width:79.969%}.pure-u-md-5-6,.pure-u-md-20-24{width:83.3333%;*width:83.3023%}.pure-u-md-7-8,.pure-u-md-21-24{width:87.5%;*width:87.469%}.pure-u-md-11-12,.pure-u-md-22-24{width:91.6667%;*width:91.6357%}.pure-u-md-23-24{width:95.8333%;*width:95.8023%}.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-5-5,.pure-u-md-24-24{width:100%}}@media screen and (min-width:64em){.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-1-2,.pure-u-lg-1-3,.pure-u-lg-2-3,.pure-u-lg-1-4,.pure-u-lg-3-4,.pure-u-lg-1-5,.pure-u-lg-2-5,.pure-u-lg-3-5,.pure-u-lg-4-5,.pure-u-lg-5-5,.pure-u-lg-1-6,.pure-u-lg-5-6,.pure-u-lg-1-8,.pure-u-lg-3-8,.pure-u-lg-5-8,.pure-u-lg-7-8,.pure-u-lg-1-12,.pure-u-lg-5-12,.pure-u-lg-7-12,.pure-u-lg-11-12,.pure-u-lg-1-24,.pure-u-lg-2-24,.pure-u-lg-3-24,.pure-u-lg-4-24,.pure-u-lg-5-24,.pure-u-lg-6-24,.pure-u-lg-7-24,.pure-u-lg-8-24,.pure-u-lg-9-24,.pure-u-lg-10-24,.pure-u-lg-11-24,.pure-u-lg-12-24,.pure-u-lg-13-24,.pure-u-lg-14-24,.pure-u-lg-15-24,.pure-u-lg-16-24,.pure-u-lg-17-24,.pure-u-lg-18-24,.pure-u-lg-19-24,.pure-u-lg-20-24,.pure-u-lg-21-24,.pure-u-lg-22-24,.pure-u-lg-23-24,.pure-u-lg-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-lg-1-24{width:4.1667%;*width:4.1357%}.pure-u-lg-1-12,.pure-u-lg-2-24{width:8.3333%;*width:8.3023%}.pure-u-lg-1-8,.pure-u-lg-3-24{width:12.5%;*width:12.469%}.pure-u-lg-1-6,.pure-u-lg-4-24{width:16.6667%;*width:16.6357%}.pure-u-lg-1-5{width:20%;*width:19.969%}.pure-u-lg-5-24{width:20.8333%;*width:20.8023%}.pure-u-lg-1-4,.pure-u-lg-6-24{width:25%;*width:24.969%}.pure-u-lg-7-24{width:29.1667%;*width:29.1357%}.pure-u-lg-1-3,.pure-u-lg-8-24{width:33.3333%;*width:33.3023%}.pure-u-lg-3-8,.pure-u-lg-9-24{width:37.5%;*width:37.469%}.pure-u-lg-2-5{width:40%;*width:39.969%}.pure-u-lg-5-12,.pure-u-lg-10-24{width:41.6667%;*width:41.6357%}.pure-u-lg-11-24{width:45.8333%;*width:45.8023%}.pure-u-lg-1-2,.pure-u-lg-12-24{width:50%;*width:49.969%}.pure-u-lg-13-24{width:54.1667%;*width:54.1357%}.pure-u-lg-7-12,.pure-u-lg-14-24{width:58.3333%;*width:58.3023%}.pure-u-lg-3-5{width:60%;*width:59.969%}.pure-u-lg-5-8,.pure-u-lg-15-24{width:62.5%;*width:62.469%}.pure-u-lg-2-3,.pure-u-lg-16-24{width:66.6667%;*width:66.6357%}.pure-u-lg-17-24{width:70.8333%;*width:70.8023%}.pure-u-lg-3-4,.pure-u-lg-18-24{width:75%;*width:74.969%}.pure-u-lg-19-24{width:79.1667%;*width:79.1357%}.pure-u-lg-4-5{width:80%;*width:79.969%}.pure-u-lg-5-6,.pure-u-lg-20-24{width:83.3333%;*width:83.3023%}.pure-u-lg-7-8,.pure-u-lg-21-24{width:87.5%;*width:87.469%}.pure-u-lg-11-12,.pure-u-lg-22-24{width:91.6667%;*width:91.6357%}.pure-u-lg-23-24{width:95.8333%;*width:95.8023%}.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-5-5,.pure-u-lg-24-24{width:100%}}@media screen and (min-width:80em){.pure-u-xl-1,.pure-u-xl-1-1,.pure-u-xl-1-2,.pure-u-xl-1-3,.pure-u-xl-2-3,.pure-u-xl-1-4,.pure-u-xl-3-4,.pure-u-xl-1-5,.pure-u-xl-2-5,.pure-u-xl-3-5,.pure-u-xl-4-5,.pure-u-xl-5-5,.pure-u-xl-1-6,.pure-u-xl-5-6,.pure-u-xl-1-8,.pure-u-xl-3-8,.pure-u-xl-5-8,.pure-u-xl-7-8,.pure-u-xl-1-12,.pure-u-xl-5-12,.pure-u-xl-7-12,.pure-u-xl-11-12,.pure-u-xl-1-24,.pure-u-xl-2-24,.pure-u-xl-3-24,.pure-u-xl-4-24,.pure-u-xl-5-24,.pure-u-xl-6-24,.pure-u-xl-7-24,.pure-u-xl-8-24,.pure-u-xl-9-24,.pure-u-xl-10-24,.pure-u-xl-11-24,.pure-u-xl-12-24,.pure-u-xl-13-24,.pure-u-xl-14-24,.pure-u-xl-15-24,.pure-u-xl-16-24,.pure-u-xl-17-24,.pure-u-xl-18-24,.pure-u-xl-19-24,.pure-u-xl-20-24,.pure-u-xl-21-24,.pure-u-xl-22-24,.pure-u-xl-23-24,.pure-u-xl-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-xl-1-24{width:4.1667%;*width:4.1357%}.pure-u-xl-1-12,.pure-u-xl-2-24{width:8.3333%;*width:8.3023%}.pure-u-xl-1-8,.pure-u-xl-3-24{width:12.5%;*width:12.469%}.pure-u-xl-1-6,.pure-u-xl-4-24{width:16.6667%;*width:16.6357%}.pure-u-xl-1-5{width:20%;*width:19.969%}.pure-u-xl-5-24{width:20.8333%;*width:20.8023%}.pure-u-xl-1-4,.pure-u-xl-6-24{width:25%;*width:24.969%}.pure-u-xl-7-24{width:29.1667%;*width:29.1357%}.pure-u-xl-1-3,.pure-u-xl-8-24{width:33.3333%;*width:33.3023%}.pure-u-xl-3-8,.pure-u-xl-9-24{width:37.5%;*width:37.469%}.pure-u-xl-2-5{width:40%;*width:39.969%}.pure-u-xl-5-12,.pure-u-xl-10-24{width:41.6667%;*width:41.6357%}.pure-u-xl-11-24{width:45.8333%;*width:45.8023%}.pure-u-xl-1-2,.pure-u-xl-12-24{width:50%;*width:49.969%}.pure-u-xl-13-24{width:54.1667%;*width:54.1357%}.pure-u-xl-7-12,.pure-u-xl-14-24{width:58.3333%;*width:58.3023%}.pure-u-xl-3-5{width:60%;*width:59.969%}.pure-u-xl-5-8,.pure-u-xl-15-24{width:62.5%;*width:62.469%}.pure-u-xl-2-3,.pure-u-xl-16-24{width:66.6667%;*width:66.6357%}.pure-u-xl-17-24{width:70.8333%;*width:70.8023%}.pure-u-xl-3-4,.pure-u-xl-18-24{width:75%;*width:74.969%}.pure-u-xl-19-24{width:79.1667%;*width:79.1357%}.pure-u-xl-4-5{width:80%;*width:79.969%}.pure-u-xl-5-6,.pure-u-xl-20-24{width:83.3333%;*width:83.3023%}.pure-u-xl-7-8,.pure-u-xl-21-24{width:87.5%;*width:87.469%}.pure-u-xl-11-12,.pure-u-xl-22-24{width:91.6667%;*width:91.6357%}.pure-u-xl-23-24{width:95.8333%;*width:95.8023%}.pure-u-xl-1,.pure-u-xl-1-1,.pure-u-xl-5-5,.pure-u-xl-24-24{width:100%}} \ No newline at end of file diff --git a/demo/public/css/pure-0.css b/demo/public/css/pure-0.css deleted file mode 100644 index 14497d9..0000000 --- a/demo/public/css/pure-0.css +++ /dev/null @@ -1,11 +0,0 @@ -/*! -Pure v0.5.0 -Copyright 2014 Yahoo! Inc. All rights reserved. -Licensed under the BSD License. -https://github.com/yui/pure/blob/master/LICENSE.md -*/ -/*! -normalize.css v1.1.3 | MIT License | git.io/normalize -Copyright (c) Nicolas Gallagher and Jonathan Neal -*/ -/*! normalize.css v1.1.3 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-size:100%;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}html,button,input,select,textarea{font-family:sans-serif}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}h2{font-size:1.5em;margin:.83em 0}h3{font-size:1.17em;margin:1em 0}h4{font-size:1em;margin:1.33em 0}h5{font-size:.83em;margin:1.67em 0}h6{font-size:.67em;margin:2.33em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:1em 40px}dfn{font-style:italic}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}p,pre{margin:1em 0}code,kbd,pre,samp{font-family:monospace,serif;_font-family:'courier new',monospace;font-size:1em}pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word}q{quotes:none}q:before,q:after{content:'';content:none}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,menu,ol,ul{margin:1em 0}dd{margin:0 0 0 40px}menu,ol,ul{padding:0 0 0 40px}nav ul,nav ol{list-style:none;list-style-image:none}img{border:0;-ms-interpolation-mode:bicubic}svg:not(:root){overflow:hidden}figure{margin:0}form{margin:0}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0;white-space:normal;*margin-left:-7px}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;*overflow:visible}button[disabled],html input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0;*height:13px;*width:13px}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}.pure-g{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-flex;-webkit-flex-flow:row wrap;display:-ms-flexbox;-ms-flex-flow:row wrap}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-g [class *="pure-u"]{font-family:sans-serif}.pure-u-1,.pure-u-1-1,.pure-u-1-2,.pure-u-1-3,.pure-u-2-3,.pure-u-1-4,.pure-u-3-4,.pure-u-1-5,.pure-u-2-5,.pure-u-3-5,.pure-u-4-5,.pure-u-5-5,.pure-u-1-6,.pure-u-5-6,.pure-u-1-8,.pure-u-3-8,.pure-u-5-8,.pure-u-7-8,.pure-u-1-12,.pure-u-5-12,.pure-u-7-12,.pure-u-11-12,.pure-u-1-24,.pure-u-2-24,.pure-u-3-24,.pure-u-4-24,.pure-u-5-24,.pure-u-6-24,.pure-u-7-24,.pure-u-8-24,.pure-u-9-24,.pure-u-10-24,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1-24{width:4.1667%;*width:4.1357%}.pure-u-1-12,.pure-u-2-24{width:8.3333%;*width:8.3023%}.pure-u-1-8,.pure-u-3-24{width:12.5%;*width:12.469%}.pure-u-1-6,.pure-u-4-24{width:16.6667%;*width:16.6357%}.pure-u-1-5{width:20%;*width:19.969%}.pure-u-5-24{width:20.8333%;*width:20.8023%}.pure-u-1-4,.pure-u-6-24{width:25%;*width:24.969%}.pure-u-7-24{width:29.1667%;*width:29.1357%}.pure-u-1-3,.pure-u-8-24{width:33.3333%;*width:33.3023%}.pure-u-3-8,.pure-u-9-24{width:37.5%;*width:37.469%}.pure-u-2-5{width:40%;*width:39.969%}.pure-u-5-12,.pure-u-10-24{width:41.6667%;*width:41.6357%}.pure-u-11-24{width:45.8333%;*width:45.8023%}.pure-u-1-2,.pure-u-12-24{width:50%;*width:49.969%}.pure-u-13-24{width:54.1667%;*width:54.1357%}.pure-u-7-12,.pure-u-14-24{width:58.3333%;*width:58.3023%}.pure-u-3-5{width:60%;*width:59.969%}.pure-u-5-8,.pure-u-15-24{width:62.5%;*width:62.469%}.pure-u-2-3,.pure-u-16-24{width:66.6667%;*width:66.6357%}.pure-u-17-24{width:70.8333%;*width:70.8023%}.pure-u-3-4,.pure-u-18-24{width:75%;*width:74.969%}.pure-u-19-24{width:79.1667%;*width:79.1357%}.pure-u-4-5{width:80%;*width:79.969%}.pure-u-5-6,.pure-u-20-24{width:83.3333%;*width:83.3023%}.pure-u-7-8,.pure-u-21-24{width:87.5%;*width:87.469%}.pure-u-11-12,.pure-u-22-24{width:91.6667%;*width:91.6357%}.pure-u-23-24{width:95.8333%;*width:95.8023%}.pure-u-1,.pure-u-1-1,.pure-u-5-5,.pure-u-24-24{width:100%}.pure-button{display:inline-block;*display:inline;zoom:1;line-height:normal;white-space:nowrap;vertical-align:baseline;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button{font-family:inherit;font-size:100%;*font-size:90%;*overflow:visible;padding:.5em 1em;color:#444;color:rgba(0,0,0,.8);*color:#444;border:1px solid #999;border:0 rgba(0,0,0,0);background-color:#E6E6E6;text-decoration:none;border-radius:2px}.pure-button-hover,.pure-button:hover,.pure-button:focus{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#1a000000', GradientType=0);background-image:-webkit-gradient(linear,0 0,0 100%,from(transparent),color-stop(40%,rgba(0,0,0,.05)),to(rgba(0,0,0,.1)));background-image:-webkit-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:-moz-linear-gradient(top,rgba(0,0,0,.05) 0,rgba(0,0,0,.1));background-image:-o-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1))}.pure-button:focus{outline:0}.pure-button-active,.pure-button:active{box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset}.pure-button[disabled],.pure-button-disabled,.pure-button-disabled:hover,.pure-button-disabled:focus,.pure-button-disabled:active{border:0;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);filter:alpha(opacity=40);-khtml-opacity:.4;-moz-opacity:.4;opacity:.4;cursor:not-allowed;box-shadow:none}.pure-button-hidden{display:none}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{background-color:#0078e7;color:#fff}.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form select,.pure-form textarea{padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input:not([type]){padding:.5em .6em;display:inline-block;border:1px solid #ccc;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.pure-form input[type=color]{padding:.2em .5em}.pure-form input[type=text]:focus,.pure-form input[type=password]:focus,.pure-form input[type=email]:focus,.pure-form input[type=url]:focus,.pure-form input[type=date]:focus,.pure-form input[type=month]:focus,.pure-form input[type=time]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=week]:focus,.pure-form input[type=number]:focus,.pure-form input[type=search]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=color]:focus,.pure-form select:focus,.pure-form textarea:focus{outline:0;outline:thin dotted \9;border-color:#129FEA}.pure-form input:not([type]):focus{outline:0;outline:thin dotted \9;border-color:#129FEA}.pure-form input[type=file]:focus,.pure-form input[type=radio]:focus,.pure-form input[type=checkbox]:focus{outline:thin dotted #333;outline:1px auto #129FEA}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input[type=text][disabled],.pure-form input[type=password][disabled],.pure-form input[type=email][disabled],.pure-form input[type=url][disabled],.pure-form input[type=date][disabled],.pure-form input[type=month][disabled],.pure-form input[type=time][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=week][disabled],.pure-form input[type=number][disabled],.pure-form input[type=search][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=color][disabled],.pure-form select[disabled],.pure-form textarea[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input:not([type])[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{background:#eee;color:#777;border-color:#ccc}.pure-form input:focus:invalid,.pure-form textarea:focus:invalid,.pure-form select:focus:invalid{color:#b94a48;border-color:#ee5f5b}.pure-form input:focus:invalid:focus,.pure-form textarea:focus:invalid:focus,.pure-form select:focus:invalid:focus{border-color:#e9322d}.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus,.pure-form input[type=checkbox]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{border:1px solid #ccc;background-color:#fff}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em}.pure-form fieldset{margin:0;padding:.35em 0 .75em;border:0}.pure-form legend{display:block;width:100%;padding:.3em 0;margin-bottom:.3em;color:#333;border-bottom:1px solid #e5e5e5}.pure-form-stacked input[type=text],.pure-form-stacked input[type=password],.pure-form-stacked input[type=email],.pure-form-stacked input[type=url],.pure-form-stacked input[type=date],.pure-form-stacked input[type=month],.pure-form-stacked input[type=time],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=week],.pure-form-stacked input[type=number],.pure-form-stacked input[type=search],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=color],.pure-form-stacked select,.pure-form-stacked label,.pure-form-stacked textarea{display:block;margin:.25em 0}.pure-form-stacked input:not([type]){display:block;margin:.25em 0}.pure-form-aligned input,.pure-form-aligned textarea,.pure-form-aligned select,.pure-form-aligned .pure-help-inline,.pure-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.pure-form-aligned textarea{vertical-align:top}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;display:inline-block;vertical-align:middle;width:10em;margin:0 1em 0 0}.pure-form-aligned .pure-controls{margin:1.5em 0 0 10em}.pure-form input.pure-input-rounded,.pure-form .pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input{display:block;padding:10px;margin:0;border-radius:0;position:relative;top:-1px}.pure-form .pure-group input:focus{z-index:2}.pure-form .pure-group input:first-child{top:1px;border-radius:4px 4px 0 0}.pure-form .pure-group input:last-child{top:-2px;border-radius:0 0 4px 4px}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form .pure-help-inline,.pure-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:.875em}.pure-form-message{display:block;color:#666;font-size:.875em}@media only screen and (max-width :480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input:not([type]),.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form label{margin-bottom:.3em;display:block}.pure-group input:not([type]),.pure-group input[type=text],.pure-group input[type=password],.pure-group input[type=email],.pure-group input[type=url],.pure-group input[type=date],.pure-group input[type=month],.pure-group input[type=time],.pure-group input[type=datetime],.pure-group input[type=datetime-local],.pure-group input[type=week],.pure-group input[type=number],.pure-group input[type=search],.pure-group input[type=tel],.pure-group input[type=color]{margin-bottom:0}.pure-form-aligned .pure-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.pure-form-aligned .pure-controls{margin:1.5em 0 0}.pure-form .pure-help-inline,.pure-form-message-inline,.pure-form-message{display:block;font-size:.75em;padding:.2em 0 .8em}}.pure-menu ul{position:absolute;visibility:hidden}.pure-menu.pure-menu-open{visibility:visible;z-index:2;width:100%}.pure-menu ul{left:-10000px;list-style:none;margin:0;padding:0;top:-10000px;z-index:1}.pure-menu>ul{position:relative}.pure-menu-open>ul{left:0;top:0;visibility:visible}.pure-menu-open>ul:focus{outline:0}.pure-menu li{position:relative}.pure-menu a,.pure-menu .pure-menu-heading{display:block;color:inherit;line-height:1.5em;padding:5px 20px;text-decoration:none;white-space:nowrap}.pure-menu.pure-menu-horizontal>.pure-menu-heading{display:inline-block;*display:inline;zoom:1;margin:0;vertical-align:middle}.pure-menu.pure-menu-horizontal>ul{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.pure-menu li a{padding:5px 20px}.pure-menu-can-have-children>.pure-menu-label:after{content:'\25B8';float:right;font-family:'Lucida Grande','Lucida Sans Unicode','DejaVu Sans',sans-serif;margin-right:-20px;margin-top:-1px}.pure-menu-can-have-children>.pure-menu-label{padding-right:30px}.pure-menu-separator{background-color:#dfdfdf;display:block;height:1px;font-size:0;margin:7px 2px;overflow:hidden}.pure-menu-hidden{display:none}.pure-menu-fixed{position:fixed;top:0;left:0;width:100%}.pure-menu-horizontal li{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.pure-menu-horizontal li li{display:block}.pure-menu-horizontal>.pure-menu-children>.pure-menu-can-have-children>.pure-menu-label:after{content:"\25BE"}.pure-menu-horizontal>.pure-menu-children>.pure-menu-can-have-children>.pure-menu-label{padding-right:30px}.pure-menu-horizontal li.pure-menu-separator{height:50%;width:1px;margin:0 7px}.pure-menu-horizontal li li.pure-menu-separator{height:1px;width:auto;margin:7px 2px}.pure-menu.pure-menu-open,.pure-menu.pure-menu-horizontal li .pure-menu-children{background:#fff;border:1px solid #b7b7b7}.pure-menu.pure-menu-horizontal,.pure-menu.pure-menu-horizontal .pure-menu-heading{border:0}.pure-menu a{border:1px solid transparent;border-left:0;border-right:0}.pure-menu a,.pure-menu .pure-menu-can-have-children>li:after{color:#777}.pure-menu .pure-menu-can-have-children>li:hover:after{color:#fff}.pure-menu .pure-menu-open{background:#dedede}.pure-menu li a:hover,.pure-menu li a:focus{background:#eee}.pure-menu li.pure-menu-disabled a:hover,.pure-menu li.pure-menu-disabled a:focus{background:#fff;color:#bfbfbf}.pure-menu .pure-menu-disabled>a{background-image:none;border-color:transparent;cursor:default}.pure-menu .pure-menu-disabled>a,.pure-menu .pure-menu-can-have-children.pure-menu-disabled>a:after{color:#bfbfbf}.pure-menu .pure-menu-heading{color:#565d64;text-transform:uppercase;font-size:90%;margin-top:.5em;border-bottom-width:1px;border-bottom-style:solid;border-bottom-color:#dfdfdf}.pure-menu .pure-menu-selected a{color:#000}.pure-menu.pure-menu-open.pure-menu-fixed{border:0;border-bottom:1px solid #b7b7b7}.pure-paginator{letter-spacing:-.31em;*letter-spacing:normal;*word-spacing:-.43em;text-rendering:optimizespeed;list-style:none;margin:0;padding:0}.opera-only :-o-prefocus,.pure-paginator{word-spacing:-.43em}.pure-paginator li{display:inline-block;*display:inline;zoom:1;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-paginator .pure-button{border-radius:0;padding:.8em 1.4em;vertical-align:top;height:1.1em}.pure-paginator .pure-button:focus,.pure-paginator .pure-button:active{outline-style:none}.pure-paginator .prev,.pure-paginator .next{color:#C0C1C3;text-shadow:0 -1px 0 rgba(0,0,0,.45)}.pure-paginator .prev{border-radius:2px 0 0 2px}.pure-paginator .next{border-radius:0 2px 2px 0}@media (max-width:480px){.pure-menu-horizontal{width:100%}.pure-menu-children li{display:block;border-bottom:1px solid #000}}.pure-table{border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;font-size:inherit;margin:0;overflow:visible;padding:.5em 1em}.pure-table td:first-child,.pure-table th:first-child{border-left-width:0}.pure-table thead{background:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:transparent}.pure-table-odd td{background-color:#f2f2f2}.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child td,.pure-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #cbcbcb}.pure-table-horizontal tbody>tr:last-child td{border-bottom-width:0} \ No newline at end of file diff --git a/demo/public/css/style.css b/demo/public/css/style.css deleted file mode 100644 index 230b848..0000000 --- a/demo/public/css/style.css +++ /dev/null @@ -1,176 +0,0 @@ -@font-face{ - font-family: 'open_sansregular'; - src: url('../fonts/opensans_regular_macroman/OpenSans-Regular-webfont.eot'); - src: url('../fonts/opensans_regular_macroman/OpenSans-Regular-webfont.eot?iefix') format('eot'), - url('../fonts/opensans_regular_macroman/OpenSans-Regular-webfont.woff') format('woff'), - url('../fonts/opensans_regular_macroman/OpenSans-Regular-webfont.ttf') format('truetype'), - url('../fonts/opensans_regular_macroman/OpenSans-Regular-webfont.svg#webfont') format('svg'); - font-weight: normal; - font-style: normal; -} - -body, .pure-g [class *= "pure-u"], .pure-g-r [class *= "pure-u"] { - font-family: "open_sansregular"; - //font-size: 14px; -} - -h1, h2, h3 { - font-size: 33.75px; -} - -a { - color: #045DB7; - text-decoration: none; -} - -a:hover, a:focus { - text-decoration: underline; -} - -i { - margin: 2px; -} - -pre { - background-color: #F5F5F5; - border: 1px solid rgba(0, 0, 0, 0.15); - border-radius: 4px 4px 4px 4px; - display: block; - font-size: 14px; - line-height: 21px; - margin: 0 0 10.5px; - padding: 10px; - white-space: pre-wrap; - word-break: break-all; - word-wrap: break-word; -} -#banner { - background-color: #EEEEEE; - min-height: 20px; - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05) inset; - font-size: 20px; - font-weight: bold; -} - -#banner a { - color: #666666; -} - -#banner a:hover, a:focus { - color: #0099DD; -} - -#banner p { - display:inline; - font-weight: normal; - margin: 0 9px; -} - -.l-box { - padding: 1em; -} - -.tag-1 { - font-size : 12pt; -} - -.tag-2 { - font-size : 10pt; -} - -.tag-3 { - font-size : 8pt; -} - -.well { - background-color: #EEEEEE; - border: 1px solid #DCDCDC; - border-radius: 4px 4px 4px 4px; - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05) inset; - margin-bottom: 20px; - min-height: 20px; - padding: 19px; -} - -.title { - font-weight: bold; -} - -#sidebar i { - margin-right: 5px; -} - -.nolist { - list-style: none outside none; - margin-left: 0; - padding-left: 15px; -} - -.label { - background-color: #CCCCCC; - color: #FFFFFF; - font-weight: bold; - display: inline-block; - padding: 2px 4px; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - vertical-align: baseline; - white-space: nowrap; - border-radius: 3px 3px 3px 3px; -} - -.comment { - clear: both; - display: block; -} - -.inline { - display:inline; -} - -#footer { - background: none repeat scroll 0 0 #EEEEEE; - border-top: 7px solid #000000; - clear: both; - color: #444444; - margin-top: 30px; - padding: 15px; -} - -.divhidden { - left: -9999px; - position: relative; -} - -.preview-markdown { - border-radius: 10px 10px 10px 10px; - -moz-border-radius: 10px 10px 10px 10px; - -webkit-border-radius: 10px 10px 10px 10px; - border: 1px dashed #000000; - background: none repeat scroll 0 0 #a0bbb6; - padding: 10px; -} - -.button-success, -.button-error, -.button-warning, -.button-secondary { - color: white; - border-radius: 4px; - text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); -} - -.button-success { - background: rgb(28, 184, 65); /* this is a green */ -} - -.button-error { - background: rgb(202, 60, 60); /* this is a maroon */ -} - -.button-warning { - background: rgb(223, 117, 20); /* this is an orange */ -} - -.button-secondary { - background: rgb(66, 184, 221); /* this is a light blue */ -} diff --git a/demo/public/fonts/awesome/FontAwesome.otf b/demo/public/fonts/awesome/FontAwesome.otf deleted file mode 100644 index 3461e3f..0000000 Binary files a/demo/public/fonts/awesome/FontAwesome.otf and /dev/null differ diff --git a/demo/public/fonts/awesome/fontawesome-webfont.eot b/demo/public/fonts/awesome/fontawesome-webfont.eot deleted file mode 100755 index 6cfd566..0000000 Binary files a/demo/public/fonts/awesome/fontawesome-webfont.eot and /dev/null differ diff --git a/demo/public/fonts/awesome/fontawesome-webfont.svg b/demo/public/fonts/awesome/fontawesome-webfont.svg deleted file mode 100755 index a9f8469..0000000 --- a/demo/public/fonts/awesome/fontawesome-webfont.svg +++ /dev/null @@ -1,504 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/demo/public/fonts/awesome/fontawesome-webfont.ttf b/demo/public/fonts/awesome/fontawesome-webfont.ttf deleted file mode 100755 index 5cd6cff..0000000 Binary files a/demo/public/fonts/awesome/fontawesome-webfont.ttf and /dev/null differ diff --git a/demo/public/fonts/awesome/fontawesome-webfont.woff b/demo/public/fonts/awesome/fontawesome-webfont.woff deleted file mode 100755 index 9eaecb3..0000000 Binary files a/demo/public/fonts/awesome/fontawesome-webfont.woff and /dev/null differ diff --git a/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-demo.html b/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-demo.html deleted file mode 100644 index 6a96514..0000000 --- a/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-demo.html +++ /dev/null @@ -1,364 +0,0 @@ - - - - - - - - - - - - -Open Sans Regular Specimen - - - - - - -
- - - -
- - -
- -
-
-
AaBb
-
-
- -
-
A​B​C​D​E​F​G​H​I​J​K​L​M​N​O​P​Q​R​S​T​U​V​W​X​Y​Z​a​b​c​d​e​f​g​h​i​j​k​l​m​n​o​p​q​r​s​t​u​v​w​x​y​z​1​2​3​4​5​6​7​8​9​0​&​.​,​?​!​@​(​)​#​$​%​*​+​-​=​:​;
-
-
-
- - - - - - - - - - - - - - - - -
10abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
11abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
12abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
13abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
14abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
16abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
18abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
20abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
24abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
30abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
36abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
48abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
60abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
72abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
90abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
- -
- -
- - - -
- - -
-
◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼◼body
body
body
body
-
- bodyOpen Sans Regular -
-
- bodyArial -
-
- bodyVerdana -
-
- bodyGeorgia -
- - - -
- - -
- -
-

10.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

- -
-
-

11.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

- -
-
-

12.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

- -
-
-

13.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

- -
-
- -
-
-
-

14.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

- -
-
-

16.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

- -
-
-

18.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

- -
- -
- -
- -
-
-

20.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

-
-
-

24.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

-
- -
- -
- -
-
-

30.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

-
-
- -
- - - -
-
-

10.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

- -
-
-

11.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

- -
-
-

12.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

- -
-
-

13.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

- -
-
- -
- -
-
-

14.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

- -
-
-

16.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

- -
-
-

18.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

- -
-
- -
- -
-
-

20.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

-
-
-

24.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

-
- -
- -
- -
-
-

30.Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.

-
-
- -
- - - - -
- -
- -
- -
-

Lorem Ipsum Dolor

-

Etiam porta sem malesuada magna mollis euismod

- - -
-
-
-
-

Donec sed odio dui. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.

- - -

Pellentesque ornare sem

- -

Maecenas sed diam eget risus varius blandit sit amet non magna. Maecenas faucibus mollis interdum. Donec ullamcorper nulla non metus auctor fringilla. Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam id dolor id nibh ultricies vehicula ut id elit.

- -

Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.

- -

Nulla vitae elit libero, a pharetra augue. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Aenean lacinia bibendum nulla sed consectetur.

- -

Nullam quis risus eget urna mollis ornare vel eu leo. Nullam quis risus eget urna mollis ornare vel eu leo. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec ullamcorper nulla non metus auctor fringilla.

- -

Cras mattis consectetur

- -

Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Aenean lacinia bibendum nulla sed consectetur. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Cras mattis consectetur purus sit amet fermentum.

- -

Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam quis risus eget urna mollis ornare vel eu leo. Cras mattis consectetur purus sit amet fermentum.

-
- - -
- -
- - - - - - - - - -
- -
- -
-
-
-

Installing Webfonts

- -

Webfonts are supported by all major browser platforms but not all in the same way. There are currently four different font formats that must be included in order to target all browsers. This includes TTF, WOFF, EOT and SVG.

- -

1. Upload your webfonts

-

You must upload your webfont kit to your website. They should be in or near the same directory as your CSS files.

- -

2. Include the webfont stylesheet

-

A special CSS @font-face declaration helps the various browsers select the appropriate font it needs without causing you a bunch of headaches. Learn more about this syntax by reading the Fontspring blog post about it. The code for it is as follows:

- - - -@font-face{ - font-family: 'MyWebFont'; - src: url('WebFont.eot'); - src: url('WebFont.eot?iefix') format('eot'), - url('WebFont.woff') format('woff'), - url('WebFont.ttf') format('truetype'), - url('WebFont.svg#webfont') format('svg'); -} - - -

We've already gone ahead and generated the code for you. All you have to do is link to the stylesheet in your HTML, like this:

- <link rel="stylesheet" href="stylesheet.css" type="text/css" charset="utf-8" /> - -

3. Modify your own stylesheet

-

To take advantage of your new fonts, you must tell your stylesheet to use them. Look at the original @font-face declaration above and find the property called "font-family." The name linked there will be what you use to reference the font. Prepend that webfont name to the font stack in the "font-family" property, inside the selector you want to change. For example:

-p { font-family: 'MyWebFont', Arial, sans-serif; } - -

4. Test

-

Getting webfonts to work cross-browser can be tricky. Use the information in the sidebar to help you if you find that fonts aren't loading in a particular browser.

-
- - -
- -
- -
- -
- - diff --git a/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.eot b/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.eot deleted file mode 100644 index 6bbc3cf..0000000 Binary files a/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.eot and /dev/null differ diff --git a/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.svg b/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.svg deleted file mode 100644 index 25a3952..0000000 --- a/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.svg +++ /dev/null @@ -1,1831 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.ttf b/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.ttf deleted file mode 100644 index c537f83..0000000 Binary files a/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.ttf and /dev/null differ diff --git a/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.woff b/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.woff deleted file mode 100644 index e231183..0000000 Binary files a/demo/public/fonts/opensans_regular_macroman/OpenSans-Regular-webfont.woff and /dev/null differ diff --git a/demo/public/fonts/opensans_regular_macroman/specimen_files/OpenSans-Regular-cleartype.png b/demo/public/fonts/opensans_regular_macroman/specimen_files/OpenSans-Regular-cleartype.png deleted file mode 100644 index 8dfaed3..0000000 Binary files a/demo/public/fonts/opensans_regular_macroman/specimen_files/OpenSans-Regular-cleartype.png and /dev/null differ diff --git a/demo/public/fonts/opensans_regular_macroman/specimen_files/easytabs.js b/demo/public/fonts/opensans_regular_macroman/specimen_files/easytabs.js deleted file mode 100644 index 167f53b..0000000 --- a/demo/public/fonts/opensans_regular_macroman/specimen_files/easytabs.js +++ /dev/null @@ -1,7 +0,0 @@ -(function($){$.fn.easyTabs=function(option){var param=jQuery.extend({fadeSpeed:"fast",defaultContent:1,activeClass:'active'},option);$(this).each(function(){var thisId="#"+this.id;if(param.defaultContent==''){param.defaultContent=1;} -if(typeof param.defaultContent=="number") -{var defaultTab=$(thisId+" .tabs li:eq("+(param.defaultContent-1)+") a").attr('href').substr(1);}else{var defaultTab=param.defaultContent;} -$(thisId+" .tabs li a").each(function(){var tabToHide=$(this).attr('href').substr(1);$("#"+tabToHide).addClass('easytabs-tab-content');});hideAll();changeContent(defaultTab);function hideAll(){$(thisId+" .easytabs-tab-content").hide();} -function changeContent(tabId){hideAll();$(thisId+" .tabs li").removeClass(param.activeClass);$(thisId+" .tabs li a[href=#"+tabId+"]").closest('li').addClass(param.activeClass);if(param.fadeSpeed!="none") -{$(thisId+" #"+tabId).fadeIn(param.fadeSpeed);}else{$(thisId+" #"+tabId).show();}} -$(thisId+" .tabs li").click(function(){var tabId=$(this).find('a').attr('href').substr(1);changeContent(tabId);return false;});});}})(jQuery); \ No newline at end of file diff --git a/demo/public/fonts/opensans_regular_macroman/specimen_files/grid_12-825-55-15.css b/demo/public/fonts/opensans_regular_macroman/specimen_files/grid_12-825-55-15.css deleted file mode 100644 index 3d6aef7..0000000 --- a/demo/public/fonts/opensans_regular_macroman/specimen_files/grid_12-825-55-15.css +++ /dev/null @@ -1,129 +0,0 @@ -/*Notes about grid: -Columns: 12 -Grid Width: 825px -Column Width: 55px -Gutter Width: 15px --------------------------------*/ - - - -.section {margin-bottom: 18px; -} -.section:after {content: ".";display: block;height: 0;clear: both;visibility: hidden;} -.section {*zoom: 1;} - -.section .firstcolumn, -.section .firstcol {margin-left: 0;} - - -/* Border on left hand side of a column. */ -.border { - padding-left: 7px; - margin-left: 7px; - border-left: 1px solid #eee; -} - -/* Border with more whitespace, spans one column. */ -.colborder { - padding-left: 42px; - margin-left: 42px; - border-left: 1px solid #eee; -} - - - -/* The Grid Classes */ -.grid1, .grid1_2cols, .grid1_3cols, .grid1_4cols, .grid2, .grid2_3cols, .grid2_4cols, .grid3, .grid3_2cols, .grid3_4cols, .grid4, .grid4_3cols, .grid5, .grid5_2cols, .grid5_3cols, .grid5_4cols, .grid6, .grid6_4cols, .grid7, .grid7_2cols, .grid7_3cols, .grid7_4cols, .grid8, .grid8_3cols, .grid9, .grid9_2cols, .grid9_4cols, .grid10, .grid10_3cols, .grid10_4cols, .grid11, .grid11_2cols, .grid11_3cols, .grid11_4cols, .grid12 -{margin-left: 15px;float: left;display: inline; overflow: hidden;} - - -.width1, .grid1, .span-1 {width: 55px;} -.width1_2cols,.grid1_2cols {width: 20px;} -.width1_3cols,.grid1_3cols {width: 8px;} -.width1_4cols,.grid1_4cols {width: 2px;} -.input_width1 {width: 49px;} - -.width2, .grid2, .span-2 {width: 125px;} -.width2_3cols,.grid2_3cols {width: 31px;} -.width2_4cols,.grid2_4cols {width: 20px;} -.input_width2 {width: 119px;} - -.width3, .grid3, .span-3 {width: 195px;} -.width3_2cols,.grid3_2cols {width: 90px;} -.width3_4cols,.grid3_4cols {width: 37px;} -.input_width3 {width: 189px;} - -.width4, .grid4, .span-4 {width: 265px;} -.width4_3cols,.grid4_3cols {width: 78px;} -.input_width4 {width: 259px;} - -.width5, .grid5, .span-5 {width: 335px;} -.width5_2cols,.grid5_2cols {width: 160px;} -.width5_3cols,.grid5_3cols {width: 101px;} -.width5_4cols,.grid5_4cols {width: 72px;} -.input_width5 {width: 329px;} - -.width6, .grid6, .span-6 {width: 405px;} -.width6_4cols,.grid6_4cols {width: 90px;} -.input_width6 {width: 399px;} - -.width7, .grid7, .span-7 {width: 475px;} -.width7_2cols,.grid7_2cols {width: 230px;} -.width7_3cols,.grid7_3cols {width: 148px;} -.width7_4cols,.grid7_4cols {width: 107px;} -.input_width7 {width: 469px;} - -.width8, .grid8, .span-8 {width: 545px;} -.width8_3cols,.grid8_3cols {width: 171px;} -.input_width8 {width: 539px;} - -.width9, .grid9, .span-9 {width: 615px;} -.width9_2cols,.grid9_2cols {width: 300px;} -.width9_4cols,.grid9_4cols {width: 142px;} -.input_width9 {width: 609px;} - -.width10, .grid10, .span-10 {width: 685px;} -.width10_3cols,.grid10_3cols {width: 218px;} -.width10_4cols,.grid10_4cols {width: 160px;} -.input_width10 {width: 679px;} - -.width11, .grid11, .span-11 {width: 755px;} -.width11_2cols,.grid11_2cols {width: 370px;} -.width11_3cols,.grid11_3cols {width: 241px;} -.width11_4cols,.grid11_4cols {width: 177px;} -.input_width11 {width: 749px;} - -.width12, .grid12, .span-12 {width: 825px;} -.input_width12 {width: 819px;} - -/* Subdivided grid spaces */ -.emptycols_left1, .prepend-1 {padding-left: 70px;} -.emptycols_right1, .append-1 {padding-right: 70px;} -.emptycols_left2, .prepend-2 {padding-left: 140px;} -.emptycols_right2, .append-2 {padding-right: 140px;} -.emptycols_left3, .prepend-3 {padding-left: 210px;} -.emptycols_right3, .append-3 {padding-right: 210px;} -.emptycols_left4, .prepend-4 {padding-left: 280px;} -.emptycols_right4, .append-4 {padding-right: 280px;} -.emptycols_left5, .prepend-5 {padding-left: 350px;} -.emptycols_right5, .append-5 {padding-right: 350px;} -.emptycols_left6, .prepend-6 {padding-left: 420px;} -.emptycols_right6, .append-6 {padding-right: 420px;} -.emptycols_left7, .prepend-7 {padding-left: 490px;} -.emptycols_right7, .append-7 {padding-right: 490px;} -.emptycols_left8, .prepend-8 {padding-left: 560px;} -.emptycols_right8, .append-8 {padding-right: 560px;} -.emptycols_left9, .prepend-9 {padding-left: 630px;} -.emptycols_right9, .append-9 {padding-right: 630px;} -.emptycols_left10, .prepend-10 {padding-left: 700px;} -.emptycols_right10, .append-10 {padding-right: 700px;} -.emptycols_left11, .prepend-11 {padding-left: 770px;} -.emptycols_right11, .append-11 {padding-right: 770px;} -.pull-1 {margin-left: -70px;} -.push-1 {margin-right: -70px;margin-left: 18px;float: right;} -.pull-2 {margin-left: -140px;} -.push-2 {margin-right: -140px;margin-left: 18px;float: right;} -.pull-3 {margin-left: -210px;} -.push-3 {margin-right: -210px;margin-left: 18px;float: right;} -.pull-4 {margin-left: -280px;} -.push-4 {margin-right: -280px;margin-left: 18px;float: right;} \ No newline at end of file diff --git a/demo/public/fonts/opensans_regular_macroman/specimen_files/specimen_stylesheet.css b/demo/public/fonts/opensans_regular_macroman/specimen_files/specimen_stylesheet.css deleted file mode 100644 index aecc43c..0000000 --- a/demo/public/fonts/opensans_regular_macroman/specimen_files/specimen_stylesheet.css +++ /dev/null @@ -1,396 +0,0 @@ -@import url('grid_12-825-55-15.css'); - -/* - CSS Reset by Eric Meyer - Released under Public Domain - http://meyerweb.com/eric/tools/css/reset/ -*/ -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, font, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -b, u, i, center, dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, table, -caption, tbody, tfoot, thead, tr, th, td - {margin: 0;padding: 0;border: 0;outline: 0; - font-size: 100%;vertical-align: baseline; - background: transparent;} -body {line-height: 1;} -ol, ul {list-style: none;} -blockquote, q {quotes: none;} -blockquote:before, blockquote:after, -q:before, q:after {content: ''; content: none;} -:focus {outline: 0;} -ins {text-decoration: none;} -del {text-decoration: line-through;} -table {border-collapse: collapse;border-spacing: 0;} - - - - -body { - color: #000; - background-color: #dcdcdc; -} - -a { - text-decoration: none; - color: #1883ba; -} - -h1{ - font-size: 32px; - font-weight: normal; - font-style: normal; - margin-bottom: 18px; -} - -h2{ - font-size: 18px; -} - -#container { - width: 865px; - margin: 0px auto; -} - - -#header { - padding: 20px; - font-size: 36px; - background-color: #000; - color: #fff; -} - -#header span { - color: #666; -} -#main_content { - background-color: #fff; - padding: 60px 20px 20px; -} - - -#footer p { - margin: 0; - padding-top: 10px; - padding-bottom: 50px; - color: #333; - font: 10px Arial, sans-serif; -} - -.tabs { - width: 100%; - height: 31px; - background-color: #444; -} -.tabs li { - float: left; - margin: 0; - overflow: hidden; - background-color: #444; -} -.tabs li a { - display: block; - color: #fff; - text-decoration: none; - font: bold 11px/11px 'Arial'; - text-transform: uppercase; - padding: 10px 15px; - border-right: 1px solid #fff; -} - -.tabs li a:hover { - background-color: #00b3ff; - -} - -.tabs li.active a { - color: #000; - background-color: #fff; -} - - - -div.huge { - - font-size: 300px; - line-height: 1em; - padding: 0; - letter-spacing: -.02em; - overflow: hidden; -} -div.glyph_range { - font-size: 72px; - line-height: 1.1em; -} - -.size10{ font-size: 10px; } -.size11{ font-size: 11px; } -.size12{ font-size: 12px; } -.size13{ font-size: 13px; } -.size14{ font-size: 14px; } -.size16{ font-size: 16px; } -.size18{ font-size: 18px; } -.size20{ font-size: 20px; } -.size24{ font-size: 24px; } -.size30{ font-size: 30px; } -.size36{ font-size: 36px; } -.size48{ font-size: 48px; } -.size60{ font-size: 60px; } -.size72{ font-size: 72px; } -.size90{ font-size: 90px; } - - -.psample_row1 { height: 120px;} -.psample_row1 { height: 120px;} -.psample_row2 { height: 160px;} -.psample_row3 { height: 160px;} -.psample_row4 { height: 160px;} - -.psample { - overflow: hidden; - position: relative; -} -.psample p { - line-height: 1.3em; - display: block; - overflow: hidden; - margin: 0; -} - -.psample span { - margin-right: .5em; -} - -.white_blend { - width: 100%; - height: 61px; - background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAVkAAAA9CAYAAAAH4BojAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAO1JREFUeNrs3TsKgFAMRUE/eer+NxztxMYuEWQG3ECKwwUF58ycAKixOAGAyAKILAAiCyCyACILgMgCiCyAyAIgsgAiCyCyAIgsgMgCiCwAIgsgsgAiC4DIAogsACIL0CWuZ3UGgLrIhjMA1EV2OAOAJQtgyQLwjOzmDAAiCyCyAIgsQFtkd2cAEFkAkQVAZAHaIns4A4AlC2DJAiCyACILILIAiCzAV5H1dQGAJQsgsgCILIDIAvwisl58AViyAJYsACILILIAIgvAe2T9EhxAZAFEFgCRBeiL7HAGgLrIhjMAWLIAliwAt1OAAQDwygTBulLIlQAAAABJRU5ErkJggg==); - position: absolute; - bottom: 0; -} -.black_blend { - width: 100%; - height: 61px; - background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAVkAAAA9CAYAAAAH4BojAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAPJJREFUeNrs3TEKhTAQRVGjibr/9QoxhY2N3Ywo50A28IrLwP9g6b1PAMSYTQAgsgAiC4DIAogsgMgCILIAIgsgsgCILIDIAogsACILILIAIguAyAKILIDIAiCyACILgMgCZCnjLWYAiFGvB0BQZJsZAFyyAC5ZAO6RXc0AILIAIguAyAKkRXYzA4DIAogsACILkBbZ3QwALlkAlywAIgsgsgAiC4DIArwVWf8uAHDJAogsACILILIAv4isH74AXLIALlkARBZAZAFEFoDnyPokOIDIAogsACILkBfZZgaAuMhWMwC4ZAE+p4x3mAEgxinAAJ+XBbPWGkwAAAAAAElFTkSuQmCC); - position: absolute; - bottom: 0; -} -.fullreverse { - background: #000 !important; - color: #fff !important; - margin-left: -20px; - padding-left: 20px; - margin-right: -20px; - padding-right: 20px; - padding: 20px; - margin-bottom:0; -} - - -.sample_table td { - padding-top: 3px; - padding-bottom:5px; - padding-left: 5px; - vertical-align: middle; - line-height: 1.2em; -} - -.sample_table td:first-child { - background-color: #eee; - text-align: right; - padding-right: 5px; - padding-left: 0; - padding: 5px; - font: 11px/12px "Courier New", Courier, mono; -} - -code { - white-space: pre; - background-color: #eee; - display: block; - padding: 10px; - margin-bottom: 18px; - overflow: auto; -} - - -.bottom,.last {margin-bottom:0 !important; padding-bottom:0 !important;} - -.box { - padding: 18px; - margin-bottom: 18px; - background: #eee; -} - -.reverse,.reversed { background: #000 !important;color: #fff !important; border: none !important;} - -#bodycomparison { - position: relative; - overflow: hidden; - font-size: 72px; - height: 90px; - white-space: nowrap; -} - -#bodycomparison div{ - font-size: 72px; - line-height: 90px; - display: inline; - margin: 0 15px 0 0; - padding: 0; -} - -#bodycomparison div span{ - font: 10px Arial; - position: absolute; - left: 0; -} -#xheight { - float: none; - position: absolute; - color: #d9f3ff; - font-size: 72px; - line-height: 90px; -} - -.fontbody { - position: relative; -} -.arialbody{ - font-family: Arial; - position: relative; -} -.verdanabody{ - font-family: Verdana; - position: relative; -} -.georgiabody{ - font-family: Georgia; - position: relative; -} - -/* @group Layout page - */ - -#layout h1 { - font-size: 36px; - line-height: 42px; - font-weight: normal; - font-style: normal; -} - -#layout h2 { - font-size: 24px; - line-height: 23px; - font-weight: normal; - font-style: normal; -} - -#layout h3 { - font-size: 22px; - line-height: 1.4em; - margin-top: 1em; - font-weight: normal; - font-style: normal; -} - - -#layout p.byline { - font-size: 12px; - margin-top: 18px; - line-height: 12px; - margin-bottom: 0; -} -#layout p { - font-size: 14px; - line-height: 21px; - margin-bottom: .5em; -} - -#layout p.large{ - font-size: 18px; - line-height: 26px; -} - -#layout .sidebar p{ - font-size: 12px; - line-height: 1.4em; -} - -#layout p.caption { - font-size: 10px; - margin-top: -16px; - margin-bottom: 18px; -} - -/* @end */ - -/* @group Glyphs */ - -#glyph_chart div{ - background-color: #d9f3ff; - color: black; - float: left; - font-size: 36px; - height: 1.2em; - line-height: 1.2em; - margin-bottom: 1px; - margin-right: 1px; - text-align: center; - width: 1.2em; - position: relative; - padding: .6em .2em .2em; -} - -#glyph_chart div p { - position: absolute; - left: 0; - top: 0; - display: block; - text-align: center; - font: bold 9px Arial, sans-serif; - background-color: #3a768f; - width: 100%; - color: #fff; - padding: 2px 0; -} - - -#glyphs h1 { - font-family: Arial, sans-serif; -} -/* @end */ - -/* @group Installing */ - -#installing { - font: 13px Arial, sans-serif; -} - -#installing p, -#glyphs p{ - line-height: 1.2em; - margin-bottom: 18px; - font: 13px Arial, sans-serif; -} - - - -#installing h3{ - font-size: 15px; - margin-top: 18px; -} - -/* @end */ - -#rendering h1 { - font-family: Arial, sans-serif; -} -.render_table td { - font: 11px "Courier New", Courier, mono; - vertical-align: middle; -} - - diff --git a/demo/public/fonts/opensans_regular_macroman/stylesheet.css b/demo/public/fonts/opensans_regular_macroman/stylesheet.css deleted file mode 100644 index 667edd4..0000000 --- a/demo/public/fonts/opensans_regular_macroman/stylesheet.css +++ /dev/null @@ -1,12 +0,0 @@ -@font-face { - font-family: 'open_sansregular'; - src: url('OpenSans-Regular-webfont.eot'); - src: url('OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'), - url('OpenSans-Regular-webfont.woff') format('woff'), - url('OpenSans-Regular-webfont.ttf') format('truetype'), - url('OpenSans-Regular-webfont.svg#open_sansregular') format('svg'); - font-weight: normal; - font-style: normal; - -} - diff --git a/demo/public/img/308a3596152a79231f3feedc49afa4ef.jpg b/demo/public/img/308a3596152a79231f3feedc49afa4ef.jpg deleted file mode 100644 index 5eab9f4..0000000 Binary files a/demo/public/img/308a3596152a79231f3feedc49afa4ef.jpg and /dev/null differ diff --git a/demo/public/img/b133b66b7edc9f7ffb5cf74a87e63652.jpg b/demo/public/img/b133b66b7edc9f7ffb5cf74a87e63652.jpg deleted file mode 100644 index 8a23176..0000000 Binary files a/demo/public/img/b133b66b7edc9f7ffb5cf74a87e63652.jpg and /dev/null differ diff --git a/demo/public/img/b74caa9a8f22ddff361b5ea413ea4f7a.jpg b/demo/public/img/b74caa9a8f22ddff361b5ea413ea4f7a.jpg deleted file mode 100644 index 04f1b1e..0000000 Binary files a/demo/public/img/b74caa9a8f22ddff361b5ea413ea4f7a.jpg and /dev/null differ diff --git a/demo/public/img/framasoft.png b/demo/public/img/framasoft.png deleted file mode 100644 index 71e74c3..0000000 Binary files a/demo/public/img/framasoft.png and /dev/null differ diff --git a/demo/public/img/id-150.jpg b/demo/public/img/id-150.jpg deleted file mode 100644 index ebc872a..0000000 Binary files a/demo/public/img/id-150.jpg and /dev/null differ diff --git a/demo/public/img/license.png b/demo/public/img/license.png deleted file mode 100644 index 0f2a0f1..0000000 Binary files a/demo/public/img/license.png and /dev/null differ diff --git a/demo/public/img/planet-link.png b/demo/public/img/planet-link.png deleted file mode 100644 index 6065de7..0000000 Binary files a/demo/public/img/planet-link.png and /dev/null differ diff --git a/demo/public/index.html b/demo/public/index.html deleted file mode 100644 index 7e016ef..0000000 --- a/demo/public/index.html +++ /dev/null @@ -1,217 +0,0 @@ - - - - - - - - Bilan 2014 de l'hébergement - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-

- -
-
-
-
-
-
-

- Bilan 2014 de l'hébergement -

-
-
-
-
- Date - Sat 20 December 2014 - - Tags - Hébergement -
-

Déjà une année que j'ai migré l'hébergement de mes services d'un serveur à la -maison vers un hébergeur, en l'occurence OVH et son VPS Classic 2, sur un -container OpenVz dédié.

-

Les services hébergés sont à peu près les mêmes avec quelques nouveautés :

-
    -
  • ce blog statique et son serveur de commentaires,
  • -
  • une instance de Shaarli pour les favoris merci SEB Sauvage,
  • -
  • un lecteur de flux RSS : Tiny Tiny RSS,
  • -
  • l'analyseur du trafic du blog : Piwik,
  • -
  • mon cloud personnel ownCloud auquel j'ai confié récemment le partage de quelques fichiers et de mon calendrier,
  • -
  • l'interface Web de consultation des e-mails Roundcube mais les e-mails restent chez Gandi.
  • -
-

J'ai commencé à utilisé Wallabag depuis quelques -semaines, c'est la pièce qui me manquait dans mon processus de veille, entre le -lien récupéré en vitesse et avant l'ajout du lien dans mes favoris Shaarli s'il -en vaut la peine. Je l'utilise dans le cadre du projet -Framabag mais je prévois d'installer ma propre -instance d'ici peu.

-
- -
- - - - - - - - -

- - - - - - - - - - - - -
-
-
-
-
- -
- -
- -
- - - - - - - - - - - - diff --git a/demo/public/js/markdown.js b/demo/public/js/markdown.js deleted file mode 100644 index 65b04e4..0000000 --- a/demo/public/js/markdown.js +++ /dev/null @@ -1,1740 +0,0 @@ -// Released under MIT license -// Copyright (c) 2009-2010 Dominic Baggott -// Copyright (c) 2009-2010 Ash Berlin -// Copyright (c) 2011 Christoph Dorn (http://www.christophdorn.com) -// Date: 2013-09-15T16:12Z - -(function(expose) { - - - - - var MarkdownHelpers = {}; - - // For Spidermonkey based engines - function mk_block_toSource() { - return "Markdown.mk_block( " + - uneval(this.toString()) + - ", " + - uneval(this.trailing) + - ", " + - uneval(this.lineNumber) + - " )"; - } - - // node - function mk_block_inspect() { - var util = require("util"); - return "Markdown.mk_block( " + - util.inspect(this.toString()) + - ", " + - util.inspect(this.trailing) + - ", " + - util.inspect(this.lineNumber) + - " )"; - - } - - MarkdownHelpers.mk_block = function(block, trail, line) { - // Be helpful for default case in tests. - if ( arguments.length === 1 ) - trail = "\n\n"; - - // We actually need a String object, not a string primitive - /* jshint -W053 */ - var s = new String(block); - s.trailing = trail; - // To make it clear its not just a string - s.inspect = mk_block_inspect; - s.toSource = mk_block_toSource; - - if ( line !== undefined ) - s.lineNumber = line; - - return s; - }; - - - var isArray = MarkdownHelpers.isArray = Array.isArray || function(obj) { - return Object.prototype.toString.call(obj) === "[object Array]"; - }; - - // Don't mess with Array.prototype. Its not friendly - if ( Array.prototype.forEach ) { - MarkdownHelpers.forEach = function forEach( arr, cb, thisp ) { - return arr.forEach( cb, thisp ); - }; - } - else { - MarkdownHelpers.forEach = function forEach(arr, cb, thisp) { - for (var i = 0; i < arr.length; i++) - cb.call(thisp || arr, arr[i], i, arr); - }; - } - - MarkdownHelpers.isEmpty = function isEmpty( obj ) { - for ( var key in obj ) { - if ( hasOwnProperty.call( obj, key ) ) - return false; - } - return true; - }; - - MarkdownHelpers.extract_attr = function extract_attr( jsonml ) { - return isArray(jsonml) - && jsonml.length > 1 - && typeof jsonml[ 1 ] === "object" - && !( isArray(jsonml[ 1 ]) ) - ? jsonml[ 1 ] - : undefined; - }; - - - - - /** - * class Markdown - * - * Markdown processing in Javascript done right. We have very particular views - * on what constitutes 'right' which include: - * - * - produces well-formed HTML (this means that em and strong nesting is - * important) - * - * - has an intermediate representation to allow processing of parsed data (We - * in fact have two, both as [JsonML]: a markdown tree and an HTML tree). - * - * - is easily extensible to add new dialects without having to rewrite the - * entire parsing mechanics - * - * - has a good test suite - * - * This implementation fulfills all of these (except that the test suite could - * do with expanding to automatically run all the fixtures from other Markdown - * implementations.) - * - * ##### Intermediate Representation - * - * *TODO* Talk about this :) Its JsonML, but document the node names we use. - * - * [JsonML]: http://jsonml.org/ "JSON Markup Language" - **/ - var Markdown = function(dialect) { - switch (typeof dialect) { - case "undefined": - this.dialect = Markdown.dialects.Gruber; - break; - case "object": - this.dialect = dialect; - break; - default: - if ( dialect in Markdown.dialects ) - this.dialect = Markdown.dialects[dialect]; - else - throw new Error("Unknown Markdown dialect '" + String(dialect) + "'"); - break; - } - this.em_state = []; - this.strong_state = []; - this.debug_indent = ""; - }; - - /** - * Markdown.dialects - * - * Namespace of built-in dialects. - **/ - Markdown.dialects = {}; - - - - - // Imported functions - var mk_block = Markdown.mk_block = MarkdownHelpers.mk_block, - isArray = MarkdownHelpers.isArray; - - /** - * parse( markdown, [dialect] ) -> JsonML - * - markdown (String): markdown string to parse - * - dialect (String | Dialect): the dialect to use, defaults to gruber - * - * Parse `markdown` and return a markdown document as a Markdown.JsonML tree. - **/ - Markdown.parse = function( source, dialect ) { - // dialect will default if undefined - var md = new Markdown( dialect ); - return md.toTree( source ); - }; - - function count_lines( str ) { - var n = 0, - i = -1; - while ( ( i = str.indexOf("\n", i + 1) ) !== -1 ) - n++; - return n; - } - - // Internal - split source into rough blocks - Markdown.prototype.split_blocks = function splitBlocks( input ) { - input = input.replace(/(\r\n|\n|\r)/g, "\n"); - // [\s\S] matches _anything_ (newline or space) - // [^] is equivalent but doesn't work in IEs. - var re = /([\s\S]+?)($|\n#|\n(?:\s*\n|$)+)/g, - blocks = [], - m; - - var line_no = 1; - - if ( ( m = /^(\s*\n)/.exec(input) ) !== null ) { - // skip (but count) leading blank lines - line_no += count_lines( m[0] ); - re.lastIndex = m[0].length; - } - - while ( ( m = re.exec(input) ) !== null ) { - if (m[2] === "\n#") { - m[2] = "\n"; - re.lastIndex--; - } - blocks.push( mk_block( m[1], m[2], line_no ) ); - line_no += count_lines( m[0] ); - } - - return blocks; - }; - - /** - * Markdown#processBlock( block, next ) -> undefined | [ JsonML, ... ] - * - block (String): the block to process - * - next (Array): the following blocks - * - * Process `block` and return an array of JsonML nodes representing `block`. - * - * It does this by asking each block level function in the dialect to process - * the block until one can. Succesful handling is indicated by returning an - * array (with zero or more JsonML nodes), failure by a false value. - * - * Blocks handlers are responsible for calling [[Markdown#processInline]] - * themselves as appropriate. - * - * If the blocks were split incorrectly or adjacent blocks need collapsing you - * can adjust `next` in place using shift/splice etc. - * - * If any of this default behaviour is not right for the dialect, you can - * define a `__call__` method on the dialect that will get invoked to handle - * the block processing. - */ - Markdown.prototype.processBlock = function processBlock( block, next ) { - var cbs = this.dialect.block, - ord = cbs.__order__; - - if ( "__call__" in cbs ) - return cbs.__call__.call(this, block, next); - - for ( var i = 0; i < ord.length; i++ ) { - //D:this.debug( "Testing", ord[i] ); - var res = cbs[ ord[i] ].call( this, block, next ); - if ( res ) { - //D:this.debug(" matched"); - if ( !isArray(res) || ( res.length > 0 && !( isArray(res[0]) ) ) ) - this.debug(ord[i], "didn't return a proper array"); - //D:this.debug( "" ); - return res; - } - } - - // Uhoh! no match! Should we throw an error? - return []; - }; - - Markdown.prototype.processInline = function processInline( block ) { - return this.dialect.inline.__call__.call( this, String( block ) ); - }; - - /** - * Markdown#toTree( source ) -> JsonML - * - source (String): markdown source to parse - * - * Parse `source` into a JsonML tree representing the markdown document. - **/ - // custom_tree means set this.tree to `custom_tree` and restore old value on return - Markdown.prototype.toTree = function toTree( source, custom_root ) { - var blocks = source instanceof Array ? source : this.split_blocks( source ); - - // Make tree a member variable so its easier to mess with in extensions - var old_tree = this.tree; - try { - this.tree = custom_root || this.tree || [ "markdown" ]; - - blocks_loop: - while ( blocks.length ) { - var b = this.processBlock( blocks.shift(), blocks ); - - // Reference blocks and the like won't return any content - if ( !b.length ) - continue blocks_loop; - - this.tree.push.apply( this.tree, b ); - } - return this.tree; - } - finally { - if ( custom_root ) - this.tree = old_tree; - } - }; - - // Noop by default - Markdown.prototype.debug = function () { - var args = Array.prototype.slice.call( arguments); - args.unshift(this.debug_indent); - if ( typeof print !== "undefined" ) - print.apply( print, args ); - if ( typeof console !== "undefined" && typeof console.log !== "undefined" ) - console.log.apply( null, args ); - }; - - Markdown.prototype.loop_re_over_block = function( re, block, cb ) { - // Dont use /g regexps with this - var m, - b = block.valueOf(); - - while ( b.length && (m = re.exec(b) ) !== null ) { - b = b.substr( m[0].length ); - cb.call(this, m); - } - return b; - }; - - // Build default order from insertion order. - Markdown.buildBlockOrder = function(d) { - var ord = []; - for ( var i in d ) { - if ( i === "__order__" || i === "__call__" ) - continue; - ord.push( i ); - } - d.__order__ = ord; - }; - - // Build patterns for inline matcher - Markdown.buildInlinePatterns = function(d) { - var patterns = []; - - for ( var i in d ) { - // __foo__ is reserved and not a pattern - if ( i.match( /^__.*__$/) ) - continue; - var l = i.replace( /([\\.*+?|()\[\]{}])/g, "\\$1" ) - .replace( /\n/, "\\n" ); - patterns.push( i.length === 1 ? l : "(?:" + l + ")" ); - } - - patterns = patterns.join("|"); - d.__patterns__ = patterns; - //print("patterns:", uneval( patterns ) ); - - var fn = d.__call__; - d.__call__ = function(text, pattern) { - if ( pattern !== undefined ) - return fn.call(this, text, pattern); - else - return fn.call(this, text, patterns); - }; - }; - - - - - var extract_attr = MarkdownHelpers.extract_attr; - - /** - * renderJsonML( jsonml[, options] ) -> String - * - jsonml (Array): JsonML array to render to XML - * - options (Object): options - * - * Converts the given JsonML into well-formed XML. - * - * The options currently understood are: - * - * - root (Boolean): wether or not the root node should be included in the - * output, or just its children. The default `false` is to not include the - * root itself. - */ - Markdown.renderJsonML = function( jsonml, options ) { - options = options || {}; - // include the root element in the rendered output? - options.root = options.root || false; - - var content = []; - - if ( options.root ) { - content.push( render_tree( jsonml ) ); - } - else { - jsonml.shift(); // get rid of the tag - if ( jsonml.length && typeof jsonml[ 0 ] === "object" && !( jsonml[ 0 ] instanceof Array ) ) - jsonml.shift(); // get rid of the attributes - - while ( jsonml.length ) - content.push( render_tree( jsonml.shift() ) ); - } - - return content.join( "\n\n" ); - }; - - - /** - * toHTMLTree( markdown, [dialect] ) -> JsonML - * toHTMLTree( md_tree ) -> JsonML - * - markdown (String): markdown string to parse - * - dialect (String | Dialect): the dialect to use, defaults to gruber - * - md_tree (Markdown.JsonML): parsed markdown tree - * - * Turn markdown into HTML, represented as a JsonML tree. If a string is given - * to this function, it is first parsed into a markdown tree by calling - * [[parse]]. - **/ - Markdown.toHTMLTree = function toHTMLTree( input, dialect , options ) { - - // convert string input to an MD tree - if ( typeof input === "string" ) - input = this.parse( input, dialect ); - - // Now convert the MD tree to an HTML tree - - // remove references from the tree - var attrs = extract_attr( input ), - refs = {}; - - if ( attrs && attrs.references ) - refs = attrs.references; - - var html = convert_tree_to_html( input, refs , options ); - merge_text_nodes( html ); - return html; - }; - - /** - * toHTML( markdown, [dialect] ) -> String - * toHTML( md_tree ) -> String - * - markdown (String): markdown string to parse - * - md_tree (Markdown.JsonML): parsed markdown tree - * - * Take markdown (either as a string or as a JsonML tree) and run it through - * [[toHTMLTree]] then turn it into a well-formated HTML fragment. - **/ - Markdown.toHTML = function toHTML( source , dialect , options ) { - var input = this.toHTMLTree( source , dialect , options ); - - return this.renderJsonML( input ); - }; - - - function escapeHTML( text ) { - return text.replace( /&/g, "&" ) - .replace( //g, ">" ) - .replace( /"/g, """ ) - .replace( /'/g, "'" ); - } - - function render_tree( jsonml ) { - // basic case - if ( typeof jsonml === "string" ) - return escapeHTML( jsonml ); - - var tag = jsonml.shift(), - attributes = {}, - content = []; - - if ( jsonml.length && typeof jsonml[ 0 ] === "object" && !( jsonml[ 0 ] instanceof Array ) ) - attributes = jsonml.shift(); - - while ( jsonml.length ) - content.push( render_tree( jsonml.shift() ) ); - - var tag_attrs = ""; - for ( var a in attributes ) - tag_attrs += " " + a + '="' + escapeHTML( attributes[ a ] ) + '"'; - - // be careful about adding whitespace here for inline elements - if ( tag === "img" || tag === "br" || tag === "hr" ) - return "<"+ tag + tag_attrs + "/>"; - else - return "<"+ tag + tag_attrs + ">" + content.join( "" ) + ""; - } - - function convert_tree_to_html( tree, references, options ) { - var i; - options = options || {}; - - // shallow clone - var jsonml = tree.slice( 0 ); - - if ( typeof options.preprocessTreeNode === "function" ) - jsonml = options.preprocessTreeNode(jsonml, references); - - // Clone attributes if they exist - var attrs = extract_attr( jsonml ); - if ( attrs ) { - jsonml[ 1 ] = {}; - for ( i in attrs ) { - jsonml[ 1 ][ i ] = attrs[ i ]; - } - attrs = jsonml[ 1 ]; - } - - // basic case - if ( typeof jsonml === "string" ) - return jsonml; - - // convert this node - switch ( jsonml[ 0 ] ) { - case "header": - jsonml[ 0 ] = "h" + jsonml[ 1 ].level; - delete jsonml[ 1 ].level; - break; - case "bulletlist": - jsonml[ 0 ] = "ul"; - break; - case "numberlist": - jsonml[ 0 ] = "ol"; - break; - case "listitem": - jsonml[ 0 ] = "li"; - break; - case "para": - jsonml[ 0 ] = "p"; - break; - case "markdown": - jsonml[ 0 ] = "html"; - if ( attrs ) - delete attrs.references; - break; - case "code_block": - jsonml[ 0 ] = "pre"; - i = attrs ? 2 : 1; - var code = [ "code" ]; - code.push.apply( code, jsonml.splice( i, jsonml.length - i ) ); - jsonml[ i ] = code; - break; - case "inlinecode": - jsonml[ 0 ] = "code"; - break; - case "img": - jsonml[ 1 ].src = jsonml[ 1 ].href; - delete jsonml[ 1 ].href; - break; - case "linebreak": - jsonml[ 0 ] = "br"; - break; - case "link": - jsonml[ 0 ] = "a"; - break; - case "link_ref": - jsonml[ 0 ] = "a"; - - // grab this ref and clean up the attribute node - var ref = references[ attrs.ref ]; - - // if the reference exists, make the link - if ( ref ) { - delete attrs.ref; - - // add in the href and title, if present - attrs.href = ref.href; - if ( ref.title ) - attrs.title = ref.title; - - // get rid of the unneeded original text - delete attrs.original; - } - // the reference doesn't exist, so revert to plain text - else { - return attrs.original; - } - break; - case "img_ref": - jsonml[ 0 ] = "img"; - - // grab this ref and clean up the attribute node - var ref = references[ attrs.ref ]; - - // if the reference exists, make the link - if ( ref ) { - delete attrs.ref; - - // add in the href and title, if present - attrs.src = ref.href; - if ( ref.title ) - attrs.title = ref.title; - - // get rid of the unneeded original text - delete attrs.original; - } - // the reference doesn't exist, so revert to plain text - else { - return attrs.original; - } - break; - } - - // convert all the children - i = 1; - - // deal with the attribute node, if it exists - if ( attrs ) { - // if there are keys, skip over it - for ( var key in jsonml[ 1 ] ) { - i = 2; - break; - } - // if there aren't, remove it - if ( i === 1 ) - jsonml.splice( i, 1 ); - } - - for ( ; i < jsonml.length; ++i ) { - jsonml[ i ] = convert_tree_to_html( jsonml[ i ], references, options ); - } - - return jsonml; - } - - - // merges adjacent text nodes into a single node - function merge_text_nodes( jsonml ) { - // skip the tag name and attribute hash - var i = extract_attr( jsonml ) ? 2 : 1; - - while ( i < jsonml.length ) { - // if it's a string check the next item too - if ( typeof jsonml[ i ] === "string" ) { - if ( i + 1 < jsonml.length && typeof jsonml[ i + 1 ] === "string" ) { - // merge the second string into the first and remove it - jsonml[ i ] += jsonml.splice( i + 1, 1 )[ 0 ]; - } - else { - ++i; - } - } - // if it's not a string recurse - else { - merge_text_nodes( jsonml[ i ] ); - ++i; - } - } - }; - - - - var DialectHelpers = {}; - DialectHelpers.inline_until_char = function( text, want ) { - var consumed = 0, - nodes = []; - - while ( true ) { - if ( text.charAt( consumed ) === want ) { - // Found the character we were looking for - consumed++; - return [ consumed, nodes ]; - } - - if ( consumed >= text.length ) { - // No closing char found. Abort. - return null; - } - - var res = this.dialect.inline.__oneElement__.call(this, text.substr( consumed ) ); - consumed += res[ 0 ]; - // Add any returned nodes. - nodes.push.apply( nodes, res.slice( 1 ) ); - } - }; - - // Helper function to make sub-classing a dialect easier - DialectHelpers.subclassDialect = function( d ) { - function Block() {} - Block.prototype = d.block; - function Inline() {} - Inline.prototype = d.inline; - - return { block: new Block(), inline: new Inline() }; - }; - - - - - var forEach = MarkdownHelpers.forEach, - extract_attr = MarkdownHelpers.extract_attr, - mk_block = MarkdownHelpers.mk_block, - isEmpty = MarkdownHelpers.isEmpty, - inline_until_char = DialectHelpers.inline_until_char; - - /** - * Gruber dialect - * - * The default dialect that follows the rules set out by John Gruber's - * markdown.pl as closely as possible. Well actually we follow the behaviour of - * that script which in some places is not exactly what the syntax web page - * says. - **/ - var Gruber = { - block: { - atxHeader: function atxHeader( block, next ) { - var m = block.match( /^(#{1,6})\s*(.*?)\s*#*\s*(?:\n|$)/ ); - - if ( !m ) - return undefined; - - var header = [ "header", { level: m[ 1 ].length } ]; - Array.prototype.push.apply(header, this.processInline(m[ 2 ])); - - if ( m[0].length < block.length ) - next.unshift( mk_block( block.substr( m[0].length ), block.trailing, block.lineNumber + 2 ) ); - - return [ header ]; - }, - - setextHeader: function setextHeader( block, next ) { - var m = block.match( /^(.*)\n([-=])\2\2+(?:\n|$)/ ); - - if ( !m ) - return undefined; - - var level = ( m[ 2 ] === "=" ) ? 1 : 2, - header = [ "header", { level : level }, m[ 1 ] ]; - - if ( m[0].length < block.length ) - next.unshift( mk_block( block.substr( m[0].length ), block.trailing, block.lineNumber + 2 ) ); - - return [ header ]; - }, - - code: function code( block, next ) { - // | Foo - // |bar - // should be a code block followed by a paragraph. Fun - // - // There might also be adjacent code block to merge. - - var ret = [], - re = /^(?: {0,3}\t| {4})(.*)\n?/; - - // 4 spaces + content - if ( !block.match( re ) ) - return undefined; - - block_search: - do { - // Now pull out the rest of the lines - var b = this.loop_re_over_block( - re, block.valueOf(), function( m ) { ret.push( m[1] ); } ); - - if ( b.length ) { - // Case alluded to in first comment. push it back on as a new block - next.unshift( mk_block(b, block.trailing) ); - break block_search; - } - else if ( next.length ) { - // Check the next block - it might be code too - if ( !next[0].match( re ) ) - break block_search; - - // Pull how how many blanks lines follow - minus two to account for .join - ret.push ( block.trailing.replace(/[^\n]/g, "").substring(2) ); - - block = next.shift(); - } - else { - break block_search; - } - } while ( true ); - - return [ [ "code_block", ret.join("\n") ] ]; - }, - - horizRule: function horizRule( block, next ) { - // this needs to find any hr in the block to handle abutting blocks - var m = block.match( /^(?:([\s\S]*?)\n)?[ \t]*([-_*])(?:[ \t]*\2){2,}[ \t]*(?:\n([\s\S]*))?$/ ); - - if ( !m ) - return undefined; - - var jsonml = [ [ "hr" ] ]; - - // if there's a leading abutting block, process it - if ( m[ 1 ] ) { - var contained = mk_block( m[ 1 ], "", block.lineNumber ); - jsonml.unshift.apply( jsonml, this.toTree( contained, [] ) ); - } - - // if there's a trailing abutting block, stick it into next - if ( m[ 3 ] ) - next.unshift( mk_block( m[ 3 ], block.trailing, block.lineNumber + 1 ) ); - - return jsonml; - }, - - // There are two types of lists. Tight and loose. Tight lists have no whitespace - // between the items (and result in text just in the
  • ) and loose lists, - // which have an empty line between list items, resulting in (one or more) - // paragraphs inside the
  • . - // - // There are all sorts weird edge cases about the original markdown.pl's - // handling of lists: - // - // * Nested lists are supposed to be indented by four chars per level. But - // if they aren't, you can get a nested list by indenting by less than - // four so long as the indent doesn't match an indent of an existing list - // item in the 'nest stack'. - // - // * The type of the list (bullet or number) is controlled just by the - // first item at the indent. Subsequent changes are ignored unless they - // are for nested lists - // - lists: (function( ) { - // Use a closure to hide a few variables. - var any_list = "[*+-]|\\d+\\.", - bullet_list = /[*+-]/, - // Capture leading indent as it matters for determining nested lists. - is_list_re = new RegExp( "^( {0,3})(" + any_list + ")[ \t]+" ), - indent_re = "(?: {0,3}\\t| {4})"; - - // TODO: Cache this regexp for certain depths. - // Create a regexp suitable for matching an li for a given stack depth - function regex_for_depth( depth ) { - - return new RegExp( - // m[1] = indent, m[2] = list_type - "(?:^(" + indent_re + "{0," + depth + "} {0,3})(" + any_list + ")\\s+)|" + - // m[3] = cont - "(^" + indent_re + "{0," + (depth-1) + "}[ ]{0,4})" - ); - } - function expand_tab( input ) { - return input.replace( / {0,3}\t/g, " " ); - } - - // Add inline content `inline` to `li`. inline comes from processInline - // so is an array of content - function add(li, loose, inline, nl) { - if ( loose ) { - li.push( [ "para" ].concat(inline) ); - return; - } - // Hmmm, should this be any block level element or just paras? - var add_to = li[li.length -1] instanceof Array && li[li.length - 1][0] === "para" - ? li[li.length -1] - : li; - - // If there is already some content in this list, add the new line in - if ( nl && li.length > 1 ) - inline.unshift(nl); - - for ( var i = 0; i < inline.length; i++ ) { - var what = inline[i], - is_str = typeof what === "string"; - if ( is_str && add_to.length > 1 && typeof add_to[add_to.length-1] === "string" ) - add_to[ add_to.length-1 ] += what; - else - add_to.push( what ); - } - } - - // contained means have an indent greater than the current one. On - // *every* line in the block - function get_contained_blocks( depth, blocks ) { - - var re = new RegExp( "^(" + indent_re + "{" + depth + "}.*?\\n?)*$" ), - replace = new RegExp("^" + indent_re + "{" + depth + "}", "gm"), - ret = []; - - while ( blocks.length > 0 ) { - if ( re.exec( blocks[0] ) ) { - var b = blocks.shift(), - // Now remove that indent - x = b.replace( replace, ""); - - ret.push( mk_block( x, b.trailing, b.lineNumber ) ); - } - else - break; - } - return ret; - } - - // passed to stack.forEach to turn list items up the stack into paras - function paragraphify(s, i, stack) { - var list = s.list; - var last_li = list[list.length-1]; - - if ( last_li[1] instanceof Array && last_li[1][0] === "para" ) - return; - if ( i + 1 === stack.length ) { - // Last stack frame - // Keep the same array, but replace the contents - last_li.push( ["para"].concat( last_li.splice(1, last_li.length - 1) ) ); - } - else { - var sublist = last_li.pop(); - last_li.push( ["para"].concat( last_li.splice(1, last_li.length - 1) ), sublist ); - } - } - - // The matcher function - return function( block, next ) { - var m = block.match( is_list_re ); - if ( !m ) - return undefined; - - function make_list( m ) { - var list = bullet_list.exec( m[2] ) - ? ["bulletlist"] - : ["numberlist"]; - - stack.push( { list: list, indent: m[1] } ); - return list; - } - - - var stack = [], // Stack of lists for nesting. - list = make_list( m ), - last_li, - loose = false, - ret = [ stack[0].list ], - i; - - // Loop to search over block looking for inner block elements and loose lists - loose_search: - while ( true ) { - // Split into lines preserving new lines at end of line - var lines = block.split( /(?=\n)/ ); - - // We have to grab all lines for a li and call processInline on them - // once as there are some inline things that can span lines. - var li_accumulate = "", nl = ""; - - // Loop over the lines in this block looking for tight lists. - tight_search: - for ( var line_no = 0; line_no < lines.length; line_no++ ) { - nl = ""; - var l = lines[line_no].replace(/^\n/, function(n) { nl = n; return ""; }); - - - // TODO: really should cache this - var line_re = regex_for_depth( stack.length ); - - m = l.match( line_re ); - //print( "line:", uneval(l), "\nline match:", uneval(m) ); - - // We have a list item - if ( m[1] !== undefined ) { - // Process the previous list item, if any - if ( li_accumulate.length ) { - add( last_li, loose, this.processInline( li_accumulate ), nl ); - // Loose mode will have been dealt with. Reset it - loose = false; - li_accumulate = ""; - } - - m[1] = expand_tab( m[1] ); - var wanted_depth = Math.floor(m[1].length/4)+1; - //print( "want:", wanted_depth, "stack:", stack.length); - if ( wanted_depth > stack.length ) { - // Deep enough for a nested list outright - //print ( "new nested list" ); - list = make_list( m ); - last_li.push( list ); - last_li = list[1] = [ "listitem" ]; - } - else { - // We aren't deep enough to be strictly a new level. This is - // where Md.pl goes nuts. If the indent matches a level in the - // stack, put it there, else put it one deeper then the - // wanted_depth deserves. - var found = false; - for ( i = 0; i < stack.length; i++ ) { - if ( stack[ i ].indent !== m[1] ) - continue; - - list = stack[ i ].list; - stack.splice( i+1, stack.length - (i+1) ); - found = true; - break; - } - - if (!found) { - //print("not found. l:", uneval(l)); - wanted_depth++; - if ( wanted_depth <= stack.length ) { - stack.splice(wanted_depth, stack.length - wanted_depth); - //print("Desired depth now", wanted_depth, "stack:", stack.length); - list = stack[wanted_depth-1].list; - //print("list:", uneval(list) ); - } - else { - //print ("made new stack for messy indent"); - list = make_list(m); - last_li.push(list); - } - } - - //print( uneval(list), "last", list === stack[stack.length-1].list ); - last_li = [ "listitem" ]; - list.push(last_li); - } // end depth of shenegains - nl = ""; - } - - // Add content - if ( l.length > m[0].length ) - li_accumulate += nl + l.substr( m[0].length ); - } // tight_search - - if ( li_accumulate.length ) { - add( last_li, loose, this.processInline( li_accumulate ), nl ); - // Loose mode will have been dealt with. Reset it - loose = false; - li_accumulate = ""; - } - - // Look at the next block - we might have a loose list. Or an extra - // paragraph for the current li - var contained = get_contained_blocks( stack.length, next ); - - // Deal with code blocks or properly nested lists - if ( contained.length > 0 ) { - // Make sure all listitems up the stack are paragraphs - forEach( stack, paragraphify, this); - - last_li.push.apply( last_li, this.toTree( contained, [] ) ); - } - - var next_block = next[0] && next[0].valueOf() || ""; - - if ( next_block.match(is_list_re) || next_block.match( /^ / ) ) { - block = next.shift(); - - // Check for an HR following a list: features/lists/hr_abutting - var hr = this.dialect.block.horizRule( block, next ); - - if ( hr ) { - ret.push.apply(ret, hr); - break; - } - - // Make sure all listitems up the stack are paragraphs - forEach( stack, paragraphify, this); - - loose = true; - continue loose_search; - } - break; - } // loose_search - - return ret; - }; - })(), - - blockquote: function blockquote( block, next ) { - if ( !block.match( /^>/m ) ) - return undefined; - - var jsonml = []; - - // separate out the leading abutting block, if any. I.e. in this case: - // - // a - // > b - // - if ( block[ 0 ] !== ">" ) { - var lines = block.split( /\n/ ), - prev = [], - line_no = block.lineNumber; - - // keep shifting lines until you find a crotchet - while ( lines.length && lines[ 0 ][ 0 ] !== ">" ) { - prev.push( lines.shift() ); - line_no++; - } - - var abutting = mk_block( prev.join( "\n" ), "\n", block.lineNumber ); - jsonml.push.apply( jsonml, this.processBlock( abutting, [] ) ); - // reassemble new block of just block quotes! - block = mk_block( lines.join( "\n" ), block.trailing, line_no ); - } - - - // if the next block is also a blockquote merge it in - while ( next.length && next[ 0 ][ 0 ] === ">" ) { - var b = next.shift(); - block = mk_block( block + block.trailing + b, b.trailing, block.lineNumber ); - } - - // Strip off the leading "> " and re-process as a block. - var input = block.replace( /^> ?/gm, "" ), - old_tree = this.tree, - processedBlock = this.toTree( input, [ "blockquote" ] ), - attr = extract_attr( processedBlock ); - - // If any link references were found get rid of them - if ( attr && attr.references ) { - delete attr.references; - // And then remove the attribute object if it's empty - if ( isEmpty( attr ) ) - processedBlock.splice( 1, 1 ); - } - - jsonml.push( processedBlock ); - return jsonml; - }, - - referenceDefn: function referenceDefn( block, next) { - var re = /^\s*\[(.*?)\]:\s*(\S+)(?:\s+(?:(['"])(.*?)\3|\((.*?)\)))?\n?/; - // interesting matches are [ , ref_id, url, , title, title ] - - if ( !block.match(re) ) - return undefined; - - // make an attribute node if it doesn't exist - if ( !extract_attr( this.tree ) ) - this.tree.splice( 1, 0, {} ); - - var attrs = extract_attr( this.tree ); - - // make a references hash if it doesn't exist - if ( attrs.references === undefined ) - attrs.references = {}; - - var b = this.loop_re_over_block(re, block, function( m ) { - - if ( m[2] && m[2][0] === "<" && m[2][m[2].length-1] === ">" ) - m[2] = m[2].substring( 1, m[2].length - 1 ); - - var ref = attrs.references[ m[1].toLowerCase() ] = { - href: m[2] - }; - - if ( m[4] !== undefined ) - ref.title = m[4]; - else if ( m[5] !== undefined ) - ref.title = m[5]; - - } ); - - if ( b.length ) - next.unshift( mk_block( b, block.trailing ) ); - - return []; - }, - - para: function para( block ) { - // everything's a para! - return [ ["para"].concat( this.processInline( block ) ) ]; - } - }, - - inline: { - - __oneElement__: function oneElement( text, patterns_or_re, previous_nodes ) { - var m, - res; - - patterns_or_re = patterns_or_re || this.dialect.inline.__patterns__; - var re = new RegExp( "([\\s\\S]*?)(" + (patterns_or_re.source || patterns_or_re) + ")" ); - - m = re.exec( text ); - if (!m) { - // Just boring text - return [ text.length, text ]; - } - else if ( m[1] ) { - // Some un-interesting text matched. Return that first - return [ m[1].length, m[1] ]; - } - - var res; - if ( m[2] in this.dialect.inline ) { - res = this.dialect.inline[ m[2] ].call( - this, - text.substr( m.index ), m, previous_nodes || [] ); - } - // Default for now to make dev easier. just slurp special and output it. - res = res || [ m[2].length, m[2] ]; - return res; - }, - - __call__: function inline( text, patterns ) { - - var out = [], - res; - - function add(x) { - //D:self.debug(" adding output", uneval(x)); - if ( typeof x === "string" && typeof out[out.length-1] === "string" ) - out[ out.length-1 ] += x; - else - out.push(x); - } - - while ( text.length > 0 ) { - res = this.dialect.inline.__oneElement__.call(this, text, patterns, out ); - text = text.substr( res.shift() ); - forEach(res, add ); - } - - return out; - }, - - // These characters are intersting elsewhere, so have rules for them so that - // chunks of plain text blocks don't include them - "]": function () {}, - "}": function () {}, - - __escape__ : /^\\[\\`\*_{}\[\]()#\+.!\-]/, - - "\\": function escaped( text ) { - // [ length of input processed, node/children to add... ] - // Only esacape: \ ` * _ { } [ ] ( ) # * + - . ! - if ( this.dialect.inline.__escape__.exec( text ) ) - return [ 2, text.charAt( 1 ) ]; - else - // Not an esacpe - return [ 1, "\\" ]; - }, - - "![": function image( text ) { - - // Unlike images, alt text is plain text only. no other elements are - // allowed in there - - // ![Alt text](/path/to/img.jpg "Optional title") - // 1 2 3 4 <--- captures - var m = text.match( /^!\[(.*?)\][ \t]*\([ \t]*([^")]*?)(?:[ \t]+(["'])(.*?)\3)?[ \t]*\)/ ); - - if ( m ) { - if ( m[2] && m[2][0] === "<" && m[2][m[2].length-1] === ">" ) - m[2] = m[2].substring( 1, m[2].length - 1 ); - - m[2] = this.dialect.inline.__call__.call( this, m[2], /\\/ )[0]; - - var attrs = { alt: m[1], href: m[2] || "" }; - if ( m[4] !== undefined) - attrs.title = m[4]; - - return [ m[0].length, [ "img", attrs ] ]; - } - - // ![Alt text][id] - m = text.match( /^!\[(.*?)\][ \t]*\[(.*?)\]/ ); - - if ( m ) { - // We can't check if the reference is known here as it likely wont be - // found till after. Check it in md tree->hmtl tree conversion - return [ m[0].length, [ "img_ref", { alt: m[1], ref: m[2].toLowerCase(), original: m[0] } ] ]; - } - - // Just consume the '![' - return [ 2, "![" ]; - }, - - "[": function link( text ) { - - var orig = String(text); - // Inline content is possible inside `link text` - var res = inline_until_char.call( this, text.substr(1), "]" ); - - // No closing ']' found. Just consume the [ - if ( !res ) - return [ 1, "[" ]; - - var consumed = 1 + res[ 0 ], - children = res[ 1 ], - link, - attrs; - - // At this point the first [...] has been parsed. See what follows to find - // out which kind of link we are (reference or direct url) - text = text.substr( consumed ); - - // [link text](/path/to/img.jpg "Optional title") - // 1 2 3 <--- captures - // This will capture up to the last paren in the block. We then pull - // back based on if there a matching ones in the url - // ([here](/url/(test)) - // The parens have to be balanced - var m = text.match( /^\s*\([ \t]*([^"']*)(?:[ \t]+(["'])(.*?)\2)?[ \t]*\)/ ); - if ( m ) { - var url = m[1]; - consumed += m[0].length; - - if ( url && url[0] === "<" && url[url.length-1] === ">" ) - url = url.substring( 1, url.length - 1 ); - - // If there is a title we don't have to worry about parens in the url - if ( !m[3] ) { - var open_parens = 1; // One open that isn't in the capture - for ( var len = 0; len < url.length; len++ ) { - switch ( url[len] ) { - case "(": - open_parens++; - break; - case ")": - if ( --open_parens === 0) { - consumed -= url.length - len; - url = url.substring(0, len); - } - break; - } - } - } - - // Process escapes only - url = this.dialect.inline.__call__.call( this, url, /\\/ )[0]; - - attrs = { href: url || "" }; - if ( m[3] !== undefined) - attrs.title = m[3]; - - link = [ "link", attrs ].concat( children ); - return [ consumed, link ]; - } - - // [Alt text][id] - // [Alt text] [id] - m = text.match( /^\s*\[(.*?)\]/ ); - - if ( m ) { - - consumed += m[ 0 ].length; - - // [links][] uses links as its reference - attrs = { ref: ( m[ 1 ] || String(children) ).toLowerCase(), original: orig.substr( 0, consumed ) }; - - link = [ "link_ref", attrs ].concat( children ); - - // We can't check if the reference is known here as it likely wont be - // found till after. Check it in md tree->hmtl tree conversion. - // Store the original so that conversion can revert if the ref isn't found. - return [ consumed, link ]; - } - - // [id] - // Only if id is plain (no formatting.) - if ( children.length === 1 && typeof children[0] === "string" ) { - - attrs = { ref: children[0].toLowerCase(), original: orig.substr( 0, consumed ) }; - link = [ "link_ref", attrs, children[0] ]; - return [ consumed, link ]; - } - - // Just consume the "[" - return [ 1, "[" ]; - }, - - - "<": function autoLink( text ) { - var m; - - if ( ( m = text.match( /^<(?:((https?|ftp|mailto):[^>]+)|(.*?@.*?\.[a-zA-Z]+))>/ ) ) !== null ) { - if ( m[3] ) - return [ m[0].length, [ "link", { href: "mailto:" + m[3] }, m[3] ] ]; - else if ( m[2] === "mailto" ) - return [ m[0].length, [ "link", { href: m[1] }, m[1].substr("mailto:".length ) ] ]; - else - return [ m[0].length, [ "link", { href: m[1] }, m[1] ] ]; - } - - return [ 1, "<" ]; - }, - - "`": function inlineCode( text ) { - // Inline code block. as many backticks as you like to start it - // Always skip over the opening ticks. - var m = text.match( /(`+)(([\s\S]*?)\1)/ ); - - if ( m && m[2] ) - return [ m[1].length + m[2].length, [ "inlinecode", m[3] ] ]; - else { - // TODO: No matching end code found - warn! - return [ 1, "`" ]; - } - }, - - " \n": function lineBreak() { - return [ 3, [ "linebreak" ] ]; - } - - } - }; - - // Meta Helper/generator method for em and strong handling - function strong_em( tag, md ) { - - var state_slot = tag + "_state", - other_slot = tag === "strong" ? "em_state" : "strong_state"; - - function CloseTag(len) { - this.len_after = len; - this.name = "close_" + md; - } - - return function ( text ) { - - if ( this[state_slot][0] === md ) { - // Most recent em is of this type - //D:this.debug("closing", md); - this[state_slot].shift(); - - // "Consume" everything to go back to the recrusion in the else-block below - return[ text.length, new CloseTag(text.length-md.length) ]; - } - else { - // Store a clone of the em/strong states - var other = this[other_slot].slice(), - state = this[state_slot].slice(); - - this[state_slot].unshift(md); - - //D:this.debug_indent += " "; - - // Recurse - var res = this.processInline( text.substr( md.length ) ); - //D:this.debug_indent = this.debug_indent.substr(2); - - var last = res[res.length - 1]; - - //D:this.debug("processInline from", tag + ": ", uneval( res ) ); - - var check = this[state_slot].shift(); - if ( last instanceof CloseTag ) { - res.pop(); - // We matched! Huzzah. - var consumed = text.length - last.len_after; - return [ consumed, [ tag ].concat(res) ]; - } - else { - // Restore the state of the other kind. We might have mistakenly closed it. - this[other_slot] = other; - this[state_slot] = state; - - // We can't reuse the processed result as it could have wrong parsing contexts in it. - return [ md.length, md ]; - } - } - }; // End returned function - } - - Gruber.inline["**"] = strong_em("strong", "**"); - Gruber.inline["__"] = strong_em("strong", "__"); - Gruber.inline["*"] = strong_em("em", "*"); - Gruber.inline["_"] = strong_em("em", "_"); - - Markdown.dialects.Gruber = Gruber; - Markdown.buildBlockOrder ( Markdown.dialects.Gruber.block ); - Markdown.buildInlinePatterns( Markdown.dialects.Gruber.inline ); - - - - var Maruku = DialectHelpers.subclassDialect( Gruber ), - extract_attr = MarkdownHelpers.extract_attr, - forEach = MarkdownHelpers.forEach; - - Maruku.processMetaHash = function processMetaHash( meta_string ) { - var meta = split_meta_hash( meta_string ), - attr = {}; - - for ( var i = 0; i < meta.length; ++i ) { - // id: #foo - if ( /^#/.test( meta[ i ] ) ) - attr.id = meta[ i ].substring( 1 ); - // class: .foo - else if ( /^\./.test( meta[ i ] ) ) { - // if class already exists, append the new one - if ( attr["class"] ) - attr["class"] = attr["class"] + meta[ i ].replace( /./, " " ); - else - attr["class"] = meta[ i ].substring( 1 ); - } - // attribute: foo=bar - else if ( /\=/.test( meta[ i ] ) ) { - var s = meta[ i ].split( /\=/ ); - attr[ s[ 0 ] ] = s[ 1 ]; - } - } - - return attr; - }; - - function split_meta_hash( meta_string ) { - var meta = meta_string.split( "" ), - parts = [ "" ], - in_quotes = false; - - while ( meta.length ) { - var letter = meta.shift(); - switch ( letter ) { - case " " : - // if we're in a quoted section, keep it - if ( in_quotes ) - parts[ parts.length - 1 ] += letter; - // otherwise make a new part - else - parts.push( "" ); - break; - case "'" : - case '"' : - // reverse the quotes and move straight on - in_quotes = !in_quotes; - break; - case "\\" : - // shift off the next letter to be used straight away. - // it was escaped so we'll keep it whatever it is - letter = meta.shift(); - /* falls through */ - default : - parts[ parts.length - 1 ] += letter; - break; - } - } - - return parts; - } - - Maruku.block.document_meta = function document_meta( block ) { - // we're only interested in the first block - if ( block.lineNumber > 1 ) - return undefined; - - // document_meta blocks consist of one or more lines of `Key: Value\n` - if ( ! block.match( /^(?:\w+:.*\n)*\w+:.*$/ ) ) - return undefined; - - // make an attribute node if it doesn't exist - if ( !extract_attr( this.tree ) ) - this.tree.splice( 1, 0, {} ); - - var pairs = block.split( /\n/ ); - for ( var p in pairs ) { - var m = pairs[ p ].match( /(\w+):\s*(.*)$/ ), - key = m[ 1 ].toLowerCase(), - value = m[ 2 ]; - - this.tree[ 1 ][ key ] = value; - } - - // document_meta produces no content! - return []; - }; - - Maruku.block.block_meta = function block_meta( block ) { - // check if the last line of the block is an meta hash - var m = block.match( /(^|\n) {0,3}\{:\s*((?:\\\}|[^\}])*)\s*\}$/ ); - if ( !m ) - return undefined; - - // process the meta hash - var attr = this.dialect.processMetaHash( m[ 2 ] ), - hash; - - // if we matched ^ then we need to apply meta to the previous block - if ( m[ 1 ] === "" ) { - var node = this.tree[ this.tree.length - 1 ]; - hash = extract_attr( node ); - - // if the node is a string (rather than JsonML), bail - if ( typeof node === "string" ) - return undefined; - - // create the attribute hash if it doesn't exist - if ( !hash ) { - hash = {}; - node.splice( 1, 0, hash ); - } - - // add the attributes in - for ( var a in attr ) - hash[ a ] = attr[ a ]; - - // return nothing so the meta hash is removed - return []; - } - - // pull the meta hash off the block and process what's left - var b = block.replace( /\n.*$/, "" ), - result = this.processBlock( b, [] ); - - // get or make the attributes hash - hash = extract_attr( result[ 0 ] ); - if ( !hash ) { - hash = {}; - result[ 0 ].splice( 1, 0, hash ); - } - - // attach the attributes to the block - for ( var a in attr ) - hash[ a ] = attr[ a ]; - - return result; - }; - - Maruku.block.definition_list = function definition_list( block, next ) { - // one or more terms followed by one or more definitions, in a single block - var tight = /^((?:[^\s:].*\n)+):\s+([\s\S]+)$/, - list = [ "dl" ], - i, m; - - // see if we're dealing with a tight or loose block - if ( ( m = block.match( tight ) ) ) { - // pull subsequent tight DL blocks out of `next` - var blocks = [ block ]; - while ( next.length && tight.exec( next[ 0 ] ) ) - blocks.push( next.shift() ); - - for ( var b = 0; b < blocks.length; ++b ) { - var m = blocks[ b ].match( tight ), - terms = m[ 1 ].replace( /\n$/, "" ).split( /\n/ ), - defns = m[ 2 ].split( /\n:\s+/ ); - - // print( uneval( m ) ); - - for ( i = 0; i < terms.length; ++i ) - list.push( [ "dt", terms[ i ] ] ); - - for ( i = 0; i < defns.length; ++i ) { - // run inline processing over the definition - list.push( [ "dd" ].concat( this.processInline( defns[ i ].replace( /(\n)\s+/, "$1" ) ) ) ); - } - } - } - else { - return undefined; - } - - return [ list ]; - }; - - // splits on unescaped instances of @ch. If @ch is not a character the result - // can be unpredictable - - Maruku.block.table = function table ( block ) { - - var _split_on_unescaped = function( s, ch ) { - ch = ch || '\\s'; - if ( ch.match(/^[\\|\[\]{}?*.+^$]$/) ) - ch = '\\' + ch; - var res = [ ], - r = new RegExp('^((?:\\\\.|[^\\\\' + ch + '])*)' + ch + '(.*)'), - m; - while ( ( m = s.match( r ) ) ) { - res.push( m[1] ); - s = m[2]; - } - res.push(s); - return res; - }; - - var leading_pipe = /^ {0,3}\|(.+)\n {0,3}\|\s*([\-:]+[\-| :]*)\n((?:\s*\|.*(?:\n|$))*)(?=\n|$)/, - // find at least an unescaped pipe in each line - no_leading_pipe = /^ {0,3}(\S(?:\\.|[^\\|])*\|.*)\n {0,3}([\-:]+\s*\|[\-| :]*)\n((?:(?:\\.|[^\\|])*\|.*(?:\n|$))*)(?=\n|$)/, - i, - m; - if ( ( m = block.match( leading_pipe ) ) ) { - // remove leading pipes in contents - // (header and horizontal rule already have the leading pipe left out) - m[3] = m[3].replace(/^\s*\|/gm, ''); - } else if ( ! ( m = block.match( no_leading_pipe ) ) ) { - return undefined; - } - - var table = [ "table", [ "thead", [ "tr" ] ], [ "tbody" ] ]; - - // remove trailing pipes, then split on pipes - // (no escaped pipes are allowed in horizontal rule) - m[2] = m[2].replace(/\|\s*$/, '').split('|'); - - // process alignment - var html_attrs = [ ]; - forEach (m[2], function (s) { - if (s.match(/^\s*-+:\s*$/)) - html_attrs.push({align: "right"}); - else if (s.match(/^\s*:-+\s*$/)) - html_attrs.push({align: "left"}); - else if (s.match(/^\s*:-+:\s*$/)) - html_attrs.push({align: "center"}); - else - html_attrs.push({}); - }); - - // now for the header, avoid escaped pipes - m[1] = _split_on_unescaped(m[1].replace(/\|\s*$/, ''), '|'); - for (i = 0; i < m[1].length; i++) { - table[1][1].push(['th', html_attrs[i] || {}].concat( - this.processInline(m[1][i].trim()))); - } - - // now for body contents - forEach (m[3].replace(/\|\s*$/mg, '').split('\n'), function (row) { - var html_row = ['tr']; - row = _split_on_unescaped(row, '|'); - for (i = 0; i < row.length; i++) - html_row.push(['td', html_attrs[i] || {}].concat(this.processInline(row[i].trim()))); - table[2].push(html_row); - }, this); - - return [table]; - }; - - Maruku.inline[ "{:" ] = function inline_meta( text, matches, out ) { - if ( !out.length ) - return [ 2, "{:" ]; - - // get the preceeding element - var before = out[ out.length - 1 ]; - - if ( typeof before === "string" ) - return [ 2, "{:" ]; - - // match a meta hash - var m = text.match( /^\{:\s*((?:\\\}|[^\}])*)\s*\}/ ); - - // no match, false alarm - if ( !m ) - return [ 2, "{:" ]; - - // attach the attributes to the preceeding element - var meta = this.dialect.processMetaHash( m[ 1 ] ), - attr = extract_attr( before ); - - if ( !attr ) { - attr = {}; - before.splice( 1, 0, attr ); - } - - for ( var k in meta ) - attr[ k ] = meta[ k ]; - - // cut out the string and replace it with nothing - return [ m[ 0 ].length, "" ]; - }; - - - Markdown.dialects.Maruku = Maruku; - Markdown.dialects.Maruku.inline.__escape__ = /^\\[\\`\*_{}\[\]()#\+.!\-|:]/; - Markdown.buildBlockOrder ( Markdown.dialects.Maruku.block ); - Markdown.buildInlinePatterns( Markdown.dialects.Maruku.inline ); - - -// Include all our depndencies and; - expose.Markdown = Markdown; - expose.parse = Markdown.parse; - expose.toHTML = Markdown.toHTML; - expose.toHTMLTree = Markdown.toHTMLTree; - expose.renderJsonML = Markdown.renderJsonML; - -})(function() { - window.markdown = {}; - return window.markdown; -}()); diff --git a/demo/public/js/mustache.js b/demo/public/js/mustache.js deleted file mode 100644 index 18d92a5..0000000 --- a/demo/public/js/mustache.js +++ /dev/null @@ -1,586 +0,0 @@ -/*! - * mustache.js - Logic-less {{mustache}} templates with JavaScript - * http://github.com/janl/mustache.js - */ - -/*global define: false*/ - -(function (global, factory) { - if (typeof exports === "object" && exports) { - factory(exports); // CommonJS - } else if (typeof define === "function" && define.amd) { - define(['exports'], factory); // AMD - } else { - factory(global.Mustache = {}); // - - - diff --git a/requirements.txt b/requirements.txt index 992beb2..0024e5f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,19 +1,29 @@ +APScheduler==3.5.3 +astroid==2.0.4 attrs==17.4.0 chardet==3.0.4 click==6.7 clize==4.0.3 docutils==0.14 Flask==0.12.2 +Flask-APScheduler==1.10.1 Flask-Cors==3.0.3 +isort==4.3.4 itsdangerous==0.24 Jinja2==2.10 jsonschema==2.6.0 +lazy-object-proxy==1.3.1 Markdown==2.6.11 MarkupSafe==1.0 +mccabe==0.6.1 od==1.0 peewee==2.10.2 -pika==0.11.2 +pylint==2.1.1 PyRSS2Gen==1.1 +python-dateutil==2.7.3 +pytz==2018.5 sigtools==2.0.1 six==1.11.0 +tzlocal==1.5.1 Werkzeug==0.14.1 +wrapt==1.10.11