Fix usericons (#1408)
* Fix and refactor user icons, add Basic tests * Remove identicon_canvas, use identicon.js * Use octicon by default
This commit is contained in:
+6
-4
@@ -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,
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 376 B |
@@ -1,4 +1,5 @@
|
||||
//= require jquery-1.7.2.min
|
||||
//= require identicon
|
||||
//= require mousetrap.min
|
||||
//= require gollum
|
||||
//= require gollum.dialog
|
||||
|
||||
@@ -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 = $('<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);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
})();
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
{{#versions}}
|
||||
<li class="Box-row border-top Box-row--hover-gray d-flex flex-items-center">
|
||||
<span class="pr-2"><input class="checkbox" type="checkbox" name="versions[]" value="{{id}}"></span>
|
||||
<span class="float-left col-2">{{>author_template}}</span>
|
||||
<span class="float-left col-2" id="user-icons">{{>author_template}}</span>
|
||||
<span class="flex-auto col-1 text-gray-light">{{date}}</span>
|
||||
<span class="flex-auto col-5">{{message}}</span>
|
||||
<span class="pl-4 float-right">[<a href="{{base_url}}/{{escaped_url_path}}/{{id}}" title="View commit">{{id7}}</a>]</span>
|
||||
|
||||
@@ -1,5 +1,2 @@
|
||||
<a href="javascript:void(0)">
|
||||
<img src="https://secure.gravatar.com/avatar/{{gravatar}}?s=16"
|
||||
alt="avatar: {{author}}" class="mini-gravatar"/>
|
||||
{{author}}
|
||||
</a>
|
||||
<img src="https://secure.gravatar.com/avatar/{{user_icon}}?s=20" class="mini-gravatar" onerror="brokenAvatarImage(this);" />
|
||||
<a href="https://gravatar.com/{{user_icon}}">{{author}}</a>
|
||||
@@ -1,5 +1,2 @@
|
||||
<a href="javascript:void(0)">
|
||||
<img src="{{#sprockets_image_path}}man_24.png{{/sprockets_image_path}}" alt="avatar: {{author}}"
|
||||
class="mini-gravatar identicon" data-identicon="{{identicon}}"/>
|
||||
{{author}}
|
||||
</a>
|
||||
<img src="" alt="avatar: {{author}}" class="identicon" data-identicon="{{user_icon}}" onerror="brokenAvatarImage(this);" />
|
||||
<span>{{author}}</span>
|
||||
@@ -1 +1,2 @@
|
||||
{{author}}
|
||||
{{#octicon}}person{{/octicon}}
|
||||
<span>{{author}}</span>
|
||||
@@ -11,7 +11,7 @@
|
||||
<div class="Box flex-auto">
|
||||
{{#versions}}
|
||||
<div class="Box-row Box-row--hover-gray border-top d-flex flex-items-center">
|
||||
<span class="float-left col-2">{{>author_template}}</span>
|
||||
<span class="float-left col-2" id="user-icons">{{>author_template}}</span>
|
||||
<span class="flex-auto col-1 text-gray-light">{{date}}</span>
|
||||
<span class="flex-auto col-7">{{message}}<br/>
|
||||
{{#files}}
|
||||
|
||||
@@ -26,9 +26,6 @@
|
||||
|
||||
</script>
|
||||
{{#sprockets_javascript_tag}}app{{/sprockets_javascript_tag}}
|
||||
{{#use_identicon}}
|
||||
{{#sprockets_javascript_tag}}identicon_canvas{{/sprockets_javascript_tag}}
|
||||
{{/use_identicon}}
|
||||
{{#mathjax}}
|
||||
{{^mathjax_config}}
|
||||
<script type="text/javascript">
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
module Precious
|
||||
module HasUserIcons
|
||||
def user_icon_code(str)
|
||||
Digest::MD5.hexdigest(str.strip.downcase)
|
||||
end
|
||||
|
||||
def partial(name)
|
||||
if name == :author_template
|
||||
self.class.partial("history_authors/#{@wiki.user_icons}")
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -3,6 +3,7 @@ module Precious
|
||||
class History < Layout
|
||||
include HasPage
|
||||
include Pagination
|
||||
include HasUserIcons
|
||||
include Sprockets::Helpers
|
||||
include Precious::Views::SprocketsHelpers
|
||||
|
||||
@@ -23,49 +24,11 @@ module Precious
|
||||
:author => v.author.name.respond_to?(:force_encoding) ? v.author.name.force_encoding('UTF-8') : v.author.name,
|
||||
:message => v.message.respond_to?(:force_encoding) ? v.message.force_encoding('UTF-8') : v.message,
|
||||
:date => v.authored_date.strftime("%B %d, %Y"),
|
||||
:gravatar => Digest::MD5.hexdigest(v.author.email.strip.downcase),
|
||||
:identicon => self._identicon_code(v.author.email),
|
||||
:user_icon => self.user_icon_code(v.author.email),
|
||||
:date_full => v.authored_date,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# http://stackoverflow.com/questions/9445760/bit-shifting-in-ruby
|
||||
def left_shift int, shift
|
||||
r = ((int & 0xFF) << (shift & 0x1F)) & 0xFFFFFFFF
|
||||
# 1>>31, 2**32
|
||||
(r & 2147483648) == 0 ? r : r - 4294967296
|
||||
end
|
||||
|
||||
def string_to_code string
|
||||
# sha bytes
|
||||
b = [Digest::SHA1.hexdigest(string)[0, 20]].pack('H*').bytes.to_a
|
||||
# Thanks donpark's IdenticonUtil.java for this.
|
||||
# Match the following Java code
|
||||
# ((b[0] & 0xFF) << 24) | ((b[1] & 0xFF) << 16) |
|
||||
# ((b[2] & 0xFF) << 8) | (b[3] & 0xFF)
|
||||
|
||||
return left_shift(b[0], 24) |
|
||||
left_shift(b[1], 16) |
|
||||
left_shift(b[2], 8) |
|
||||
b[3] & 0xFF
|
||||
end
|
||||
|
||||
def _identicon_code(blob)
|
||||
string_to_code blob + @request.host
|
||||
end
|
||||
|
||||
def use_identicon
|
||||
@page.wiki.user_icons == 'identicon'
|
||||
end
|
||||
|
||||
def partial(name)
|
||||
if name == :author_template
|
||||
self.class.partial("history_authors/#{@page.wiki.user_icons}")
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def editable
|
||||
@editable
|
||||
|
||||
@@ -2,6 +2,7 @@ module Precious
|
||||
module Views
|
||||
class LatestChanges < Layout
|
||||
include Pagination
|
||||
include HasUserIcons
|
||||
|
||||
attr_reader :wiki
|
||||
|
||||
@@ -19,8 +20,7 @@ module Precious
|
||||
:author => v.author.name.respond_to?(:force_encoding) ? v.author.name.force_encoding('UTF-8') : v.author.name,
|
||||
:message => v.message.respond_to?(:force_encoding) ? v.message.force_encoding('UTF-8') : v.message,
|
||||
:date => v.authored_date.strftime("%B %d, %Y"),
|
||||
:gravatar => Digest::MD5.hexdigest(v.author.email.strip.downcase),
|
||||
:identicon => self._identicon_code(v.author.email),
|
||||
:user_icon => self.user_icon_code(v.author.email),
|
||||
:date_full => v.authored_date,
|
||||
:files => v.stats.files.map { |f,*rest|
|
||||
page_path = extract_renamed_path_destination(f)
|
||||
@@ -36,43 +36,6 @@ module Precious
|
||||
return file.gsub(/{.* => (.*)}/, '\1').gsub(/.* => (.*)/, '\1')
|
||||
end
|
||||
|
||||
# http://stackoverflow.com/questions/9445760/bit-shifting-in-ruby
|
||||
def left_shift(int, shift)
|
||||
r = ((int & 0xFF) << (shift & 0x1F)) & 0xFFFFFFFF
|
||||
# 1>>31, 2**32
|
||||
(r & 2147483648) == 0 ? r : r - 4294967296
|
||||
end
|
||||
|
||||
def string_to_code(string)
|
||||
# sha bytes
|
||||
b = [Digest::SHA1.hexdigest(string)[0, 20]].pack('H*').bytes.to_a
|
||||
# Thanks donpark's IdenticonUtil.java for this.
|
||||
# Match the following Java code
|
||||
# ((b[0] & 0xFF) << 24) | ((b[1] & 0xFF) << 16) |
|
||||
# ((b[2] & 0xFF) << 8) | (b[3] & 0xFF)
|
||||
|
||||
return left_shift(b[0], 24) |
|
||||
left_shift(b[1], 16) |
|
||||
left_shift(b[2], 8) |
|
||||
b[3] & 0xFF
|
||||
end
|
||||
|
||||
def _identicon_code(blob)
|
||||
string_to_code blob + @request.host
|
||||
end
|
||||
|
||||
def use_identicon
|
||||
@wiki.user_icons == 'identicon'
|
||||
end
|
||||
|
||||
def partial(name)
|
||||
if name == :author_template
|
||||
self.class.partial("history_authors/#{@wiki.user_icons}")
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def previous_link
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user