Skip to content

Commit

Permalink
Fix usericons (gollum#1408)
Browse files Browse the repository at this point in the history
* Fix and refactor user icons, add Basic tests
* Remove identicon_canvas, use identicon.js
* Use octicon by default
  • Loading branch information
dometto authored Sep 1, 2019
1 parent 569eac3 commit d1b1375
Show file tree
Hide file tree
Showing 23 changed files with 292 additions and 620 deletions.
6 changes: 3 additions & 3 deletions bin/gollum
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 6 additions & 4 deletions lib/gollum/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
require 'gollum/views/layout'
require 'gollum/views/editable'
require 'gollum/views/has_page'
require 'gollum/views/has_user_icons'
require 'gollum/views/pagination'


Expand Down Expand Up @@ -397,11 +398,12 @@ class App < Sinatra::Base
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,
Expand Down
Binary file removed lib/gollum/public/gollum/images/man_24.png
Binary file not shown.
1 change: 1 addition & 0 deletions lib/gollum/public/gollum/javascript/app.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//= require jquery-1.7.2.min
//= require identicon
//= require mousetrap.min
//= require gollum
//= require gollum.dialog
Expand Down
30 changes: 17 additions & 13 deletions lib/gollum/public/gollum/javascript/gollum.js.erb
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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 = $('<canvas width=16 height=16/>').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);
});
}
});
205 changes: 205 additions & 0 deletions lib/gollum/public/gollum/javascript/identicon.js
Original file line number Diff line number Diff line change
@@ -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 = "<svg xmlns='http://www.w3.org/2000/svg'"
+ " width='" + this.size + "' height='" + this.size + "'"
+ " style='background-color:" + bg + ";'>"
+ "<g style='fill:" + fg + "; stroke:" + fg + "; stroke-width:" + stroke + ";'>";

for (i = 0; i < this.rectangles.length; i++) {
rect = this.rectangles[i];
if (rect.color == bg) continue;
xml += "<rect "
+ " x='" + rect.x + "'"
+ " y='" + rect.y + "'"
+ " width='" + rect.w + "'"
+ " height='" + rect.h + "'"
+ "/>";
}
xml += "</g></svg>"

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;
}
})();
Loading

0 comments on commit d1b1375

Please sign in to comment.