diff --git a/gratipay/main.py b/gratipay/main.py index b04f3faaf2..bc63412e5a 100644 --- a/gratipay/main.py +++ b/gratipay/main.py @@ -10,7 +10,7 @@ from gratipay.security import authentication, csrf, security_headers from gratipay.utils import erase_cookie, http_caching, i18n, set_cookie, timer from gratipay.version import get_version -from gratipay.renderers import csv_dump, jinja2_htmlescaped, eval_ +from gratipay.renderers import csv_dump, jinja2_htmlescaped, eval_, scss import aspen from aspen.website import Website @@ -27,8 +27,10 @@ website.renderer_factories['csv_dump'] = csv_dump.Factory(website) website.renderer_factories['eval'] = eval_.Factory(website) website.renderer_factories['jinja2_htmlescaped'] = jinja2_htmlescaped.Factory(website) +website.renderer_factories['scss'] = scss.Factory(website) website.default_renderers_by_media_type['text/html'] = 'jinja2_htmlescaped' website.default_renderers_by_media_type['text/plain'] = 'jinja2' # unescaped is fine here +website.default_renderers_by_media_type['text/css'] = 'scss' website.default_renderers_by_media_type['image/*'] = 'eval' website.renderer_factories['jinja2'].Renderer.global_context = { diff --git a/gratipay/renderers/scss.py b/gratipay/renderers/scss.py new file mode 100644 index 0000000000..50008d396d --- /dev/null +++ b/gratipay/renderers/scss.py @@ -0,0 +1,43 @@ +from __future__ import absolute_import, division, print_function, unicode_literals + +import re +from urlparse import urlsplit + +import sass +from aspen import renderers + + +class Renderer(renderers.Renderer): + + def __init__(self, *a, **kw): + renderers.Renderer.__init__(self, *a, **kw) + self.website = self._factory._configuration + + url_re = re.compile(r"""\burl\((['"])(.+?)['"]\)""") + + def url_sub(self, m): + url = urlsplit(m.group(2)) + if url.scheme or url.netloc: + # We need both tests because "//example.com" has no scheme and "data:" + # has no netloc. In either case, we want to leave the URL untouched. + return m.group(0) + repl = self.website.asset(url.path) \ + + (url.query and '&'+url.query) \ + + (url.fragment and '#'+url.fragment) + return 'url({0}{1}{0})'.format(m.group(1), repl) + + def replace_urls(self, css): + return self.url_re.sub(self.url_sub, css) + + def render_content(self, context): + output_style = 'compressed' if self.website.compress_assets else 'nested' + kw = dict(output_style=output_style, string=self.compiled) + if self.website.project_root is not None: + kw['include_paths'] = self.website.project_root + css = sass.compile(**kw) + if self.website.cache_static: + css = self.replace_urls(css) + return css + +class Factory(renderers.Factory): + Renderer = Renderer diff --git a/scss/gratipay.scss b/scss/gratipay.scss deleted file mode 100644 index 57cef0c59a..0000000000 --- a/scss/gratipay.scss +++ /dev/null @@ -1,66 +0,0 @@ -/* - This is the top of the Gratipay CSS tree. - - Our code organization here was inspired by Brad Frost's Atomic Web Design: - - http://bradfrost.com/blog/post/atomic-web-design/ - - We dropped the "atomic" metaphor and tried to use nomenclature consistent - with how the W3C talks about HTML. - -*/ - -@import "mixins/layout"; -@import "mixins/icons"; -@import "mixins/borders-backgrounds-shadows"; -@import "mixins/animation"; -@import "mixins/transform"; -@import "utils"; -@import "variables"; - -@import "elements/reset"; -@import "elements/buttons-knobs"; -@import "elements/elements"; - -@import "components/banner"; -@import "components/chart"; -@import "components/chosen"; -@import "components/communities"; -@import "components/cta"; -@import "components/dropdown"; -@import "components/emails"; -@import "components/js-edit"; -@import "components/linear_gradient"; -@import "components/loading-indicators"; -@import "components/memberships"; -@import "components/nav"; -@import "components/payments-by"; -@import "components/profile-statement"; -@import "components/sidebar"; -@import "components/sign_in"; -@import "components/status-icon"; -@import "components/support_gratipay"; -@import "components/table"; -@import "components/tip-distribution"; -@import "components/tipr"; -@import "components/upgrade"; - -@import "fragments/accounts"; -@import "fragments/members"; -@import "fragments/mini-user"; -@import "fragments/notifications"; -@import "fragments/pagination"; - -@import "layouts/layout"; -@import "layouts/responsiveness"; - -@import "pages/homepage"; -@import "pages/history"; -@import "pages/team"; -@import "pages/profile-edit"; -@import "pages/giving"; -@import "pages/settings"; -@import "pages/cc-ba"; -@import "pages/on-confirm"; -@import "pages/search"; -@import "pages/hall-of-fame"; diff --git a/www/assets/gratipay.css.spt b/www/assets/gratipay.css.spt index 9ed384eaa2..4838bc5b00 100644 --- a/www/assets/gratipay.css.spt +++ b/www/assets/gratipay.css.spt @@ -1,25 +1,68 @@ -import os -import re -from urlparse import urlsplit +[---] +[---] text/css via scss +/* + This is the top of the Gratipay CSS tree. -import sass + Our code organization here was inspired by Brad Frost's Atomic Web Design: -filename = os.path.join(website.project_root, 'scss', 'gratipay.scss') -output_style = 'compressed' if website.compress_assets else 'nested' + http://bradfrost.com/blog/post/atomic-web-design/ -def url_sub(m): - url = urlsplit(m.group(2)) - if url.scheme or url.netloc: - # We need both tests because "//example.com" has no scheme and "data:" - # has no netloc. In either case, we want to leave the URL untouched. - return m.group(0) - repl = website.asset(url.path) + (url.query and '&'+url.query) + (url.fragment and '#'+url.fragment) - return 'url({0}{1}{0})'.format(m.group(1), repl) + We dropped the "atomic" metaphor and tried to use nomenclature consistent + with how the W3C talks about HTML. -url_re = re.compile(r"""\burl\((['"])(.+?)['"]\)""") -[---] -css = sass.compile(output_style=str(output_style),filename=str(filename)) -if website.cache_static: - css = url_re.sub(url_sub, css) -[---] via stdlib_format -{css} +*/ + +@import "scss/mixins/layout"; +@import "scss/mixins/icons"; +@import "scss/mixins/borders-backgrounds-shadows"; +@import "scss/mixins/animation"; +@import "scss/mixins/transform"; +@import "scss/utils"; +@import "scss/variables"; + +@import "scss/elements/reset"; +@import "scss/elements/buttons-knobs"; +@import "scss/elements/elements"; + +@import "scss/components/banner"; +@import "scss/components/chart"; +@import "scss/components/chosen"; +@import "scss/components/communities"; +@import "scss/components/cta"; +@import "scss/components/dropdown"; +@import "scss/components/emails"; +@import "scss/components/js-edit"; +@import "scss/components/linear_gradient"; +@import "scss/components/loading-indicators"; +@import "scss/components/memberships"; +@import "scss/components/nav"; +@import "scss/components/payments-by"; +@import "scss/components/profile-statement"; +@import "scss/components/sidebar"; +@import "scss/components/sign_in"; +@import "scss/components/status-icon"; +@import "scss/components/support_gratipay"; +@import "scss/components/table"; +@import "scss/components/tip-distribution"; +@import "scss/components/tipr"; +@import "scss/components/upgrade"; + +@import "scss/fragments/accounts"; +@import "scss/fragments/members"; +@import "scss/fragments/mini-user"; +@import "scss/fragments/notifications"; +@import "scss/fragments/pagination"; + +@import "scss/layouts/layout"; +@import "scss/layouts/responsiveness"; + +@import "scss/pages/homepage"; +@import "scss/pages/history"; +@import "scss/pages/team"; +@import "scss/pages/profile-edit"; +@import "scss/pages/giving"; +@import "scss/pages/settings"; +@import "scss/pages/cc-ba"; +@import "scss/pages/on-confirm"; +@import "scss/pages/search"; +@import "scss/pages/hall-of-fame";