diff --git a/bin/gollum b/bin/gollum index dc8aeea4..80faedce 100755 --- a/bin/gollum +++ b/bin/gollum @@ -149,9 +149,9 @@ MSG opts.on("--no-display-metadata", "Do not render metadata tables in pages.") do wiki_options[:display_metadata] = false end - opts.on("--user-icons [MODE]", [:gravatar, :identicon, :none], "Use specific user-icons for history view.", - "Can be set to 'gravatar', 'identicon' or 'none'. Default: 'none'.") do |mode| - wiki_options[:user_icons] = mode + opts.on("--user-icons [MODE]", [:gravatar, :identicon], "Use specific user-icons for history view.", + "Can be set to 'gravatar' or 'identicon'. Default: standard avatar.") do |mode| + wiki_options[:user_icons] = mode.to_s end opts.on("--template-dir [PATH]", "Specify custom mustache template directory.") do |path| wiki_options[:template_dir] = path diff --git a/lib/gollum/app.rb b/lib/gollum/app.rb index 1676f760..915e24e3 100644 --- a/lib/gollum/app.rb +++ b/lib/gollum/app.rb @@ -17,6 +17,7 @@ require 'gollum/views/helpers' require 'gollum/views/layout' require 'gollum/views/editable' require 'gollum/views/has_page' +require 'gollum/views/has_user_icons' require 'gollum/views/pagination' @@ -397,11 +398,12 @@ module Precious end get '/history/*' do - wikip = wiki_page(params[:splat].first) - @name = wikip.fullname - @page = wikip.page - @page_num = [params[:page_num].to_i, 1].max + wikip = wiki_page(params[:splat].first) + @name = wikip.fullname + @page = wikip.page + @page_num = [params[:page_num].to_i, 1].max @max_count = settings.wiki_options.fetch(:pagination_count, 10) + @wiki = @page.wiki unless @page.nil? @versions = @page.versions( per_page: @max_count, diff --git a/lib/gollum/public/gollum/images/man_24.png b/lib/gollum/public/gollum/images/man_24.png deleted file mode 100644 index 0734f1f5..00000000 Binary files a/lib/gollum/public/gollum/images/man_24.png and /dev/null differ diff --git a/lib/gollum/public/gollum/javascript/app.js b/lib/gollum/public/gollum/javascript/app.js index 861c1858..3452f6da 100644 --- a/lib/gollum/public/gollum/javascript/app.js +++ b/lib/gollum/public/gollum/javascript/app.js @@ -1,4 +1,5 @@ //= require jquery-1.7.2.min +//= require identicon //= require mousetrap.min //= require gollum //= require gollum.dialog diff --git a/lib/gollum/public/gollum/javascript/gollum.js.erb b/lib/gollum/public/gollum/javascript/gollum.js.erb index b8e305df..7abdad2e 100755 --- a/lib/gollum/public/gollum/javascript/gollum.js.erb +++ b/lib/gollum/public/gollum/javascript/gollum.js.erb @@ -1,5 +1,12 @@ // Helpers +// Replace broken user icons with 'person' octicon +function brokenAvatarImage(image){ + image.onerror = ''; + image.src = 'data:image/svg+xml;utf8,<%= rocticon_css(:person) %>'; + return true; +} + // Get path for named route, prefixing baseUrl if necessary // Uses the route definitions in /lib/gollum/views/helpers.rb. // For example, routePath('delete') is equivalent to 'delete_path' in the mustache templates. @@ -498,20 +505,17 @@ $(document).ready(function() { }); } - if( $('#wiki-history').length ){ - var lookup = {}; + if( $('#wiki-history').length || $('#page-history').length){ + var options = { + format: 'svg', + background: [255, 255, 255, 255] // rgba white + }; $('img.identicon').each(function(index, element){ - var $item = $(element); - var code = parseInt($item.data('identicon'), 10); - var img_bin = lookup[code]; - if( img_bin === undefined ){ - var size = 16; - var canvas = $('').get(0); - render_identicon(canvas, code, 16); - img_bin = canvas.toDataURL("image/png"); - lookup[code] = img_bin; - } - $item.attr('src', img_bin); + var item = $(element); + var code = item.data('identicon'); + var img_bin = new Identicon(code, options).toString(); + img_bin = 'data:image/svg+xml;base64,' + img_bin; + item.attr('src', img_bin); }); } }); diff --git a/lib/gollum/public/gollum/javascript/identicon.js b/lib/gollum/public/gollum/javascript/identicon.js new file mode 100644 index 00000000..cd351cce --- /dev/null +++ b/lib/gollum/public/gollum/javascript/identicon.js @@ -0,0 +1,205 @@ +/** + * Identicon.js 2.3.3 + * http://github.com/stewartlord/identicon.js + * + * PNGLib required for PNG output + * http://www.xarg.org/download/pnglib.js + * + * Copyright 2018, Stewart Lord + * Released under the BSD license + * http://www.opensource.org/licenses/bsd-license.php + */ + +(function() { + var PNGlib; + if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { + PNGlib = require('./pnglib'); + } else { + PNGlib = window.PNGlib; + } + + var Identicon = function(hash, options){ + if (typeof(hash) !== 'string' || hash.length < 15) { + throw 'A hash of at least 15 characters is required.'; + } + + this.defaults = { + background: [240, 240, 240, 255], + margin: 0.08, + size: 64, + saturation: 0.7, + brightness: 0.5, + format: 'png' + }; + + this.options = typeof(options) === 'object' ? options : this.defaults; + + // backward compatibility with old constructor (hash, size, margin) + if (typeof(arguments[1]) === 'number') { this.options.size = arguments[1]; } + if (arguments[2]) { this.options.margin = arguments[2]; } + + this.hash = hash + this.background = this.options.background || this.defaults.background; + this.size = this.options.size || this.defaults.size; + this.format = this.options.format || this.defaults.format; + this.margin = this.options.margin !== undefined ? this.options.margin : this.defaults.margin; + + // foreground defaults to last 7 chars as hue at 70% saturation, 50% brightness + var hue = parseInt(this.hash.substr(-7), 16) / 0xfffffff; + var saturation = this.options.saturation || this.defaults.saturation; + var brightness = this.options.brightness || this.defaults.brightness; + this.foreground = this.options.foreground || this.hsl2rgb(hue, saturation, brightness); + }; + + Identicon.prototype = { + background: null, + foreground: null, + hash: null, + margin: null, + size: null, + format: null, + + image: function(){ + return this.isSvg() + ? new Svg(this.size, this.foreground, this.background) + : new PNGlib(this.size, this.size, 256); + }, + + render: function(){ + var image = this.image(), + size = this.size, + baseMargin = Math.floor(size * this.margin), + cell = Math.floor((size - (baseMargin * 2)) / 5), + margin = Math.floor((size - cell * 5) / 2), + bg = image.color.apply(image, this.background), + fg = image.color.apply(image, this.foreground); + + // the first 15 characters of the hash control the pixels (even/odd) + // they are drawn down the middle first, then mirrored outwards + var i, color; + for (i = 0; i < 15; i++) { + color = parseInt(this.hash.charAt(i), 16) % 2 ? bg : fg; + if (i < 5) { + this.rectangle(2 * cell + margin, i * cell + margin, cell, cell, color, image); + } else if (i < 10) { + this.rectangle(1 * cell + margin, (i - 5) * cell + margin, cell, cell, color, image); + this.rectangle(3 * cell + margin, (i - 5) * cell + margin, cell, cell, color, image); + } else if (i < 15) { + this.rectangle(0 * cell + margin, (i - 10) * cell + margin, cell, cell, color, image); + this.rectangle(4 * cell + margin, (i - 10) * cell + margin, cell, cell, color, image); + } + } + + return image; + }, + + rectangle: function(x, y, w, h, color, image){ + if (this.isSvg()) { + image.rectangles.push({x: x, y: y, w: w, h: h, color: color}); + } else { + var i, j; + for (i = x; i < x + w; i++) { + for (j = y; j < y + h; j++) { + image.buffer[image.index(i, j)] = color; + } + } + } + }, + + // adapted from: https://gist.github.com/aemkei/1325937 + hsl2rgb: function(h, s, b){ + h *= 6; + s = [ + b += s *= b < .5 ? b : 1 - b, + b - h % 1 * s * 2, + b -= s *= 2, + b, + b + h % 1 * s, + b + s + ]; + + return[ + s[ ~~h % 6 ] * 255, // red + s[ (h|16) % 6 ] * 255, // green + s[ (h|8) % 6 ] * 255 // blue + ]; + }, + + toString: function(raw){ + // backward compatibility with old toString, default to base64 + if (raw) { + return this.render().getDump(); + } else { + return this.render().getBase64(); + } + }, + + isSvg: function(){ + return this.format.match(/svg/i) + } + }; + + var Svg = function(size, foreground, background){ + this.size = size; + this.foreground = this.color.apply(this, foreground); + this.background = this.color.apply(this, background); + this.rectangles = []; + }; + + Svg.prototype = { + size: null, + foreground: null, + background: null, + rectangles: null, + + color: function(r, g, b, a){ + var values = [r, g, b].map(Math.round); + values.push((a >= 0) && (a <= 255) ? a/255 : 1); + return 'rgba(' + values.join(',') + ')'; + }, + + getDump: function(){ + var i, + xml, + rect, + fg = this.foreground, + bg = this.background, + stroke = this.size * 0.005; + + xml = "" + + ""; + + for (i = 0; i < this.rectangles.length; i++) { + rect = this.rectangles[i]; + if (rect.color == bg) continue; + xml += ""; + } + xml += "" + + return xml; + }, + + getBase64: function(){ + if ('function' === typeof btoa) { + return btoa(this.getDump()); + } else if (Buffer) { + return new Buffer(this.getDump(), 'binary').toString('base64'); + } else { + throw 'Cannot generate base64 output'; + } + } + }; + + if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { + module.exports = Identicon; + } else { + window.Identicon = Identicon; + } +})(); diff --git a/lib/gollum/public/gollum/javascript/identicon_canvas.js b/lib/gollum/public/gollum/javascript/identicon_canvas.js deleted file mode 100644 index a533d8ef..00000000 --- a/lib/gollum/public/gollum/javascript/identicon_canvas.js +++ /dev/null @@ -1,111 +0,0 @@ -/* -Client-side Canvas tag based Identicon rendering code - -@author Don Park -@version 0.2 -@date January 21th, 2007 -*/ - -var patch0 = new Array( 0, 4, 24, 20 ); -var patch1 = new Array( 0, 4, 20 ); -var patch2 = new Array( 2, 24, 20 ); -var patch3 = new Array( 0, 2, 20, 22 ); -var patch4 = new Array( 2, 14, 22, 10 ); -var patch5 = new Array( 0, 14, 24, 22 ); -var patch6 = new Array( 2, 24, 22, 13, 11, 22, 20 ); -var patch7 = new Array( 0, 14, 22 ); -var patch8 = new Array( 6, 8, 18, 16 ); -var patch9 = new Array( 4, 20, 10, 12, 2 ); -var patch10 = new Array( 0, 2, 12, 10 ); -var patch11 = new Array( 10, 14, 22 ); -var patch12 = new Array( 20, 12, 24 ); -var patch13 = new Array( 10, 2, 12 ); -var patch14 = new Array( 0, 2, 10 ); -var patchTypes = new Array( patch0, patch1, patch2, patch3, patch4, - patch5, patch6, patch7, patch8, patch9, patch10, patch11, - patch12, patch13, patch14, patch0 ); -var centerPatchTypes = new Array(0, 4, 8, 15); - -function render_identicon_patch(ctx, x, y, size, patch, turn, invert, foreColor, backColor) { - patch %= patchTypes.length; - turn %= 4; - if (patch == 15) - invert = !invert; - - var vertices = patchTypes[patch]; - var offset = size / 2; - var scale = size / 4; - - ctx.save(); - - // paint background - ctx.fillStyle = invert ? foreColor : backColor; - ctx.fillRect(x, y, size, size); - - // build patch path - ctx.translate(x + offset, y + offset); - ctx.rotate(turn * Math.PI / 2); - ctx.beginPath(); - ctx.moveTo((vertices[0] % 5 * scale - offset), (Math.floor(vertices[0] / 5) * scale - offset)); - for (var i = 1; i < vertices.length; i++) - ctx.lineTo((vertices[i] % 5 * scale - offset), (Math.floor(vertices[i] / 5) * scale - offset)); - ctx.closePath(); - - // offset and rotate coordinate space by patch position (x, y) and - // 'turn' before rendering patch shape - - // render rotated patch using fore color (back color if inverted) - ctx.fillStyle = invert ? backColor : foreColor; - ctx.fill(); - - // restore rotation - ctx.restore(); -} - -function render_identicon(node, code, size) { - if (!node || !code || !size) return; - - var patchSize = size / 3; - var middleType = centerPatchTypes[code & 3]; - var middleInvert = ((code >> 2) & 1) != 0; - var cornerType = (code >> 3) & 15; - var cornerInvert = ((code >> 7) & 1) != 0; - var cornerTurn = (code >> 8) & 3; - var sideType = (code >> 10) & 15; - var sideInvert = ((code >> 14) & 1) != 0; - var sideTurn = (code >> 15) & 3; - var blue = (code >> 16) & 31; - var green = (code >> 21) & 31; - var red = (code >> 27) & 31; - var foreColor = "rgb(" + (red << 3) + "," + (green << 3) + "," + (blue << 3) + ")"; - var backColor = "rgb(255,255,255)"; - - var ctx = node.getContext("2d"); - - // middle patch - render_identicon_patch(ctx, patchSize, patchSize, patchSize, middleType, 0, middleInvert, foreColor, backColor); - // side patchs, starting from top and moving clock-wise - render_identicon_patch(ctx, patchSize, 0, patchSize, sideType, sideTurn++, sideInvert, foreColor, backColor); - render_identicon_patch(ctx, patchSize * 2, patchSize, patchSize, sideType, sideTurn++, sideInvert, foreColor, backColor); - render_identicon_patch(ctx, patchSize, patchSize * 2, patchSize, sideType, sideTurn++, sideInvert, foreColor, backColor); - render_identicon_patch(ctx, 0, patchSize, patchSize, sideType, sideTurn++, sideInvert, foreColor, backColor); - // corner patchs, starting from top left and moving clock-wise - render_identicon_patch(ctx, 0, 0, patchSize, cornerType, cornerTurn++, cornerInvert, foreColor, backColor); - render_identicon_patch(ctx, patchSize * 2, 0, patchSize, cornerType, cornerTurn++, cornerInvert, foreColor, backColor); - render_identicon_patch(ctx, patchSize * 2, patchSize * 2, patchSize, cornerType, cornerTurn++, cornerInvert, foreColor, backColor); - render_identicon_patch(ctx, 0, patchSize * 2, patchSize, cornerType, cornerTurn++, cornerInvert, foreColor, backColor); -} - -function render_identicon_canvases(prefix) { - var canvases = document.getElementsByTagName("canvas"); - var n = canvases.length; - for (var i = 0; i < n; i++) { - var node = canvases[i]; - if (node.title && node.title.indexOf(prefix) == 0) { - if (node.style.display == 'none') node.style.display = "inline"; - var code = node.title.substring(prefix.length) * 1; - var size = node.width; - render_identicon(node, code, size); - } - } -} diff --git a/lib/gollum/public/gollum/stylesheets/tables.scss b/lib/gollum/public/gollum/stylesheets/tables.scss index e468072b..2a8ee530 100644 --- a/lib/gollum/public/gollum/stylesheets/tables.scss +++ b/lib/gollum/public/gollum/stylesheets/tables.scss @@ -2,6 +2,15 @@ /* @section history */ +#user-icons { + a, img, span, svg { + vertical-align: middle; + } + img, svg { + width: 20px; + height: 20px; + } +} .history #footer { margin-bottom: 7em; diff --git a/lib/gollum/templates/history.mustache b/lib/gollum/templates/history.mustache index 8d8c92d0..fe3138bf 100644 --- a/lib/gollum/templates/history.mustache +++ b/lib/gollum/templates/history.mustache @@ -15,7 +15,7 @@ {{#versions}}
  • - {{>author_template}} + {{>author_template}} {{date}} {{message}} [{{id7}}] diff --git a/lib/gollum/templates/history_authors/gravatar.mustache b/lib/gollum/templates/history_authors/gravatar.mustache index 270a4265..174ef7d8 100644 --- a/lib/gollum/templates/history_authors/gravatar.mustache +++ b/lib/gollum/templates/history_authors/gravatar.mustache @@ -1,5 +1,2 @@ - -avatar: {{author}} - {{author}} - +{{author}} \ No newline at end of file diff --git a/lib/gollum/templates/history_authors/identicon.mustache b/lib/gollum/templates/history_authors/identicon.mustache index 7c73c2e5..43c23cb7 100644 --- a/lib/gollum/templates/history_authors/identicon.mustache +++ b/lib/gollum/templates/history_authors/identicon.mustache @@ -1,5 +1,2 @@ - - avatar: {{author}} - {{author}} - \ No newline at end of file +avatar: {{author}}{{author}} \ No newline at end of file diff --git a/lib/gollum/templates/history_authors/none.mustache b/lib/gollum/templates/history_authors/none.mustache index 012393ab..55f67f35 100644 --- a/lib/gollum/templates/history_authors/none.mustache +++ b/lib/gollum/templates/history_authors/none.mustache @@ -1 +1,2 @@ - {{author}} \ No newline at end of file +{{#octicon}}person{{/octicon}} + {{author}} \ No newline at end of file diff --git a/lib/gollum/templates/latest_changes.mustache b/lib/gollum/templates/latest_changes.mustache index 7badc150..0946c18c 100644 --- a/lib/gollum/templates/latest_changes.mustache +++ b/lib/gollum/templates/latest_changes.mustache @@ -11,7 +11,7 @@
    {{#versions}}
    - {{>author_template}} + {{>author_template}} {{date}} {{message}}
    {{#files}} diff --git a/lib/gollum/templates/layout.mustache b/lib/gollum/templates/layout.mustache index 025eb4ab..a6c3d98b 100644 --- a/lib/gollum/templates/layout.mustache +++ b/lib/gollum/templates/layout.mustache @@ -26,9 +26,6 @@ {{#sprockets_javascript_tag}}app{{/sprockets_javascript_tag}} - {{#use_identicon}} - {{#sprockets_javascript_tag}}identicon_canvas{{/sprockets_javascript_tag}} - {{/use_identicon}} {{#mathjax}} {{^mathjax_config}}