From b79ac583e82f52824c0411578200aa0fadc73766 Mon Sep 17 00:00:00 2001 From: Martin Zimmermann Date: Sun, 24 Nov 2013 21:43:57 +0100 Subject: [PATCH] use sphinx for documentation --- Makefile | 3 + README.md | 60 - docs/API.md | 100 -- docs/_isso/html5.py | 1298 +++++++++++++++++ docs/_isso/layout.html | 123 ++ docs/_isso/page.html | 16 + docs/_isso/search.html | 54 + docs/_isso/searchbox.html | 12 + docs/_isso/sidebar-docs.html | 27 + docs/_isso/theme.conf | 3 + .../bourbon/_bourbon-deprecated-upcoming.scss | 13 + docs/_static/css/bourbon/_bourbon.scss | 59 + docs/_static/css/bourbon/addons/_button.scss | 273 ++++ .../_static/css/bourbon/addons/_clearfix.scss | 29 + .../css/bourbon/addons/_font-family.scss | 5 + .../css/bourbon/addons/_hide-text.scss | 5 + .../bourbon/addons/_html5-input-types.scss | 56 + .../_static/css/bourbon/addons/_position.scss | 42 + .../_static/css/bourbon/addons/_prefixer.scss | 49 + .../css/bourbon/addons/_retina-image.scss | 32 + docs/_static/css/bourbon/addons/_size.scss | 44 + .../css/bourbon/addons/_timing-functions.scss | 32 + .../_static/css/bourbon/addons/_triangle.scss | 45 + docs/_static/css/bourbon/css3/_animation.scss | 52 + .../_static/css/bourbon/css3/_appearance.scss | 3 + .../bourbon/css3/_backface-visibility.scss | 6 + .../css/bourbon/css3/_background-image.scss | 48 + .../_static/css/bourbon/css3/_background.scss | 103 ++ .../css/bourbon/css3/_border-image.scss | 55 + .../css/bourbon/css3/_border-radius.scss | 22 + .../_static/css/bourbon/css3/_box-sizing.scss | 4 + docs/_static/css/bourbon/css3/_columns.scss | 47 + docs/_static/css/bourbon/css3/_flex-box.scss | 52 + docs/_static/css/bourbon/css3/_font-face.scss | 23 + .../css/bourbon/css3/_hidpi-media-query.scss | 10 + .../css/bourbon/css3/_image-rendering.scss | 13 + .../css/bourbon/css3/_inline-block.scss | 8 + docs/_static/css/bourbon/css3/_keyframes.scss | 43 + .../css/bourbon/css3/_linear-gradient.scss | 41 + .../css/bourbon/css3/_perspective.scss | 8 + .../css/bourbon/css3/_placeholder.scss | 29 + .../css/bourbon/css3/_radial-gradient.scss | 44 + docs/_static/css/bourbon/css3/_transform.scss | 15 + .../_static/css/bourbon/css3/_transition.scss | 34 + .../css/bourbon/css3/_user-select.scss | 3 + .../css/bourbon/functions/_compact.scss | 11 + .../css/bourbon/functions/_flex-grid.scss | 39 + .../css/bourbon/functions/_grid-width.scss | 13 + .../bourbon/functions/_linear-gradient.scss | 13 + .../css/bourbon/functions/_modular-scale.scss | 40 + .../css/bourbon/functions/_px-to-em.scss | 8 + .../bourbon/functions/_radial-gradient.scss | 23 + .../css/bourbon/functions/_tint-shade.scss | 9 + .../functions/_transition-property-name.scss | 22 + .../helpers/_deprecated-webkit-gradient.scss | 39 + .../helpers/_gradient-positions-parser.scss | 13 + .../helpers/_linear-positions-parser.scss | 61 + .../bourbon/helpers/_radial-arg-parser.scss | 69 + .../helpers/_radial-positions-parser.scss | 18 + .../bourbon/helpers/_render-gradients.scss | 26 + .../bourbon/helpers/_shape-size-stripper.scss | 10 + docs/_static/css/neat/_neat-helpers.scss | 8 + docs/_static/css/neat/_neat.scss | 21 + .../css/neat/functions/_new-breakpoint.scss | 16 + docs/_static/css/neat/functions/_private.scss | 107 ++ .../_static/css/neat/functions/_px-to-em.scss | 3 + docs/_static/css/neat/grid/_fill-parent.scss | 7 + docs/_static/css/neat/grid/_grid.scss | 5 + docs/_static/css/neat/grid/_media.scss | 51 + docs/_static/css/neat/grid/_omega.scss | 79 + .../css/neat/grid/_outer-container.scss | 8 + docs/_static/css/neat/grid/_pad.scss | 8 + docs/_static/css/neat/grid/_private.scss | 50 + docs/_static/css/neat/grid/_reset.scss | 12 + docs/_static/css/neat/grid/_row.scss | 17 + docs/_static/css/neat/grid/_shift.scss | 9 + docs/_static/css/neat/grid/_span-columns.scss | 38 + docs/_static/css/neat/grid/_to-deprecate.scss | 57 + docs/_static/css/neat/grid/_visual-grid.scss | 41 + docs/_static/css/neat/settings/_grid.scss | 7 + .../css/neat/settings/_visual-grid.scss | 5 + docs/_static/css/site.css | 118 ++ docs/_static/css/site.scss | 325 +++++ docs/_static/duty_calls.png | Bin 0 -> 14103 bytes docs/conf.py | 258 ++++ docs/contribute.rst | 4 + docs/docs/configuration/client.rst | 52 + .../configuration/server.rst} | 80 +- docs/docs/configuration/setup.rst | 90 ++ docs/docs/extras/api.rst | 98 ++ docs/docs/extras/multi-site.rst | 56 + docs/docs/extras/uwsgi.rst | 57 + docs/docs/index.rst | 47 + docs/docs/install.rst | 24 + docs/docs/quickstart.rst | 187 +++ docs/docs/troubleshooting.rst | 4 + docs/docs/usage.rst | 2 + docs/index.html | 60 + docs/news.rst | 4 + docs/uWSGI.md | 55 - 100 files changed, 5246 insertions(+), 281 deletions(-) delete mode 100644 docs/API.md create mode 100644 docs/_isso/html5.py create mode 100644 docs/_isso/layout.html create mode 100644 docs/_isso/page.html create mode 100644 docs/_isso/search.html create mode 100644 docs/_isso/searchbox.html create mode 100644 docs/_isso/sidebar-docs.html create mode 100644 docs/_isso/theme.conf create mode 100644 docs/_static/css/bourbon/_bourbon-deprecated-upcoming.scss create mode 100644 docs/_static/css/bourbon/_bourbon.scss create mode 100644 docs/_static/css/bourbon/addons/_button.scss create mode 100644 docs/_static/css/bourbon/addons/_clearfix.scss create mode 100644 docs/_static/css/bourbon/addons/_font-family.scss create mode 100644 docs/_static/css/bourbon/addons/_hide-text.scss create mode 100644 docs/_static/css/bourbon/addons/_html5-input-types.scss create mode 100644 docs/_static/css/bourbon/addons/_position.scss create mode 100644 docs/_static/css/bourbon/addons/_prefixer.scss create mode 100644 docs/_static/css/bourbon/addons/_retina-image.scss create mode 100644 docs/_static/css/bourbon/addons/_size.scss create mode 100644 docs/_static/css/bourbon/addons/_timing-functions.scss create mode 100644 docs/_static/css/bourbon/addons/_triangle.scss create mode 100644 docs/_static/css/bourbon/css3/_animation.scss create mode 100644 docs/_static/css/bourbon/css3/_appearance.scss create mode 100644 docs/_static/css/bourbon/css3/_backface-visibility.scss create mode 100644 docs/_static/css/bourbon/css3/_background-image.scss create mode 100644 docs/_static/css/bourbon/css3/_background.scss create mode 100644 docs/_static/css/bourbon/css3/_border-image.scss create mode 100644 docs/_static/css/bourbon/css3/_border-radius.scss create mode 100644 docs/_static/css/bourbon/css3/_box-sizing.scss create mode 100644 docs/_static/css/bourbon/css3/_columns.scss create mode 100644 docs/_static/css/bourbon/css3/_flex-box.scss create mode 100644 docs/_static/css/bourbon/css3/_font-face.scss create mode 100644 docs/_static/css/bourbon/css3/_hidpi-media-query.scss create mode 100644 docs/_static/css/bourbon/css3/_image-rendering.scss create mode 100644 docs/_static/css/bourbon/css3/_inline-block.scss create mode 100644 docs/_static/css/bourbon/css3/_keyframes.scss create mode 100644 docs/_static/css/bourbon/css3/_linear-gradient.scss create mode 100644 docs/_static/css/bourbon/css3/_perspective.scss create mode 100644 docs/_static/css/bourbon/css3/_placeholder.scss create mode 100644 docs/_static/css/bourbon/css3/_radial-gradient.scss create mode 100644 docs/_static/css/bourbon/css3/_transform.scss create mode 100644 docs/_static/css/bourbon/css3/_transition.scss create mode 100644 docs/_static/css/bourbon/css3/_user-select.scss create mode 100644 docs/_static/css/bourbon/functions/_compact.scss create mode 100644 docs/_static/css/bourbon/functions/_flex-grid.scss create mode 100644 docs/_static/css/bourbon/functions/_grid-width.scss create mode 100644 docs/_static/css/bourbon/functions/_linear-gradient.scss create mode 100644 docs/_static/css/bourbon/functions/_modular-scale.scss create mode 100644 docs/_static/css/bourbon/functions/_px-to-em.scss create mode 100644 docs/_static/css/bourbon/functions/_radial-gradient.scss create mode 100644 docs/_static/css/bourbon/functions/_tint-shade.scss create mode 100644 docs/_static/css/bourbon/functions/_transition-property-name.scss create mode 100644 docs/_static/css/bourbon/helpers/_deprecated-webkit-gradient.scss create mode 100644 docs/_static/css/bourbon/helpers/_gradient-positions-parser.scss create mode 100644 docs/_static/css/bourbon/helpers/_linear-positions-parser.scss create mode 100644 docs/_static/css/bourbon/helpers/_radial-arg-parser.scss create mode 100644 docs/_static/css/bourbon/helpers/_radial-positions-parser.scss create mode 100644 docs/_static/css/bourbon/helpers/_render-gradients.scss create mode 100644 docs/_static/css/bourbon/helpers/_shape-size-stripper.scss create mode 100644 docs/_static/css/neat/_neat-helpers.scss create mode 100644 docs/_static/css/neat/_neat.scss create mode 100644 docs/_static/css/neat/functions/_new-breakpoint.scss create mode 100644 docs/_static/css/neat/functions/_private.scss create mode 100644 docs/_static/css/neat/functions/_px-to-em.scss create mode 100644 docs/_static/css/neat/grid/_fill-parent.scss create mode 100644 docs/_static/css/neat/grid/_grid.scss create mode 100644 docs/_static/css/neat/grid/_media.scss create mode 100644 docs/_static/css/neat/grid/_omega.scss create mode 100644 docs/_static/css/neat/grid/_outer-container.scss create mode 100644 docs/_static/css/neat/grid/_pad.scss create mode 100644 docs/_static/css/neat/grid/_private.scss create mode 100644 docs/_static/css/neat/grid/_reset.scss create mode 100644 docs/_static/css/neat/grid/_row.scss create mode 100644 docs/_static/css/neat/grid/_shift.scss create mode 100644 docs/_static/css/neat/grid/_span-columns.scss create mode 100644 docs/_static/css/neat/grid/_to-deprecate.scss create mode 100644 docs/_static/css/neat/grid/_visual-grid.scss create mode 100644 docs/_static/css/neat/settings/_grid.scss create mode 100644 docs/_static/css/neat/settings/_visual-grid.scss create mode 100644 docs/_static/css/site.css create mode 100644 docs/_static/css/site.scss create mode 100644 docs/_static/duty_calls.png create mode 100644 docs/conf.py create mode 100644 docs/contribute.rst create mode 100644 docs/docs/configuration/client.rst rename docs/{CONFIGURATION.rst => docs/configuration/server.rst} (71%) create mode 100644 docs/docs/configuration/setup.rst create mode 100644 docs/docs/extras/api.rst create mode 100644 docs/docs/extras/multi-site.rst create mode 100644 docs/docs/extras/uwsgi.rst create mode 100644 docs/docs/index.rst create mode 100644 docs/docs/install.rst create mode 100644 docs/docs/quickstart.rst create mode 100644 docs/docs/troubleshooting.rst create mode 100644 docs/docs/usage.rst create mode 100644 docs/index.html create mode 100644 docs/news.rst delete mode 100644 docs/uWSGI.md diff --git a/Makefile b/Makefile index 8921e15..96bb84e 100644 --- a/Makefile +++ b/Makefile @@ -8,3 +8,6 @@ js: r.js -o isso/js/build.embed.js optimize="none" out="isso/js/embed.dev.js" r.js -o isso/js/build.count.js r.js -o isso/js/build.count.js optimize="none" out="isso/js/count.dev.js" +site: + cd docs/ && sphinx-build -E -b dirhtml -a . _build + diff --git a/README.md b/README.md index 0a6a05c..35001fa 100644 --- a/README.md +++ b/README.md @@ -92,66 +92,6 @@ This functionality is already included when you embed `embed.min.js`, do ### Client Configuration -You can configure the client (the JS part) via `data-` attributes: - -* data-title - - When you start a new thread (= first comment on a page), Isso sends - a GET request that page to see if it a) exists and b) parse the site's - heading (currently used as subject in emails). - - Isso assumes that the title is inside an `h1` tag near the isso thread: - - ```html - - -

Website Title

-
-
-

Post Title

