Added advanced PluginPropertyType::SELECT for improved Avatars plugin

This commit is contained in:
the-djmaze 2022-12-01 19:09:05 +01:00
parent 2f4e17da51
commit c56cc7be73
7 changed files with 57 additions and 59 deletions

View file

@ -22,10 +22,18 @@
return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); // convert bytes to hex string return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); // convert bytes to hex string
}, },
fromChars = from => fromChars = from =>
((from.name || from.email).split(/[^\p{L}]+/gu) || []) // (from.name?.split(/[^\p{Lu}]+/gu) || []).reduce((a, s) => a + (s || '')), '')
.reduce((a, s) => a + (s[0] || ''), '') (from.name?.split(/[^\p{L}]+/gu) || []).reduce((a, s) => a + (s[0] || ''), '')
.slice(0,2) .slice(0,2)
.toUpperCase(), .toUpperCase(),
setIdenticon = (from, fn) => window.identiconSvg && hash(from.email).then(hash =>
fn('data:image/svg+xml;base64,' + btoa(window.identiconSvg(hash, fromChars(from))))
),
addQueue = (msg, fn) => {
setIdenticon(msg.from[0], fn);
queue.push([msg, fn]);
runQueue();
},
runQueue = (() => { runQueue = (() => {
let item = queue.shift(); let item = queue.shift();
while (item) { while (item) {
@ -40,13 +48,6 @@
url = `data:${data.Result.type};base64,${data.Result.data}`; url = `data:${data.Result.type};base64,${data.Result.data}`;
avatars.set(getAvatarUid(item[0]), url); avatars.set(getAvatarUid(item[0]), url);
item[1](url); item[1](url);
} else if (window.identiconSvg) {
// rl.pluginSettingsGet('avatars', 'identicon');
hash(from.email).then(hash => {
let url = window.identiconSvg(hash, fromChars(from));
avatars.set(getAvatarUid(item[0]), url);
item[1](url);
});
} }
runQueue(); runQueue();
}, 'Avatar', { }, 'Avatar', {
@ -115,22 +116,15 @@
init: (element, self, dummy, msg) => { init: (element, self, dummy, msg) => {
if (msg) { if (msg) {
let url = getAvatar(msg), let url = getAvatar(msg),
from = msg.from[0],
fn = url=>{element.src = url}; fn = url=>{element.src = url};
if (url) { if (url) {
fn(url); fn(url);
} else if (msg.avatar) { } else if (msg.avatar) {
let from = msg.from[0], element.onerror = () => setIdenticon(from, fn);
bimi = 'pass' == from.dkimStatus ? 1 : 0; fn(`?Avatar/${'pass' == from.dkimStatus ? 1 : 0}/${msg.avatar}`);
if (window.identiconSvg) {
// rl.pluginSettingsGet('avatars', 'identicon');
element.onerror = () => hash(from.email).then(hash =>
fn(window.identiconSvg(hash, fromChars(from)))
);
}
fn(`?Avatar/${bimi}/${msg.avatar}`);
} else { } else {
queue.push([msg, fn]); addQueue(msg, fn);
runQueue();
} }
} }
} }
@ -164,14 +158,12 @@
if (url) { if (url) {
fn(url); fn(url);
} else if (msg.avatar) { } else if (msg.avatar) {
let bimi = 'pass' == msg.from[0].dkimStatus ? 1 : 0; fn(`?Avatar/${'pass' == msg.from[0].dkimStatus ? 1 : 0}/${msg.avatar}`);
fn(`?Avatar/${bimi}/${msg.avatar}`);
} else { } else {
// let from = msg.from[0], bimi = 'pass' == from.dkimStatus ? 1 : 0; // let from = msg.from[0];
// view.viewUserPic(`?Avatar/${bimi}/${encodeURIComponent(from.email)}`); // view.viewUserPic(`?Avatar/${'pass' == from.dkimStatus ? 1 : 0}/${encodeURIComponent(from.email)}`);
// view.viewUserPicVisible(true); // view.viewUserPicVisible(true);
queue.push([msg, fn]); addQueue(msg, fn);
runQueue();
} }
} }
}); });

View file

