mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-08 22:18:28 +03:00
Avatars plugin added other identicon types (needs config settings)
This commit is contained in:
parent
9df052a435
commit
9502fa3f5a
4 changed files with 97 additions and 23 deletions
|
|
@ -11,6 +11,21 @@
|
|||
return `${bimi}/${from.email.toLowerCase()}`;
|
||||
},
|
||||
getAvatar = msg => ncAvatars.get(msg.from[0].email.toLowerCase()) || avatars.get(getAvatarUid(msg)),
|
||||
hash = async txt => {
|
||||
if (/^[0-9a-f]{15,}$/i.test(txt)) {
|
||||
return txt;
|
||||
}
|
||||
const hashArray = Array.from(new Uint8Array(
|
||||
// await crypto.subtle.digest('SHA-256', (new TextEncoder()).encode(txt.toLowerCase()))
|
||||
await crypto.subtle.digest('SHA-1', (new TextEncoder()).encode(txt.toLowerCase()))
|
||||
));
|
||||
return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); // convert bytes to hex string
|
||||
},
|
||||
fromChars = from =>
|
||||
((from.name || from.email).split(/[^\p{L}]+/gu) || [])
|
||||
.reduce((a, s) => a + (s[0] || ''), '')
|
||||
.slice(0,2)
|
||||
.toUpperCase(),
|
||||
runQueue = (() => {
|
||||
let item = queue.shift();
|
||||
while (item) {
|
||||
|
|
@ -27,7 +42,8 @@
|
|||
item[1](url);
|
||||
} else if (window.identiconSvg) {
|
||||
// rl.pluginSettingsGet('avatars', 'identicon');
|
||||
window.identiconSvg(from.email).then(url => {
|
||||
hash(from.email).then(hash => {
|
||||
let url = window.identiconSvg(hash, fromChars(from));
|
||||
avatars.set(getAvatarUid(item[0]), url);
|
||||
item[1](url);
|
||||
});
|
||||
|
|
@ -81,7 +97,8 @@
|
|||
for (let i = 0; i < responseList.length; ++i) {
|
||||
const item = responseList.item(i);
|
||||
if (1 == getElementValue(item, nsNC, 'has-photo')) {
|
||||
[...getElementValue(item, nsCard, 'address-data').matchAll(/EMAIL.*?:([^@\r\n]+@[^@\r\n]+)/g)].forEach(match => {
|
||||
[...getElementValue(item, nsCard, 'address-data').matchAll(/EMAIL.*?:([^@\r\n]+@[^@\r\n]+)/g)]
|
||||
.forEach(match => {
|
||||
ncAvatars.set(
|
||||
match[1].toLowerCase(),
|
||||
getElementValue(item, nsDAV, 'href') + '?photo'
|
||||
|
|
@ -102,10 +119,13 @@
|
|||
if (url) {
|
||||
fn(url);
|
||||
} else if (msg.avatar) {
|
||||
let bimi = 'pass' == msg.from[0].dkimStatus ? 1 : 0;
|
||||
let from = msg.from[0],
|
||||
bimi = 'pass' == from.dkimStatus ? 1 : 0;
|
||||
if (window.identiconSvg) {
|
||||
// rl.pluginSettingsGet('avatars', 'identicon');
|
||||
element.onerror = () => window.identiconSvg(msg.from[0].email).then(fn);
|
||||
element.onerror = () => hash(from.email).then(hash =>
|
||||
fn(window.identiconSvg(hash, fromChars(from)))
|
||||
);
|
||||
}
|
||||
fn(`?Avatar/${bimi}/${msg.avatar}`);
|
||||
} else {
|
||||
|
|
|
|||
69
plugins/avatars/identicon.js
Normal file
69
plugins/avatars/identicon.js
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
|
||||
(()=>{
|
||||
|
||||
const size = 50,
|
||||
margin = 0.08;
|
||||
|
||||
window.identiconSvg = (hash, txt) => {
|
||||
// color defaults to last 7 chars as hue at 70% saturation, 50% brightness
|
||||
// hsl2rgb adapted from: https://gist.github.com/aemkei/1325937
|
||||
let h = (parseInt(hash.substr(-7), 16) / 0xfffffff) * 6, s = 0.7, l = 0.5,
|
||||
v = [
|
||||
l += s *= l < .5 ? l : 1 - l,
|
||||
l - h % 1 * s * 2,
|
||||
l -= s *= 2,
|
||||
l,
|
||||
l + h % 1 * s,
|
||||
l + s
|
||||
];
|
||||
|
||||
if (txt) {
|
||||
const color = 'rgb(' + [
|
||||
v[ ~~h % 6 ] * 255 / 2, // red
|
||||
v[ (h | 16) % 6 ] * 255 / 2, // green
|
||||
v[ (h | 8) % 6 ] * 255 / 2 // blue
|
||||
].map(Math.round).join(',') + ')';
|
||||
txt = `<svg xmlns="http://www.w3.org/2000/svg" width="${size}px" height="${size}px" viewBox="0 0 ${size} ${size}" version="1.1">
|
||||
<circle fill="${color}" width="${size}" height="${size}" cx="${size/2}" cy="${size/2}" r="${size/2}"/>
|
||||
<text x="${size}%" y="${size}%" style="color:#FFF" alignment-baseline="middle" text-anchor="middle"
|
||||
font-weight="bold" font-size="${Math.round(size*0.5)}"
|
||||
dy=".1em" dominant-baseline="middle" fill="#FFF">${txt}</text>
|
||||
</svg>`;
|
||||
} else {
|
||||
const color = 'rgb(' + [
|
||||
v[ ~~h % 6 ] * 255, // red
|
||||
v[ (h | 16) % 6 ] * 255, // green
|
||||
v[ (h | 8) % 6 ] * 255 // blue
|
||||
].map(Math.round).join(',') + ')',
|
||||
cell = Math.floor((size - ((Math.floor(size * margin)) * 2)) / 5),
|
||||
imargin = Math.floor((size - cell * 5) / 2),
|
||||
rectangles = [],
|
||||
add = (x, y) => rectangles.push("<rect x='" + (x * cell + imargin)
|
||||
+ "' y='" + (y * cell + imargin)
|
||||
+ "' width='" + cell + "' height='" + cell + "'/>");
|
||||
|
||||
// the first 15 characters of the hash control the pixels (even/odd)
|
||||
// they are drawn down the middle first, then mirrored outwards
|
||||
for (let i = 0; i < 15; ++i) {
|
||||
if (!(parseInt(hash.charAt(i), 16) % 2)) {
|
||||
if (i < 5) {
|
||||
add(2, i);
|
||||
} else if (i < 10) {
|
||||
add(1, (i - 5));
|
||||
add(3, (i - 5));
|
||||
} else if (i < 15) {
|
||||
add(0, (i - 10));
|
||||
add(4, (i - 10));
|
||||
}
|
||||
}
|
||||
}
|
||||
txt = "<g style='fill:" + color + "; stroke:" + color + "; stroke-width:" + (size * 0.005) + ";'>"
|
||||
+ rectangles.join('')
|
||||
+ "</g>";
|
||||
}
|
||||
return 'data:image/svg+xml;base64,' + btoa(
|
||||
"<svg xmlns='http://www.w3.org/2000/svg' width='" + size + "' height='" + size + "'>" + txt + "</svg>"
|
||||
);
|
||||
};
|
||||
|
||||
})();
|
||||
|
|
@ -19,7 +19,9 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
$this->addJs('avatars.js');
|
||||
$this->addJsonHook('Avatar', 'DoAvatar');
|
||||
$this->addPartHook('Avatar', 'ServiceAvatar');
|
||||
$this->Config()->Get('plugin', 'identicon', false) && $this->addJs('jdenticon.js');
|
||||
// $this->Config()->Get('plugin', 'identicon', false) && $this->addJs('jdenticon.js');
|
||||
// GitHub-style
|
||||
$this->Config()->Get('plugin', 'identicon', false) && $this->addJs('identicon.js');
|
||||
// https://github.com/the-djmaze/snappymail/issues/714
|
||||
$this->Config()->Get('plugin', 'delay', true) || $this->addHook('filter.json-response', 'FilterJsonResponse');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -386,21 +386,6 @@ function outerShape(index, g, cell) {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a SHA1 hash for any value and returns it as a hexadecimal string.
|
||||
*
|
||||
* This function is optimized for minimal code size and rather short messages.
|
||||
*
|
||||
* @param {string} message
|
||||
*/
|
||||
async function computeHash(message) {
|
||||
const hashArray = Array.from(new Uint8Array(
|
||||
// await crypto.subtle.digest('SHA-256', (new TextEncoder()).encode(message))
|
||||
await crypto.subtle.digest('SHA-1', (new TextEncoder()).encode(message))
|
||||
));
|
||||
return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); // convert bytes to hex string
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a measure to be used as a measure in an SVG path, by
|
||||
* rounding the measure to a single decimal. This reduces the file
|
||||
|
|
@ -597,11 +582,9 @@ class SvgWriter
|
|||
* @param {*} hashOrValue - A hexadecimal hash string or any value that will be hashed by Jdenticon.
|
||||
* @returns {string} SVG string
|
||||
*/
|
||||
window.identiconSvg = async (hashOrValue) => {
|
||||
window.identiconSvg = hash => {
|
||||
const writer = new SvgWriter(50),
|
||||
renderer = new SvgRenderer(writer),
|
||||
hash = (/^[0-9a-f]{11,}$/i.test(hashOrValue) && hashOrValue)
|
||||
|| await computeHash(hashOrValue == null ? "" : "" + hashOrValue),
|
||||
config = {
|
||||
p/*colorSaturation*/: 0.5,
|
||||
H/*grayscaleSaturation*/: 0,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue