diff options
author | Andreas Stöckel <astoecke@techfak.uni-bielefeld.de> | 2015-03-22 01:14:35 +0100 |
---|---|---|
committer | Andreas Stöckel <astoecke@techfak.uni-bielefeld.de> | 2015-03-22 01:14:35 +0100 |
commit | 93119091893c2f2bf396785e6bd7dcbb9b4f207d (patch) | |
tree | 408e37ec0bf4ebb1e5427f37b1e12344f70eae9c | |
parent | f6fc9fa292e8da56fe7078f9ac5bdfefa55abe94 (diff) |
* Implement code highlighting using Prism
* Automatically build ousia.js
-rw-r--r-- | Makefile | 9 | ||||
-rw-r--r-- | script/highlight.js | 29 | ||||
-rw-r--r-- | script/prism.js | 428 | ||||
-rw-r--r-- | script/prism_bash.js (renamed from script/ousia.js) | 19 | ||||
-rw-r--r-- | style/prism.css | 137 | ||||
-rw-r--r-- | style/style.less | 3 | ||||
-rw-r--r-- | xsl/webpage.xsl | 9 |
7 files changed, 622 insertions, 12 deletions
@@ -15,7 +15,7 @@ TARGET_XML=$(SOURCE_OSML:.osml=.xml) TARGET_HTML=$(SOURCE_OSML:.osml=.html) # Builds the style and all webpages -all: style/style.css $(TARGET_XML) $(TARGET_HTML) +all: script/ousia.js style/style.css $(TARGET_XML) $(TARGET_HTML) # Build the stylesheet using lessc # On Fedora you can install lessc using the following commands (as root): @@ -24,6 +24,13 @@ all: style/style.css $(TARGET_XML) $(TARGET_HTML) style/style.css: style/*.less lessc style/style.less style/style.css --clean-css="--s1" +# Build the JavaScript using uglifyjs +# On Fedora you can install uglifyjs using the following commands (as root): +# yum install nodejs npm +# npm install -g uglifyjs +script/ousia.js: script/prism.js script/prism_bash.js script/highlight.js + uglifyjs script/prism.js script/prism_bash.js script/highlight.js > script/ousia.js + # Compile all osml files to xml %.xml: %.osml ontology/*.osml ousia -F xml -o $@ $< diff --git a/script/highlight.js b/script/highlight.js new file mode 100644 index 0000000..c50c172 --- /dev/null +++ b/script/highlight.js @@ -0,0 +1,29 @@ +// @license magnet:?xt=urn:btih:5305d91886084f776adcf57509a648432709a7c7&dn=x11.txt +/* + * Ousía Website JS + * + * (c) Andreas Stöckel, 2015 + * + * This work is licensed under the X11 (MIT) license. + * http://www.opensource.org/licenses/mit-license.php + */ +(function () { + "use strict"; + + /* Enable code highlighting (this will eventually be natively supported by + Ousía) */ + document.addEventListener("DOMContentLoaded", function(event) { + var codeBlocks = document.querySelectorAll("pre.code[data-lang]"); + for (var i = 0; i < codeBlocks.length; i++) { + var block = codeBlocks[i]; + var lang = block.getAttribute("data-lang"); + var text = block.textContent; + if (lang in Prism.languages) { + var res = Prism.highlight(text, Prism.languages[lang]); + block.innerHTML = res; + } + } + }); +})(); +// @license-end + diff --git a/script/prism.js b/script/prism.js new file mode 100644 index 0000000..fc62daa --- /dev/null +++ b/script/prism.js @@ -0,0 +1,428 @@ +/* http://prismjs.com/download.html?themes=prism */ +self = (typeof window !== 'undefined') + ? window // if in browser + : ( + (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) + ? self // if in worker + : {} // if in node js + ); + +/** + * Prism: Lightweight, robust, elegant syntax highlighting + * MIT license http://www.opensource.org/licenses/mit-license.php/ + * @author Lea Verou http://lea.verou.me + */ + +var Prism = (function(){ + +// Private helper vars +var lang = /\blang(?:uage)?-(?!\*)(\w+)\b/i; + +var _ = self.Prism = { + util: { + encode: function (tokens) { + if (tokens instanceof Token) { + return new Token(tokens.type, _.util.encode(tokens.content), tokens.alias); + } else if (_.util.type(tokens) === 'Array') { + return tokens.map(_.util.encode); + } else { + return tokens.replace(/&/g, '&').replace(/</g, '<').replace(/\u00a0/g, ' '); + } + }, + + type: function (o) { + return Object.prototype.toString.call(o).match(/\[object (\w+)\]/)[1]; + }, + + // Deep clone a language definition (e.g. to extend it) + clone: function (o) { + var type = _.util.type(o); + + switch (type) { + case 'Object': + var clone = {}; + + for (var key in o) { + if (o.hasOwnProperty(key)) { + clone[key] = _.util.clone(o[key]); + } + } + + return clone; + + case 'Array': + return o.map(function(v) { return _.util.clone(v); }); + } + + return o; + } + }, + + languages: { + extend: function (id, redef) { + var lang = _.util.clone(_.languages[id]); + + for (var key in redef) { + lang[key] = redef[key]; + } + + return lang; + }, + + /** + * Insert a token before another token in a language literal + * As this needs to recreate the object (we cannot actually insert before keys in object literals), + * we cannot just provide an object, we need anobject and a key. + * @param inside The key (or language id) of the parent + * @param before The key to insert before. If not provided, the function appends instead. + * @param insert Object with the key/value pairs to insert + * @param root The object that contains `inside`. If equal to Prism.languages, it can be omitted. + */ + insertBefore: function (inside, before, insert, root) { + root = root || _.languages; + var grammar = root[inside]; + + if (arguments.length == 2) { + insert = arguments[1]; + + for (var newToken in insert) { + if (insert.hasOwnProperty(newToken)) { + grammar[newToken] = insert[newToken]; + } + } + + return grammar; + } + + var ret = {}; + + for (var token in grammar) { + + if (grammar.hasOwnProperty(token)) { + + if (token == before) { + + for (var newToken in insert) { + + if (insert.hasOwnProperty(newToken)) { + ret[newToken] = insert[newToken]; + } + } + } + + ret[token] = grammar[token]; + } + } + + // Update references in other language definitions + _.languages.DFS(_.languages, function(key, value) { + if (value === root[inside] && key != inside) { + this[key] = ret; + } + }); + + return root[inside] = ret; + }, + + // Traverse a language definition with Depth First Search + DFS: function(o, callback, type) { + for (var i in o) { + if (o.hasOwnProperty(i)) { + callback.call(o, i, o[i], type || i); + + if (_.util.type(o[i]) === 'Object') { + _.languages.DFS(o[i], callback); + } + else if (_.util.type(o[i]) === 'Array') { + _.languages.DFS(o[i], callback, i); + } + } + } + } + }, + + highlightAll: function(async, callback) { + var elements = document.querySelectorAll('code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'); + + for (var i=0, element; element = elements[i++];) { + _.highlightElement(element, async === true, callback); + } + }, + + highlightElement: function(element, async, callback) { + // Find language + var language, grammar, parent = element; + + while (parent && !lang.test(parent.className)) { + parent = parent.parentNode; + } + + if (parent) { + language = (parent.className.match(lang) || [,''])[1]; + grammar = _.languages[language]; + } + + if (!grammar) { + return; + } + + // Set language on the element, if not present + element.className = element.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language; + + // Set language on the parent, for styling + parent = element.parentNode; + + if (/pre/i.test(parent.nodeName)) { + parent.className = parent.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language; + } + + var code = element.textContent; + + if(!code) { + return; + } + + code = code.replace(/^(?:\r?\n|\r)/,''); + + var env = { + element: element, + language: language, + grammar: grammar, + code: code + }; + + _.hooks.run('before-highlight', env); + + if (async && self.Worker) { + var worker = new Worker(_.filename); + + worker.onmessage = function(evt) { + env.highlightedCode = Token.stringify(JSON.parse(evt.data), language); + + _.hooks.run('before-insert', env); + + env.element.innerHTML = env.highlightedCode; + + callback && callback.call(env.element); + _.hooks.run('after-highlight', env); + }; + + worker.postMessage(JSON.stringify({ + language: env.language, + code: env.code + })); + } + else { + env.highlightedCode = _.highlight(env.code, env.grammar, env.language); + + _.hooks.run('before-insert', env); + + env.element.innerHTML = env.highlightedCode; + + callback && callback.call(element); + + _.hooks.run('after-highlight', env); + } + }, + + highlight: function (text, grammar, language) { + var tokens = _.tokenize(text, grammar); + return Token.stringify(_.util.encode(tokens), language); + }, + + tokenize: function(text, grammar, language) { + var Token = _.Token; + + var strarr = [text]; + + var rest = grammar.rest; + + if (rest) { + for (var token in rest) { + grammar[token] = rest[token]; + } + + delete grammar.rest; + } + + tokenloop: for (var token in grammar) { + if(!grammar.hasOwnProperty(token) || !grammar[token]) { + continue; + } + + var patterns = grammar[token]; + patterns = (_.util.type(patterns) === "Array") ? patterns : [patterns]; + + for (var j = 0; j < patterns.length; ++j) { + var pattern = patterns[j], + inside = pattern.inside, + lookbehind = !!pattern.lookbehind, + lookbehindLength = 0, + alias = pattern.alias; + + pattern = pattern.pattern || pattern; + + for (var i=0; i<strarr.length; i++) { // Don’t cache length as it changes during the loop + + var str = strarr[i]; + + if (strarr.length > text.length) { + // Something went terribly wrong, ABORT, ABORT! + break tokenloop; + } + + if (str instanceof Token) { + continue; + } + + pattern.lastIndex = 0; + + var match = pattern.exec(str); + + if (match) { + if(lookbehind) { + lookbehindLength = match[1].length; + } + + var from = match.index - 1 + lookbehindLength, + match = match[0].slice(lookbehindLength), + len = match.length, + to = from + len, + before = str.slice(0, from + 1), + after = str.slice(to + 1); + + var args = [i, 1]; + + if (before) { + args.push(before); + } + + var wrapped = new Token(token, inside? _.tokenize(match, inside) : match, alias); + + args.push(wrapped); + + if (after) { + args.push(after); + } + + Array.prototype.splice.apply(strarr, args); + } + } + } + } + + return strarr; + }, + + hooks: { + all: {}, + + add: function (name, callback) { + var hooks = _.hooks.all; + + hooks[name] = hooks[name] || []; + + hooks[name].push(callback); + }, + + run: function (name, env) { + var callbacks = _.hooks.all[name]; + + if (!callbacks || !callbacks.length) { + return; + } + + for (var i=0, callback; callback = callbacks[i++];) { + callback(env); + } + } + } +}; + +var Token = _.Token = function(type, content, alias) { + this.type = type; + this.content = content; + this.alias = alias; +}; + +Token.stringify = function(o, language, parent) { + if (typeof o == 'string') { + return o; + } + + if (_.util.type(o) === 'Array') { + return o.map(function(element) { + return Token.stringify(element, language, o); + }).join(''); + } + + var env = { + type: o.type, + content: Token.stringify(o.content, language, parent), + tag: 'span', + classes: ['token', o.type], + attributes: {}, + language: language, + parent: parent + }; + + if (env.type == 'comment') { + env.attributes['spellcheck'] = 'true'; + } + + if (o.alias) { + var aliases = _.util.type(o.alias) === 'Array' ? o.alias : [o.alias]; + Array.prototype.push.apply(env.classes, aliases); + } + + _.hooks.run('wrap', env); + + var attributes = ''; + + for (var name in env.attributes) { + attributes += name + '="' + (env.attributes[name] || '') + '"'; + } + + return '<' + env.tag + ' class="' + env.classes.join(' ') + '" ' + attributes + '>' + env.content + '</' + env.tag + '>'; + +}; + +if (!self.document) { + if (!self.addEventListener) { + // in Node.js + return self.Prism; + } + // In worker + self.addEventListener('message', function(evt) { + var message = JSON.parse(evt.data), + lang = message.language, + code = message.code; + + self.postMessage(JSON.stringify(_.util.encode(_.tokenize(code, _.languages[lang])))); + self.close(); + }, false); + + return self.Prism; +} + +// Get current script and highlight +var script = document.getElementsByTagName('script'); + +script = script[script.length - 1]; + +if (script) { + _.filename = script.src; + + if (document.addEventListener && !script.hasAttribute('data-manual')) { + document.addEventListener('DOMContentLoaded', _.highlightAll); + } +} + +return self.Prism; + +})(); + +if (typeof module !== 'undefined' && module.exports) { + module.exports = Prism; +} +; diff --git a/script/ousia.js b/script/prism_bash.js index 0ef203a..24d5025 100644 --- a/script/ousia.js +++ b/script/prism_bash.js @@ -7,16 +7,15 @@ * This work is licensed under the X11 (MIT) license. * http://www.opensource.org/licenses/mit-license.php */ + (function () { - "use strict"; - var header = document.querySelector("header"); - window.addEventListener("scroll", function () { - if (window.pageYOffset > 40) { - header.className = "scrolled"; - } else { - header.className = ""; - } - }); +"use strict"; +Prism.languages.bash = { + 'comment': /#.*$/, + 'string': /("|')(\\\n|\\?.)*?\1/, + 'url': /http:\/\/[^\s]*/, + 'keyword': /(^| )(cd|mkdir|git|echo|clone|sudo|yum|apt-get|cmake|make|install)( |$)/ +}; })(); -// @license-end +// @license-end diff --git a/style/prism.css b/style/prism.css new file mode 100644 index 0000000..81b7382 --- /dev/null +++ b/style/prism.css @@ -0,0 +1,137 @@ +/* http://prismjs.com/download.html?themes=prism */ +/** + * prism.js default theme for JavaScript, CSS and HTML + * Based on dabblet (http://dabblet.com) + * @author Lea Verou + */ + +code[class*="language-"], +pre[class*="language-"] { + color: black; + text-shadow: 0 1px white; + font-family: Consolas, Monaco, 'Andale Mono', monospace; + direction: ltr; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection, +code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection { + text-shadow: none; + background: #b3d4fc; +} + +pre[class*="language-"]::selection, pre[class*="language-"] ::selection, +code[class*="language-"]::selection, code[class*="language-"] ::selection { + text-shadow: none; + background: #b3d4fc; +} + +@media print { + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; +} + +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: #f5f2f0; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: .1em; + border-radius: .3em; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: slategray; +} + +.token.punctuation { + color: #999; +} + +.namespace { + opacity: .7; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol, +.token.deleted { + color: #905; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: #690; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: #a67f59; + background: hsla(0, 0%, 100%, .5); +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: #07a; +} + +.token.function { + color: #DD4A68; +} + +.token.regex, +.token.important, +.token.variable { + color: #e90; +} + +.token.important, +.token.bold { + font-weight: bold; +} +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} + diff --git a/style/style.less b/style/style.less index 3527378..f5603d0 100644 --- a/style/style.less +++ b/style/style.less @@ -13,6 +13,9 @@ @import "footer.less"; @import "header.less"; +/* Import the prism code highlighter stylesheet */ +@import "prism.css"; + span.ipa { color: gray; } diff --git a/xsl/webpage.xsl b/xsl/webpage.xsl index b66a594..fcb5d8a 100644 --- a/xsl/webpage.xsl +++ b/xsl/webpage.xsl @@ -79,6 +79,7 @@ </p> </section> </footer> + <script src="script/ousia.js"/> </body> </html> </xsl:template> @@ -225,7 +226,13 @@ </xsl:template> <xsl:template match="webpage:code"> - <pre class="code"> + <pre> + <xsl:attribute name="class">code</xsl:attribute> + <xsl:if test="@lang != ''"> + <xsl:attribute name="data-lang"> + <xsl:value-of select="@lang"/> + </xsl:attribute> + </xsl:if> <xsl:apply-templates select="webpage:*"/> </pre> </xsl:template> |