@ -15,14 +15,15 @@ window.identiconSvg = (hash, txt) => {
l, l,
l + h % 1 * s, l + h % 1 * s,
l + s l + s
]; ],
m = txt ? 128 : 255,
color = 'rgb(' + [
v[ ~~h % 6 ] * m, // red
v[ (h | 16) % 6 ] * m, // green
v[ (h | 8) % 6 ] * m // blue
].map(Math.round).join(',') + ')';
if (txt) { 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"> 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}"/> <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" <text x="${size}%" y="${size}%" style="color:#FFF" alignment-baseline="middle" text-anchor="middle"
@ -30,18 +31,12 @@ window.identiconSvg = (hash, txt) => {
dy=".1em" dominant-baseline="middle" fill="#FFF">${txt}</text> dy=".1em" dominant-baseline="middle" fill="#FFF">${txt}</text>
</svg>`; </svg>`;
} else { } else {
const color = 'rgb(' + [ const cell = Math.floor((size - ((Math.floor(size * margin)) * 2)) / 5),
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), imargin = Math.floor((size - cell * 5) / 2),
rectangles = [], rectangles = [],
add = (x, y) => rectangles.push("<rect x='" + (x * cell + imargin) add = (x, y) => rectangles.push("<rect x='" + (x * cell + imargin)
+ "' y='" + (y * cell + imargin) + "' y='" + (y * cell + imargin)
+ "' width='" + cell + "' height='" + cell + "'/>"); + "' width='" + cell + "' height='" + cell + "'/>");
// the first 15 characters of the hash control the pixels (even/odd) // the first 15 characters of the hash control the pixels (even/odd)
// they are drawn down the middle first, then mirrored outwards // they are drawn down the middle first, then mirrored outwards
for (let i = 0; i < 15; ++i) { for (let i = 0; i < 15; ++i) {
@ -51,7 +46,7 @@ window.identiconSvg = (hash, txt) => {
} else if (i < 10) { } else if (i < 10) {
add(1, (i - 5)); add(1, (i - 5));
add(3, (i - 5)); add(3, (i - 5));
} else if (i < 15) { } else {
add(0, (i - 10)); add(0, (i - 10));
add(4, (i - 10)); add(4, (i - 10));
} }
@ -61,9 +56,7 @@ window.identiconSvg = (hash, txt) => {
+ rectangles.join('') + rectangles.join('')
+ "</g>"; + "</g>";
} }
return 'data:image/svg+xml;base64,' + btoa( return "<svg xmlns='http://www.w3.org/2000/svg' width='" + size + "' height='" + size + "'>" + txt + "</svg>";
"<svg xmlns='http://www.w3.org/2000/svg' width='" + size + "' height='" + size + "'>" + txt + "</svg>"
);
}; };
})(); })();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -8,10 +8,10 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
URL = 'https://snappymail.eu/', URL = 'https://snappymail.eu/',
VERSION = '1.2', VERSION = '1.2',
RELEASE = '2022-11-29', RELEASE = '2022-11-29',
REQUIRED = '2.22.4', REQUIRED = '2.22.5',
CATEGORY = 'Contacts', CATEGORY = 'Contacts',
LICENSE = 'MIT', LICENSE = 'MIT',
DESCRIPTION = 'Show photo of sender in message and messages list (supports BIMI and Gravatar, Contacts is still TODO)'; DESCRIPTION = 'Show photo of sender in message and messages list (supports BIMI, Gravatar and identicon, Contacts is still TODO)';
public function Init() : void public function Init() : void
{ {
@ -19,9 +19,14 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
$this->addJs('avatars.js'); $this->addJs('avatars.js');
$this->addJsonHook('Avatar', 'DoAvatar'); $this->addJsonHook('Avatar', 'DoAvatar');
$this->addPartHook('Avatar', 'ServiceAvatar'); $this->addPartHook('Avatar', 'ServiceAvatar');
// $this->Config()->Get('plugin', 'identicon', false) && $this->addJs('jdenticon.js'); $identicon = $this->Config()->Get('plugin', 'identicon', 'identicon');
// GitHub-style if ('none' != $identicon) {
$this->Config()->Get('plugin', 'identicon', false) && $this->addJs('identicon.js'); if ('jdenticon' === $this->Config()->Get('plugin', 'identicon', 'identicon')) {
$this->addJs('jdenticon.js');
} else {
$this->addJs('identicon.js');
}
}
// https://github.com/the-djmaze/snappymail/issues/714 // https://github.com/the-djmaze/snappymail/issues/714
$this->Config()->Get('plugin', 'delay', true) || $this->addHook('filter.json-response', 'FilterJsonResponse'); $this->Config()->Get('plugin', 'delay', true) || $this->addHook('filter.json-response', 'FilterJsonResponse');
} }
@ -84,9 +89,15 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
protected function configMapping() : array protected function configMapping() : array
{ {
$aResult = array( $aResult = array(
\RainLoop\Plugins\Property::NewInstance('delay')->SetLabel('Delay loading') \RainLoop\Plugins\Property::NewInstance('identicon')->SetLabel('Identicon')
->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL) ->SetType(\RainLoop\Enumerations\PluginPropertyType::SELECT)
->SetDefaultValue(true), // ->SetAllowedInJs(true)
->SetDefaultValue([
['id' => 'identicon', 'name' => 'Name characters or squares'],
['id' => 'jdenticon', 'name' => 'Triangles shape'],
['id' => 'none', 'name' => 'none']
])
->SetDescription('https://wikipedia.org/wiki/Identicon'),
\RainLoop\Plugins\Property::NewInstance('bimi')->SetLabel('Lookup BIMI') \RainLoop\Plugins\Property::NewInstance('bimi')->SetLabel('Lookup BIMI')
->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL) ->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL)
->SetDefaultValue(false) ->SetDefaultValue(false)
@ -94,7 +105,10 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
\RainLoop\Plugins\Property::NewInstance('gravatar')->SetLabel('Lookup Gravatar') \RainLoop\Plugins\Property::NewInstance('gravatar')->SetLabel('Lookup Gravatar')
->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL) ->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL)
->SetDefaultValue(false) ->SetDefaultValue(false)
->SetDescription('https://wikipedia.org/wiki/Gravatar') ->SetDescription('https://wikipedia.org/wiki/Gravatar'),
\RainLoop\Plugins\Property::NewInstance('delay')->SetLabel('Delay lookup loading')
->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL)
->SetDefaultValue(true),
); );
/* /*
if (\class_exists('OC') && isset(\OC::$server)) { if (\class_exists('OC') && isset(\OC::$server)) {
@ -104,11 +118,6 @@ class AvatarsPlugin extends \RainLoop\Plugins\AbstractPlugin
->SetDefaultValue(false); ->SetDefaultValue(false);
} }
*/ */
$aResult[] = \RainLoop\Plugins\Property::NewInstance('identicon')->SetLabel('Else create Identicon')
->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL)
// ->SetAllowedInJs(true)
->SetDefaultValue(false)
->SetDescription('https://wikipedia.org/wiki/Identicon');
return $aResult; return $aResult;
} }

View file

@ -671,7 +671,7 @@ window.identiconSvg = hash => {
renderer.finish(); renderer.finish();
return 'data:image/svg+xml;base64,' + btoa(writer); return writer;
}; };
})(); })();

View file

@ -12,5 +12,6 @@ class PluginPropertyType
PASSWORD = 3, PASSWORD = 3,
SELECTION = 4, SELECTION = 4,
BOOL = 5, BOOL = 5,
URL = 6; URL = 6,
SELECT = 8;
} }

View file

@ -28,6 +28,9 @@
<input type="url" <input type="url"
data-bind="value: value, attr: {placeholder: placeholder}"> data-bind="value: value, attr: {placeholder: placeholder}">
<!-- /ko --> <!-- /ko -->
<!-- ko if: 8 === Type -->
<select data-bind="options: Default, optionsText: 'name', optionsValue: 'id', value: value"></select>
<!-- /ko -->
<!-- ko if: '' !== Desc --> <!-- ko if: '' !== Desc -->
<span tabindex="0" class="help-block"><span data-bind="text: Desc"></span></span> <span tabindex="0" class="help-block"><span data-bind="text: Desc"></span></span>
<!-- /ko --> <!-- /ko -->