-
- ... - ``` - - In this example, the detected title is `Post Title` as expected, but some - older sites may only use a single `h1` as their website's maintitle, and - a `h2` for the post title. Unfortunately this is unambiguous and you have - to tell Isso what's the actual post title: - - ```html -
- ``` - - Make sure to escape the attribute value. - -* data-isso - - Isso usually detects the REST API automatically, but when you serve the JS - script on a different location, this may fail. Use `data-isso` to - override the API location: - - ```html - - ``` - -* data-isso-css - - Set to `false` prevents Isso from automatically appending the stylesheet. - Defaults to `true`. - - ```html - - ``` - -* data-isso-lang - - Override useragent's preferred language. Currently available: german (de), - english (en) and french (fr). - -* data-isso-reply-to-self - - Set to `true` when spam guard is configured with `reply-to-self = true`. - ### Webserver configuration * nginx configuration to run Isso on `/isso`: diff --git a/docs/API.md b/docs/API.md deleted file mode 100644 index 1d25a48..0000000 --- a/docs/API.md +++ /dev/null @@ -1,100 +0,0 @@ -Isso API -======== - -The Isso API uses HTTP and JSON as primary communication protocol. - - -## JSON format - -When querying the API you either get an error, an object or list of objects -representing the comment. Here's a example JSON returned from Isso: - -```json -{ - "text": "Hello, World!", - "author": "Bernd", - "website": null, - "votes": 0, - "mode": 1, - "id": 1, - "parent": null, - "hash": "68b329da9893e34099c7d8ad5cb9c940", - "created": 1379001637.50, - "modified": null -} -``` - -text -: required, comment as HTML - -author -: author's name, may be `null` - -website -: author's website, may be `null` - -votes -: sum of up- and downvotes, defaults to zero. - -mode -: * 1, accepted comment - * 2, comment in moderation queue - * 4, comment deleted, but is referenced - -id -: unique comment number per thread - -parent -: answer to a parent id, may be `null` - -hash -: user identification, used to generate identicons - -created -: time in seconds sinde epoch - -modified -: last modification time in seconds, may be `null` - - -## List comments - -List all visible comments for a thread. Does not include deleted and -comments currently in moderation queue. - - GET /?uri=path - -You must encode `path`, e.g. to retrieve comments for `/hello-world/`: - - GET /?uri=%2Fhello-world%2F - -To disable automatic Markdown-to-HTML conversion, pass `plain=1` to the -query URL: - - GET /?uri=...&plain=1 - -As response, you either get 200, 400, or 404, which are pretty self-explanatory. - - GET / - 400 BAD REQUEST - - GET /?uri=%2Fhello-world%2F - 404 NOT FOUND - - GET /?uri=%2Fcomment-me%2F - [{comment 1}, {comment 2}, ...] - - -## Create comments - -... - - -## Delete comments - -... - - -## Up- and downvote comments - -... diff --git a/docs/_isso/html5.py b/docs/_isso/html5.py new file mode 100644 index 0000000..d6138ab --- /dev/null +++ b/docs/_isso/html5.py @@ -0,0 +1,1298 @@ +#:Author: David Goodger (goodger@python.org) +#:Contributor: Michael Beaumont (mjboamail@gmail.com) +#:Copyright: This module has been placed in the public domain. + + +try: + str = unicode +except NameError: + pass + +import sys +import os +import os.path +import re +import urllib + +try: # check for the Python Imaging Library + import PIL.Image +except ImportError: + try: # sometimes PIL modules are put in PYTHONPATH's root + import Image + class PIL(object): pass # dummy wrapper + PIL.Image = Image + except ImportError: + PIL = None + +from docutils import nodes, utils +from docutils.utils.math import unichar2tex, pick_math_environment, math2html +from docutils.utils.math.latex2mathml import parse_latex_math + +from sphinx.writers.html import HTMLTranslator + + +class Isso(HTMLTranslator): + + def __init__(self, builder, *args, **kwds): + HTMLTranslator.__init__(self, builder, *args, **kwds) + self.initial_header_level = 1 + + def starttag(self, node, tagname, suffix='\n', empty=False, **attributes): + """ + Construct and return a start tag given a node (id & class attributes + are extracted), tag name, and optional attributes. + """ + tagname = tagname.lower() + prefix = [] + atts = {} + ids = [] + for (name, value) in list(attributes.items()): + atts[name.lower()] = value + classes = [] + languages = [] + # unify class arguments and move language specification + for cls in node.get('classes', []) + atts.pop('class', '').split() : + if cls.startswith('language-'): + languages.append(cls[9:]) + elif cls.strip() and cls not in classes: + classes.append(cls) + if languages: + # attribute name is 'lang' in XHTML 1.0 but 'xml:lang' in 1.1 + atts[self.lang_attribute] = languages[0] + if classes: + atts['class'] = ' '.join(classes) + assert 'id' not in atts + ids.extend(node.get('ids', [])) + if 'ids' in atts: + ids.extend(atts['ids']) + del atts['ids'] + if ids: + atts['id'] = ids[0] + for id in ids[1:]: + # Add empty "span" elements for additional IDs. Note + # that we cannot use empty "a" elements because there + # may be targets inside of references, but nested "a" + # elements aren't allowed in XHTML (even if they do + # not all have a "href" attribute). + if empty: + # Empty tag. Insert target right in front of element. + prefix.append('' % id) + else: + # Non-empty tag. Place the auxiliary tag + # *inside* the element, as the first child. + suffix += '' % id + attlist = list(atts.items()) + attlist.sort() + parts = [tagname] + for name, value in attlist: + # value=None was used for boolean attributes without + # value, but this isn't supported by XHTML. + assert value is not None + if isinstance(value, list): + values = [str(v) for v in value] + parts.append('%s="%s"' % (name.lower(), + self.attval(' '.join(values)))) + else: + parts.append('%s="%s"' % (name.lower(), + self.attval(str(value)))) + if empty: + infix = ' /' + else: + infix = '' + return ''.join(prefix) + '<%s%s>' % (' '.join(parts), infix) + suffix + + def emptytag(self, node, tagname, suffix='\n', **attributes): + """Construct and return an XML-compatible empty tag.""" + return self.starttag(node, tagname, suffix, empty=True, **attributes) + + def set_class_on_child(self, node, class_, index=0): + """ + Set class `class_` on the visible child no. index of `node`. + Do nothing if node has fewer children than `index`. + """ + children = [n for n in node if not isinstance(n, nodes.Invisible)] + try: + child = children[index] + except IndexError: + return + child['classes'].append(class_) + + def set_first_last(self, node): + self.set_class_on_child(node, 'first', 0) + self.set_class_on_child(node, 'last', -1) + + def visit_Text(self, node): + text = node.astext() + encoded = self.encode(text) + if self.in_mailto and self.settings.cloak_email_addresses: + encoded = self.cloak_email(encoded) + self.body.append(encoded) + + def depart_Text(self, node): + pass + + def visit_abbreviation(self, node): + attrs = {} + if node.hasattr('explanation'): + attrs['title'] = node['explanation'] + self.body.append(self.starttag(node, 'abbr', '', **attrs)) + + def depart_abbreviation(self, node): + self.body.append('') + + def visit_acronym(self, node): + # @@@ implementation incomplete ("title" attribute) + self.body.append(self.starttag(node, 'acronym', '')) + + def depart_acronym(self, node): + self.body.append('') + + def visit_address(self, node): + self.visit_docinfo_item(node, 'address', meta=False) + self.body.append(self.starttag(node, 'pre', CLASS='address')) + + def depart_address(self, node): + self.body.append('\n\n') + self.depart_docinfo_item() + + def visit_admonition(self, node): + self.body.append(self.starttag(node, 'div')) + self.set_first_last(node) + + def depart_admonition(self, node=None): + self.body.append('\n') + + attribution_formats = {'dash': ('—', ''), + 'parentheses': ('(', ')'), + 'parens': ('(', ')'), + 'none': ('', '')} + + def visit_attribution(self, node): + prefix, suffix = self.attribution_formats[self.settings.attribution] + self.context.append(suffix) + self.body.append( + self.starttag(node, 'p', prefix, CLASS='attribution')) + + def depart_attribution(self, node): + self.body.append(self.context.pop() + '

\n') + + def visit_author(self, node): + if isinstance(node.parent, nodes.authors): + if self.author_in_authors: + self.body.append('\n
') + else: + self.visit_docinfo_item(node, 'author') + + def depart_author(self, node): + if isinstance(node.parent, nodes.authors): + self.author_in_authors = True + else: + self.depart_docinfo_item() + + def visit_authors(self, node): + self.visit_docinfo_item(node, 'authors') + self.author_in_authors = False # initialize + + def depart_authors(self, node): + self.depart_docinfo_item() + + def visit_block_quote(self, node): + self.body.append(self.starttag(node, 'blockquote')) + + def depart_block_quote(self, node): + self.body.append('\n') + + def check_simple_list(self, node): + """Check for a simple list that can be rendered compactly.""" + visitor = SimpleListChecker(self.document) + try: + node.walk(visitor) + except nodes.NodeFound: + return None + else: + return 1 + + def is_compactable(self, node): + return ('compact' in node['classes'] + or (self.settings.compact_lists + and 'open' not in node['classes'] + and (self.compact_simple + or self.topic_classes == ['contents'] + or self.check_simple_list(node)))) + + def visit_bullet_list(self, node): + atts = {} + old_compact_simple = self.compact_simple + self.context.append((self.compact_simple, self.compact_p)) + self.compact_p = None + self.compact_simple = self.is_compactable(node) + if self.compact_simple and not old_compact_simple: + atts['class'] = 'simple' + self.body.append(self.starttag(node, 'ul', **atts)) + + def depart_bullet_list(self, node): + self.compact_simple, self.compact_p = self.context.pop() + self.body.append('\n') + + def visit_caption(self, node): + self.body.append(self.starttag(node, 'figcaption')) + + def depart_caption(self, node): + self.body.append('\n') + +#changed + def visit_citation(self, node): + self.body.append(self.starttag(node, 'table', + CLASS='docutils citation', + frame="void", rules="none")) + self.body.append('\n' + '\n' + '') + self.footnote_backrefs(node) + + def depart_citation(self, node): + self.body.append('\n' + '\n\n') + + def visit_citation_reference(self, node): + href = '#' + if 'refid' in node: + href += node['refid'] + elif 'refname' in node: + href += self.document.nameids[node['refname']] + # else: # TODO system message (or already in the transform)? + # 'Citation reference missing.' + self.body.append(self.starttag( + node, 'a', '[', CLASS='citation-reference', href=href)) + + def depart_citation_reference(self, node): + self.body.append(']') + + def visit_classifier(self, node): + self.body.append(' : ') + self.body.append(self.starttag(node, 'span', '', CLASS='classifier')) + + def depart_classifier(self, node): + self.body.append('
') + + def visit_colspec(self, node): + self.colspecs.append(node) + # "stubs" list is an attribute of the tgroup element: + node.parent.stubs.append(node.attributes.get('stub')) + + def depart_colspec(self, node): + pass + + def write_colspecs(self): + width = 0 + for node in self.colspecs: + width += node['colwidth'] + for node in self.colspecs: + colwidth = int(node['colwidth'] * 100.0 / width + 0.5) + self.body.append(self.emptytag(node, 'col', + width='%i%%' % colwidth)) + self.colspecs = [] + + def visit_comment(self, node, + sub=re.compile('-(?=-)').sub): + """Escape double-dashes in comment text.""" + self.body.append('\n' % sub('- ', node.astext())) + # Content already processed: + raise nodes.SkipNode + + def visit_compound(self, node): + self.body.append(self.starttag(node, 'div', CLASS='compound')) + if len(node) > 1: + node[0]['classes'].append('compound-first') + node[-1]['classes'].append('compound-last') + for child in node[1:-1]: + child['classes'].append('compound-middle') + + def depart_compound(self, node): + self.body.append('\n') + + def visit_container(self, node): + self.body.append(self.starttag(node, 'div', CLASS='container')) + + def depart_container(self, node): + self.body.append('\n') + + def visit_contact(self, node): + self.visit_docinfo_item(node, 'contact', meta=False) + + def depart_contact(self, node): + self.depart_docinfo_item() + + def visit_copyright(self, node): + self.visit_docinfo_item(node, 'copyright') + + def depart_copyright(self, node): + self.depart_docinfo_item() + + def visit_date(self, node): + self.visit_docinfo_item(node, 'date') + + def depart_date(self, node): + self.depart_docinfo_item() + + def visit_decoration(self, node): + pass + + def depart_decoration(self, node): + pass + + def visit_definition(self, node): + self.body.append('\n') + self.body.append(self.starttag(node, 'dd', '')) + self.set_first_last(node) + + def depart_definition(self, node): + self.body.append('\n') + + def visit_definition_list(self, node): + self.body.append(self.starttag(node, 'dl', CLASS='docutils')) + + def depart_definition_list(self, node): + self.body.append('\n') + + def visit_definition_list_item(self, node): + pass + + def depart_definition_list_item(self, node): + pass + + def visit_description(self, node): + self.body.append(self.starttag(node, 'td', '')) + self.set_first_last(node) + + def depart_description(self, node): + self.body.append('') + + def visit_docinfo(self, node): + self.context.append(len(self.body)) + self.body.append(self.starttag(node, 'table', + CLASS='docinfo', + frame="void", rules="none")) + self.body.append('\n' + '\n' + '\n') + self.in_docinfo = True + + def depart_docinfo(self, node): + self.body.append('\n\n') + self.in_docinfo = False + start = self.context.pop() + self.docinfo = self.body[start:] + self.body = [] + + def visit_docinfo_item(self, node, name, meta=True): + if meta: + meta_tag = '\n' \ + % (name, self.attval(node.astext())) + self.add_meta(meta_tag) + self.body.append(self.starttag(node, 'tr', '')) + self.body.append('%s:\n' + % self.language.labels[name]) + if len(node): + if isinstance(node[0], nodes.Element): + node[0]['classes'].append('first') + if isinstance(node[-1], nodes.Element): + node[-1]['classes'].append('last') + + def depart_docinfo_item(self): + self.body.append('\n') + + def visit_doctest_block(self, node): + self.body.append(self.starttag(node, 'pre', CLASS='doctest-block')) + + def depart_doctest_block(self, node): + self.body.append('\n\n') + + def visit_document(self, node): + self.head.append('%s\n' + % self.encode(node.get('title', ''))) + + def depart_document(self, node): + self.head_prefix.extend([self.doctype, + self.head_prefix_template % + {'lang': self.settings.language_code}]) + self.html_prolog.append(self.doctype) + self.meta.insert(0, self.content_type % self.settings.output_encoding) + self.head.insert(0, self.content_type % self.settings.output_encoding) + if self.math_header: + if self.math_output == 'mathjax': + self.head.extend(self.math_header) + else: + self.stylesheet.extend(self.math_header) + # skip content-type meta tag with interpolated charset value: + self.html_head.extend(self.head[1:]) + self.body_prefix.append(self.starttag(node, 'div', CLASS='document')) + self.body_suffix.insert(0, '\n') + self.fragment.extend(self.body) # self.fragment is the "naked" body + self.html_body.extend(self.body_prefix[1:] + self.body_pre_docinfo + + self.docinfo + self.body + + self.body_suffix[:-1]) + assert not self.context, 'len(context) = %s' % len(self.context) + + def visit_emphasis(self, node): + self.body.append(self.starttag(node, 'em', '')) + + def depart_emphasis(self, node): + self.body.append('') + + def visit_entry(self, node): + atts = {'class': []} + if isinstance(node.parent.parent, nodes.thead): + atts['class'].append('head') + if node.parent.parent.parent.stubs[node.parent.column]: + # "stubs" list is an attribute of the tgroup element + atts['class'].append('stub') + if atts['class']: + tagname = 'th' + atts['class'] = ' '.join(atts['class']) + else: + tagname = 'td' + del atts['class'] + node.parent.column += 1 + if 'morerows' in node: + atts['rowspan'] = node['morerows'] + 1 + if 'morecols' in node: + atts['colspan'] = node['morecols'] + 1 + node.parent.column += node['morecols'] + self.body.append(self.starttag(node, tagname, '', **atts)) + self.context.append('\n' % tagname.lower()) + if len(node) == 0: # empty cell + self.body.append(' ') + self.set_first_last(node) + + def depart_entry(self, node): + self.body.append(self.context.pop()) + + def visit_enumerated_list(self, node): + atts = {} + if 'start' in node: + atts['start'] = node['start'] + if 'enumtype' in node: + atts['class'] = node['enumtype'] + # @@@ To do: prefix, suffix. How? Change prefix/suffix to a + # single "format" attribute? Use CSS2? + old_compact_simple = self.compact_simple + self.context.append((self.compact_simple, self.compact_p)) + self.compact_p = None + self.compact_simple = self.is_compactable(node) + if self.compact_simple and not old_compact_simple: + atts['class'] = (atts.get('class', '') + ' simple').strip() + self.body.append(self.starttag(node, 'ol', **atts)) + + def depart_enumerated_list(self, node): + self.compact_simple, self.compact_p = self.context.pop() + self.body.append('\n') + + def visit_field(self, node): + self.body.append(self.starttag(node, 'tr', '', CLASS='field')) + + def depart_field(self, node): + self.body.append('\n') + + def visit_field_body(self, node): + self.body.append(self.starttag(node, 'td', '', CLASS='field-body')) + self.set_class_on_child(node, 'first', 0) + field = node.parent + if (self.compact_field_list or + isinstance(field.parent, nodes.docinfo) or + field.parent.index(field) == len(field.parent) - 1): + # If we are in a compact list, the docinfo, or if this is + # the last field of the field list, do not add vertical + # space after last element. + self.set_class_on_child(node, 'last', -1) + + def depart_field_body(self, node): + self.body.append('\n') + + def visit_field_list(self, node): + self.context.append((self.compact_field_list, self.compact_p)) + self.compact_p = None + if 'compact' in node['classes']: + self.compact_field_list = True + elif (self.settings.compact_field_lists + and 'open' not in node['classes']): + self.compact_field_list = True + if self.compact_field_list: + for field in node: + field_body = field[-1] + assert isinstance(field_body, nodes.field_body) + children = [n for n in field_body + if not isinstance(n, nodes.Invisible)] + if not (len(children) == 0 or + len(children) == 1 and + isinstance(children[0], + (nodes.paragraph, nodes.line_block))): + self.compact_field_list = False + break + self.body.append(self.starttag(node, 'table', frame='void', + rules='none', + CLASS='docutils field-list')) + self.body.append('\n' + '\n' + '\n') + + def depart_field_list(self, node): + self.body.append('\n\n') + self.compact_field_list, self.compact_p = self.context.pop() + + def visit_field_name(self, node): + atts = {} + if self.in_docinfo: + atts['class'] = 'docinfo-name' + else: + atts['class'] = 'field-name' + if ( self.settings.field_name_limit + and len(node.astext()) > self.settings.field_name_limit): + atts['colspan'] = 2 + self.context.append('\n' + + self.starttag(node.parent, 'tr', '', + CLASS='field') + + ' ') + else: + self.context.append('') + self.body.append(self.starttag(node, 'th', '', **atts)) + + def depart_field_name(self, node): + self.body.append(':') + self.body.append(self.context.pop()) + + def visit_figure(self, node): + atts = {'class': 'figure'} + if node.get('width'): + atts['style'] = 'width: %s' % node['width'] + if node.get('align'): + atts['class'] += " align-" + node['align'] + self.body.append(self.starttag(node, 'figure', **atts)) + + def depart_figure(self, node): + self.body.append('\n') + + def visit_footer(self, node): + self.context.append(len(self.body)) + + def depart_footer(self, node): + start = self.context.pop() + footer = [self.starttag(node, 'footer'), + '\n'] + footer.extend(self.body[start:]) + footer.append('\n\n') + self.footer.extend(footer) + self.body_suffix[:0] = footer + del self.body[start:] + + def visit_footnote(self, node): + self.body.append(self.starttag(node, 'table', + CLASS='docutils footnote', + frame="void", rules="none")) + self.body.append('\n' + '\n' + '') + self.footnote_backrefs(node) + + def footnote_backrefs(self, node): + backlinks = [] + backrefs = node['backrefs'] + if self.settings.footnote_backlinks and backrefs: + if len(backrefs) == 1: + self.context.append('') + self.context.append('') + self.context.append('' + % backrefs[0]) + else: + i = 1 + for backref in backrefs: + backlinks.append('%s' + % (backref, i)) + i += 1 + self.context.append('(%s) ' % ', '.join(backlinks)) + self.context += ['', ''] + else: + self.context.append('') + self.context += ['', ''] + # If the node does not only consist of a label. + if len(node) > 1: + # If there are preceding backlinks, we do not set class + # 'first', because we need to retain the top-margin. + if not backlinks: + node[1]['classes'].append('first') + node[-1]['classes'].append('last') + + def depart_footnote(self, node): + self.body.append('\n' + '\n\n') + + def visit_footnote_reference(self, node): + href = '#' + node['refid'] + format = self.settings.footnote_references + if format == 'brackets': + suffix = '[' + self.context.append(']') + else: + assert format == 'superscript' + suffix = '' + self.context.append('') + self.body.append(self.starttag(node, 'a', suffix, + CLASS='footnote-reference', href=href)) + + def depart_footnote_reference(self, node): + self.body.append(self.context.pop() + '') + + def visit_generated(self, node): + pass + + def depart_generated(self, node): + pass + + def visit_header(self, node): + self.context.append(len(self.body)) + + def depart_header(self, node): + start = self.context.pop() + header = [self.starttag(node, 'header')] + header.extend(self.body[start:]) + header.append('\n
\n
\n') + self.body_prefix.extend(header) + self.header.extend(header) + del self.body[start:] + + def visit_image(self, node): + atts = {} + uri = node['uri'] + # place SVG and SWF images in an element + types = {'.svg': 'image/svg+xml', + '.swf': 'application/x-shockwave-flash'} + ext = os.path.splitext(uri)[1].lower() + if ext in ('.svg', '.swf'): + atts['data'] = uri + atts['type'] = types[ext] + else: + atts['src'] = uri + atts['alt'] = node.get('alt', uri) + # image size + if 'width' in node: + atts['width'] = node['width'] + if 'height' in node: + atts['height'] = node['height'] + if 'scale' in node: + if (PIL and not ('width' in node and 'height' in node) + and self.settings.file_insertion_enabled): + imagepath = urllib.url2pathname(uri) + try: + img = PIL.Image.open( + imagepath.encode(sys.getfilesystemencoding())) + except (IOError, UnicodeEncodeError): + pass # TODO: warn? + else: + self.settings.record_dependencies.add( + imagepath.replace('\\', '/')) + if 'width' not in atts: + atts['width'] = str(img.size[0]) + if 'height' not in atts: + atts['height'] = str(img.size[1]) + del img + for att_name in 'width', 'height': + if att_name in atts: + match = re.match(r'([0-9.]+)(\S*)$', atts[att_name]) + assert match + atts[att_name] = '%s%s' % ( + float(match.group(1)) * (float(node['scale']) / 100), + match.group(2)) + style = [] + for att_name in 'width', 'height': + if att_name in atts: + if re.match(r'^[0-9.]+$', atts[att_name]): + # Interpret unitless values as pixels. + atts[att_name] += 'px' + style.append('%s: %s;' % (att_name, atts[att_name])) + del atts[att_name] + if style: + atts['style'] = ' '.join(style) + if (isinstance(node.parent, nodes.TextElement) or + (isinstance(node.parent, nodes.reference) and + not isinstance(node.parent.parent, nodes.TextElement))): + # Inline context or surrounded by .... + suffix = '' + else: + suffix = '\n' + if 'align' in node: + atts['class'] = 'align-%s' % node['align'] + self.context.append('') + if ext in ('.svg', '.swf'): # place in an object element, + # do NOT use an empty tag: incorrect rendering in browsers + self.body.append(self.starttag(node, 'object', suffix, **atts) + + node.get('alt', uri) + '' + suffix) + else: + self.body.append(self.emptytag(node, 'img', suffix, **atts)) + + def depart_image(self, node): + self.body.append(self.context.pop()) + + def visit_inline(self, node): + self.body.append(self.starttag(node, 'span', '')) + + def depart_inline(self, node): + self.body.append('') + + def visit_label(self, node): + # Context added in footnote_backrefs. + self.body.append(self.starttag(node, 'td', '%s[' % self.context.pop(), + CLASS='label')) + + def depart_label(self, node): + # Context added in footnote_backrefs. + self.body.append(']%s%s' % (self.context.pop(), self.context.pop())) + + def visit_legend(self, node): + self.body.append(self.starttag(node, 'div', CLASS='legend')) + + def depart_legend(self, node): + self.body.append('\n') + + def visit_line(self, node): + self.body.append(self.starttag(node, 'div', suffix='', CLASS='line')) + if not len(node): + self.body.append('
') + + def depart_line(self, node): + self.body.append('\n') + + def visit_line_block(self, node): + self.body.append(self.starttag(node, 'div', CLASS='line-block')) + + def depart_line_block(self, node): + self.body.append('\n') + + def visit_list_item(self, node): + self.body.append(self.starttag(node, 'li', '')) + if len(node): + node[0]['classes'].append('first') + + def visit_math(self, node, math_env=''): + # If the method is called from visit_math_block(), math_env != ''. + + # As there is no native HTML math support, we provide alternatives: + # LaTeX and MathJax math_output modes simply wrap the content, + # HTML and MathML math_output modes also convert the math_code. + if self.math_output not in ('mathml', 'html', 'mathjax', 'latex'): + self.document.reporter.error( + 'math-output format "%s" not supported ' + 'falling back to "latex"'% self.math_output) + self.math_output = 'latex' + # + # HTML container + tags = {# math_output: (block, inline, class-arguments) + 'mathml': ('div', '', ''), + 'html': ('div', 'span', 'formula'), + 'mathjax': ('div', 'span', 'math'), + 'latex': ('pre', 'tt', 'math'), + } + tag = tags[self.math_output][math_env == ''] + clsarg = tags[self.math_output][2] + # LaTeX container + wrappers = {# math_mode: (inline, block) + 'mathml': (None, None), + 'html': ('$%s$', u'\\begin{%s}\n%s\n\\end{%s}'), + 'mathjax': ('\(%s\)', u'\\begin{%s}\n%s\n\\end{%s}'), + 'latex': (None, None), + } + wrapper = wrappers[self.math_output][math_env != ''] + # get and wrap content + math_code = node.astext().translate(unichar2tex.uni2tex_table) + if wrapper and math_env: + math_code = wrapper % (math_env, math_code, math_env) + elif wrapper: + math_code = wrapper % math_code + # settings and conversion + if self.math_output in ('latex', 'mathjax'): + math_code = self.encode(math_code) + if self.math_output == 'mathjax' and not self.math_header: + if self.math_output_options: + self.mathjax_url = self.math_output_options[0] + self.math_header = [self.mathjax_script % self.mathjax_url] + elif self.math_output == 'html': + if self.math_output_options and not self.math_header: + self.math_header = [self.stylesheet_call( + utils.find_file_in_dirs(s, self.settings.stylesheet_dirs)) + for s in self.math_output_options[0].split(',')] + # TODO: fix display mode in matrices and fractions + math2html.DocumentParameters.displaymode = (math_env != '') + math_code = math2html.math2html(math_code) + elif self.math_output == 'mathml': + self.doctype = self.doctype_mathml + self.content_type = self.content_type_mathml + try: + mathml_tree = parse_latex_math(math_code, inline=not(math_env)) + math_code = ''.join(mathml_tree.xml()) + except SyntaxError as err: + err_node = self.document.reporter.error(err, base_node=node) + self.visit_system_message(err_node) + self.body.append(self.starttag(node, 'p')) + self.body.append(u','.join(err.args)) + self.body.append('

\n') + self.body.append(self.starttag(node, 'pre', + CLASS='literal-block')) + self.body.append(self.encode(math_code)) + self.body.append('\n\n') + self.depart_system_message(err_node) + raise nodes.SkipNode + # append to document body + if tag: + self.body.append(self.starttag(node, tag, + suffix='\n'*bool(math_env), + CLASS=clsarg)) + self.body.append(math_code) + if math_env: + self.body.append('\n') + if tag: + self.body.append('\n' % tag) + # Content already processed: + raise nodes.SkipNode + + def depart_math(self, node): + pass # never reached + + def visit_math_block(self, node): + # print node.astext().encode('utf8') + math_env = pick_math_environment(node.astext()) + self.visit_math(node, math_env=math_env) + + def depart_math_block(self, node): + pass # never reached + + def visit_meta(self, node): + meta = self.emptytag(node, 'meta', **node.non_default_attributes()) + self.add_meta(meta) + + def depart_meta(self, node): + pass + + def add_meta(self, tag): + self.meta.append(tag) + self.head.append(tag) + + def visit_option(self, node): + if self.context[-1]: + self.body.append(', ') + self.body.append(self.starttag(node, 'span', '', CLASS='option')) + + def depart_option(self, node): + self.body.append('') + self.context[-1] += 1 + + def visit_option_argument(self, node): + self.body.append(node.get('delimiter', ' ')) + self.body.append(self.starttag(node, 'var', '')) + + def depart_option_argument(self, node): + self.body.append('') + + def visit_option_group(self, node): + atts = {} + if ( self.settings.option_limit + and len(node.astext()) > self.settings.option_limit): + atts['colspan'] = 2 + self.context.append('\n ') + else: + self.context.append('') + self.body.append( + self.starttag(node, 'td', CLASS='option-group', **atts)) + self.body.append('') + self.context.append(0) # count number of options + + def depart_option_group(self, node): + self.context.pop() + self.body.append('\n') + self.body.append(self.context.pop()) + + def visit_option_list(self, node): + self.body.append( + self.starttag(node, 'table', CLASS='docutils option-list', + frame="void", rules="none")) + self.body.append('\n' + '\n' + '\n') + + def depart_option_list(self, node): + self.body.append('\n\n') + + def visit_option_list_item(self, node): + self.body.append(self.starttag(node, 'tr', '')) + + def depart_option_list_item(self, node): + self.body.append('\n') + + def visit_option_string(self, node): + pass + + def depart_option_string(self, node): + pass + + def visit_organization(self, node): + self.visit_docinfo_item(node, 'organization') + + def depart_organization(self, node): + self.depart_docinfo_item() + + def should_be_compact_paragraph(self, node): + """ + Determine if the

tags around paragraph ``node`` can be omitted. + """ + if (isinstance(node.parent, nodes.document) or + isinstance(node.parent, nodes.compound)): + # Never compact paragraphs in document or compound. + return False + for key, value in node.attlist(): + if (node.is_not_default(key) and + not (key == 'classes' and value in + ([], ['first'], ['last'], ['first', 'last']))): + # Attribute which needs to survive. + return False + first = isinstance(node.parent[0], nodes.label) # skip label + for child in node.parent.children[first:]: + # only first paragraph can be compact + if isinstance(child, nodes.Invisible): + continue + if child is node: + break + return False + parent_length = len([n for n in node.parent if not isinstance( + n, (nodes.Invisible, nodes.label))]) + if ( self.compact_simple + or self.compact_field_list + or self.compact_p and parent_length == 1): + return True + return False + + def visit_paragraph(self, node): + if self.should_be_compact_paragraph(node): + self.context.append('') + else: + self.body.append(self.starttag(node, 'p', '')) + self.context.append('

\n') + + def depart_paragraph(self, node): + self.body.append(self.context.pop()) + + def visit_problematic(self, node): + if node.hasattr('refid'): + self.body.append('' % node['refid']) + self.context.append('') + else: + self.context.append('') + self.body.append(self.starttag(node, 'span', '', CLASS='problematic')) + + def depart_problematic(self, node): + self.body.append('') + self.body.append(self.context.pop()) + + def visit_raw(self, node): + if 'html' in node.get('format', '').split(): + t = isinstance(node.parent, nodes.TextElement) and 'span' or 'div' + if node['classes']: + self.body.append(self.starttag(node, t, suffix='')) + self.body.append(node.astext()) + if node['classes']: + self.body.append('' % t) + # Keep non-HTML raw text out of output: + raise nodes.SkipNode + + def visit_reference(self, node): + atts = {'class': 'reference'} + if 'refuri' in node: + atts['href'] = node['refuri'] + if ( self.settings.cloak_email_addresses + and atts['href'].startswith('mailto:')): + atts['href'] = self.cloak_mailto(atts['href']) + self.in_mailto = True + atts['class'] += ' external' + else: + assert 'refid' in node, \ + 'References must have "refuri" or "refid" attribute.' + atts['href'] = '#' + node['refid'] + atts['class'] += ' internal' + if not isinstance(node.parent, nodes.TextElement): + assert len(node) == 1 and isinstance(node[0], nodes.image) + atts['class'] += ' image-reference' + self.body.append(self.starttag(node, 'a', '', **atts)) + + def depart_reference(self, node): + self.body.append('') + if not isinstance(node.parent, nodes.TextElement): + self.body.append('\n') + self.in_mailto = False + + def visit_revision(self, node): + self.visit_docinfo_item(node, 'revision', meta=False) + + def depart_revision(self, node): + self.depart_docinfo_item() + + def visit_row(self, node): + self.body.append(self.starttag(node, 'tr', '')) + node.column = 0 + + def depart_row(self, node): + self.body.append('\n') + + def visit_rubric(self, node): + self.body.append(self.starttag(node, 'p', '', CLASS='rubric')) + + def depart_rubric(self, node): + self.body.append('

\n') + + def visit_section(self, node): + self.section_level += 1 + self.body.append(self.starttag(node, 'section')) + + def depart_section(self, node): + self.section_level -= 1 + self.body.append('\n') + + def visit_sidebar(self, node): + self.body.append( + self.starttag(node, 'aside')) + self.set_first_last(node) + self.in_sidebar = True + + def depart_sidebar(self, node): + self.body.append('\n') + self.in_sidebar = False + + def visit_status(self, node): + self.visit_docinfo_item(node, 'status', meta=False) + + def depart_status(self, node): + self.depart_docinfo_item() + + def visit_strong(self, node): + self.body.append(self.starttag(node, 'strong', '')) + + def depart_strong(self, node): + self.body.append('') + + def visit_subscript(self, node): + self.body.append(self.starttag(node, 'sub', '')) + + def depart_subscript(self, node): + self.body.append('') + + def visit_substitution_definition(self, node): + """Internal only.""" + raise nodes.SkipNode + + def visit_substitution_reference(self, node): + self.unimplemented_visit(node) + + def visit_subtitle(self, node): + if isinstance(node.parent, nodes.sidebar): + self.body.append(self.starttag(node, 'p', '', + CLASS='sidebar-subtitle')) + self.context.append('

\n') + elif isinstance(node.parent, nodes.document): + self.body.append(self.starttag(node, 'h2', '', CLASS='subtitle')) + self.context.append('\n') + self.in_document_title = len(self.body) + elif isinstance(node.parent, nodes.section): + tag = 'h%s' % (self.section_level + self.initial_header_level - 1) + self.body.append( + self.starttag(node, tag, '', CLASS='section-subtitle') + + self.starttag({}, 'span', '', CLASS='section-subtitle')) + self.context.append('\n' % tag) + + def depart_subtitle(self, node): + self.body.append(self.context.pop()) + if self.in_document_title: + self.subtitle = self.body[self.in_document_title:-1] + self.in_document_title = 0 + self.body_pre_docinfo.extend(self.body) + self.html_subtitle.extend(self.body) + del self.body[:] + + def visit_superscript(self, node): + self.body.append(self.starttag(node, 'sup', '')) + + def depart_superscript(self, node): + self.body.append('') + + def visit_system_message(self, node): + self.body.append(self.starttag(node, 'div', CLASS='system-message')) + self.body.append('

') + backref_text = '' + if len(node['backrefs']): + backrefs = node['backrefs'] + if len(backrefs) == 1: + backref_text = ('; backlink' + % backrefs[0]) + else: + i = 1 + backlinks = [] + for backref in backrefs: + backlinks.append('%s' % (backref, i)) + i += 1 + backref_text = ('; backlinks: %s' + % ', '.join(backlinks)) + if node.hasattr('line'): + line = ', line %s' % node['line'] + else: + line = '' + self.body.append('System Message: %s/%s ' + '(%s%s)%s

\n' + % (node['type'], node['level'], + self.encode(node['source']), line, backref_text)) + + def depart_system_message(self, node): + self.body.append('\n') + + def visit_table(self, node): + self.context.append(self.compact_p) + self.compact_p = True + classes = ' '.join(['docutils', self.settings.table_style]).strip() + self.body.append( + self.starttag(node, 'table', CLASS=classes, border="1")) + + def depart_table(self, node): + self.compact_p = self.context.pop() + self.body.append('\n') + + def visit_target(self, node): + if not ('refuri' in node or 'refid' in node + or 'refname' in node): + self.body.append(self.starttag(node, 'span', '', CLASS='target')) + self.context.append('') + else: + self.context.append('') + + def depart_target(self, node): + self.body.append(self.context.pop()) + + def visit_tbody(self, node): + self.write_colspecs() + self.body.append(self.context.pop()) # '\n' or '' + self.body.append(self.starttag(node, 'tbody', valign='top')) + + def depart_tbody(self, node): + self.body.append('\n') + + def visit_term(self, node): + self.body.append(self.starttag(node, 'dt', '')) + + def depart_term(self, node): + """ + Leave the end tag to `self.visit_definition()`, in case there's a + classifier. + """ + pass + + def visit_tgroup(self, node): + # Mozilla needs : + self.body.append(self.starttag(node, 'colgroup')) + # Appended by thead or tbody: + self.context.append('\n') + node.stubs = [] + + def depart_tgroup(self, node): + pass + + def visit_thead(self, node): + self.write_colspecs() + self.body.append(self.context.pop()) # '\n' + # There may or may not be a ; this is for to use: + self.context.append('') + self.body.append(self.starttag(node, 'thead', valign='bottom')) + + def depart_thead(self, node): + self.body.append('\n') + + def visit_title(self, node): + if self.section_level == 1: + raise nodes.SkipNode + HTMLTranslator.visit_title(self, node) + + def depart_title(self, node): + self.body.append(self.context.pop()) + if self.in_document_title: + self.title = self.body[self.in_document_title:-1] + self.in_document_title = 0 + self.body_pre_docinfo.extend(self.body) + self.html_title.extend(self.body) + del self.body[:] + + def visit_title_reference(self, node): + self.body.append(self.starttag(node, 'cite', '')) + + def depart_title_reference(self, node): + self.body.append('') + + def visit_topic(self, node): + self.body.append(self.starttag(node, 'section', CLASS='topic')) + self.topic_classes = node['classes'] + + def depart_topic(self, node): + self.body.append('\n') + self.topic_classes = [] + + def visit_transition(self, node): + self.body.append(self.emptytag(node, 'hr', CLASS='docutils')) + + def depart_transition(self, node): + pass + + def visit_version(self, node): + self.visit_docinfo_item(node, 'version', meta=False) + + def depart_version(self, node): + self.depart_docinfo_item() + + def unimplemented_visit(self, node): + raise NotImplementedError('visiting unimplemented node type: %s' + % node.__class__.__name__) + + +class SimpleListChecker(nodes.GenericNodeVisitor): + + """ + Raise `nodes.NodeFound` if non-simple list item is encountered. + + Here "simple" means a list item containing nothing other than a single + paragraph, a simple list, or a paragraph followed by a simple list. + """ + + def default_visit(self, node): + raise nodes.NodeFound + + def visit_bullet_list(self, node): + pass + + def visit_enumerated_list(self, node): + pass + + def visit_list_item(self, node): + children = [] + for child in node.children: + if not isinstance(child, nodes.Invisible): + children.append(child) + if (children and isinstance(children[0], nodes.paragraph) + and (isinstance(children[-1], nodes.bullet_list) + or isinstance(children[-1], nodes.enumerated_list))): + children.pop() + if len(children) <= 1: + return + else: + raise nodes.NodeFound + + def visit_paragraph(self, node): + raise nodes.SkipNode + + def invisible_visit(self, node): + """Invisible nodes should be ignored.""" + raise nodes.SkipNode + + visit_comment = invisible_visit + visit_substitution_definition = invisible_visit + visit_target = invisible_visit + visit_pending = invisible_visit diff --git a/docs/_isso/layout.html b/docs/_isso/layout.html new file mode 100644 index 0000000..70c11ab --- /dev/null +++ b/docs/_isso/layout.html @@ -0,0 +1,123 @@ + +{%- set url_root = pathto('', 1) %} +{# XXX necessary? #} +{%- if url_root == '#' %}{% set url_root = '' %}{% endif %} + +{%- macro script() %} + + {%- for scriptfile in script_files %} + + {%- endfor %} +{%- endmacro %} + +{%- macro css() %} + + + + {%- for cssfile in css_files %} + + {%- endfor %} +{%- endmacro %} + + + + + {{ metatags }} + {%- block htmltitle %} + {{ title|striptags|e }} + {%- endblock %} + {{ css() }} + {%- if not embedded %} + {{ script() }} + {%- if use_opensearch %} + + {%- endif %} + {%- if favicon %} + + {%- endif %} + {%- endif %} +{%- block linktags %} + {%- if hasdoc('about') %} + + {%- endif %} + {%- if hasdoc('genindex') %} + + {%- endif %} + {%- if hasdoc('search') %} + + {%- endif %} + {%- if hasdoc('copyright') %} + + {%- endif %} + + {%- if parents %} + + {%- endif %} + {%- if next %} + + {%- endif %} + {%- if prev %} + + {%- endif %} +{%- endblock %} +{%- block extrahead %} {% endblock %} + + +
+ + +
+ {% block header %} {% endblock %} +
+ + +
+ {% block body %} {% endblock %} +
+ +
+
+ + + +{%- endblock %} + + diff --git a/docs/_isso/page.html b/docs/_isso/page.html new file mode 100644 index 0000000..593c09a --- /dev/null +++ b/docs/_isso/page.html @@ -0,0 +1,16 @@ +{%- extends "layout.html" %} +{% block header %} +
+

{{- title -}}

+
+{% endblock %} +{% block body %} + +
+ {{ body }} +
+{% endblock %} diff --git a/docs/_isso/search.html b/docs/_isso/search.html new file mode 100644 index 0000000..1cd9cc8 --- /dev/null +++ b/docs/_isso/search.html @@ -0,0 +1,54 @@ +{% extends "layout.html" %} +{% block header %} +
+

Search

+
+{% endblock %} +{% set title = _('Search') %} +{% set script_files = script_files + ['_static/searchtools.js'] %} +{% block extrahead %} + + {# this is used when loading the search index using $.ajax fails, + such as on Chrome for documents on localhost #} + + {{ super() }} +{% endblock %} +{% block body %} +
+ +

+ {% trans %}Please activate JavaScript to enable the search + functionality.{% endtrans %} +

+
+

+ {% trans %}From here you can search these documents. Enter your search + words into the box below and click "search". Note that the search + function will automatically search for all of the words. Pages + containing fewer words won't appear in the result list.{% endtrans %} +

+
+ + + +
+ {% if search_performed %} +

{{ _('Search Results') }}

+ {% if not search_results %} +

{{ _("Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories.") }}

+ {% endif %} + {% endif %} +
+ {% if search_results %} +
    + {% for href, caption, context in search_results %} +
  • {{ caption }} +
    {{ context|e }}
    +
  • + {% endfor %} +
+ {% endif %} +
+{% endblock %} diff --git a/docs/_isso/searchbox.html b/docs/_isso/searchbox.html new file mode 100644 index 0000000..1426c34 --- /dev/null +++ b/docs/_isso/searchbox.html @@ -0,0 +1,12 @@ +{%- if pagename != "search" %} + + +{%- endif %} diff --git a/docs/_isso/sidebar-docs.html b/docs/_isso/sidebar-docs.html new file mode 100644 index 0000000..a9a7dde --- /dev/null +++ b/docs/_isso/sidebar-docs.html @@ -0,0 +1,27 @@ +{% macro doc(path, title) %} + {%- if pagename == path -%} +
  • + {% else %} +
  • + {%- endif -%} + {{ title }}
  • +{% endmacro %} + +Getting Started + +Configuration + +Advanced + diff --git a/docs/_isso/theme.conf b/docs/_isso/theme.conf new file mode 100644 index 0000000..ccf3c9b --- /dev/null +++ b/docs/_isso/theme.conf @@ -0,0 +1,3 @@ +[theme] +inherit = basic +stylesheet = css/site.css diff --git a/docs/_static/css/bourbon/_bourbon-deprecated-upcoming.scss b/docs/_static/css/bourbon/_bourbon-deprecated-upcoming.scss new file mode 100644 index 0000000..5332496 --- /dev/null +++ b/docs/_static/css/bourbon/_bourbon-deprecated-upcoming.scss @@ -0,0 +1,13 @@ +//************************************************************************// +// These mixins/functions are deprecated +// They will be removed in the next MAJOR version release +//************************************************************************// +@mixin box-shadow ($shadows...) { + @include prefixer(box-shadow, $shadows, spec); + @warn "box-shadow is deprecated and will be removed in the next major version release"; +} + +@mixin background-size ($lengths...) { + @include prefixer(background-size, $lengths, spec); + @warn "background-size is deprecated and will be removed in the next major version release"; +} diff --git a/docs/_static/css/bourbon/_bourbon.scss b/docs/_static/css/bourbon/_bourbon.scss new file mode 100644 index 0000000..53fbca8 --- /dev/null +++ b/docs/_static/css/bourbon/_bourbon.scss @@ -0,0 +1,59 @@ +// Custom Helpers +@import "helpers/deprecated-webkit-gradient"; +@import "helpers/gradient-positions-parser"; +@import "helpers/linear-positions-parser"; +@import "helpers/radial-arg-parser"; +@import "helpers/radial-positions-parser"; +@import "helpers/render-gradients"; +@import "helpers/shape-size-stripper"; + +// Custom Functions +@import "functions/compact"; +@import "functions/flex-grid"; +@import "functions/grid-width"; +@import "functions/linear-gradient"; +@import "functions/modular-scale"; +@import "functions/px-to-em"; +@import "functions/radial-gradient"; +@import "functions/tint-shade"; +@import "functions/transition-property-name"; + +// CSS3 Mixins +@import "css3/animation"; +@import "css3/appearance"; +@import "css3/backface-visibility"; +@import "css3/background"; +@import "css3/background-image"; +@import "css3/border-image"; +@import "css3/border-radius"; +@import "css3/box-sizing"; +@import "css3/columns"; +@import "css3/flex-box"; +@import "css3/font-face"; +@import "css3/hidpi-media-query"; +@import "css3/image-rendering"; +@import "css3/inline-block"; +@import "css3/keyframes"; +@import "css3/linear-gradient"; +@import "css3/perspective"; +@import "css3/radial-gradient"; +@import "css3/transform"; +@import "css3/transition"; +@import "css3/user-select"; +@import "css3/placeholder"; + +// Addons & other mixins +@import "addons/button"; +@import "addons/clearfix"; +@import "addons/font-family"; +@import "addons/hide-text"; +@import "addons/html5-input-types"; +@import "addons/position"; +@import "addons/prefixer"; +@import "addons/retina-image"; +@import "addons/size"; +@import "addons/timing-functions"; +@import "addons/triangle"; + +// Soon to be deprecated Mixins +@import "bourbon-deprecated-upcoming"; diff --git a/docs/_static/css/bourbon/addons/_button.scss b/docs/_static/css/bourbon/addons/_button.scss new file mode 100644 index 0000000..3ae393c --- /dev/null +++ b/docs/_static/css/bourbon/addons/_button.scss @@ -0,0 +1,273 @@ +@mixin button ($style: simple, $base-color: #4294f0) { + + @if type-of($style) == color { + $base-color: $style; + $style: simple; + } + + // Grayscale button + @if $base-color == grayscale($base-color) { + @if $style == simple { + @include simple($base-color, $grayscale: true); + } + + @else if $style == shiny { + @include shiny($base-color, $grayscale: true); + } + + @else if $style == pill { + @include pill($base-color, $grayscale: true); + } + } + + // Colored button + @else { + @if $style == simple { + @include simple($base-color); + } + + @else if $style == shiny { + @include shiny($base-color); + } + + @else if $style == pill { + @include pill($base-color); + } + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } +} + + +// Simple Button +//************************************************************************// +@mixin simple($base-color, $grayscale: false) { + $color: hsl(0, 0, 100%); + $border: adjust-color($base-color, $saturation: 9%, $lightness: -14%); + $inset-shadow: adjust-color($base-color, $saturation: -8%, $lightness: 15%); + $stop-gradient: adjust-color($base-color, $saturation: 9%, $lightness: -11%); + $text-shadow: adjust-color($base-color, $saturation: 15%, $lightness: -18%); + + @if lightness($base-color) > 70% { + $color: hsl(0, 0, 20%); + $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%); + } + + @if $grayscale == true { + $border: grayscale($border); + $inset-shadow: grayscale($inset-shadow); + $stop-gradient: grayscale($stop-gradient); + $text-shadow: grayscale($text-shadow); + } + + border: 1px solid $border; + border-radius: 3px; + box-shadow: inset 0 1px 0 0 $inset-shadow; + color: $color; + display: inline-block; + font-size: 11px; + font-weight: bold; + @include linear-gradient ($base-color, $stop-gradient); + padding: 7px 18px; + text-decoration: none; + text-shadow: 0 1px 0 $text-shadow; + background-clip: padding-box; + + &:hover:not(:disabled) { + $base-color-hover: adjust-color($base-color, $saturation: -4%, $lightness: -5%); + $inset-shadow-hover: adjust-color($base-color, $saturation: -7%, $lightness: 5%); + $stop-gradient-hover: adjust-color($base-color, $saturation: 8%, $lightness: -14%); + + @if $grayscale == true { + $base-color-hover: grayscale($base-color-hover); + $inset-shadow-hover: grayscale($inset-shadow-hover); + $stop-gradient-hover: grayscale($stop-gradient-hover); + } + + box-shadow: inset 0 1px 0 0 $inset-shadow-hover; + cursor: pointer; + @include linear-gradient ($base-color-hover, $stop-gradient-hover); + } + + &:active:not(:disabled) { + $border-active: adjust-color($base-color, $saturation: 9%, $lightness: -14%); + $inset-shadow-active: adjust-color($base-color, $saturation: 7%, $lightness: -17%); + + @if $grayscale == true { + $border-active: grayscale($border-active); + $inset-shadow-active: grayscale($inset-shadow-active); + } + + border: 1px solid $border-active; + box-shadow: inset 0 0 8px 4px $inset-shadow-active, inset 0 0 8px 4px $inset-shadow-active, 0 1px 1px 0 #eee; + } +} + + +// Shiny Button +//************************************************************************// +@mixin shiny($base-color, $grayscale: false) { + $color: hsl(0, 0, 100%); + $border: adjust-color($base-color, $red: -117, $green: -111, $blue: -81); + $border-bottom: adjust-color($base-color, $red: -126, $green: -127, $blue: -122); + $fourth-stop: adjust-color($base-color, $red: -79, $green: -70, $blue: -46); + $inset-shadow: adjust-color($base-color, $red: 37, $green: 29, $blue: 12); + $second-stop: adjust-color($base-color, $red: -56, $green: -50, $blue: -33); + $text-shadow: adjust-color($base-color, $red: -140, $green: -141, $blue: -114); + $third-stop: adjust-color($base-color, $red: -86, $green: -75, $blue: -48); + + @if lightness($base-color) > 70% { + $color: hsl(0, 0, 20%); + $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%); + } + + @if $grayscale == true { + $border: grayscale($border); + $border-bottom: grayscale($border-bottom); + $fourth-stop: grayscale($fourth-stop); + $inset-shadow: grayscale($inset-shadow); + $second-stop: grayscale($second-stop); + $text-shadow: grayscale($text-shadow); + $third-stop: grayscale($third-stop); + } + + border: 1px solid $border; + border-bottom: 1px solid $border-bottom; + border-radius: 5px; + box-shadow: inset 0 1px 0 0 $inset-shadow; + color: $color; + display: inline-block; + font-size: 14px; + font-weight: bold; + @include linear-gradient(top, $base-color 0%, $second-stop 50%, $third-stop 50%, $fourth-stop 100%); + padding: 8px 20px; + text-align: center; + text-decoration: none; + text-shadow: 0 -1px 1px $text-shadow; + + &:hover:not(:disabled) { + $first-stop-hover: adjust-color($base-color, $red: -13, $green: -15, $blue: -18); + $second-stop-hover: adjust-color($base-color, $red: -66, $green: -62, $blue: -51); + $third-stop-hover: adjust-color($base-color, $red: -93, $green: -85, $blue: -66); + $fourth-stop-hover: adjust-color($base-color, $red: -86, $green: -80, $blue: -63); + + @if $grayscale == true { + $first-stop-hover: grayscale($first-stop-hover); + $second-stop-hover: grayscale($second-stop-hover); + $third-stop-hover: grayscale($third-stop-hover); + $fourth-stop-hover: grayscale($fourth-stop-hover); + } + + cursor: pointer; + @include linear-gradient(top, $first-stop-hover 0%, + $second-stop-hover 50%, + $third-stop-hover 50%, + $fourth-stop-hover 100%); + } + + &:active:not(:disabled) { + $inset-shadow-active: adjust-color($base-color, $red: -111, $green: -116, $blue: -122); + + @if $grayscale == true { + $inset-shadow-active: grayscale($inset-shadow-active); + } + + box-shadow: inset 0 0 20px 0 $inset-shadow-active, 0 1px 0 #fff; + } +} + + +// Pill Button +//************************************************************************// +@mixin pill($base-color, $grayscale: false) { + $color: hsl(0, 0, 100%); + $border-bottom: adjust-color($base-color, $hue: 8, $saturation: -11%, $lightness: -26%); + $border-sides: adjust-color($base-color, $hue: 4, $saturation: -21%, $lightness: -21%); + $border-top: adjust-color($base-color, $hue: -1, $saturation: -30%, $lightness: -15%); + $inset-shadow: adjust-color($base-color, $hue: -1, $saturation: -1%, $lightness: 7%); + $stop-gradient: adjust-color($base-color, $hue: 8, $saturation: 14%, $lightness: -10%); + $text-shadow: adjust-color($base-color, $hue: 5, $saturation: -19%, $lightness: -15%); + + @if lightness($base-color) > 70% { + $color: hsl(0, 0, 20%); + $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%); + } + + @if $grayscale == true { + $border-bottom: grayscale($border-bottom); + $border-sides: grayscale($border-sides); + $border-top: grayscale($border-top); + $inset-shadow: grayscale($inset-shadow); + $stop-gradient: grayscale($stop-gradient); + $text-shadow: grayscale($text-shadow); + } + + border: 1px solid $border-top; + border-color: $border-top $border-sides $border-bottom; + border-radius: 16px; + box-shadow: inset 0 1px 0 0 $inset-shadow, 0 1px 2px 0 #b3b3b3; + color: $color; + display: inline-block; + font-size: 11px; + font-weight: normal; + line-height: 1; + @include linear-gradient ($base-color, $stop-gradient); + padding: 5px 16px; + text-align: center; + text-decoration: none; + text-shadow: 0 -1px 1px $text-shadow; + background-clip: padding-box; + + &:hover:not(:disabled) { + $base-color-hover: adjust-color($base-color, $lightness: -4.5%); + $border-bottom: adjust-color($base-color, $hue: 8, $saturation: 13.5%, $lightness: -32%); + $border-sides: adjust-color($base-color, $hue: 4, $saturation: -2%, $lightness: -27%); + $border-top: adjust-color($base-color, $hue: -1, $saturation: -17%, $lightness: -21%); + $inset-shadow-hover: adjust-color($base-color, $saturation: -1%, $lightness: 3%); + $stop-gradient-hover: adjust-color($base-color, $hue: 8, $saturation: -4%, $lightness: -15.5%); + $text-shadow-hover: adjust-color($base-color, $hue: 5, $saturation: -5%, $lightness: -22%); + + @if $grayscale == true { + $base-color-hover: grayscale($base-color-hover); + $border-bottom: grayscale($border-bottom); + $border-sides: grayscale($border-sides); + $border-top: grayscale($border-top); + $inset-shadow-hover: grayscale($inset-shadow-hover); + $stop-gradient-hover: grayscale($stop-gradient-hover); + $text-shadow-hover: grayscale($text-shadow-hover); + } + + border: 1px solid $border-top; + border-color: $border-top $border-sides $border-bottom; + box-shadow: inset 0 1px 0 0 $inset-shadow-hover; + cursor: pointer; + @include linear-gradient ($base-color-hover, $stop-gradient-hover); + text-shadow: 0 -1px 1px $text-shadow-hover; + background-clip: padding-box; + } + + &:active:not(:disabled) { + $active-color: adjust-color($base-color, $hue: 4, $saturation: -12%, $lightness: -10%); + $border-active: adjust-color($base-color, $hue: 6, $saturation: -2.5%, $lightness: -30%); + $border-bottom-active: adjust-color($base-color, $hue: 11, $saturation: 6%, $lightness: -31%); + $inset-shadow-active: adjust-color($base-color, $hue: 9, $saturation: 2%, $lightness: -21.5%); + $text-shadow-active: adjust-color($base-color, $hue: 5, $saturation: -12%, $lightness: -21.5%); + + @if $grayscale == true { + $active-color: grayscale($active-color); + $border-active: grayscale($border-active); + $border-bottom-active: grayscale($border-bottom-active); + $inset-shadow-active: grayscale($inset-shadow-active); + $text-shadow-active: grayscale($text-shadow-active); + } + + background: $active-color; + border: 1px solid $border-active; + border-bottom: 1px solid $border-bottom-active; + box-shadow: inset 0 0 6px 3px $inset-shadow-active, 0 1px 0 0 #fff; + text-shadow: 0 -1px 1px $text-shadow-active; + } +} diff --git a/docs/_static/css/bourbon/addons/_clearfix.scss b/docs/_static/css/bourbon/addons/_clearfix.scss new file mode 100644 index 0000000..ca9903c --- /dev/null +++ b/docs/_static/css/bourbon/addons/_clearfix.scss @@ -0,0 +1,29 @@ +// Micro clearfix provides an easy way to contain floats without adding additional markup +// +// Example usage: +// +// // Contain all floats within .wrapper +// .wrapper { +// @include clearfix; +// .content, +// .sidebar { +// float : left; +// } +// } + +@mixin clearfix { + *zoom: 1; + + &:before, + &:after { + content: " "; + display: table; + } + + &:after { + clear: both; + } +} + +// Acknowledgements +// Micro clearfix: [Nicolas Gallagher](http://nicolasgallagher.com/micro-clearfix-hack/) diff --git a/docs/_static/css/bourbon/addons/_font-family.scss b/docs/_static/css/bourbon/addons/_font-family.scss new file mode 100644 index 0000000..df8a80d --- /dev/null +++ b/docs/_static/css/bourbon/addons/_font-family.scss @@ -0,0 +1,5 @@ +$georgia: Georgia, Cambria, "Times New Roman", Times, serif; +$helvetica: "Helvetica Neue", Helvetica, Arial, sans-serif; +$lucida-grande: "Lucida Grande", Tahoma, Verdana, Arial, sans-serif; +$monospace: "Bitstream Vera Sans Mono", Consolas, Courier, monospace; +$verdana: Verdana, Geneva, sans-serif; diff --git a/docs/_static/css/bourbon/addons/_hide-text.scss b/docs/_static/css/bourbon/addons/_hide-text.scss new file mode 100644 index 0000000..68d4bf8 --- /dev/null +++ b/docs/_static/css/bourbon/addons/_hide-text.scss @@ -0,0 +1,5 @@ +@mixin hide-text { + color: transparent; + font: 0/0 a; + text-shadow: none; +} diff --git a/docs/_static/css/bourbon/addons/_html5-input-types.scss b/docs/_static/css/bourbon/addons/_html5-input-types.scss new file mode 100644 index 0000000..b184382 --- /dev/null +++ b/docs/_static/css/bourbon/addons/_html5-input-types.scss @@ -0,0 +1,56 @@ +//************************************************************************// +// Generate a variable ($all-text-inputs) with a list of all html5 +// input types that have a text-based input, excluding textarea. +// http://diveintohtml5.org/forms.html +//************************************************************************// +$inputs-list: 'input[type="email"]', + 'input[type="number"]', + 'input[type="password"]', + 'input[type="search"]', + 'input[type="tel"]', + 'input[type="text"]', + 'input[type="url"]', + + // Webkit & Gecko may change the display of these in the future + 'input[type="color"]', + 'input[type="date"]', + 'input[type="datetime"]', + 'input[type="datetime-local"]', + 'input[type="month"]', + 'input[type="time"]', + 'input[type="week"]'; + +$unquoted-inputs-list: (); +@each $input-type in $inputs-list { + $unquoted-inputs-list: append($unquoted-inputs-list, unquote($input-type), comma); +} + +$all-text-inputs: $unquoted-inputs-list; + + +// Hover Pseudo-class +//************************************************************************// +$all-text-inputs-hover: (); +@each $input-type in $unquoted-inputs-list { + $input-type-hover: $input-type + ":hover"; + $all-text-inputs-hover: append($all-text-inputs-hover, $input-type-hover, comma); +} + +// Focus Pseudo-class +//************************************************************************// +$all-text-inputs-focus: (); +@each $input-type in $unquoted-inputs-list { + $input-type-focus: $input-type + ":focus"; + $all-text-inputs-focus: append($all-text-inputs-focus, $input-type-focus, comma); +} + +// You must use interpolation on the variable: +// #{$all-text-inputs} +// #{$all-text-inputs-hover} +// #{$all-text-inputs-focus} + +// Example +//************************************************************************// +// #{$all-text-inputs}, textarea { +// border: 1px solid red; +// } diff --git a/docs/_static/css/bourbon/addons/_position.scss b/docs/_static/css/bourbon/addons/_position.scss new file mode 100644 index 0000000..faad1ca --- /dev/null +++ b/docs/_static/css/bourbon/addons/_position.scss @@ -0,0 +1,42 @@ +@mixin position ($position: relative, $coordinates: 0 0 0 0) { + + @if type-of($position) == list { + $coordinates: $position; + $position: relative; + } + + $top: nth($coordinates, 1); + $right: nth($coordinates, 2); + $bottom: nth($coordinates, 3); + $left: nth($coordinates, 4); + + position: $position; + + @if $top == auto { + top: $top; + } + @else if not(unitless($top)) { + top: $top; + } + + @if $right == auto { + right: $right; + } + @else if not(unitless($right)) { + right: $right; + } + + @if $bottom == auto { + bottom: $bottom; + } + @else if not(unitless($bottom)) { + bottom: $bottom; + } + + @if $left == auto { + left: $left; + } + @else if not(unitless($left)) { + left: $left; + } +} diff --git a/docs/_static/css/bourbon/addons/_prefixer.scss b/docs/_static/css/bourbon/addons/_prefixer.scss new file mode 100644 index 0000000..6bfd23a --- /dev/null +++ b/docs/_static/css/bourbon/addons/_prefixer.scss @@ -0,0 +1,49 @@ +//************************************************************************// +// Example: @include prefixer(border-radius, $radii, webkit ms spec); +//************************************************************************// +$prefix-for-webkit: true !default; +$prefix-for-mozilla: true !default; +$prefix-for-microsoft: true !default; +$prefix-for-opera: true !default; +$prefix-for-spec: true !default; // required for keyframe mixin + +@mixin prefixer ($property, $value, $prefixes) { + @each $prefix in $prefixes { + @if $prefix == webkit { + @if $prefix-for-webkit { + -webkit-#{$property}: $value; + } + } + @else if $prefix == moz { + @if $prefix-for-mozilla { + -moz-#{$property}: $value; + } + } + @else if $prefix == ms { + @if $prefix-for-microsoft { + -ms-#{$property}: $value; + } + } + @else if $prefix == o { + @if $prefix-for-opera { + -o-#{$property}: $value; + } + } + @else if $prefix == spec { + @if $prefix-for-spec { + #{$property}: $value; + } + } + @else { + @warn "Unrecognized prefix: #{$prefix}"; + } + } +} + +@mixin disable-prefix-for-all() { + $prefix-for-webkit: false; + $prefix-for-mozilla: false; + $prefix-for-microsoft: false; + $prefix-for-opera: false; + $prefix-for-spec: false; +} diff --git a/docs/_static/css/bourbon/addons/_retina-image.scss b/docs/_static/css/bourbon/addons/_retina-image.scss new file mode 100644 index 0000000..a84b6fa --- /dev/null +++ b/docs/_static/css/bourbon/addons/_retina-image.scss @@ -0,0 +1,32 @@ +@mixin retina-image($filename, $background-size, $extension: png, $retina-filename: null, $asset-pipeline: false) { + @if $asset-pipeline { + background-image: image-url("#{$filename}.#{$extension}"); + } + @else { + background-image: url("#{$filename}.#{$extension}"); + } + + @include hidpi { + + @if $asset-pipeline { + @if $retina-filename { + background-image: image-url("#{$retina-filename}.#{$extension}"); + } + @else { + background-image: image-url("#{$filename}@2x.#{$extension}"); + } + } + + @else { + @if $retina-filename { + background-image: url("#{$retina-filename}.#{$extension}"); + } + @else { + background-image: url("#{$filename}@2x.#{$extension}"); + } + } + + background-size: $background-size; + + } +} diff --git a/docs/_static/css/bourbon/addons/_size.scss b/docs/_static/css/bourbon/addons/_size.scss new file mode 100644 index 0000000..342e41b --- /dev/null +++ b/docs/_static/css/bourbon/addons/_size.scss @@ -0,0 +1,44 @@ +@mixin size($size) { + @if length($size) == 1 { + @if $size == auto { + width: $size; + height: $size; + } + + @else if unitless($size) { + width: $size + px; + height: $size + px; + } + + @else if not(unitless($size)) { + width: $size; + height: $size; + } + } + + // Width x Height + @if length($size) == 2 { + $width: nth($size, 1); + $height: nth($size, 2); + + @if $width == auto { + width: $width; + } + @else if not(unitless($width)) { + width: $width; + } + @else if unitless($width) { + width: $width + px; + } + + @if $height == auto { + height: $height; + } + @else if not(unitless($height)) { + height: $height; + } + @else if unitless($height) { + height: $height + px; + } + } +} diff --git a/docs/_static/css/bourbon/addons/_timing-functions.scss b/docs/_static/css/bourbon/addons/_timing-functions.scss new file mode 100644 index 0000000..51b2410 --- /dev/null +++ b/docs/_static/css/bourbon/addons/_timing-functions.scss @@ -0,0 +1,32 @@ +// CSS cubic-bezier timing functions. Timing functions courtesy of jquery.easie (github.com/jaukia/easie) +// Timing functions are the same as demo'ed here: http://jqueryui.com/demos/effect/easing.html + +// EASE IN +$ease-in-quad: cubic-bezier(0.550, 0.085, 0.680, 0.530); +$ease-in-cubic: cubic-bezier(0.550, 0.055, 0.675, 0.190); +$ease-in-quart: cubic-bezier(0.895, 0.030, 0.685, 0.220); +$ease-in-quint: cubic-bezier(0.755, 0.050, 0.855, 0.060); +$ease-in-sine: cubic-bezier(0.470, 0.000, 0.745, 0.715); +$ease-in-expo: cubic-bezier(0.950, 0.050, 0.795, 0.035); +$ease-in-circ: cubic-bezier(0.600, 0.040, 0.980, 0.335); +$ease-in-back: cubic-bezier(0.600, -0.280, 0.735, 0.045); + +// EASE OUT +$ease-out-quad: cubic-bezier(0.250, 0.460, 0.450, 0.940); +$ease-out-cubic: cubic-bezier(0.215, 0.610, 0.355, 1.000); +$ease-out-quart: cubic-bezier(0.165, 0.840, 0.440, 1.000); +$ease-out-quint: cubic-bezier(0.230, 1.000, 0.320, 1.000); +$ease-out-sine: cubic-bezier(0.390, 0.575, 0.565, 1.000); +$ease-out-expo: cubic-bezier(0.190, 1.000, 0.220, 1.000); +$ease-out-circ: cubic-bezier(0.075, 0.820, 0.165, 1.000); +$ease-out-back: cubic-bezier(0.175, 0.885, 0.320, 1.275); + +// EASE IN OUT +$ease-in-out-quad: cubic-bezier(0.455, 0.030, 0.515, 0.955); +$ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1.000); +$ease-in-out-quart: cubic-bezier(0.770, 0.000, 0.175, 1.000); +$ease-in-out-quint: cubic-bezier(0.860, 0.000, 0.070, 1.000); +$ease-in-out-sine: cubic-bezier(0.445, 0.050, 0.550, 0.950); +$ease-in-out-expo: cubic-bezier(1.000, 0.000, 0.000, 1.000); +$ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.150, 0.860); +$ease-in-out-back: cubic-bezier(0.680, -0.550, 0.265, 1.550); diff --git a/docs/_static/css/bourbon/addons/_triangle.scss b/docs/_static/css/bourbon/addons/_triangle.scss new file mode 100644 index 0000000..0e02aca --- /dev/null +++ b/docs/_static/css/bourbon/addons/_triangle.scss @@ -0,0 +1,45 @@ +@mixin triangle ($size, $color, $direction) { + height: 0; + width: 0; + + @if ($direction == up) or ($direction == down) or ($direction == right) or ($direction == left) { + border-color: transparent; + border-style: solid; + border-width: $size / 2; + + @if $direction == up { + border-bottom-color: $color; + + } @else if $direction == right { + border-left-color: $color; + + } @else if $direction == down { + border-top-color: $color; + + } @else if $direction == left { + border-right-color: $color; + } + } + + @else if ($direction == up-right) or ($direction == up-left) { + border-top: $size solid $color; + + @if $direction == up-right { + border-left: $size solid transparent; + + } @else if $direction == up-left { + border-right: $size solid transparent; + } + } + + @else if ($direction == down-right) or ($direction == down-left) { + border-bottom: $size solid $color; + + @if $direction == down-right { + border-left: $size solid transparent; + + } @else if $direction == down-left { + border-right: $size solid transparent; + } + } +} diff --git a/docs/_static/css/bourbon/css3/_animation.scss b/docs/_static/css/bourbon/css3/_animation.scss new file mode 100644 index 0000000..08c3dbf --- /dev/null +++ b/docs/_static/css/bourbon/css3/_animation.scss @@ -0,0 +1,52 @@ +// http://www.w3.org/TR/css3-animations/#the-animation-name-property- +// Each of these mixins support comma separated lists of values, which allows different transitions for individual properties to be described in a single style rule. Each value in the list corresponds to the value at that same position in the other properties. + +// Official animation shorthand property. +@mixin animation ($animations...) { + @include prefixer(animation, $animations, webkit moz spec); +} + +// Individual Animation Properties +@mixin animation-name ($names...) { + @include prefixer(animation-name, $names, webkit moz spec); +} + + +@mixin animation-duration ($times...) { + @include prefixer(animation-duration, $times, webkit moz spec); +} + + +@mixin animation-timing-function ($motions...) { +// ease | linear | ease-in | ease-out | ease-in-out + @include prefixer(animation-timing-function, $motions, webkit moz spec); +} + + +@mixin animation-iteration-count ($values...) { +// infinite | + @include prefixer(animation-iteration-count, $values, webkit moz spec); +} + + +@mixin animation-direction ($directions...) { +// normal | alternate + @include prefixer(animation-direction, $directions, webkit moz spec); +} + + +@mixin animation-play-state ($states...) { +// running | paused + @include prefixer(animation-play-state, $states, webkit moz spec); +} + + +@mixin animation-delay ($times...) { + @include prefixer(animation-delay, $times, webkit moz spec); +} + + +@mixin animation-fill-mode ($modes...) { +// none | forwards | backwards | both + @include prefixer(animation-fill-mode, $modes, webkit moz spec); +} diff --git a/docs/_static/css/bourbon/css3/_appearance.scss b/docs/_static/css/bourbon/css3/_appearance.scss new file mode 100644 index 0000000..3eb16e4 --- /dev/null +++ b/docs/_static/css/bourbon/css3/_appearance.scss @@ -0,0 +1,3 @@ +@mixin appearance ($value) { + @include prefixer(appearance, $value, webkit moz ms o spec); +} diff --git a/docs/_static/css/bourbon/css3/_backface-visibility.scss b/docs/_static/css/bourbon/css3/_backface-visibility.scss new file mode 100644 index 0000000..1161fe6 --- /dev/null +++ b/docs/_static/css/bourbon/css3/_backface-visibility.scss @@ -0,0 +1,6 @@ +//************************************************************************// +// Backface-visibility mixin +//************************************************************************// +@mixin backface-visibility($visibility) { + @include prefixer(backface-visibility, $visibility, webkit spec); +} diff --git a/docs/_static/css/bourbon/css3/_background-image.scss b/docs/_static/css/bourbon/css3/_background-image.scss new file mode 100644 index 0000000..17016b9 --- /dev/null +++ b/docs/_static/css/bourbon/css3/_background-image.scss @@ -0,0 +1,48 @@ +//************************************************************************// +// Background-image property for adding multiple background images with +// gradients, or for stringing multiple gradients together. +//************************************************************************// + +@mixin background-image($images...) { + background-image: _add-prefix($images, webkit); + background-image: _add-prefix($images); +} + +@function _add-prefix($images, $vendor: false) { + $images-prefixed: (); + $gradient-positions: false; + @for $i from 1 through length($images) { + $type: type-of(nth($images, $i)); // Get type of variable - List or String + + // If variable is a list - Gradient + @if $type == list { + $gradient-type: nth(nth($images, $i), 1); // linear or radial + $gradient-pos: null; + $gradient-args: null; + + @if ($gradient-type == linear) or ($gradient-type == radial) { + $gradient-pos: nth(nth($images, $i), 2); // Get gradient position + $gradient-args: nth(nth($images, $i), 3); // Get actual gradient (red, blue) + } + @else { + $gradient-args: nth(nth($images, $i), 2); // Get actual gradient (red, blue) + } + + $gradient-positions: _gradient-positions-parser($gradient-type, $gradient-pos); + $gradient: _render-gradients($gradient-positions, $gradient-args, $gradient-type, $vendor); + $images-prefixed: append($images-prefixed, $gradient, comma); + } + // If variable is a string - Image + @else if $type == string { + $images-prefixed: join($images-prefixed, nth($images, $i), comma); + } + } + @return $images-prefixed; +} + +//Examples: + //@include background-image(linear-gradient(top, orange, red)); + //@include background-image(radial-gradient(50% 50%, cover circle, orange, red)); + //@include background-image(url("/images/a.png"), linear-gradient(orange, red)); + //@include background-image(url("image.png"), linear-gradient(orange, red), url("image.png")); + //@include background-image(linear-gradient(hsla(0, 100%, 100%, 0.25) 0%, hsla(0, 100%, 100%, 0.08) 50%, transparent 50%), linear-gradient(orange, red)); diff --git a/docs/_static/css/bourbon/css3/_background.scss b/docs/_static/css/bourbon/css3/_background.scss new file mode 100644 index 0000000..766d5d3 --- /dev/null +++ b/docs/_static/css/bourbon/css3/_background.scss @@ -0,0 +1,103 @@ +//************************************************************************// +// Background property for adding multiple backgrounds using shorthand +// notation. +//************************************************************************// + +@mixin background( + $background-1 , $background-2: false, + $background-3: false, $background-4: false, + $background-5: false, $background-6: false, + $background-7: false, $background-8: false, + $background-9: false, $background-10: false, + $fallback: false +) { + $backgrounds: compact($background-1, $background-2, + $background-3, $background-4, + $background-5, $background-6, + $background-7, $background-8, + $background-9, $background-10); + + $fallback-color: false; + @if (type-of($fallback) == color) or ($fallback == "transparent") { + $fallback-color: $fallback; + } + @else { + $fallback-color: _extract-background-color($backgrounds); + } + + @if $fallback-color { + background-color: $fallback-color; + } + background: _background-add-prefix($backgrounds, webkit); + background: _background-add-prefix($backgrounds); +} + +@function _extract-background-color($backgrounds) { + $final-bg-layer: nth($backgrounds, length($backgrounds)); + @if type-of($final-bg-layer) == list { + @for $i from 1 through length($final-bg-layer) { + $value: nth($final-bg-layer, $i); + @if type-of($value) == color { + @return $value; + } + } + } + @return false; +} + +@function _background-add-prefix($backgrounds, $vendor: false) { + $backgrounds-prefixed: (); + + @for $i from 1 through length($backgrounds) { + $shorthand: nth($backgrounds, $i); // Get member for current index + $type: type-of($shorthand); // Get type of variable - List (gradient) or String (image) + + // If shorthand is a list (gradient) + @if $type == list { + $first-member: nth($shorthand, 1); // Get first member of shorthand + + // Linear Gradient + @if index(linear radial, nth($first-member, 1)) { + $gradient-type: nth($first-member, 1); // linear || radial + $gradient-args: false; + $gradient-positions: false; + $shorthand-start: false; + @if type-of($first-member) == list { // Linear gradient plus additional shorthand values - lg(red,orange)repeat,... + $gradient-positions: nth($first-member, 2); + $gradient-args: nth($first-member, 3); + $shorthand-start: 2; + } + @else { // Linear gradient only - lg(red,orange),... + $gradient-positions: nth($shorthand, 2); + $gradient-args: nth($shorthand, 3); // Get gradient (red, blue) + } + + $gradient-positions: _gradient-positions-parser($gradient-type, $gradient-positions); + $gradient: _render-gradients($gradient-positions, $gradient-args, $gradient-type, $vendor); + + // Append any additional shorthand args to gradient + @if $shorthand-start { + @for $j from $shorthand-start through length($shorthand) { + $gradient: join($gradient, nth($shorthand, $j), space); + } + } + $backgrounds-prefixed: append($backgrounds-prefixed, $gradient, comma); + } + // Image with additional properties + @else { + $backgrounds-prefixed: append($backgrounds-prefixed, $shorthand, comma); + } + } + // If shorthand is a simple string (color or image) + @else if $type == string { + $backgrounds-prefixed: join($backgrounds-prefixed, $shorthand, comma); + } + } + @return $backgrounds-prefixed; +} + +//Examples: + //@include background(linear-gradient(top, orange, red)); + //@include background(radial-gradient(circle at 40% 40%, orange, red)); + //@include background(url("/images/a.png") no-repeat, linear-gradient(orange, red)); + //@include background(url("image.png") center center, linear-gradient(orange, red), url("image.png")); diff --git a/docs/_static/css/bourbon/css3/_border-image.scss b/docs/_static/css/bourbon/css3/_border-image.scss new file mode 100644 index 0000000..1fff212 --- /dev/null +++ b/docs/_static/css/bourbon/css3/_border-image.scss @@ -0,0 +1,55 @@ +@mixin border-image($images) { + -webkit-border-image: _border-add-prefix($images, webkit); + -moz-border-image: _border-add-prefix($images, moz); + -o-border-image: _border-add-prefix($images, o); + border-image: _border-add-prefix($images); +} + +@function _border-add-prefix($images, $vendor: false) { + $border-image: null; + $images-type: type-of(nth($images, 1)); + $first-var: nth(nth($images, 1), 1); // Get type of Gradient (Linear || radial) + + // If input is a gradient + @if $images-type == string { + @if ($first-var == "linear") or ($first-var == "radial") { + $gradient-type: nth($images, 1); // Get type of gradient (linear || radial) + $gradient-pos: nth($images, 2); // Get gradient position + $gradient-args: nth($images, 3); // Get actual gradient (red, blue) + $gradient-positions: _gradient-positions-parser($gradient-type, $gradient-pos); + $border-image: _render-gradients($gradient-positions, $gradient-args, $gradient-type, $vendor); + } + // If input is a URL + @else { + $border-image: $images; + } + } + // If input is gradient or url + additional args + @else if $images-type == list { + $type: type-of(nth($images, 1)); // Get type of variable - List or String + + // If variable is a list - Gradient + @if $type == list { + $gradient: nth($images, 1); + $gradient-type: nth($gradient, 1); // Get type of gradient (linear || radial) + $gradient-pos: nth($gradient, 2); // Get gradient position + $gradient-args: nth($gradient, 3); // Get actual gradient (red, blue) + $gradient-positions: _gradient-positions-parser($gradient-type, $gradient-pos); + $border-image: _render-gradients($gradient-positions, $gradient-args, $gradient-type, $vendor); + + @for $i from 2 through length($images) { + $border-image: append($border-image, nth($images, $i)); + } + } + } + @return $border-image; +} + +//Examples: +// @include border-image(url("image.png")); +// @include border-image(url("image.png") 20 stretch); +// @include border-image(linear-gradient(45deg, orange, yellow)); +// @include border-image(linear-gradient(45deg, orange, yellow) stretch); +// @include border-image(linear-gradient(45deg, orange, yellow) 20 30 40 50 stretch round); +// @include border-image(radial-gradient(top, cover, orange, yellow, orange)); + diff --git a/docs/_static/css/bourbon/css3/_border-radius.scss b/docs/_static/css/bourbon/css3/_border-radius.scss new file mode 100644 index 0000000..7c17190 --- /dev/null +++ b/docs/_static/css/bourbon/css3/_border-radius.scss @@ -0,0 +1,22 @@ +//************************************************************************// +// Shorthand Border-radius mixins +//************************************************************************// +@mixin border-top-radius($radii) { + @include prefixer(border-top-left-radius, $radii, spec); + @include prefixer(border-top-right-radius, $radii, spec); +} + +@mixin border-bottom-radius($radii) { + @include prefixer(border-bottom-left-radius, $radii, spec); + @include prefixer(border-bottom-right-radius, $radii, spec); +} + +@mixin border-left-radius($radii) { + @include prefixer(border-top-left-radius, $radii, spec); + @include prefixer(border-bottom-left-radius, $radii, spec); +} + +@mixin border-right-radius($radii) { + @include prefixer(border-top-right-radius, $radii, spec); + @include prefixer(border-bottom-right-radius, $radii, spec); +} diff --git a/docs/_static/css/bourbon/css3/_box-sizing.scss b/docs/_static/css/bourbon/css3/_box-sizing.scss new file mode 100644 index 0000000..f07e1d4 --- /dev/null +++ b/docs/_static/css/bourbon/css3/_box-sizing.scss @@ -0,0 +1,4 @@ +@mixin box-sizing ($box) { +// content-box | border-box | inherit + @include prefixer(box-sizing, $box, webkit moz spec); +} diff --git a/docs/_static/css/bourbon/css3/_columns.scss b/docs/_static/css/bourbon/css3/_columns.scss new file mode 100644 index 0000000..42274a4 --- /dev/null +++ b/docs/_static/css/bourbon/css3/_columns.scss @@ -0,0 +1,47 @@ +@mixin columns($arg: auto) { +// || + @include prefixer(columns, $arg, webkit moz spec); +} + +@mixin column-count($int: auto) { +// auto || integer + @include prefixer(column-count, $int, webkit moz spec); +} + +@mixin column-gap($length: normal) { +// normal || length + @include prefixer(column-gap, $length, webkit moz spec); +} + +@mixin column-fill($arg: auto) { +// auto || length + @include prefixer(columns-fill, $arg, webkit moz spec); +} + +@mixin column-rule($arg) { +// || || + @include prefixer(column-rule, $arg, webkit moz spec); +} + +@mixin column-rule-color($color) { + @include prefixer(column-rule-color, $color, webkit moz spec); +} + +@mixin column-rule-style($style: none) { +// none | hidden | dashed | dotted | double | groove | inset | inset | outset | ridge | solid + @include prefixer(column-rule-style, $style, webkit moz spec); +} + +@mixin column-rule-width ($width: none) { + @include prefixer(column-rule-width, $width, webkit moz spec); +} + +@mixin column-span($arg: none) { +// none || all + @include prefixer(column-span, $arg, webkit moz spec); +} + +@mixin column-width($length: auto) { +// auto || length + @include prefixer(column-width, $length, webkit moz spec); +} diff --git a/docs/_static/css/bourbon/css3/_flex-box.scss b/docs/_static/css/bourbon/css3/_flex-box.scss new file mode 100644 index 0000000..3e741e6 --- /dev/null +++ b/docs/_static/css/bourbon/css3/_flex-box.scss @@ -0,0 +1,52 @@ +// CSS3 Flexible Box Model and property defaults + +// Custom shorthand notation for flexbox +@mixin box($orient: inline-axis, $pack: start, $align: stretch) { + @include display-box; + @include box-orient($orient); + @include box-pack($pack); + @include box-align($align); +} + +@mixin display-box { + display: -webkit-box; + display: -moz-box; + display: box; +} + +@mixin box-orient($orient: inline-axis) { +// horizontal|vertical|inline-axis|block-axis|inherit + @include prefixer(box-orient, $orient, webkit moz spec); +} + +@mixin box-pack($pack: start) { +// start|end|center|justify + @include prefixer(box-pack, $pack, webkit moz spec); +} + +@mixin box-align($align: stretch) { +// start|end|center|baseline|stretch + @include prefixer(box-align, $align, webkit moz spec); +} + +@mixin box-direction($direction: normal) { +// normal|reverse|inherit + @include prefixer(box-direction, $direction, webkit moz spec); +} + +@mixin box-lines($lines: single) { +// single|multiple + @include prefixer(box-lines, $lines, webkit moz spec); +} + +@mixin box-ordinal-group($int: 1) { + @include prefixer(box-ordinal-group, $int, webkit moz spec); +} + +@mixin box-flex($value: 0.0) { + @include prefixer(box-flex, $value, webkit moz spec); +} + +@mixin box-flex-group($int: 1) { + @include prefixer(box-flex-group, $int, webkit moz spec); +} diff --git a/docs/_static/css/bourbon/css3/_font-face.scss b/docs/_static/css/bourbon/css3/_font-face.scss new file mode 100644 index 0000000..029ee8f --- /dev/null +++ b/docs/_static/css/bourbon/css3/_font-face.scss @@ -0,0 +1,23 @@ +// Order of the includes matters, and it is: normal, bold, italic, bold+italic. + +@mixin font-face($font-family, $file-path, $weight: normal, $style: normal, $asset-pipeline: false ) { + @font-face { + font-family: $font-family; + font-weight: $weight; + font-style: $style; + + @if $asset-pipeline == true { + src: font-url('#{$file-path}.eot'); + src: font-url('#{$file-path}.eot?#iefix') format('embedded-opentype'), + font-url('#{$file-path}.woff') format('woff'), + font-url('#{$file-path}.ttf') format('truetype'), + font-url('#{$file-path}.svg##{$font-family}') format('svg'); + } @else { + src: url('#{$file-path}.eot'); + src: url('#{$file-path}.eot?#iefix') format('embedded-opentype'), + url('#{$file-path}.woff') format('woff'), + url('#{$file-path}.ttf') format('truetype'), + url('#{$file-path}.svg##{$font-family}') format('svg'); + } + } +} diff --git a/docs/_static/css/bourbon/css3/_hidpi-media-query.scss b/docs/_static/css/bourbon/css3/_hidpi-media-query.scss new file mode 100644 index 0000000..111e400 --- /dev/null +++ b/docs/_static/css/bourbon/css3/_hidpi-media-query.scss @@ -0,0 +1,10 @@ +// HiDPI mixin. Default value set to 1.3 to target Google Nexus 7 (http://bjango.com/articles/min-device-pixel-ratio/) +@mixin hidpi($ratio: 1.3) { + @media only screen and (-webkit-min-device-pixel-ratio: $ratio), + only screen and (min--moz-device-pixel-ratio: $ratio), + only screen and (-o-min-device-pixel-ratio: #{$ratio}/1), + only screen and (min-resolution: #{round($ratio*96)}dpi), + only screen and (min-resolution: #{$ratio}dppx) { + @content; + } +} diff --git a/docs/_static/css/bourbon/css3/_image-rendering.scss b/docs/_static/css/bourbon/css3/_image-rendering.scss new file mode 100644 index 0000000..abc7ee1 --- /dev/null +++ b/docs/_static/css/bourbon/css3/_image-rendering.scss @@ -0,0 +1,13 @@ +@mixin image-rendering ($mode:optimizeQuality) { + + @if ($mode == optimize-contrast) { + image-rendering: -moz-crisp-edges; + image-rendering: -o-crisp-edges; + image-rendering: -webkit-optimize-contrast; + image-rendering: optimize-contrast; + } + + @else { + image-rendering: $mode; + } +} diff --git a/docs/_static/css/bourbon/css3/_inline-block.scss b/docs/_static/css/bourbon/css3/_inline-block.scss new file mode 100644 index 0000000..3272a00 --- /dev/null +++ b/docs/_static/css/bourbon/css3/_inline-block.scss @@ -0,0 +1,8 @@ +// Legacy support for inline-block in IE7 (maybe IE6) +@mixin inline-block { + display: inline-block; + vertical-align: baseline; + zoom: 1; + *display: inline; + *vertical-align: auto; +} diff --git a/docs/_static/css/bourbon/css3/_keyframes.scss b/docs/_static/css/bourbon/css3/_keyframes.scss new file mode 100644 index 0000000..dca61f2 --- /dev/null +++ b/docs/_static/css/bourbon/css3/_keyframes.scss @@ -0,0 +1,43 @@ +// Adds keyframes blocks for supported prefixes, removing redundant prefixes in the block's content +@mixin keyframes($name) { + $original-prefix-for-webkit: $prefix-for-webkit; + $original-prefix-for-mozilla: $prefix-for-mozilla; + $original-prefix-for-microsoft: $prefix-for-microsoft; + $original-prefix-for-opera: $prefix-for-opera; + $original-prefix-for-spec: $prefix-for-spec; + + @if $original-prefix-for-webkit { + @include disable-prefix-for-all(); + $prefix-for-webkit: true; + @-webkit-keyframes #{$name} { + @content; + } + } + @if $original-prefix-for-mozilla { + @include disable-prefix-for-all(); + $prefix-for-mozilla: true; + @-moz-keyframes #{$name} { + @content; + } + } + @if $original-prefix-for-opera { + @include disable-prefix-for-all(); + $prefix-for-opera: true; + @-o-keyframes #{$name} { + @content; + } + } + @if $original-prefix-for-spec { + @include disable-prefix-for-all(); + $prefix-for-spec: true; + @keyframes #{$name} { + @content; + } + } + + $prefix-for-webkit: $original-prefix-for-webkit; + $prefix-for-mozilla: $original-prefix-for-mozilla; + $prefix-for-microsoft: $original-prefix-for-microsoft; + $prefix-for-opera: $original-prefix-for-opera; + $prefix-for-spec: $original-prefix-for-spec; +} diff --git a/docs/_static/css/bourbon/css3/_linear-gradient.scss b/docs/_static/css/bourbon/css3/_linear-gradient.scss new file mode 100644 index 0000000..d5b687b --- /dev/null +++ b/docs/_static/css/bourbon/css3/_linear-gradient.scss @@ -0,0 +1,41 @@ +@mixin linear-gradient($pos, $G1, $G2: false, + $G3: false, $G4: false, + $G5: false, $G6: false, + $G7: false, $G8: false, + $G9: false, $G10: false, + $deprecated-pos1: left top, + $deprecated-pos2: left bottom, + $fallback: false) { + // Detect what type of value exists in $pos + $pos-type: type-of(nth($pos, 1)); + $pos-spec: null; + $pos-degree: null; + + // If $pos is missing from mixin, reassign vars and add default position + @if ($pos-type == color) or (nth($pos, 1) == "transparent") { + $G10: $G9; $G9: $G8; $G8: $G7; $G7: $G6; $G6: $G5; + $G5: $G4; $G4: $G3; $G3: $G2; $G2: $G1; $G1: $pos; + $pos: null; + } + + @if $pos { + $positions: _linear-positions-parser($pos); + $pos-degree: nth($positions, 1); + $pos-spec: nth($positions, 2); + } + + $full: compact($G1, $G2, $G3, $G4, $G5, $G6, $G7, $G8, $G9, $G10); + + // Set $G1 as the default fallback color + $fallback-color: nth($G1, 1); + + // If $fallback is a color use that color as the fallback color + @if (type-of($fallback) == color) or ($fallback == "transparent") { + $fallback-color: $fallback; + } + + background-color: $fallback-color; + background-image: _deprecated-webkit-gradient(linear, $deprecated-pos1, $deprecated-pos2, $full); // Safari <= 5.0 + background-image: -webkit-linear-gradient($pos-degree $full); // Safari 5.1+, Chrome + background-image: unquote("linear-gradient(#{$pos-spec}#{$full})"); +} diff --git a/docs/_static/css/bourbon/css3/_perspective.scss b/docs/_static/css/bourbon/css3/_perspective.scss new file mode 100644 index 0000000..0e4deb8 --- /dev/null +++ b/docs/_static/css/bourbon/css3/_perspective.scss @@ -0,0 +1,8 @@ +@mixin perspective($depth: none) { + // none | + @include prefixer(perspective, $depth, webkit moz spec); +} + +@mixin perspective-origin($value: 50% 50%) { + @include prefixer(perspective-origin, $value, webkit moz spec); +} diff --git a/docs/_static/css/bourbon/css3/_placeholder.scss b/docs/_static/css/bourbon/css3/_placeholder.scss new file mode 100644 index 0000000..22fd92b --- /dev/null +++ b/docs/_static/css/bourbon/css3/_placeholder.scss @@ -0,0 +1,29 @@ +$placeholders: '-webkit-input-placeholder', + '-moz-placeholder', + '-ms-input-placeholder'; + +@mixin placeholder { + @each $placeholder in $placeholders { + @if $placeholder == "-webkit-input-placeholder" { + &::#{$placeholder} { + @content; + } + } + @else if $placeholder == "-moz-placeholder" { + // FF 18- + &:#{$placeholder} { + @content; + } + + // FF 19+ + &::#{$placeholder} { + @content; + } + } + @else { + &:#{$placeholder} { + @content; + } + } + } +} diff --git a/docs/_static/css/bourbon/css3/_radial-gradient.scss b/docs/_static/css/bourbon/css3/_radial-gradient.scss new file mode 100644 index 0000000..e87b45a --- /dev/null +++ b/docs/_static/css/bourbon/css3/_radial-gradient.scss @@ -0,0 +1,44 @@ +// Requires Sass 3.1+ +@mixin radial-gradient($G1, $G2, + $G3: false, $G4: false, + $G5: false, $G6: false, + $G7: false, $G8: false, + $G9: false, $G10: false, + $pos: null, + $shape-size: null, + $deprecated-pos1: center center, + $deprecated-pos2: center center, + $deprecated-radius1: 0, + $deprecated-radius2: 460, + $fallback: false) { + + $data: _radial-arg-parser($G1, $G2, $pos, $shape-size); + $G1: nth($data, 1); + $G2: nth($data, 2); + $pos: nth($data, 3); + $shape-size: nth($data, 4); + + $full: compact($G1, $G2, $G3, $G4, $G5, $G6, $G7, $G8, $G9, $G10); + + // Strip deprecated cover/contain for spec + $shape-size-spec: _shape-size-stripper($shape-size); + + // Set $G1 as the default fallback color + $first-color: nth($full, 1); + $fallback-color: nth($first-color, 1); + + @if (type-of($fallback) == color) or ($fallback == "transparent") { + $fallback-color: $fallback; + } + + // Add Commas and spaces + $shape-size: if($shape-size, '#{$shape-size}, ', null); + $pos: if($pos, '#{$pos}, ', null); + $pos-spec: if($pos, 'at #{$pos}', null); + $shape-size-spec: if(($shape-size-spec != ' ') and ($pos == null), '#{$shape-size-spec}, ', '#{$shape-size-spec} '); + + background-color: $fallback-color; + background-image: _deprecated-webkit-gradient(radial, $deprecated-pos1, $deprecated-pos2, $full, $deprecated-radius1, $deprecated-radius2); // Safari <= 5.0 && IOS 4 + background-image: -webkit-radial-gradient(unquote(#{$pos}#{$shape-size}#{$full})); + background-image: unquote("radial-gradient(#{$shape-size-spec}#{$pos-spec}#{$full})"); +} diff --git a/docs/_static/css/bourbon/css3/_transform.scss b/docs/_static/css/bourbon/css3/_transform.scss new file mode 100644 index 0000000..8cc3596 --- /dev/null +++ b/docs/_static/css/bourbon/css3/_transform.scss @@ -0,0 +1,15 @@ +@mixin transform($property: none) { +// none | + @include prefixer(transform, $property, webkit moz ms o spec); +} + +@mixin transform-origin($axes: 50%) { +// x-axis - left | center | right | length | % +// y-axis - top | center | bottom | length | % +// z-axis - length + @include prefixer(transform-origin, $axes, webkit moz ms o spec); +} + +@mixin transform-style ($style: flat) { + @include prefixer(transform-style, $style, webkit moz ms o spec); +} diff --git a/docs/_static/css/bourbon/css3/_transition.scss b/docs/_static/css/bourbon/css3/_transition.scss new file mode 100644 index 0000000..180cde6 --- /dev/null +++ b/docs/_static/css/bourbon/css3/_transition.scss @@ -0,0 +1,34 @@ +// Shorthand mixin. Supports multiple parentheses-deliminated values for each variable. +// Example: @include transition (all, 2.0s, ease-in-out); +// @include transition ((opacity, width), (1.0s, 2.0s), ease-in, (0, 2s)); +// @include transition ($property:(opacity, width), $delay: (1.5s, 2.5s)); + +@mixin transition ($properties...) { + @if length($properties) >= 1 { + @include prefixer(transition, $properties, webkit moz spec); + } + + @else { + $properties: all 0.15s ease-out 0; + @include prefixer(transition, $properties, webkit moz spec); + } +} + +@mixin transition-property ($properties...) { + -webkit-transition-property: transition-property-names($properties, 'webkit'); + -moz-transition-property: transition-property-names($properties, 'moz'); + transition-property: transition-property-names($properties, false); +} + +@mixin transition-duration ($times...) { + @include prefixer(transition-duration, $times, webkit moz spec); +} + +@mixin transition-timing-function ($motions...) { +// ease | linear | ease-in | ease-out | ease-in-out | cubic-bezier() + @include prefixer(transition-timing-function, $motions, webkit moz spec); +} + +@mixin transition-delay ($times...) { + @include prefixer(transition-delay, $times, webkit moz spec); +} diff --git a/docs/_static/css/bourbon/css3/_user-select.scss b/docs/_static/css/bourbon/css3/_user-select.scss new file mode 100644 index 0000000..1380aa8 --- /dev/null +++ b/docs/_static/css/bourbon/css3/_user-select.scss @@ -0,0 +1,3 @@ +@mixin user-select($arg: none) { + @include prefixer(user-select, $arg, webkit moz ms spec); +} diff --git a/docs/_static/css/bourbon/functions/_compact.scss b/docs/_static/css/bourbon/functions/_compact.scss new file mode 100644 index 0000000..871500e --- /dev/null +++ b/docs/_static/css/bourbon/functions/_compact.scss @@ -0,0 +1,11 @@ +// Remove `false` values from a list + +@function compact($vars...) { + $list: (); + @each $var in $vars { + @if $var { + $list: append($list, $var, comma); + } + } + @return $list; +} diff --git a/docs/_static/css/bourbon/functions/_flex-grid.scss b/docs/_static/css/bourbon/functions/_flex-grid.scss new file mode 100644 index 0000000..3bbd866 --- /dev/null +++ b/docs/_static/css/bourbon/functions/_flex-grid.scss @@ -0,0 +1,39 @@ +// Flexible grid +@function flex-grid($columns, $container-columns: $fg-max-columns) { + $width: $columns * $fg-column + ($columns - 1) * $fg-gutter; + $container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter; + @return percentage($width / $container-width); +} + +// Flexible gutter +@function flex-gutter($container-columns: $fg-max-columns, $gutter: $fg-gutter) { + $container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter; + @return percentage($gutter / $container-width); +} + +// The $fg-column, $fg-gutter and $fg-max-columns variables must be defined in your base stylesheet to properly use the flex-grid function. +// This function takes the fluid grid equation (target / context = result) and uses columns to help define each. +// +// The calculation presumes that your column structure will be missing the last gutter: +// +// -- column -- gutter -- column -- gutter -- column +// +// $fg-column: 60px; // Column Width +// $fg-gutter: 25px; // Gutter Width +// $fg-max-columns: 12; // Total Columns For Main Container +// +// div { +// width: flex-grid(4); // returns (315px / 995px) = 31.65829%; +// margin-left: flex-gutter(); // returns (25px / 995px) = 2.51256%; +// +// p { +// width: flex-grid(2, 4); // returns (145px / 315px) = 46.031746%; +// float: left; +// margin: flex-gutter(4); // returns (25px / 315px) = 7.936508%; +// } +// +// blockquote { +// float: left; +// width: flex-grid(2, 4); // returns (145px / 315px) = 46.031746%; +// } +// } \ No newline at end of file diff --git a/docs/_static/css/bourbon/functions/_grid-width.scss b/docs/_static/css/bourbon/functions/_grid-width.scss new file mode 100644 index 0000000..8e63d83 --- /dev/null +++ b/docs/_static/css/bourbon/functions/_grid-width.scss @@ -0,0 +1,13 @@ +@function grid-width($n) { + @return $n * $gw-column + ($n - 1) * $gw-gutter; +} + +// The $gw-column and $gw-gutter variables must be defined in your base stylesheet to properly use the grid-width function. +// +// $gw-column: 100px; // Column Width +// $gw-gutter: 40px; // Gutter Width +// +// div { +// width: grid-width(4); // returns 520px; +// margin-left: $gw-gutter; // returns 40px; +// } diff --git a/docs/_static/css/bourbon/functions/_linear-gradient.scss b/docs/_static/css/bourbon/functions/_linear-gradient.scss new file mode 100644 index 0000000..c8454d8 --- /dev/null +++ b/docs/_static/css/bourbon/functions/_linear-gradient.scss @@ -0,0 +1,13 @@ +@function linear-gradient($pos, $gradients...) { + $type: linear; + $pos-type: type-of(nth($pos, 1)); + + // if $pos doesn't exist, fix $gradient + @if ($pos-type == color) or (nth($pos, 1) == "transparent") { + $gradients: zip($pos $gradients); + $pos: false; + } + + $type-gradient: $type, $pos, $gradients; + @return $type-gradient; +} diff --git a/docs/_static/css/bourbon/functions/_modular-scale.scss b/docs/_static/css/bourbon/functions/_modular-scale.scss new file mode 100644 index 0000000..dddccb5 --- /dev/null +++ b/docs/_static/css/bourbon/functions/_modular-scale.scss @@ -0,0 +1,40 @@ +@function modular-scale($value, $increment, $ratio) { + @if $increment > 0 { + @for $i from 1 through $increment { + $value: ($value * $ratio); + } + } + + @if $increment < 0 { + $increment: abs($increment); + @for $i from 1 through $increment { + $value: ($value / $ratio); + } + } + + @return $value; +} + +// div { +// Increment Up GR with positive value +// font-size: modular-scale(14px, 1, 1.618); // returns: 22.652px +// +// Increment Down GR with negative value +// font-size: modular-scale(14px, -1, 1.618); // returns: 8.653px +// +// Can be used with ceil(round up) or floor(round down) +// font-size: floor( modular-scale(14px, 1, 1.618) ); // returns: 22px +// font-size: ceil( modular-scale(14px, 1, 1.618) ); // returns: 23px +// } +// +// modularscale.com + +@function golden-ratio($value, $increment) { + @return modular-scale($value, $increment, 1.618) +} + +// div { +// font-size: golden-ratio(14px, 1); // returns: 22.652px +// } +// +// goldenratiocalculator.com diff --git a/docs/_static/css/bourbon/functions/_px-to-em.scss b/docs/_static/css/bourbon/functions/_px-to-em.scss new file mode 100644 index 0000000..2eb1031 --- /dev/null +++ b/docs/_static/css/bourbon/functions/_px-to-em.scss @@ -0,0 +1,8 @@ +// Convert pixels to ems +// eg. for a relational value of 12px write em(12) when the parent is 16px +// if the parent is another value say 24px write em(12, 24) + +@function em($pxval, $base: 16) { + @return ($pxval / $base) * 1em; +} + diff --git a/docs/_static/css/bourbon/functions/_radial-gradient.scss b/docs/_static/css/bourbon/functions/_radial-gradient.scss new file mode 100644 index 0000000..7558406 --- /dev/null +++ b/docs/_static/css/bourbon/functions/_radial-gradient.scss @@ -0,0 +1,23 @@ +// This function is required and used by the background-image mixin. +@function radial-gradient($G1, $G2, + $G3: false, $G4: false, + $G5: false, $G6: false, + $G7: false, $G8: false, + $G9: false, $G10: false, + $pos: null, + $shape-size: null) { + + $data: _radial-arg-parser($G1, $G2, $pos, $shape-size); + $G1: nth($data, 1); + $G2: nth($data, 2); + $pos: nth($data, 3); + $shape-size: nth($data, 4); + + $type: radial; + $gradient: compact($G1, $G2, $G3, $G4, $G5, $G6, $G7, $G8, $G9, $G10); + + $type-gradient: $type, $shape-size $pos, $gradient; + @return $type-gradient; +} + + diff --git a/docs/_static/css/bourbon/functions/_tint-shade.scss b/docs/_static/css/bourbon/functions/_tint-shade.scss new file mode 100644 index 0000000..f717200 --- /dev/null +++ b/docs/_static/css/bourbon/functions/_tint-shade.scss @@ -0,0 +1,9 @@ +// Add percentage of white to a color +@function tint($color, $percent){ + @return mix(white, $color, $percent); +} + +// Add percentage of black to a color +@function shade($color, $percent){ + @return mix(black, $color, $percent); +} diff --git a/docs/_static/css/bourbon/functions/_transition-property-name.scss b/docs/_static/css/bourbon/functions/_transition-property-name.scss new file mode 100644 index 0000000..54cd422 --- /dev/null +++ b/docs/_static/css/bourbon/functions/_transition-property-name.scss @@ -0,0 +1,22 @@ +// Return vendor-prefixed property names if appropriate +// Example: transition-property-names((transform, color, background), moz) -> -moz-transform, color, background +//************************************************************************// +@function transition-property-names($props, $vendor: false) { + $new-props: (); + + @each $prop in $props { + $new-props: append($new-props, transition-property-name($prop, $vendor), comma); + } + + @return $new-props; +} + +@function transition-property-name($prop, $vendor: false) { + // put other properties that need to be prefixed here aswell + @if $vendor and $prop == transform { + @return unquote('-'+$vendor+'-'+$prop); + } + @else { + @return $prop; + } +} \ No newline at end of file diff --git a/docs/_static/css/bourbon/helpers/_deprecated-webkit-gradient.scss b/docs/_static/css/bourbon/helpers/_deprecated-webkit-gradient.scss new file mode 100644 index 0000000..cd17e28 --- /dev/null +++ b/docs/_static/css/bourbon/helpers/_deprecated-webkit-gradient.scss @@ -0,0 +1,39 @@ +// Render Deprecated Webkit Gradient - Linear || Radial +//************************************************************************// +@function _deprecated-webkit-gradient($type, + $deprecated-pos1, $deprecated-pos2, + $full, + $deprecated-radius1: false, $deprecated-radius2: false) { + $gradient-list: (); + $gradient: false; + $full-length: length($full); + $percentage: false; + $gradient-type: $type; + + @for $i from 1 through $full-length { + $gradient: nth($full, $i); + + @if length($gradient) == 2 { + $color-stop: color-stop(nth($gradient, 2), nth($gradient, 1)); + $gradient-list: join($gradient-list, $color-stop, comma); + } + @else if $gradient != null { + @if $i == $full-length { + $percentage: 100%; + } + @else { + $percentage: ($i - 1) * (100 / ($full-length - 1)) + "%"; + } + $color-stop: color-stop(unquote($percentage), $gradient); + $gradient-list: join($gradient-list, $color-stop, comma); + } + } + + @if $type == radial { + $gradient: -webkit-gradient(radial, $deprecated-pos1, $deprecated-radius1, $deprecated-pos2, $deprecated-radius2, $gradient-list); + } + @else if $type == linear { + $gradient: -webkit-gradient(linear, $deprecated-pos1, $deprecated-pos2, $gradient-list); + } + @return $gradient; +} diff --git a/docs/_static/css/bourbon/helpers/_gradient-positions-parser.scss b/docs/_static/css/bourbon/helpers/_gradient-positions-parser.scss new file mode 100644 index 0000000..07d30b6 --- /dev/null +++ b/docs/_static/css/bourbon/helpers/_gradient-positions-parser.scss @@ -0,0 +1,13 @@ +@function _gradient-positions-parser($gradient-type, $gradient-positions) { + @if $gradient-positions + and ($gradient-type == linear) + and (type-of($gradient-positions) != color) { + $gradient-positions: _linear-positions-parser($gradient-positions); + } + @else if $gradient-positions + and ($gradient-type == radial) + and (type-of($gradient-positions) != color) { + $gradient-positions: _radial-positions-parser($gradient-positions); + } + @return $gradient-positions; +} diff --git a/docs/_static/css/bourbon/helpers/_linear-positions-parser.scss b/docs/_static/css/bourbon/helpers/_linear-positions-parser.scss new file mode 100644 index 0000000..d26383e --- /dev/null +++ b/docs/_static/css/bourbon/helpers/_linear-positions-parser.scss @@ -0,0 +1,61 @@ +@function _linear-positions-parser($pos) { + $type: type-of(nth($pos, 1)); + $spec: null; + $degree: null; + $side: null; + $corner: null; + $length: length($pos); + // Parse Side and corner positions + @if ($length > 1) { + @if nth($pos, 1) == "to" { // Newer syntax + $side: nth($pos, 2); + + @if $length == 2 { // eg. to top + // Swap for backwards compatability + $degree: _position-flipper(nth($pos, 2)); + } + @else if $length == 3 { // eg. to top left + $corner: nth($pos, 3); + } + } + @else if $length == 2 { // Older syntax ("top left") + $side: _position-flipper(nth($pos, 1)); + $corner: _position-flipper(nth($pos, 2)); + } + + @if ("#{$side} #{$corner}" == "left top") or ("#{$side} #{$corner}" == "top left") { + $degree: _position-flipper(#{$side}) _position-flipper(#{$corner}); + } + @else if ("#{$side} #{$corner}" == "right top") or ("#{$side} #{$corner}" == "top right") { + $degree: _position-flipper(#{$side}) _position-flipper(#{$corner}); + } + @else if ("#{$side} #{$corner}" == "right bottom") or ("#{$side} #{$corner}" == "bottom right") { + $degree: _position-flipper(#{$side}) _position-flipper(#{$corner}); + } + @else if ("#{$side} #{$corner}" == "left bottom") or ("#{$side} #{$corner}" == "bottom left") { + $degree: _position-flipper(#{$side}) _position-flipper(#{$corner}); + } + $spec: to $side $corner; + } + @else if $length == 1 { + // Swap for backwards compatability + @if $type == string { + $degree: $pos; + $spec: to _position-flipper($pos); + } + @else { + $degree: -270 - $pos; //rotate the gradient opposite from spec + $spec: $pos; + } + } + $degree: unquote($degree + ","); + $spec: unquote($spec + ","); + @return $degree $spec; +} + +@function _position-flipper($pos) { + @return if($pos == left, right, null) + if($pos == right, left, null) + if($pos == top, bottom, null) + if($pos == bottom, top, null); +} diff --git a/docs/_static/css/bourbon/helpers/_radial-arg-parser.scss b/docs/_static/css/bourbon/helpers/_radial-arg-parser.scss new file mode 100644 index 0000000..3466695 --- /dev/null +++ b/docs/_static/css/bourbon/helpers/_radial-arg-parser.scss @@ -0,0 +1,69 @@ +@function _radial-arg-parser($G1, $G2, $pos, $shape-size) { + @each $value in $G1, $G2 { + $first-val: nth($value, 1); + $pos-type: type-of($first-val); + $spec-at-index: null; + + // Determine if spec was passed to mixin + @if type-of($value) == list { + $spec-at-index: if(index($value, at), index($value, at), false); + } + @if $spec-at-index { + @if $spec-at-index > 1 { + @for $i from 1 through ($spec-at-index - 1) { + $shape-size: $shape-size nth($value, $i); + } + @for $i from ($spec-at-index + 1) through length($value) { + $pos: $pos nth($value, $i); + } + } + @else if $spec-at-index == 1 { + @for $i from ($spec-at-index + 1) through length($value) { + $pos: $pos nth($value, $i); + } + } + $G1: false; + } + + // If not spec calculate correct values + @else { + @if ($pos-type != color) or ($first-val != "transparent") { + @if ($pos-type == number) + or ($first-val == "center") + or ($first-val == "top") + or ($first-val == "right") + or ($first-val == "bottom") + or ($first-val == "left") { + + $pos: $value; + + @if $pos == $G1 { + $G1: false; + } + } + + @else if + ($first-val == "ellipse") + or ($first-val == "circle") + or ($first-val == "closest-side") + or ($first-val == "closest-corner") + or ($first-val == "farthest-side") + or ($first-val == "farthest-corner") + or ($first-val == "contain") + or ($first-val == "cover") { + + $shape-size: $value; + + @if $value == $G1 { + $G1: false; + } + + @else if $value == $G2 { + $G2: false; + } + } + } + } + } + @return $G1, $G2, $pos, $shape-size; +} diff --git a/docs/_static/css/bourbon/helpers/_radial-positions-parser.scss b/docs/_static/css/bourbon/helpers/_radial-positions-parser.scss new file mode 100644 index 0000000..6a5b477 --- /dev/null +++ b/docs/_static/css/bourbon/helpers/_radial-positions-parser.scss @@ -0,0 +1,18 @@ +@function _radial-positions-parser($gradient-pos) { + $shape-size: nth($gradient-pos, 1); + $pos: nth($gradient-pos, 2); + $shape-size-spec: _shape-size-stripper($shape-size); + + $pre-spec: unquote(if($pos, "#{$pos}, ", null)) + unquote(if($shape-size, "#{$shape-size},", null)); + $pos-spec: if($pos, "at #{$pos}", null); + + $spec: "#{$shape-size-spec} #{$pos-spec}"; + + // Add comma + @if ($spec != ' ') { + $spec: "#{$spec}," + } + + @return $pre-spec $spec; +} diff --git a/docs/_static/css/bourbon/helpers/_render-gradients.scss b/docs/_static/css/bourbon/helpers/_render-gradients.scss new file mode 100644 index 0000000..5765676 --- /dev/null +++ b/docs/_static/css/bourbon/helpers/_render-gradients.scss @@ -0,0 +1,26 @@ +// User for linear and radial gradients within background-image or border-image properties + +@function _render-gradients($gradient-positions, $gradients, $gradient-type, $vendor: false) { + $pre-spec: null; + $spec: null; + $vendor-gradients: null; + @if $gradient-type == linear { + @if $gradient-positions { + $pre-spec: nth($gradient-positions, 1); + $spec: nth($gradient-positions, 2); + } + } + @else if $gradient-type == radial { + $pre-spec: nth($gradient-positions, 1); + $spec: nth($gradient-positions, 2); + } + + @if $vendor { + $vendor-gradients: -#{$vendor}-#{$gradient-type}-gradient(#{$pre-spec} $gradients); + } + @else if $vendor == false { + $vendor-gradients: "#{$gradient-type}-gradient(#{$spec} #{$gradients})"; + $vendor-gradients: unquote($vendor-gradients); + } + @return $vendor-gradients; +} diff --git a/docs/_static/css/bourbon/helpers/_shape-size-stripper.scss b/docs/_static/css/bourbon/helpers/_shape-size-stripper.scss new file mode 100644 index 0000000..ee5eda4 --- /dev/null +++ b/docs/_static/css/bourbon/helpers/_shape-size-stripper.scss @@ -0,0 +1,10 @@ +@function _shape-size-stripper($shape-size) { + $shape-size-spec: null; + @each $value in $shape-size { + @if ($value == "cover") or ($value == "contain") { + $value: null; + } + $shape-size-spec: "#{$shape-size-spec} #{$value}"; + } + @return $shape-size-spec; +} diff --git a/docs/_static/css/neat/_neat-helpers.scss b/docs/_static/css/neat/_neat-helpers.scss new file mode 100644 index 0000000..86021b1 --- /dev/null +++ b/docs/_static/css/neat/_neat-helpers.scss @@ -0,0 +1,8 @@ +// Functions +@import "functions/private"; +@import "functions/new-breakpoint"; +@import "functions/px-to-em"; + +// Settings +@import "settings/grid"; +@import "settings/visual-grid"; diff --git a/docs/_static/css/neat/_neat.scss b/docs/_static/css/neat/_neat.scss new file mode 100644 index 0000000..cb5876b --- /dev/null +++ b/docs/_static/css/neat/_neat.scss @@ -0,0 +1,21 @@ +// Bourbon Neat +// MIT Licensed +// Copyright (c) 2012-2013 thoughtbot, inc. + +// Helpers +@import "neat-helpers"; + +// Grid +@import "grid/private"; +@import "grid/reset"; +@import "grid/grid"; +@import "grid/omega"; +@import "grid/outer-container"; +@import "grid/span-columns"; +@import "grid/row"; +@import "grid/shift"; +@import "grid/pad"; +@import "grid/fill-parent"; +@import "grid/media"; +@import "grid/to-deprecate"; +@import "grid/visual-grid"; diff --git a/docs/_static/css/neat/functions/_new-breakpoint.scss b/docs/_static/css/neat/functions/_new-breakpoint.scss new file mode 100644 index 0000000..d89dcd1 --- /dev/null +++ b/docs/_static/css/neat/functions/_new-breakpoint.scss @@ -0,0 +1,16 @@ +@function new-breakpoint($query:$feature $value $columns, $total-columns: $grid-columns) { + + @if length($query) == 1 { + $query: $default-feature nth($query, 1) $total-columns; + } + + @else if length($query) == 2 or length($query) == 4 { + $query: append($query, $total-columns); + } + + @if not belongs-to($query, $visual-grid-breakpoints) { + $visual-grid-breakpoints: append($visual-grid-breakpoints, $query, comma); + } + + @return $query; +} diff --git a/docs/_static/css/neat/functions/_private.scss b/docs/_static/css/neat/functions/_private.scss new file mode 100644 index 0000000..49683d7 --- /dev/null +++ b/docs/_static/css/neat/functions/_private.scss @@ -0,0 +1,107 @@ +// Checks if a number is even +@function is-even($int) { + @if $int%2 == 0 { + @return true; + } + + @return false; +} + +// Checks if an element belongs to a list +@function belongs-to($tested-item, $list) { + @each $item in $list { + @if $item == $tested-item { + @return true; + } + } + + @return false; +} + +// Contains display value +@function contains-display-value($query) { + @if belongs-to(table, $query) or belongs-to(block, $query) or belongs-to(inline-block, $query) or belongs-to(inline, $query) { + @return true; + } + + @return false; +} + +// Parses the first argument of span-columns() +@function container-span($span: $span) { + @if length($span) == 3 { + $container-columns: nth($span, 3); + @return $container-columns; + } + + @else if length($span) == 2 { + $container-columns: nth($span, 2); + @return $container-columns; + } + + @else { + @return $grid-columns; + } +} + +// Generates a striped background +@function gradient-stops($grid-columns, $color: $visual-grid-color) { + $transparent: rgba(0,0,0,0); + + $column-width: flex-grid(1, $grid-columns); + $gutter-width: flex-gutter($grid-columns); + $column-offset: $column-width; + + $values: ($transparent 0, $color 0); + + @for $i from 1 to $grid-columns*2 { + @if is-even($i) { + $values: append($values, $transparent $column-offset, comma); + $values: append($values, $color $column-offset, comma); + $column-offset: $column-offset + $column-width; + } + + @else { + $values: append($values, $color $column-offset, comma); + $values: append($values, $transparent $column-offset, comma); + $column-offset: $column-offset + $gutter-width; + } + } + + @return $values; +} + +// Layout direction +@function get-direction($layout, $default) { + $direction: nil; + + @if $layout == LTR or $layout == RTL { + $direction: direction-from-layout($layout); + } @else { + $direction: direction-from-layout($default); + } + + @return $direction; +} + +@function direction-from-layout($layout) { + $direction: nil; + + @if $layout == LTR { + $direction: right; + } @else { + $direction: left; + } + + @return $direction; +} + +@function get-opposite-direction($direction) { + $opposite-direction: left; + + @if $direction == left { + $opposite-direction: right; + } + + @return $opposite-direction; +} diff --git a/docs/_static/css/neat/functions/_px-to-em.scss b/docs/_static/css/neat/functions/_px-to-em.scss new file mode 100644 index 0000000..058e51e --- /dev/null +++ b/docs/_static/css/neat/functions/_px-to-em.scss @@ -0,0 +1,3 @@ +@function em($pxval, $base: 16) { + @return ($pxval / $base) * 1em; +} diff --git a/docs/_static/css/neat/grid/_fill-parent.scss b/docs/_static/css/neat/grid/_fill-parent.scss new file mode 100644 index 0000000..859c977 --- /dev/null +++ b/docs/_static/css/neat/grid/_fill-parent.scss @@ -0,0 +1,7 @@ +@mixin fill-parent() { + width: 100%; + + @if $border-box-sizing == false { + @include box-sizing(border-box); + } +} diff --git a/docs/_static/css/neat/grid/_grid.scss b/docs/_static/css/neat/grid/_grid.scss new file mode 100644 index 0000000..e074b6c --- /dev/null +++ b/docs/_static/css/neat/grid/_grid.scss @@ -0,0 +1,5 @@ +@if $border-box-sizing == true { + * { + @include box-sizing(border-box); + } +} diff --git a/docs/_static/css/neat/grid/_media.scss b/docs/_static/css/neat/grid/_media.scss new file mode 100644 index 0000000..7c9872f --- /dev/null +++ b/docs/_static/css/neat/grid/_media.scss @@ -0,0 +1,51 @@ +@mixin media($query:$feature $value $columns, $total-columns: $grid-columns) { + + @if length($query) == 1 { + @media screen and ($default-feature: nth($query, 1)) { + $default-grid-columns: $grid-columns; + $grid-columns: $total-columns; + @content; + $grid-columns: $default-grid-columns; + } + } + + @else if length($query) == 2 { + @media screen and (nth($query, 1): nth($query, 2)) { + $default-grid-columns: $grid-columns; + $grid-columns: $total-columns; + @content; + $grid-columns: $default-grid-columns; + } + } + + @else if length($query) == 3 { + @media screen and (nth($query, 1): nth($query, 2)) { + $default-grid-columns: $grid-columns; + $grid-columns: nth($query, 3); + @content; + $grid-columns: $default-grid-columns; + } + } + + @else if length($query) == 4 { + @media screen and (nth($query, 1): nth($query, 2)) and (nth($query, 3): nth($query, 4)) { + $default-grid-columns: $grid-columns; + $grid-columns: $total-columns; + @content; + $grid-columns: $default-grid-columns; + } + } + + @else if length($query) == 5 { + @media screen and (nth($query, 1): nth($query, 2)) and (nth($query, 3): nth($query, 4)) { + $default-grid-columns: $grid-columns; + $grid-columns: nth($query, 5); + @content; + $grid-columns: $default-grid-columns; + } + } + + @else { + @warn "Wrong number of arguments for breakpoint(). Read the documentation for more details."; + } +} diff --git a/docs/_static/css/neat/grid/_omega.scss b/docs/_static/css/neat/grid/_omega.scss new file mode 100644 index 0000000..902459b --- /dev/null +++ b/docs/_static/css/neat/grid/_omega.scss @@ -0,0 +1,79 @@ +// Remove last element gutter +@mixin omega($query: block, $direction: default) { + $table: if(belongs-to(table, $query), true, false); + $auto: if(belongs-to(auto, $query), true, false); + + @if $direction != default { + @warn "The omega mixin will no longer take a $direction argument. To change the layout direction, use row($direction) or set $default-layout-direction instead." + } @else { + $direction: get-direction($layout-direction, $default-layout-direction); + } + + @if length($query) == 1 { + @if $auto { + &:last-child { + margin-#{$direction}: 0; + } + } + + @else if contains-display-value($query) { + @if $table { + padding-#{$direction}: 0; + } + + @else { + margin-#{$direction}: 0; + } + } + + @else { + @include nth-child($query, $direction); + } + } + + @else if length($query) == 2 { + @if $table { + @if $auto { + &:last-child { + padding-#{$direction}: 0; + } + } + + @else { + &:nth-child(#{nth($query, 1)}) { + padding-#{$direction}: 0; + } + } + } + + @else { + @if $auto { + &:last-child { + margin-#{$direction}: 0; + } + } + + @else { + @include nth-child(nth($query, 1), $direction); + } + } + } + + @else { + @warn "Too many arguments passed to the omega() mixin." + } +} + +@mixin nth-child($query, $direction) { + $opposite-direction: get-opposite-direction($direction); + + &:nth-child(#{$query}) { + margin-#{$direction}: 0; + } + + @if type-of($query) == number { + &:nth-child(#{$query}+1) { + clear: $opposite-direction; + } + } +} diff --git a/docs/_static/css/neat/grid/_outer-container.scss b/docs/_static/css/neat/grid/_outer-container.scss new file mode 100644 index 0000000..22c541f --- /dev/null +++ b/docs/_static/css/neat/grid/_outer-container.scss @@ -0,0 +1,8 @@ +@mixin outer-container { + @include clearfix; + max-width: $max-width; + margin: { + left: auto; + right: auto; + } +} diff --git a/docs/_static/css/neat/grid/_pad.scss b/docs/_static/css/neat/grid/_pad.scss new file mode 100644 index 0000000..3ef5d80 --- /dev/null +++ b/docs/_static/css/neat/grid/_pad.scss @@ -0,0 +1,8 @@ +@mixin pad($padding: flex-gutter()) { + $padding-list: null; + @each $value in $padding { + $value: if($value == 'default', flex-gutter(), $value); + $padding-list: join($padding-list, $value); + } + padding: $padding-list; +} diff --git a/docs/_static/css/neat/grid/_private.scss b/docs/_static/css/neat/grid/_private.scss new file mode 100644 index 0000000..38d3fce --- /dev/null +++ b/docs/_static/css/neat/grid/_private.scss @@ -0,0 +1,50 @@ +$parent-columns: $grid-columns !default; +$fg-column: $column; +$fg-gutter: $gutter; +$fg-max-columns: $grid-columns; +$container-display-table: false !default; +$layout-direction: nil !default; + +@function flex-grid($columns, $container-columns: $fg-max-columns) { + $width: $columns * $fg-column + ($columns - 1) * $fg-gutter; + $container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter; + @return percentage($width / $container-width); +} + +@function flex-gutter($container-columns: $fg-max-columns, $gutter: $fg-gutter) { + $container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter; + @return percentage($gutter / $container-width); +} + +@function grid-width($n) { + @return $n * $gw-column + ($n - 1) * $gw-gutter; +} + +@function get-parent-columns($columns) { + @if $columns != $grid-columns { + $parent-columns: $columns; + } @else { + $parent-columns: $grid-columns; + } + + @return $parent-columns; +} + +@function is-display-table($container-is-display-table, $display) { + $display-table: false; + + @if $container-is-display-table == true { + $display-table: true; + } @else if $display == table { + $display-table: true; + } + + @return $display-table; +} + +@function get-padding-for-table-layout($columns, $total-columns) { + $total-padding: flex-gutter($total-columns) * ($columns - 1); + $padding: $total-padding / $columns; + + @return $padding; +} diff --git a/docs/_static/css/neat/grid/_reset.scss b/docs/_static/css/neat/grid/_reset.scss new file mode 100644 index 0000000..f670019 --- /dev/null +++ b/docs/_static/css/neat/grid/_reset.scss @@ -0,0 +1,12 @@ +@mixin reset-display { + $container-display-table: false; +} + +@mixin reset-layout-direction { + $layout-direction: $default-layout-direction; +} + +@mixin reset-all { + @include reset-display; + @include reset-layout-direction; +} diff --git a/docs/_static/css/neat/grid/_row.scss b/docs/_static/css/neat/grid/_row.scss new file mode 100644 index 0000000..582603d --- /dev/null +++ b/docs/_static/css/neat/grid/_row.scss @@ -0,0 +1,17 @@ +@mixin row($display: block, $direction: $default-layout-direction) { + @include clearfix; + $layout-direction: $direction; + + @if $display == table { + display: table; + @include fill-parent; + table-layout: fixed; + $container-display-table: true; + } + + @else { + display: block; + $container-display-table: false; + } +} + diff --git a/docs/_static/css/neat/grid/_shift.scss b/docs/_static/css/neat/grid/_shift.scss new file mode 100644 index 0000000..b3cb503 --- /dev/null +++ b/docs/_static/css/neat/grid/_shift.scss @@ -0,0 +1,9 @@ +@mixin shift($n-columns: 1) { + $direction: get-direction($layout-direction, $default-layout-direction); + $opposite-direction: get-opposite-direction($direction); + + margin-#{$opposite-direction}: $n-columns * flex-grid(1, $parent-columns) + $n-columns * flex-gutter($parent-columns); + + // Reset nesting context + $parent-columns: $grid-columns; +} diff --git a/docs/_static/css/neat/grid/_span-columns.scss b/docs/_static/css/neat/grid/_span-columns.scss new file mode 100644 index 0000000..609c349 --- /dev/null +++ b/docs/_static/css/neat/grid/_span-columns.scss @@ -0,0 +1,38 @@ +@mixin span-columns($span: $columns of $container-columns, $display: block) { + $columns: nth($span, 1); + $container-columns: container-span($span); + + // Set nesting context (used by shift()) + $parent-columns: get-parent-columns($container-columns); + + $direction: get-direction($layout-direction, $default-layout-direction); + $opposite-direction: get-opposite-direction($direction); + + $display-table: is-display-table($container-display-table, $display); + + @if $display-table { + $padding: get-padding-for-table-layout($columns, $container-columns); + display: table-cell; + padding-#{$direction}: $padding; + width: flex-grid($columns, $container-columns) + $padding; + } @else { + display: block; + float: #{$opposite-direction}; + + @if $display == collapse { + width: flex-grid($columns, $container-columns) + flex-gutter($container-columns); + + &:last-child { + width: flex-grid($columns, $container-columns); + } + + } @else { + margin-#{$direction}: flex-gutter($container-columns); + width: flex-grid($columns, $container-columns); + + &:last-child { + margin-#{$direction}: 0; + } + } + } +} diff --git a/docs/_static/css/neat/grid/_to-deprecate.scss b/docs/_static/css/neat/grid/_to-deprecate.scss new file mode 100644 index 0000000..d0a681f --- /dev/null +++ b/docs/_static/css/neat/grid/_to-deprecate.scss @@ -0,0 +1,57 @@ +@mixin breakpoint($query:$feature $value $columns, $total-columns: $grid-columns) { + @warn "The breakpoint() mixin was renamed to media() in Neat 1.0. Please update your project with the new syntax before the next version bump."; + + @if length($query) == 1 { + @media screen and ($default-feature: nth($query, 1)) { + $default-grid-columns: $grid-columns; + $grid-columns: $total-columns; + @content; + $grid-columns: $default-grid-columns; + } + } + + @else if length($query) == 2 { + @media screen and (nth($query, 1): nth($query, 2)) { + $default-grid-columns: $grid-columns; + $grid-columns: $total-columns; + @content; + $grid-columns: $default-grid-columns; + } + } + + @else if length($query) == 3 { + @media screen and (nth($query, 1): nth($query, 2)) { + $default-grid-columns: $grid-columns; + $grid-columns: nth($query, 3); + @content; + $grid-columns: $default-grid-columns; + } + } + + @else if length($query) == 4 { + @media screen and (nth($query, 1): nth($query, 2)) and (nth($query, 3): nth($query, 4)) { + $default-grid-columns: $grid-columns; + $grid-columns: $total-columns; + @content; + $grid-columns: $default-grid-columns; + } + } + + @else if length($query) == 5 { + @media screen and (nth($query, 1): nth($query, 2)) and (nth($query, 3): nth($query, 4)) { + $default-grid-columns: $grid-columns; + $grid-columns: nth($query, 5); + @content; + $grid-columns: $default-grid-columns; + } + } + + @else { + @warn "Wrong number of arguments for breakpoint(). Read the documentation for more details."; + } +} + +@mixin nth-omega($nth, $display: block, $direction: default) { + @warn "The nth-omega() mixin is deprecated. Please use omega() instead."; + @include omega($nth $display, $direction); +} diff --git a/docs/_static/css/neat/grid/_visual-grid.scss b/docs/_static/css/neat/grid/_visual-grid.scss new file mode 100644 index 0000000..1c822fd --- /dev/null +++ b/docs/_static/css/neat/grid/_visual-grid.scss @@ -0,0 +1,41 @@ +@mixin grid-column-gradient($values...) { + background-image: deprecated-webkit-gradient(linear, left top, left bottom, $values); + background-image: -webkit-linear-gradient(left, $values); + background-image: -moz-linear-gradient(left, $values); + background-image: -ms-linear-gradient(left, $values); + background-image: -o-linear-gradient(left, $values); + background-image: unquote("linear-gradient(left, #{$values})"); +} + +@if $visual-grid == true or $visual-grid == yes { + body:before { + content: ''; + display: inline-block; + @include grid-column-gradient(gradient-stops($grid-columns)); + height: 100%; + left: 0; + margin: 0 auto; + max-width: $max-width; + opacity: $visual-grid-opacity; + position: fixed; + right: 0; + width: 100%; + pointer-events: none; + + @if $visual-grid-index == back { + z-index: -1; + } + + @else if $visual-grid-index == front { + z-index: 9999; + } + + @each $breakpoint in $visual-grid-breakpoints { + @if $breakpoint != nil { + @include media($breakpoint) { + @include grid-column-gradient(gradient-stops($grid-columns)); + } + } + } + } +} diff --git a/docs/_static/css/neat/settings/_grid.scss b/docs/_static/css/neat/settings/_grid.scss new file mode 100644 index 0000000..f1dcda4 --- /dev/null +++ b/docs/_static/css/neat/settings/_grid.scss @@ -0,0 +1,7 @@ +$column: golden-ratio(1em, 3) !default; // Column width +$gutter: golden-ratio(1em, 1) !default; // Gutter between each two columns +$grid-columns: 12 !default; // Total number of columns in the grid +$max-width: em(1088) !default; // Max-width of the outer container +$border-box-sizing: true !default; // Makes all elements have a border-box layout +$default-feature: min-width; // Default @media feature for the breakpoint() mixin +$default-layout-direction: LTR !default; diff --git a/docs/_static/css/neat/settings/_visual-grid.scss b/docs/_static/css/neat/settings/_visual-grid.scss new file mode 100644 index 0000000..611c2b3 --- /dev/null +++ b/docs/_static/css/neat/settings/_visual-grid.scss @@ -0,0 +1,5 @@ +$visual-grid: false !default; // Display the base grid +$visual-grid-color: #EEE !default; +$visual-grid-index: back !default; // Show grid behind content (back) or overlay it over the content (front) +$visual-grid-opacity: 0.4 !default; +$visual-grid-breakpoints: () !default; diff --git a/docs/_static/css/site.css b/docs/_static/css/site.css new file mode 100644 index 0000000..8e0383a --- /dev/null +++ b/docs/_static/css/site.css @@ -0,0 +1,118 @@ +@import url(http://fonts.googleapis.com/css?family=Open+Sans); +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } + +* { + margin: 0; + padding: 0; } + +body { + background-color: green; + font-family: Open Sans, sans-serif; } + +.wrapper, main { + margin: 0 auto; + max-width: 960px; } + +nav { + *zoom: 1; + max-width: 68em; + margin-left: auto; + margin-right: auto; + border-bottom: solid black 1px; + height: 80px; + max-width: none; + color: #f2efe3; } + nav:before, nav:after { + content: " "; + display: table; } + nav:after { + clear: both; } + nav header { + display: block; + float: left; + margin-right: 3.57866%; + width: 48.21067%; + text-align: left; } + nav header:last-child { + margin-right: 0; } + nav ul { + display: block; + float: left; + margin-right: 3.57866%; + width: 48.21067%; + margin-top: 30px; } + nav ul:last-child { + margin-right: 0; } + nav ul li { + display: block; + float: right; + margin-left: 2em; } + nav a { + color: #f2efe3; + font-weight: 400; + text-decoration: none; } + +h1, h2, h3, h4, h5, h6 { + margin: 0 0 20px; } + +p, ul, ol, table, pre, dl { + margin: 0 0 20px; } + +h1, h2, h3 { + line-height: 1.1; } + +h1 { + font-size: 28px; } + +a small { + font-size: 11px; + margin-top: -0.6em; + display: block; } + +main { + *zoom: 1; + max-width: 68em; + margin-left: auto; + margin-right: auto; + max-width: 840px; + padding: 17px 30px 20px 30px; + background-color: #fbfbfb; + border: 1px solid #c5c1b4; + font-size: 14px; + color: #585755; + line-height: 1.75em; } + main:before, main:after { + content: " "; + display: table; } + main:after { + clear: both; } + main #left { + display: block; + float: left; + margin-right: 3.57866%; + width: 61.158%; } + main #left:last-child { + margin-right: 0; } + main #left figure { + text-align: center; } + main #left figure img { + margin: 0 auto; } + main #left #demo { + margin-top: 20px; } + main #right { + display: block; + float: left; + margin-right: 3.57866%; + width: 35.26334%; } + main #right:last-child { + margin-right: 0; } + main #right ul { + margin-left: 20px; } + main #right pre { + font-size: 0.90em; } + main #right #features > ul > li strong { + color: #222; + font-weight: 700; } diff --git a/docs/_static/css/site.scss b/docs/_static/css/site.scss new file mode 100644 index 0000000..669c4f9 --- /dev/null +++ b/docs/_static/css/site.scss @@ -0,0 +1,325 @@ +@import "bourbon/bourbon"; +@import "neat/neat"; + +$blue: #00aac2; +$text: #5c5c5c; + +* { + margin: 0; + padding: 0; +} + +html, body { + height: 100%; +} + +h1, h2, h3, h4, h5, h6 { + font-weight: normal; + font-style: normal; +} + +body { + font: 14px "Helvetica Neue", Helvetica, Arial, sans-serif; + color: black; +} + +.wrapper { + min-height: 100%; + height: auto !important; + height: 100%; + margin: 0 auto -2em; +} + +.push, .footer { + height: 2em; + + a, a:visited { + color: $blue; + text-decoration: none; + } +} + +.header { + @include outer-container; + padding-top: 1em; + padding-bottom: 1em; + + a, a:visited { + color: #4d4c4c; + text-decoration: none; + } + + header { + @include span-columns(1 of 2); + + font-weight: normal; + + .logo { + max-height: 60px; + padding-right: 12px; + float: left; + } + + h1 { + font-size: 1.55em; + margin-bottom: 0.3em; + } + + h2 { + font-size: 1.05em; + } + } + + nav { + @include span-columns(1 of 2); + + ul { + padding-top: 1.55em + 0.3em; + + li { + display: block; + float: right; + margin-left: 2em; + + font-weight: 300; + text-transform: uppercase; + + color: #444; + letter-spacing: 0.05em; + } + } + } +} + +.outer { + background-color: rgb(238, 238, 238); + box-shadow: inset 0 0 0.5em #c0c0c0; + + header { + @include outer-container; + padding: 1em 0; + + h1 { + color: #555; + font: 300 35px Lato, "Helvetica Neue", Helvetica, Arial, sans-serif; + text-shadow: 1px 0 #c0c0c0; + letter-spacing: 0.05em; + } + } + + footer { + @include outer-container; + } + + .index { + @include outer-container; + padding: 1em; + + figure { + @include span-columns(2 of 5); + + text-align: center; + max-width: 300px; + + img { + max-width: 100%; + max-height: 100%; + } + } + + ul { + @include span-columns(3 of 5); + margin-top: 1em; + + li { + list-style-type: none; + margin-bottom: 2em; + + strong { + font-weight: normal; + font-size: 18px; + color: black; + } + + p { + color: $text; + } + + p + p { + margin-top: 0.5em; + } + } + } + } +} + +.footer { + text-align: right; + padding: 0.5em 0; +} + +main { + @include outer-container; + margin-top: 1.6em; + margin-bottom: 2em; + line-height: 1.6em; + + h2:before { + content: "» "; + } + + .links { + @include span-columns(1 of 3); + + h2 { + margin-bottom: 1em; + margin-top: 1em; + } + + > h2:first-child { + margin-top: 0; + } + + li { + list-style-type: none; + } + + .search { + input { + padding: 0.1em 0.4em; + } + } + } + + .demo { + @include span-columns(2 of 3); + + h2 { + margin-bottom: 1em; + } + + h4 { + margin-bottom: 0.5em; + } + } + + .sidebar { + @include span-columns(1 of 5); + display: block; + + strong { + color: $blue; + font-weight: bold; + text-transform: uppercase; + } + + ul { + margin-top: 0.5em; + margin-bottom: 1em; + } + + ul:last-child { + margin-bottom: 0; + } + + li { + border-left: solid 2px #d3d3d3; + margin-left: 0.25em; + padding-left: 0.75em; + + padding-top: 0.25em; + padding-bottom: 0.25em; + + font-weight: 300; + list-style-type: none; + + a { + color: $text; + } + } + + .active { + border-left-color: $blue; + + a { + color: $blue; + font-weight: bold; + } + } + } + + .docs { + @include span-columns(4 of 5); + + font-size: 15px; + color: $text; + + h2, h3, h4 { + margin-top: 1em; + margin-bottom: 1em; + + } + + h2 { + font-size: 24px; + } + + h3 { + font-size: 18px; + } + + h4 { + font-size: 14px; + } + + pre, code { + color: rgb(77, 77, 76); + font-size: 12px; + font-family: Monaco, Menlo, Consolas, monospaced; + } + + .highlight { + margin: 1.2em 0; + padding: 10px 15px; + + background-color: rgb(238, 238, 238); + color: rgb(77, 77, 76); + + border: 1px solid rgb(221, 221, 221); + border-radius: 4px; + overflow: auto; + } + + p + p { + margin-top: 1em; + } + + p + p:last-child { + margin-bottom: 0; + } + + ul { + + margin: 1em 0; + + li { + list-style-type: square; + margin-left: 2em; + margin-right: 2em; + } + } + } + + a { + text-decoration: none; + color: $blue; + } + + dt { + font-weight: bold; + margin: 0.4em 0; + } + dd { + margin-left: 1.2em; + } +} diff --git a/docs/_static/duty_calls.png b/docs/_static/duty_calls.png new file mode 100644 index 0000000000000000000000000000000000000000..4683d638b90e2b1820922757a6b63fe5b50a30cd GIT binary patch literal 14103 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⪼SofR=zjYOWERn5N5?(b=_^5rm(fD;5!nw--p| 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 literal 0 HcmV?d00001 diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..011c681 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,258 @@ +# -*- coding: utf-8 -*- +# +# Isso documentation build configuration file, created by +# sphinx-quickstart on Thu Nov 21 11:28:01 2013. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os + +from os.path import join, dirname +sys.path.insert(0, join(dirname(__file__), "_isso/")) + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.todo', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'docs/index' + +# General information about the project. +project = u'Isso' +copyright = u'2013, Martin Zimmermann' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.5' +# The full version, including alpha/beta/rc tags. +release = '0.5.1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'trac' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = '_isso' +html_translator_class = "html5.Isso" + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +html_theme_path = ["."] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +html_additional_pages = {"index": "index.html"} + +# If false, no module index is generated. +html_domain_indices = False + +# If false, no index is generated. +html_use_index = False + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Issodoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ('index', 'Isso.tex', u'Isso Documentation', + u'Martin Zimmermann', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'isso', u'Isso Documentation', + [u'Martin Zimmermann'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'Isso', u'Isso Documentation', + u'Martin Zimmermann', 'Isso', 'a commenting server similar to Disqus', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False diff --git a/docs/contribute.rst b/docs/contribute.rst new file mode 100644 index 0000000..709ae46 --- /dev/null +++ b/docs/contribute.rst @@ -0,0 +1,4 @@ +Contribute +========== + +To be writ.. wait. diff --git a/docs/docs/configuration/client.rst b/docs/docs/configuration/client.rst new file mode 100644 index 0000000..4db5151 --- /dev/null +++ b/docs/docs/configuration/client.rst @@ -0,0 +1,52 @@ +Client Configuration +==================== + +You can configure the client (the JS part) with custom data attributes, +preferably in the script tag which embeds the JS: + +.. code-block:: html + + + +Furthermore you can override the automatic title detection inside +the embed tag, e.g.: + +.. code-block:: html + +
    + +data-isso +--------- + +Isso usually detects the REST API automatically, but when you serve the JS +script on a different location, this may fail. Use `data-isso` to +override the API location: + +.. code-block:: html + + + +data-isso-css +------------- + +Set to `false` prevents Isso from automatically appending the stylesheet. +Defaults to `true`. + +.. code-block:: html + + + +data-isso-lang +-------------- + +Override useragent's preferred language. Currently available: german (de), +english (en) and french (fr). + +data-isso-reply-to-self +----------------------- + +Set to `true` when spam guard is configured with `reply-to-self = true`. diff --git a/docs/CONFIGURATION.rst b/docs/docs/configuration/server.rst similarity index 71% rename from docs/CONFIGURATION.rst rename to docs/docs/configuration/server.rst index 3e3cd6f..0e964f8 100644 --- a/docs/CONFIGURATION.rst +++ b/docs/docs/configuration/server.rst @@ -1,8 +1,8 @@ -Isso Configuration -================== +Server Configuration +==================== -The Isso configuration file is an `INI-style`__ textfile. Below is an example for -a basic Isso configuration. Each section has its own documentation. +The Isso configuration file is an `INI-style`__ textfile. It reads integers, +booleans and strings. Below is a basic example: .. code-block:: ini @@ -12,8 +12,8 @@ a basic Isso configuration. Each section has its own documentation. [server] port = 1234 -You can point Isso to your configuration file either witg a command-line parameter -or using an environment variable: +To use your configuration file with Isso, append ``-c /path/to/cfg`` to the +executable or run Isso with an environment variable: .. code-block:: sh @@ -22,6 +22,11 @@ or using an environment variable: __ https://en.wikipedia.org/wiki/INI_file +Sections covered in this document: + +.. contents:: + :local: + General ------- @@ -35,7 +40,7 @@ session key and hostname. Here are the default values for this section: dbpath = /tmp/isso.db host = http://localhost:8080/ max-age = 15m - session-key = ... # python: binascii.b2a_hex(os.urandom(24)) + session-key = ... ; python: binascii.b2a_hex(os.urandom(24)) notify = dbpath @@ -131,6 +136,7 @@ profile show 10 most time consuming function in Isso after each request. Do not use in production. +.. _configure-smtp: SMTP ---- @@ -208,66 +214,8 @@ reply-to-self Do not forget to configure the client. -Multiple Sites --------------- - -Isso is designed to serve comments for a single website and therefore stores -comments for a relative URL to support HTTP, HTTPS and even domain transfers -without manual intervention. But you can chain Isso to support multiple -websites on different domains. - -The following example uses `gunicorn `_ as WSGI server ( -you can use uWSGI as well). Let's say you maintain two websites, like -foo.example and other.foo: - -.. code-block:: bash - - $ cat /etc/isso.d/foo.example.cfg - [general] - host = http://foo.example/ - dbpath = /var/lib/isso/foo.example.db - - $ cat /etc/isso.d/other.foo.cfg - [general] - host = http://other.foo/ - dbpath = /var/lib/isso/other.foo.db - -Then you run Isso using gunicorn: - -.. code-block:: bash - - $ export ISSO_SETTINGS="/etc/isso.d/foo.example.cfg;/etc/isso.d/other.foo.cfg" - $ gunicorn isso.dispatch -b localhost:8080 - -In your webserver configuration, proxy Isso as usual: - -.. code-block:: nginx - - server { - listen [::]:80; - server_name comments.example; - - location / { - proxy_pass http://localhost:8080; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Real-IP $remote_addr; - } - } - -To verify the setup, run: - -.. code-block:: bash - - $ curl -vH "Origin: http://foo.example" http://comments.example/ - ... - $ curl -vH "Origin: http://other.foo" http://comments.example/ - ... - -In case of a 418 (I'm a teapot), the setup is *not* correctly configured. - - Appendum ---------- +-------- .. _appendum-timedelta: diff --git a/docs/docs/configuration/setup.rst b/docs/docs/configuration/setup.rst new file mode 100644 index 0000000..adc1584 --- /dev/null +++ b/docs/docs/configuration/setup.rst @@ -0,0 +1,90 @@ +Setup +===== + +Sub-URI +------- + +You can run Isso on the same domain as your website, which circumvents issues +originating from CORS_. Also, privacy-protecting browser addons such as +`Request Policy`_ wont block comments. + +.. code-block:: nginx + + server { + listen [::]:80; + listen [::]:443 ssl; + server_name example.tld; + root /var/www/example.tld; + + location /isso { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Script-Name /isso; + proxy_pass http://localhost:8080; + } + } + +Now, the website integration is just as described in :doc:`../quickstart` but +with a different location. + +.. _CORS: https://developer.mozilla.org/en/docs/HTTP/Access_control_CORS +.. _Request Policy: https://www.requestpolicy.com/ + + +.. _configure-multiple-sites: + +Multiple Sites +-------------- + +Isso is designed to serve comments for a single website and therefore stores +comments for a relative URL to support HTTP, HTTPS and even domain transfers +without manual intervention. But you can chain Isso to support multiple +websites on different domains. + +The following example uses `gunicorn `_ as WSGI server ( +you can use uWSGI as well). Let's say you maintain two websites, like +foo.example and other.foo: + +.. code-block:: sh + + $ cat /etc/isso.d/foo.example.cfg + [general] + host = http://foo.example/ + dbpath = /var/lib/isso/foo.example.db + + $ cat /etc/isso.d/other.foo.cfg + [general] + host = http://other.foo/ + dbpath = /var/lib/isso/other.foo.db + +Then you run Isso using gunicorn: + +.. code-block:: sh + + $ export ISSO_SETTINGS="/etc/isso.d/foo.example.cfg;/etc/isso.d/other.foo.cfg" + $ gunicorn isso.dispatch -b localhost:8080 + +In your webserver configuration, proxy Isso as usual: + +.. code-block:: nginx + + server { + listen [::]:80; + server_name comments.example; + + location / { + proxy_pass http://localhost:8080; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Real-IP $remote_addr; + } + } + +To verify the setup, run: + +.. code-block:: sh + + $ curl -vH "Origin: http://foo.example" http://comments.example/ + ... + $ curl -vH "Origin: http://other.foo" http://comments.example/ + ... + +In case of a 418 (I'm a teapot), the setup is *not* correctly configured. diff --git a/docs/docs/extras/api.rst b/docs/docs/extras/api.rst new file mode 100644 index 0000000..281f836 --- /dev/null +++ b/docs/docs/extras/api.rst @@ -0,0 +1,98 @@ +Isso API +======== + +The Isso API uses HTTP and JSON as primary communication protocol. + +JSON format +----------- + +When querying the API you either get an error, an object or list of +objects representing the comment. Here's a example JSON returned from +Isso: + +.. code-block:: js + + { + "text": "Hello, World!", + "author": "Bernd", + "website": null, + "votes": 0, + "mode": 1, + "id": 1, + "parent": null, + "hash": "68b329da9893e34099c7d8ad5cb9c940", + "created": 1379001637.50, + "modified": null + } + +text : required, comment as HTML + +author : author's name, may be ``null`` + +website : author's website, may be ``null`` + +votes : sum of up- and downvotes, defaults to zero. + +mode : \* 1, accepted comment \* 2, comment in moderation queue \* 4, +comment deleted, but is referenced + +id : unique comment number per thread + +parent : answer to a parent id, may be ``null`` + +hash : user identification, used to generate identicons + +created : time in seconds sinde epoch + +modified : last modification time in seconds, may be ``null`` + +List comments +------------- + +List all visible comments for a thread. Does not include deleted and +comments currently in moderation queue. + + GET /?uri=path + +You must encode ``path``, e.g. to retrieve comments for +``/hello-world/``: + +:: + + GET /?uri=%2Fhello-world%2F + +To disable automatic Markdown-to-HTML conversion, pass ``plain=1`` to +the query URL: + +:: + + GET /?uri=...&plain=1 + +As response, you either get 200, 400, or 404, which are pretty +self-explanatory. + +:: + + GET / + 400 BAD REQUEST + + GET /?uri=%2Fhello-world%2F + 404 NOT FOUND + + GET /?uri=%2Fcomment-me%2F + [{comment 1}, {comment 2}, ...] + +Create comments +--------------- + +... + +Delete comments +--------------- + +... + +Up- and downvote comments +------------------------- + +... diff --git a/docs/docs/extras/multi-site.rst b/docs/docs/extras/multi-site.rst new file mode 100644 index 0000000..be51242 --- /dev/null +++ b/docs/docs/extras/multi-site.rst @@ -0,0 +1,56 @@ +Multi Site Configuration +======================== + +Isso is designed to serve comments for a single website and therefore stores +comments for a relative URL to support HTTP, HTTPS and even domain transfers +without manual intervention. But you can chain Isso to support multiple +websites on different domains. + +The following example uses `gunicorn `_ as WSGI server ( +you can use uWSGI as well). Let's say you maintain two websites, like +foo.example and other.foo: + +.. code-block:: bash + + $ cat /etc/isso.d/foo.example.cfg + [general] + host = http://foo.example/ + dbpath = /var/lib/isso/foo.example.db + + $ cat /etc/isso.d/other.foo.cfg + [general] + host = http://other.foo/ + dbpath = /var/lib/isso/other.foo.db + +Then you run Isso using gunicorn: + +.. code-block:: bash + + $ export ISSO_SETTINGS="/etc/isso.d/foo.example.cfg;/etc/isso.d/other.foo.cfg" + $ gunicorn isso.dispatch -b localhost:8080 + +In your webserver configuration, proxy Isso as usual: + +.. code-block:: nginx + + server { + listen [::]:80; + server_name comments.example; + + location / { + proxy_pass http://localhost:8080; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Real-IP $remote_addr; + } + } + +To verify the setup, run: + +.. code-block:: bash + + $ curl -vH "Origin: http://foo.example" http://comments.example/ + ... + $ curl -vH "Origin: http://other.foo" http://comments.example/ + ... + +In case of a 418 (I'm a teapot), the setup is *not* correctly configured. diff --git a/docs/docs/extras/uwsgi.rst b/docs/docs/extras/uwsgi.rst new file mode 100644 index 0000000..018a86d --- /dev/null +++ b/docs/docs/extras/uwsgi.rst @@ -0,0 +1,57 @@ +uWSGI +===== + +In short: `uWSGI `_ is awesome. Isso +has builtin support for it (and simple fallback if uWSGI is not +available). Use uWSGI if you think that the builtin WSGI server is a bad +choice or slow (hint: it's both). + +With uWSGI, you have roughly 100% performance improvements for just +using it. Instead of one thread per request, you can use multiple +processes, hence it is more "web scale". Other side effects: spooling, +fast inter-process caching. + +Installation +------------ + +You need uWSGI 1.9 or higher, fortunately you can install it with +Python: + +.. code-block:: sh + + ~> apt-get install build-essential python-dev + ~> pip install uwsgi + +Configuration +------------- + +For convenience, I recommend a INI-style configuration (you can also +supply everything as command-line arguments): + +.. code-block:: ini + + [uwsgi] + http = :8080 + master = true + processes = 4 + cache2 = name=hash,items=1024,blocksize=32 + spooler = %d/mail + module = isso + virtualenv = %d + env = ISSO_SETTINGS=%d/sample.cfg + +You shoud adjust ``processes`` to your CPU count. Then, save this file +to a directory if choice. Next to this file, create an empty directory +called ``mail``: + +.. code-block:: sh + + ~> mkdir mail/ + ~> ls + uwsgi.ini mail/ + +Now start Isso: + +.. code-block:: sh + + ~> uwsgi /path/to/uwsgi.ini diff --git a/docs/docs/index.rst b/docs/docs/index.rst new file mode 100644 index 0000000..a740aa6 --- /dev/null +++ b/docs/docs/index.rst @@ -0,0 +1,47 @@ +Overview +======== + +Welcome to the Isso's documentation. This documentation will help you get +started fast. If you get any problems when using Isso, you can find the answer +in troubleshooting or you can ask me on IRC or GitHub. + +What's Isso? +------------ + +Isso is a lightweight commenting server similar to Disqus. It allows anonymous +comments, maintains identity and is simple to administrate. It uses JavaScript +and cross-origin ressource sharing for easy integration into static websites. + +No, I meant "Isso" +------------------ + +Isso is an informal, german abbreviation for "Ich schrei sonst!", which can +roughly be translated to "I'm yelling otherwise". It usually ends the +discussion without any further arguments. + +In germany, Isso `is also pokémon N° 360`__. + +.. __: http://bulbapedia.bulbagarden.net/wiki/Wynaut_(Pok%C3%A9mon) + +What's wrong with Disqus? +------------------------- + +No anonymous comments (IP address, email and name recorded), hosted in the USA, +third-party. Just like IntenseDebate, livefrye etc. When you embed Disqus, they +can do anything with your readers (and probably mine Bitcoins, see the loading +times). + +Isso is not the first open-source commenting server: + + * `talkatv `_, written in Python. + Unfortunately, talkatv's (read "talkative") development stalled. Neither + anonymous nor threaded comments. + + * `Juvia `_, written in Ruby (on Rails). + No threaded comments, nice administration webinterface, but... yeah... Ruby. + + * `Tildehash.com `_, + written in PHP. Did I forgot something? + + * `Unobtrusive, self-hosted comments `_, + discussion on StackOverflow. diff --git a/docs/docs/install.rst b/docs/docs/install.rst new file mode 100644 index 0000000..6a86d30 --- /dev/null +++ b/docs/docs/install.rst @@ -0,0 +1,24 @@ +Installation +------------ + +Requirements: + +- Python 2.6, 2.7 or 3.3 +- a working C compiler + +Install Isso with PIP_: + +.. code-block:: sh + + ~> pip install isso + +.. _PIP: http://www.pip-installer.org/en/latest/ + +Init scripts: + +- SystemD: `isso.service + `_ +- SysVinit: `isso.init + `_ +- OpenBSD: `GH:Gist `_ + diff --git a/docs/docs/quickstart.rst b/docs/docs/quickstart.rst new file mode 100644 index 0000000..4defe85 --- /dev/null +++ b/docs/docs/quickstart.rst @@ -0,0 +1,187 @@ +Quickstart +========== + +Assuming you have successfully :doc:`installed ` Isso, here's +a quickstart quide that covers common setups. + + +Configuration +------------- + +You must provide a custom configuration. Most default parameters are useful for +development, not persistence. Two most important options are `dbpath` to set +the location of your database and `host` which is your website: + +.. code-block:: ini + + [general] + ; database location, check permissions + dbpath = /var/lib/isso/comments.db + ; your website or blog (not the location of Isso!) + host = http://example.tld/ + ; you can add multiple hosts for local development + ; or SSL connections. There is no wildcard to allow + ; any domain. + host = + http://localhost:1234/ + http://example.tld/ + https://example.tld/ + +Note, that multiple, *different* websites are **not** supported in a single +configuration. To serve comments for diffent websites, refer to +:ref:`Multiple Sites `. + +You moderate Isso through signed URLs sent by email or logged. By default, +comments are accepted and immediately shown to other users. To enable +moderation queue, add: + +.. code-block:: ini + + [moderation] + enabled = true + +To moderate comments, either use the activation or deletion URL in the logs or +:ref:`use SMTP ` to get notified on new comments including the +URLs for activation and deletion: + +.. code-block:: ini + + [general] + notify = smtp + [smtp] + ; SMTP settings + + +Migration +--------- + +You can migrate your existing comments from Disqus_. Log into Disqus, go to +your website, click on *Discussions* and select the *Export* tab. You'll +receive an email with your comments. Unfortunately, Disqus does not export +up- and downvotes. + +To import existing comments, run Isso with your new configuration file: + +.. code-block:: sh + + ~> isso -c /path/to/isso.cfg import user-2013-09-02T11_39_22.971478-all.xml + [100%] 53 threads, 192 comments + +.. _Disqus: + + +Running Isso +------------ + +To run Isso, simply execute: + +.. code-block:: sh + + $ isso -c /path/to/isso.cfg run + 2013-11-25 15:31:34,773 INFO: connected to HTTP server + +Next, we configure Nginx_ to proxy Isso. Do not run Isso on a public interface! +A popular but often error-prone (because of CORS_) setup to host Isso uses a +dedicated domain such as ``comments.example.tld``; see +:doc:`configuration/setup` for alternate ways. + +Assuming both, your website and Isso are on the same server, the nginx +configuration looks like this: + +.. code-block:: nginx + + server { + listen [::]:80 default ipv6only=off; + server_name example.tld; + root ...; + } + + server { + listen [::]:80; + server_name comments.example.tld; + + location / { + proxy_pass http://localhost:8080; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $host; + } + } + +Now, you embed Isso to your website: + +.. code-block:: html + + + +
    + +Note, that `data-isso` is optional, but when a website includes a script using +``async`` it is no longer possible to determine the script's external URL. + +That's it. When you open your website, you should see a commenting form. Leave +a comment to see if the setup works. If not, see :doc:`troubleshooting`. + +.. _Nginx: http://nginx.org/ +.. _CORS: https://developer.mozilla.org/en/docs/HTTP/Access_control_CORS + + +Deployment +---------- + +Isso ships with a built-in web server, which is useful for the initial setup. +But it is not recommended to use the built-in web server for production. A few +WSGI servers are supported out-of-the-box: + + * gevent_, coroutine-based network library + * uWSGI_, full-featured uWSGI server + * gunicorn_, Python WSGI HTTP Server for UNIX + +.. _gevent: http://www.gevent.org/ +.. _uWSGI: http://uwsgi-docs.readthedocs.org/en/latest/ +.. _gunicorn: http://gunicorn.org/ + +gevent +^^^^^^ + +Probably the easiest deployment method. Install with PIP (requires libevent): + +.. code-block:: sh + + $ pip install gevent + +Then, just use the ``isso`` executable as usual. Gevent monkey-patches Python's +standard library to work with greenlets. + +To execute Isso, just use the commandline interface: + +.. code-block:: sh + + $ isso -c my.cfg run + +Unfortunately, gevent 0.13.2 does not support UNIX domain sockets (see `#295 +`_ and `#299 +`_ for details). + +uWSGI +^^^^^ + +The author's favourite WSGI server. Due the complexity of uWSGI, there is a +:doc:`separate document ` on how to setup uWSGI for use +with Isso. + +gunicorn +^^^^^^^^ + +Install gunicorn_ via PIP: + +.. code-block:: sh + + $ pip install gunicorn + +To execute Isso, use a command similar to: + +.. code-block:: sh + + $ export ISSO_SETTINGS="/path/to/isso.cfg" + $ gunicorn -b localhost:8080 -w 4 --preload isso diff --git a/docs/docs/troubleshooting.rst b/docs/docs/troubleshooting.rst new file mode 100644 index 0000000..56d1a8a --- /dev/null +++ b/docs/docs/troubleshooting.rst @@ -0,0 +1,4 @@ +Troubleshooting +=============== + +To be written. diff --git a/docs/docs/usage.rst b/docs/docs/usage.rst new file mode 100644 index 0000000..d4b9a15 --- /dev/null +++ b/docs/docs/usage.rst @@ -0,0 +1,2 @@ +Usage +===== diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..7086a69 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,60 @@ +{%- extends "layout.html" %} +{% set title = "Isso – a commenting server similar to Disqus" %} +{% block extrahead %} + +{% endblock %} +{% block header %} +
    +
    + + XKCD Duty Calls + +
    by Randall Munroe, CC BY-NC 2.5
    +
    +
      +
    • +

      Create, Edit and Remove Comments

      +

      Commenters can edit or delete their own comments (within + 15 minutes by default).

      +

      Comments in moderation queue are not publicly visible until + activation.

      +
    • +
    • +

      SQLite backend

      +

      Because.

      +
    • +
    • +

      Disqus Import

      +

      You can migrate your Disqus comments without any hassle.

      +
    • +
    • +

      client-side JavaScript

      +

      Embed a single JS file, 54kb (18kb gzipped) and you are + done.

      +

      Supports Firefox, Safari, Chrome and IE10.

      +
    • +
    +
    +{% endblock %} +{% block body %} + + +
    +

    Demo

    +
    +
    +{% endblock %} diff --git a/docs/news.rst b/docs/news.rst new file mode 100644 index 0000000..5ae8f8b --- /dev/null +++ b/docs/news.rst @@ -0,0 +1,4 @@ +News +==== + +To be written. diff --git a/docs/uWSGI.md b/docs/uWSGI.md deleted file mode 100644 index 8321769..0000000 --- a/docs/uWSGI.md +++ /dev/null @@ -1,55 +0,0 @@ -uWSGI -===== - -In short: [uWSGI](http://uwsgi-docs.readthedocs.org/) is awesome. Isso has -builtin support for it (and simple fallback if uWSGI is not available). Use -uWSGI if you think that the builtin WSGI server is a bad choice or slow (hint: -it's both). - -With uWSGI, you have roughly 100% performance improvements for just using it. -Instead of one thread per request, you can use multiple processes, hence it -is more "web scale". Other side effects: spooling, fast inter-process caching. - - -Installation ------------- - -You need uWSGI 1.9 or higher, fortunately you can install it with Python: - -```sh -~> apt-get install build-essential python-dev -~> pip install uwsgi -``` - -Configuration -------------- - -For convenience, I recommend a INI-style configuration (you can also supply everything -as command-line arguments): - -```ini -[uwsgi] -http = :8080 -master = true -processes = 4 -cache2 = name=hash,items=1024,blocksize=32 -spooler = %d/mail -module = isso -virtualenv = %d -env = ISSO_SETTINGS=%d/sample.cfg -``` - -You shoud adjust `processes` to your CPU count. Then, save this file to a directory -if choice. Next to this file, create an empty directory called `mail`: - -```sh -~> mkdir mail/ -~> ls -uwsgi.ini mail/ -``` - -Now start Isso: - -```sh -~> uwsgi /path/to/uwsgi.ini -```