diff --git a/r2/Makefile b/r2/Makefile index 0b6923f5f..584c7c711 100644 --- a/r2/Makefile +++ b/r2/Makefile @@ -95,7 +95,7 @@ clean_ini: #################### CSS file lists SPRITED_STYLESHEETS += reddit.css compact.css -OTHER_STYLESHEETS := reddit-ie6-hax.css reddit-ie7-hax.css mobile.css +OTHER_STYLESHEETS := reddit-ie6-hax.css reddit-ie7-hax.css mobile.css highlight.css #################### Static Files STATIC_ROOT := r2/public diff --git a/r2/r2/controllers/front.py b/r2/r2/controllers/front.py index b9341e869..2b27e7adb 100755 --- a/r2/r2/controllers/front.py +++ b/r2/r2/controllers/front.py @@ -617,6 +617,12 @@ class FrontController(RedditController, OAuth2ResourceController): c.allow_styles = True pane = SubredditStylesheet(site = c.site, stylesheet_contents = stylesheet_contents) + elif (location == 'stylesheet' + and c.site.can_view(c.user) + and not g.css_killswitch): + stylesheet = (c.site.stylesheet_contents_user or + c.site.stylesheet_contents) + pane = SubredditStylesheetSource(stylesheet_contents=stylesheet) elif (location in ('reports', 'spam', 'trials', 'modqueue', 'unmoderated') and is_moderator): c.allow_styles = True diff --git a/r2/r2/lib/js.py b/r2/r2/lib/js.py index 4ec646f6a..f2b1f8ba6 100755 --- a/r2/r2/lib/js.py +++ b/r2/r2/lib/js.py @@ -325,6 +325,11 @@ module["qrcode"] = Module("qrcode.js", "qrcode.js", ) +module["highlight"] = Module("highlight.js", + "lib/highlight.pack.js", + "highlight.js", +) + def use(*names): return "\n".join(module[name].use() for name in names) diff --git a/r2/r2/lib/menus.py b/r2/r2/lib/menus.py index 3f84ebbb7..0edb6b8a6 100644 --- a/r2/r2/lib/menus.py +++ b/r2/r2/lib/menus.py @@ -123,6 +123,7 @@ menu = MenuHandler(hot = _('hot'), details = _("details"), duplicates = _("other discussions (%(num)s)"), traffic = _("traffic stats"), + stylesheet = _("stylesheet"), # reddits home = _("home"), diff --git a/r2/r2/lib/pages/pages.py b/r2/r2/lib/pages/pages.py index 90cacb36c..7dc5423c2 100755 --- a/r2/r2/lib/pages/pages.py +++ b/r2/r2/lib/pages/pages.py @@ -1606,6 +1606,11 @@ class SubredditStylesheet(Templated): Templated.__init__(self, site = site, stylesheet_contents = stylesheet_contents) +class SubredditStylesheetSource(Templated): + """A view of the unminified source of a subreddit's stylesheet.""" + def __init__(self, stylesheet_contents): + Templated.__init__(self, stylesheet_contents=stylesheet_contents) + class CssError(Templated): """Rendered error returned to the stylesheet editing page via ajax""" def __init__(self, error): diff --git a/r2/r2/public/static/css/highlight.css b/r2/r2/public/static/css/highlight.css new file mode 100644 index 000000000..95b717b77 --- /dev/null +++ b/r2/r2/public/static/css/highlight.css @@ -0,0 +1,238 @@ +/* subreddit stylesheet source viewer */ +.subreddit-stylesheet-source { + padding: 0.5em; + overflow-x: auto; + margin: 10px 7px; + font-size: medium; + font-family: "Bitstream Vera Sans Mono", Consolas, monospace; +} + +/* github.com style (c) Vasily Polovnyov - from the highlight.js source */ +pre { + color: #333; + background-color: #f8f8ff; +} + +pre .comment, +pre .template_comment, +pre .diff .header, +pre .javadoc { + color: #998; + font-style: italic +} + +pre .keyword, +pre .css .rule .keyword, +pre .winutils, +pre .javascript .title, +pre .nginx .title, +pre .subst, +pre .request, +pre .status { + color: #333; + font-weight: bold +} + +pre .number, +pre .hexcolor, +pre .ruby .constant { + color: #099; +} + +pre .string, +pre .tag .value, +pre .phpdoc, +pre .tex .formula { + color: #d14 +} + +pre .title, +pre .id { + color: #900; + font-weight: bold +} + +pre .javascript .title, +pre .lisp .title, +pre .clojure .title, +pre .subst { + font-weight: normal +} + +pre .class .title, +pre .haskell .type, +pre .vhdl .literal, +pre .tex .command { + color: #458; + font-weight: bold +} + +pre .tag, +pre .tag .title, +pre .rules .property, +pre .django .tag .keyword { + color: #000080; + font-weight: normal +} + +pre .attribute, +pre .variable, +pre .lisp .body { + color: #008080 +} + +pre .regexp { + color: #009926 +} + +pre .class { + color: #458; + font-weight: bold +} + +pre .symbol, +pre .ruby .symbol .string, +pre .lisp .keyword, +pre .tex .special, +pre .input_number { + color: #990073 +} + +pre .built_in, +pre .lisp .title, +pre .clojure .built_in { + color: #0086b3 +} + +pre .preprocessor, +pre .pi, +pre .doctype, +pre .shebang, +pre .cdata { + color: #999; + font-weight: bold +} + +pre .deletion { + background: #fdd +} + +pre .addition { + background: #dfd +} + +pre .diff .change { + background: #0086b3 +} + +pre .chunk { + color: #aaa +} + +/* Monokai style - ported by Luigi Maselli - http://grigio.org - from the highlight.js source */ +.res-nightmode pre { + background: #272822; + color: #ddd; +} + +.res-nightmode pre .tag, +.res-nightmode pre .tag .title, +.res-nightmode pre .keyword, +.res-nightmode pre .literal, +.res-nightmode pre .change, +.res-nightmode pre .winutils, +.res-nightmode pre .flow, +.res-nightmode pre .lisp .title, +.res-nightmode pre .clojure .built_in, +.res-nightmode pre .nginx .title, +.res-nightmode pre .tex .special { + color: #F92672; +} + +.res-nightmode pre code .constant { + color: #66D9EF; +} + +.res-nightmode pre .class .title { + color: white; +} + +.res-nightmode pre .attribute, +.res-nightmode pre .symbol, +.res-nightmode pre .symbol .string, +.res-nightmode pre .value, +.res-nightmode pre .regexp { + color: #BF79DB; +} + +.res-nightmode pre .tag .value, +.res-nightmode pre .string, +.res-nightmode pre .subst, +.res-nightmode pre .title, +.res-nightmode pre .haskell .type, +.res-nightmode pre ..res-nightmode preprocessor, +.res-nightmode pre .ruby .class .parent, +.res-nightmode pre .built_in, +.res-nightmode pre .sql .aggregate, +.res-nightmode pre .django .template_tag, +.res-nightmode pre .django .variable, +.res-nightmode pre .smalltalk .class, +.res-nightmode pre .javadoc, +.res-nightmode pre .django .filter .argument, +.res-nightmode pre .smalltalk .localvars, +.res-nightmode pre .smalltalk .array, +.res-nightmode pre .attr_selector, +.res-nightmode pre .pseudo, +.res-nightmode pre .addition, +.res-nightmode pre .stream, +.res-nightmode pre .envvar, +.res-nightmode pre .apache .tag, +.res-nightmode pre .apache .cbracket, +.res-nightmode pre .tex .command, +.res-nightmode pre .input_number { + color: #A6E22E; +} + +.res-nightmode pre .comment, +.res-nightmode pre .java .annotation, +.res-nightmode pre .python .decorator, +.res-nightmode pre .template_comment, +.res-nightmode pre .pi, +.res-nightmode pre .doctype, +.res-nightmode pre .deletion, +.res-nightmode pre .shebang, +.res-nightmode pre .apache .sqbracket, +.res-nightmode pre .tex .formula { + color: #75715E; +} + +.res-nightmode pre .keyword, +.res-nightmode pre .literal, +.res-nightmode pre .css .id, +.res-nightmode pre .phpdoc, +.res-nightmode pre .title, +.res-nightmode pre .haskell .type, +.res-nightmode pre .vbscript .built_in, +.res-nightmode pre .sql .aggregate, +.res-nightmode pre .rsl .built_in, +.res-nightmode pre .smalltalk .class, +.res-nightmode pre .diff .header, +.res-nightmode pre .chunk, +.res-nightmode pre .winutils, +.res-nightmode pre .bash .variable, +.res-nightmode pre .apache .tag, +.res-nightmode pre .tex .special, +.res-nightmode pre .request, +.res-nightmode pre .status { + font-weight: bold; +} + +.res-nightmode pre .coffeescript .javascript, +.res-nightmode pre .javascript .xml, +.res-nightmode pre .tex .formula, +.res-nightmode pre .xml .javascript, +.res-nightmode pre .xml .vbscript, +.res-nightmode pre .xml .css, +.res-nightmode pre .xml .cdata { + opacity: 0.5; +} diff --git a/r2/r2/public/static/js/highlight.js b/r2/r2/public/static/js/highlight.js new file mode 100644 index 000000000..4d79da605 --- /dev/null +++ b/r2/r2/public/static/js/highlight.js @@ -0,0 +1 @@ +hljs.initHighlightingOnLoad() diff --git a/r2/r2/public/static/js/lib/highlight.pack.js b/r2/r2/public/static/js/lib/highlight.pack.js new file mode 100644 index 000000000..2042277af --- /dev/null +++ b/r2/r2/public/static/js/lib/highlight.pack.js @@ -0,0 +1,701 @@ +/* +Copyright (c) 2006, Ivan Sagalaev +All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of highlight.js nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +var hljs = new function() { + + /* Utility functions */ + + function escape(value) { + return value.replace(/&/gm, '&').replace(/'; + } + + while (stream1.length || stream2.length) { + var current = selectStream().splice(0, 1)[0]; + result += escape(value.substr(processed, current.offset - processed)); + processed = current.offset; + if ( current.event == 'start') { + result += open(current.node); + nodeStack.push(current.node); + } else if (current.event == 'stop') { + var node, i = nodeStack.length; + do { + i--; + node = nodeStack[i]; + result += (''); + } while (node != current.node); + nodeStack.splice(i, 1); + while (i < nodeStack.length) { + result += open(nodeStack[i]); + i++; + } + } + } + return result + escape(value.substr(processed)); + } + + /* Initialization */ + + function compileLanguage(language) { + + function langRe(value, global) { + return RegExp( + value, + 'm' + (language.case_insensitive ? 'i' : '') + (global ? 'g' : '') + ); + } + + function compileMode(mode, parent) { + if (mode.compiled) + return; + mode.compiled = true; + + var keywords = []; // used later with beginWithKeyword but filled as a side-effect of keywords compilation + if (mode.keywords) { + var compiled_keywords = {}; + + function flatten(className, str) { + str.split(' ').forEach(function(kw) { + var pair = kw.split('|'); + compiled_keywords[pair[0]] = [className, pair[1] ? Number(pair[1]) : 1]; + keywords.push(pair[0]); + }); + } + + mode.lexemsRe = langRe(mode.lexems || hljs.IDENT_RE, true); + if (typeof mode.keywords == 'string') { // string + flatten('keyword', mode.keywords) + } else { + for (var className in mode.keywords) { + if (!mode.keywords.hasOwnProperty(className)) + continue; + flatten(className, mode.keywords[className]); + } + } + mode.keywords = compiled_keywords; + } + if (parent) { + if (mode.beginWithKeyword) { + mode.begin = '\\b(' + keywords.join('|') + ')\\s'; + } + mode.beginRe = langRe(mode.begin ? mode.begin : '\\B|\\b'); + if (!mode.end && !mode.endsWithParent) + mode.end = '\\B|\\b'; + if (mode.end) + mode.endRe = langRe(mode.end); + mode.terminator_end = mode.end || ''; + if (mode.endsWithParent && parent.terminator_end) + mode.terminator_end += (mode.end ? '|' : '') + parent.terminator_end; + } + if (mode.illegal) + mode.illegalRe = langRe(mode.illegal); + if (mode.relevance === undefined) + mode.relevance = 1; + if (!mode.contains) { + mode.contains = []; + } + for (var i = 0; i < mode.contains.length; i++) { + if (mode.contains[i] == 'self') { + mode.contains[i] = mode; + } + compileMode(mode.contains[i], mode); + } + if (mode.starts) { + compileMode(mode.starts, parent); + } + + var terminators = []; + for (var i = 0; i < mode.contains.length; i++) { + terminators.push(mode.contains[i].begin); + } + if (mode.terminator_end) { + terminators.push(mode.terminator_end); + } + if (mode.illegal) { + terminators.push(mode.illegal); + } + mode.terminators = terminators.length ? langRe(terminators.join('|'), true) : {exec: function(s) {return null;}}; + } + + compileMode(language); + } + + /* + Core highlighting function. Accepts a language name and a string with the + code to highlight. Returns an object with the following properties: + + - relevance (int) + - keyword_count (int) + - value (an HTML string with highlighting markup) + + */ + function highlight(language_name, value) { + + function subMode(lexem, mode) { + for (var i = 0; i < mode.contains.length; i++) { + var match = mode.contains[i].beginRe.exec(lexem); + if (match && match.index == 0) { + return mode.contains[i]; + } + } + } + + function endOfMode(mode, lexem) { + if (mode.end && mode.endRe.test(lexem)) { + return mode; + } + if (mode.endsWithParent) { + return endOfMode(mode.parent, lexem); + } + } + + function isIllegal(lexem, mode) { + return mode.illegal && mode.illegalRe.test(lexem); + } + + function keywordMatch(mode, match) { + var match_str = language.case_insensitive ? match[0].toLowerCase() : match[0]; + return mode.keywords.hasOwnProperty(match_str) && mode.keywords[match_str]; + } + + function processKeywords() { + var buffer = escape(mode_buffer); + if (!top.keywords) + return buffer; + var result = ''; + var last_index = 0; + top.lexemsRe.lastIndex = 0; + var match = top.lexemsRe.exec(buffer); + while (match) { + result += buffer.substr(last_index, match.index - last_index); + var keyword_match = keywordMatch(top, match); + if (keyword_match) { + keyword_count += keyword_match[1]; + result += '' + match[0] + ''; + } else { + result += match[0]; + } + last_index = top.lexemsRe.lastIndex; + match = top.lexemsRe.exec(buffer); + } + return result + buffer.substr(last_index); + } + + function processSubLanguage() { + if (top.subLanguage && !languages[top.subLanguage]) { + return escape(mode_buffer); + } + var result = top.subLanguage ? highlight(top.subLanguage, mode_buffer) : highlightAuto(mode_buffer); + // Counting embedded language score towards the host language may be disabled + // with zeroing the containing mode relevance. Usecase in point is Markdown that + // allows XML everywhere and makes every XML snippet to have a much larger Markdown + // score. + if (top.relevance > 0) { + keyword_count += result.keyword_count; + relevance += result.relevance; + } + return '' + result.value + ''; + } + + function processBuffer() { + return top.subLanguage !== undefined ? processSubLanguage() : processKeywords(); + } + + function startNewMode(mode, lexem) { + var markup = mode.className? '': ''; + if (mode.returnBegin) { + result += markup; + mode_buffer = ''; + } else if (mode.excludeBegin) { + result += escape(lexem) + markup; + mode_buffer = ''; + } else { + result += markup; + mode_buffer = lexem; + } + top = Object.create(mode, {parent: {value: top}}); + relevance += mode.relevance; + } + + function processModeInfo(buffer, lexem) { + mode_buffer += buffer; + if (lexem === undefined) { + result += processBuffer(); + return; + } + + var new_mode = subMode(lexem, top); + if (new_mode) { + result += processBuffer(); + startNewMode(new_mode, lexem); + return new_mode.returnBegin; + } + + var end_mode = endOfMode(top, lexem); + if (end_mode) { + if (!(end_mode.returnEnd || end_mode.excludeEnd)) { + mode_buffer += lexem; + } + result += processBuffer(); + do { + if (top.className) { + result += ''; + } + top = top.parent; + } while (top != end_mode.parent); + if (end_mode.excludeEnd) { + result += escape(lexem); + } + mode_buffer = ''; + if (end_mode.starts) { + startNewMode(end_mode.starts, ''); + } + return end_mode.returnEnd; + } + + if (isIllegal(lexem, top)) + throw 'Illegal'; + } + + var language = languages[language_name]; + compileLanguage(language); + var top = language; + var mode_buffer = ''; + var relevance = 0; + var keyword_count = 0; + var result = ''; + try { + var match, index = 0; + while (true) { + top.terminators.lastIndex = index; + match = top.terminators.exec(value); + if (!match) + break; + var return_lexem = processModeInfo(value.substr(index, match.index - index), match[0]); + index = match.index + (return_lexem ? 0 : match[0].length); + } + processModeInfo(value.substr(index), undefined); + return { + relevance: relevance, + keyword_count: keyword_count, + value: result, + language: language_name + }; + } catch (e) { + if (e == 'Illegal') { + return { + relevance: 0, + keyword_count: 0, + value: escape(value) + }; + } else { + throw e; + } + } + } + + /* + Highlighting with language detection. Accepts a string with the code to + highlight. Returns an object with the following properties: + + - language (detected language) + - relevance (int) + - keyword_count (int) + - value (an HTML string with highlighting markup) + - second_best (object with the same structure for second-best heuristically + detected language, may be absent) + + */ + function highlightAuto(text) { + var result = { + keyword_count: 0, + relevance: 0, + value: escape(text) + }; + var second_best = result; + for (var key in languages) { + if (!languages.hasOwnProperty(key)) + continue; + var current = highlight(key, text); + current.language = key; + if (current.keyword_count + current.relevance > second_best.keyword_count + second_best.relevance) { + second_best = current; + } + if (current.keyword_count + current.relevance > result.keyword_count + result.relevance) { + second_best = result; + result = current; + } + } + if (second_best.language) { + result.second_best = second_best; + } + return result; + } + + /* + Post-processing of the highlighted markup: + + - replace TABs with something more useful + - replace real line-breaks with '
' for non-pre containers + + */ + function fixMarkup(value, tabReplace, useBR) { + if (tabReplace) { + value = value.replace(/^((<[^>]+>|\t)+)/gm, function(match, p1, offset, s) { + return p1.replace(/\t/g, tabReplace); + }); + } + if (useBR) { + value = value.replace(/\n/g, '
'); + } + return value; + } + + /* + Applies highlighting to a DOM node containing code. Accepts a DOM node and + two optional parameters for fixMarkup. + */ + function highlightBlock(block, tabReplace, useBR) { + var text = blockText(block, useBR); + var language = blockLanguage(block); + if (language == 'no-highlight') + return; + var result = language ? highlight(language, text) : highlightAuto(text); + language = result.language; + var original = nodeStream(block); + if (original.length) { + var pre = document.createElement('pre'); + pre.innerHTML = result.value; + result.value = mergeStreams(original, nodeStream(pre), text); + } + result.value = fixMarkup(result.value, tabReplace, useBR); + + var class_name = block.className; + if (!class_name.match('(\\s|^)(language-)?' + language + '(\\s|$)')) { + class_name = class_name ? (class_name + ' ' + language) : language; + } + block.innerHTML = result.value; + block.className = class_name; + block.result = { + language: language, + kw: result.keyword_count, + re: result.relevance + }; + if (result.second_best) { + block.second_best = { + language: result.second_best.language, + kw: result.second_best.keyword_count, + re: result.second_best.relevance + }; + } + } + + /* + Applies highlighting to all
..
blocks on a page. + */ + function initHighlighting() { + if (initHighlighting.called) + return; + initHighlighting.called = true; + Array.prototype.map.call(document.getElementsByTagName('pre'), findCode). + filter(Boolean). + forEach(function(code){highlightBlock(code, hljs.tabReplace)}); + } + + /* + Attaches highlighting to the page load event. + */ + function initHighlightingOnLoad() { + window.addEventListener('DOMContentLoaded', initHighlighting, false); + window.addEventListener('load', initHighlighting, false); + } + + var languages = {}; // a shortcut to avoid writing "this." everywhere + + /* Interface definition */ + + this.LANGUAGES = languages; + this.highlight = highlight; + this.highlightAuto = highlightAuto; + this.fixMarkup = fixMarkup; + this.highlightBlock = highlightBlock; + this.initHighlighting = initHighlighting; + this.initHighlightingOnLoad = initHighlightingOnLoad; + + // Common regexps + this.IDENT_RE = '[a-zA-Z][a-zA-Z0-9_]*'; + this.UNDERSCORE_IDENT_RE = '[a-zA-Z_][a-zA-Z0-9_]*'; + this.NUMBER_RE = '\\b\\d+(\\.\\d+)?'; + this.C_NUMBER_RE = '(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)'; // 0x..., 0..., decimal, float + this.BINARY_NUMBER_RE = '\\b(0b[01]+)'; // 0b... + this.RE_STARTERS_RE = '!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|\\.|-|-=|/|/=|:|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~'; + + // Common modes + this.BACKSLASH_ESCAPE = { + begin: '\\\\[\\s\\S]', relevance: 0 + }; + this.APOS_STRING_MODE = { + className: 'string', + begin: '\'', end: '\'', + illegal: '\\n', + contains: [this.BACKSLASH_ESCAPE], + relevance: 0 + }; + this.QUOTE_STRING_MODE = { + className: 'string', + begin: '"', end: '"', + illegal: '\\n', + contains: [this.BACKSLASH_ESCAPE], + relevance: 0 + }; + this.C_LINE_COMMENT_MODE = { + className: 'comment', + begin: '//', end: '$' + }; + this.C_BLOCK_COMMENT_MODE = { + className: 'comment', + begin: '/\\*', end: '\\*/' + }; + this.HASH_COMMENT_MODE = { + className: 'comment', + begin: '#', end: '$' + }; + this.NUMBER_MODE = { + className: 'number', + begin: this.NUMBER_RE, + relevance: 0 + }; + this.C_NUMBER_MODE = { + className: 'number', + begin: this.C_NUMBER_RE, + relevance: 0 + }; + this.BINARY_NUMBER_MODE = { + className: 'number', + begin: this.BINARY_NUMBER_RE, + relevance: 0 + }; + + // Utility functions + this.inherit = function(parent, obj) { + var result = {} + for (var key in parent) + result[key] = parent[key]; + if (obj) + for (var key in obj) + result[key] = obj[key]; + return result; + } +}(); +hljs.LANGUAGES['css'] = function(hljs) { + var FUNCTION = { + className: 'function', + begin: hljs.IDENT_RE + '\\(', end: '\\)', + contains: [hljs.NUMBER_MODE, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE] + }; + return { + case_insensitive: true, + illegal: '[=/|\']', + contains: [ + hljs.C_BLOCK_COMMENT_MODE, + { + className: 'id', begin: '\\#[A-Za-z0-9_-]+' + }, + { + className: 'class', begin: '\\.[A-Za-z0-9_-]+', + relevance: 0 + }, + { + className: 'attr_selector', + begin: '\\[', end: '\\]', + illegal: '$' + }, + { + className: 'pseudo', + begin: ':(:)?[a-zA-Z0-9\\_\\-\\+\\(\\)\\"\\\']+' + }, + { + className: 'at_rule', + begin: '@(font-face|page)', + lexems: '[a-z-]+', + keywords: 'font-face page' + }, + { + className: 'at_rule', + begin: '@', end: '[{;]', // at_rule eating first "{" is a good thing + // because it doesn’t let it to be parsed as + // a rule set but instead drops parser into + // the default mode which is how it should be. + excludeEnd: true, + keywords: 'import page media charset', + contains: [ + FUNCTION, + hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, + hljs.NUMBER_MODE + ] + }, + { + className: 'tag', begin: hljs.IDENT_RE, + relevance: 0 + }, + { + className: 'rules', + begin: '{', end: '}', + illegal: '[^\\s]', + relevance: 0, + contains: [ + hljs.C_BLOCK_COMMENT_MODE, + { + className: 'rule', + begin: '[^\\s]', returnBegin: true, end: ';', endsWithParent: true, + contains: [ + { + className: 'attribute', + begin: '[A-Z\\_\\.\\-]+', end: ':', + excludeEnd: true, + illegal: '[^\\s]', + starts: { + className: 'value', + endsWithParent: true, excludeEnd: true, + contains: [ + FUNCTION, + hljs.NUMBER_MODE, + hljs.QUOTE_STRING_MODE, + hljs.APOS_STRING_MODE, + hljs.C_BLOCK_COMMENT_MODE, + { + className: 'hexcolor', begin: '\\#[0-9A-F]+' + }, + { + className: 'important', begin: '!important' + } + ] + } + } + ] + } + ] + } + ] + }; +}(hljs); diff --git a/r2/r2/templates/subredditstylesheetsource.html b/r2/r2/templates/subredditstylesheetsource.html new file mode 100644 index 000000000..b416ad2b3 --- /dev/null +++ b/r2/r2/templates/subredditstylesheetsource.html @@ -0,0 +1,35 @@ +## The contents of this file are subject to the Common Public Attribution +## License Version 1.0. (the "License"); you may not use this file except in +## compliance with the License. You may obtain a copy of the License at +## http://code.reddit.com/LICENSE. The License is based on the Mozilla Public +## License Version 1.1, but Sections 14 and 15 have been added to cover use of +## software over a computer network and provide for limited attribution for the +## Original Developer. In addition, Exhibit A has been modified to be +## consistent with Exhibit B. +## +## Software distributed under the License is distributed on an "AS IS" basis, +## WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for +## the specific language governing rights and limitations under the License. +## +## The Original Code is reddit. +## +## The Original Developer is the Initial Developer. The Initial Developer of +## the Original Code is reddit Inc. +## +## All portions of the code written by reddit are Copyright (c) 2006-2012 +## reddit Inc. All Rights Reserved. +############################################################################### + +<%! + from r2.lib import js + from r2.lib.filters import SC_OFF, SC_ON + from r2.lib.template_helpers import static +%> + + + +
+${unsafe(SC_OFF)}${thing.stylesheet_contents}${unsafe(SC_ON)}
+
+ +${unsafe(js.use("highlight"))}