Subreddit CSS: Make /about/stylesheet show unminified CSS.

This is for non-moderators to be able to view the commented / unminified
CSS so they can learn / cherry-pick bits from it.

This uses highlight.js from
isagalaev/highlight.js@45c03cd045.
This commit is contained in:
Neil Williams
2012-09-27 08:01:04 -07:00
parent 4b1651fea4
commit 09de367263
9 changed files with 993 additions and 1 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -123,6 +123,7 @@ menu = MenuHandler(hot = _('hot'),
details = _("details"),
duplicates = _("other discussions (%(num)s)"),
traffic = _("traffic stats"),
stylesheet = _("stylesheet"),
# reddits
home = _("home"),

View File

@@ -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):

View File

@@ -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 <vast@whiteants.net> - 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;
}

View File

@@ -0,0 +1 @@
hljs.initHighlightingOnLoad()

View File

@@ -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, '&amp;').replace(/</gm, '&lt;');
}
function findCode(pre) {
for (var node = pre.firstChild; node; node = node.nextSibling) {
if (node.nodeName == 'CODE')
return node;
if (!(node.nodeType == 3 && node.nodeValue.match(/\s+/)))
break;
}
}
function blockText(block, ignoreNewLines) {
return Array.prototype.map.call(block.childNodes, function(node) {
if (node.nodeType == 3) {
return ignoreNewLines ? node.nodeValue.replace(/\n/g, '') : node.nodeValue;
}
if (node.nodeName == 'BR') {
return '\n';
}
return blockText(node, ignoreNewLines);
}).join('');
}
function blockLanguage(block) {
var classes = (block.className + ' ' + block.parentNode.className).split(/\s+/);
classes = classes.map(function(c) {return c.replace(/^language-/, '')});
for (var i = 0; i < classes.length; i++) {
if (languages[classes[i]] || classes[i] == 'no-highlight') {
return classes[i];
}
}
}
/* Stream merging */
function nodeStream(node) {
var result = [];
(function _nodeStream(node, offset) {
for (var child = node.firstChild; child; child = child.nextSibling) {
if (child.nodeType == 3)
offset += child.nodeValue.length;
else if (child.nodeName == 'BR')
offset += 1;
else if (child.nodeType == 1) {
result.push({
event: 'start',
offset: offset,
node: child
});
offset = _nodeStream(child, offset);
result.push({
event: 'stop',
offset: offset,
node: child
});
}
}
return offset;
})(node, 0);
return result;
}
function mergeStreams(stream1, stream2, value) {
var processed = 0;
var result = '';
var nodeStack = [];
function selectStream() {
if (stream1.length && stream2.length) {
if (stream1[0].offset != stream2[0].offset)
return (stream1[0].offset < stream2[0].offset) ? stream1 : stream2;
else {
/*
To avoid starting the stream just before it should stop the order is
ensured that stream1 always starts first and closes last:
if (event1 == 'start' && event2 == 'start')
return stream1;
if (event1 == 'start' && event2 == 'stop')
return stream2;
if (event1 == 'stop' && event2 == 'start')
return stream1;
if (event1 == 'stop' && event2 == 'stop')
return stream2;
... which is collapsed to:
*/
return stream2[0].event == 'start' ? stream1 : stream2;
}
} else {
return stream1.length ? stream1 : stream2;
}
}
function open(node) {
function attr_str(a) {return ' ' + a.nodeName + '="' + escape(a.value) + '"'};
return '<' + node.nodeName + Array.prototype.map.call(node.attributes, attr_str).join('') + '>';
}
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 += ('</' + node.nodeName.toLowerCase() + '>');
} 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 += '<span class="'+ keyword_match[0] +'">' + match[0] + '</span>';
} 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 '<span class="' + result.language + '">' + result.value + '</span>';
}
function processBuffer() {
return top.subLanguage !== undefined ? processSubLanguage() : processKeywords();
}
function startNewMode(mode, lexem) {
var markup = mode.className? '<span class="' + 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 += '</span>';
}
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 '<br>' 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, '<br>');
}
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 <pre><code>..</code></pre> 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 doesnt 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);

View File

@@ -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
%>
<link rel="stylesheet" type="text/css" href="${static("highlight.css")}">
<pre class="subreddit-stylesheet-source">
<code class="language-css">${unsafe(SC_OFF)}${thing.stylesheet_contents}${unsafe(SC_ON)}</code>
</pre>
${unsafe(js.use("highlight"))}