openvibers/openvibe.mail Commits
Mail settings suggest your name; new mail goes out from it (#35)
Changes
@@@ -26,6 +26,7 @@addresses.forEach(function (a) { var o = node('option', a.address + (a.kind === 'mailbox' ? '' : ' · ' + a.kind)); o.value = a.address; from.appendChild(o); });if (p.from) from.value = p.from;else if (original.delivered_to && addresses.some(function (a) { return a.address === original.delivered_to; })) from.value = original.delivered_to;++ else { var named = addresses.find(function (a) { return a.kind === 'name'; }); if (named) from.value = named.address; } // not the random mailbox addressfield('From',from);function addr(value) { var match = /<([^<>]+)>/.exec(value || ''); return (match ? match[1] : value || '').trim().toLowerCase(); }var owned = addresses.map(function (a) { return a.address; });@@@ -77,8 +77,22 @@side.appendChild(ul);var personal = el('section', 'ib-person');personal.appendChild(el('h2', 'ib-h', 'Your address'));++ // The mailbox is named after the account id (private, and nobody is stuck with their name), which reads as noise++ // to whoever gets mail from it: say so, and suggest a name, until the person has one (11-09).++ var named = addresses.find(function (a) { return a.kind === 'name'; });++ personal.appendChild(el('p', 'ib-quiet', named++ ? 'You are ' + named.address + '. Another name here becomes an alias of it.'++ : 'Your mailbox\'s own address is a random one, so it says nothing about you. Pick a name people can write to; mail to it lands in this inbox.'));var addressForm = el('form', 'ib-rule-form');var addressName = el('input', 'ib-input'); addressName.placeholder = 'name'; addressName.required = true; addressName.setAttribute('aria-label', 'New address name');++ if (!named) {++ // A suggestion only (the person presses Create): their OpenVibe username, in the characters an address allows.++ fetch('/auth/me', { credentials: 'same-origin', headers: { Accept: 'application/json' } }).then(function (r) { return r.ok ? r.json() : null; }).then(function (me) {++ // Names are 3-32 letters, numbers or single hyphens (server/domain/person-mail.js validateName).++ var u = me && me.user && String(me.user.username || '').toLowerCase().replace(/[._\s]+/g, '-').replace(/[^a-z0-9-]+/g, '').replace(/-+/g, '-').slice(0, 32).replace(/^-+|-+$/g, '');++ if (u && u.length >= 3 && !addressName.value) addressName.value = u;++ }).catch(function () { /* the field stays empty */ });++ }var addressDomain = el('select', 'ib-input'); addressDomain.setAttribute('aria-label', 'Address domain');[{ domain: 'openvibe.email' }].concat(customDomains.filter(function (d) { return d.mx_verified; })).forEach(function (d) { var option = el('option', '', '@' + d.domain); option.value = d.domain; addressDomain.appendChild(option); });var addressGo = el('button', 'ib-btn is-quiet', 'Create address'); addressGo.type = 'submit';nnew file mode 100644@@@ -0,0 +1,33 @@++'use strict';++const test = require('node:test');++const assert = require('node:assert');++const fs = require('fs');++const path = require('path');++++// 11-09: a person's mailbox is named after their account id (01m3…@openvibe.email), and settings showed that with++// nothing suggesting a real name; compose sent new mail from it. Now settings says what that address is and suggests++// the person's username as a name, and a new message goes out from their named address when they have one.++const inbox = fs.readFileSync(path.join(__dirname, '..', 'public', 'js', 'inbox.js'), 'utf8');++const compose = fs.readFileSync(path.join(__dirname, '..', 'public', 'js', 'compose.js'), 'utf8');++++test('settings explain the random mailbox address and suggest a name', () => {++ assert.match(inbox, /Your mailbox\\'s own address is a random one, so it says nothing about you\./);++ assert.match(inbox, /You are ' \+ named\.address \+ '\. Another name here becomes an alias of it\./);++ assert.match(inbox, /fetch\('\/auth\/me'/, 'the suggestion comes from the signed-in username');++ assert.match(inbox, /if \(u && u\.length >= 3 && !addressName\.value\) addressName\.value = u;/, 'only a suggestion, never over what was typed');++});++++test('the suggestion fits the server rule for names', () => {++ const toName = (username) => String(username).toLowerCase().replace(/[._\s]+/g, '-').replace(/[^a-z0-9-]+/g, '').replace(/-+/g, '-').slice(0, 32).replace(/^-+|-+$/g, '');++ assert.ok(inbox.includes(".replace(/[._\\s]+/g, '-').replace(/[^a-z0-9-]+/g, '').replace(/-+/g, '-').slice(0, 32).replace(/^-+|-+$/g, '')"), 'the same mapping as below');++ assert.strictEqual(toName('ovprobe_merge_keep'), 'ovprobe-merge-keep');++ assert.strictEqual(toName('JapaneseOldGuy'), 'japaneseoldguy');++ assert.strictEqual(toName('__x.y__'), 'x-y');++ ['ovprobe_merge_keep', 'JapaneseOldGuy', 'a.b.c', 'x'.repeat(40) + '_'].map(toName).forEach((n) => assert.match(n, /^[a-z0-9]+(?:-[a-z0-9]+)*$/));++});++++test('a new message goes out from the named address, not the random one', () => {++ assert.match(compose, /var named = addresses\.find\(function \(a\) \{ return a\.kind === 'name'; \}\); if \(named\) from\.value = named\.address;/);++ const at = compose.indexOf('var named = addresses.find');++ assert.ok(at > compose.indexOf('if (p.from) from.value = p.from;'), 'a reply still answers from the address the mail came to');++});
@@ -26,6 +26,7 @@
@@ -26,6 +26,7 @@
addresses.forEach(function (a) { var o = node('option', a.address + (a.kind === 'mailbox' ? '' : ' · ' + a.kind)); o.value = a.address; from.appendChild(o); });
addresses.forEach(function (a) { var o = node('option', a.address + (a.kind === 'mailbox' ? '' : ' · ' + a.kind)); o.value = a.address; from.appendChild(o); });
if (p.from) from.value = p.from;
if (p.from) from.value = p.from;
else if (original.delivered_to && addresses.some(function (a) { return a.address === original.delivered_to; })) from.value = original.delivered_to;
else if (original.delivered_to && addresses.some(function (a) { return a.address === original.delivered_to; })) from.value = original.delivered_to;
+ else { var named = addresses.find(function (a) { return a.kind === 'name'; }); if (named) from.value = named.address; } // not the random mailbox address
field('From',from);
field('From',from);
function addr(value) { var match = /<([^<>]+)>/.exec(value || ''); return (match ? match[1] : value || '').trim().toLowerCase(); }
function addr(value) { var match = /<([^<>]+)>/.exec(value || ''); return (match ? match[1] : value || '').trim().toLowerCase(); }
var owned = addresses.map(function (a) { return a.address; });
var owned = addresses.map(function (a) { return a.address; });
@@ -77,8 +77,22 @@
@@ -77,8 +77,22 @@
side.appendChild(ul);
side.appendChild(ul);
var personal = el('section', 'ib-person');
var personal = el('section', 'ib-person');
personal.appendChild(el('h2', 'ib-h', 'Your address'));
personal.appendChild(el('h2', 'ib-h', 'Your address'));
+ // The mailbox is named after the account id (private, and nobody is stuck with their name), which reads as noise
+ // to whoever gets mail from it: say so, and suggest a name, until the person has one (11-09).
+ var named = addresses.find(function (a) { return a.kind === 'name'; });
+ personal.appendChild(el('p', 'ib-quiet', named
+ ? 'You are ' + named.address + '. Another name here becomes an alias of it.'
+ : 'Your mailbox\'s own address is a random one, so it says nothing about you. Pick a name people can write to; mail to it lands in this inbox.'));
var addressForm = el('form', 'ib-rule-form');
var addressForm = el('form', 'ib-rule-form');
var addressName = el('input', 'ib-input'); addressName.placeholder = 'name'; addressName.required = true; addressName.setAttribute('aria-label', 'New address name');
var addressName = el('input', 'ib-input'); addressName.placeholder = 'name'; addressName.required = true; addressName.setAttribute('aria-label', 'New address name');
+ if (!named) {
+ // A suggestion only (the person presses Create): their OpenVibe username, in the characters an address allows.
+ fetch('/auth/me', { credentials: 'same-origin', headers: { Accept: 'application/json' } }).then(function (r) { return r.ok ? r.json() : null; }).then(function (me) {
+ // Names are 3-32 letters, numbers or single hyphens (server/domain/person-mail.js validateName).
+ var u = me && me.user && String(me.user.username || '').toLowerCase().replace(/[._\s]+/g, '-').replace(/[^a-z0-9-]+/g, '').replace(/-+/g, '-').slice(0, 32).replace(/^-+|-+$/g, '');
+ if (u && u.length >= 3 && !addressName.value) addressName.value = u;
+ }).catch(function () { /* the field stays empty */ });
+ }
var addressDomain = el('select', 'ib-input'); addressDomain.setAttribute('aria-label', 'Address domain');
var addressDomain = el('select', 'ib-input'); addressDomain.setAttribute('aria-label', 'Address domain');
[{ domain: 'openvibe.email' }].concat(customDomains.filter(function (d) { return d.mx_verified; })).forEach(function (d) { var option = el('option', '', '@' + d.domain); option.value = d.domain; addressDomain.appendChild(option); });
[{ domain: 'openvibe.email' }].concat(customDomains.filter(function (d) { return d.mx_verified; })).forEach(function (d) { var option = el('option', '', '@' + d.domain); option.value = d.domain; addressDomain.appendChild(option); });
var addressGo = el('button', 'ib-btn is-quiet', 'Create address'); addressGo.type = 'submit';
var addressGo = el('button', 'ib-btn is-quiet', 'Create address'); addressGo.type = 'submit';
new file mode 100644
new file mode 100644
@@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
+'use strict';
+const test = require('node:test');
+const assert = require('node:assert');
+const fs = require('fs');
+const path = require('path');
+
+// 11-09: a person's mailbox is named after their account id (01m3…@openvibe.email), and settings showed that with
+// nothing suggesting a real name; compose sent new mail from it. Now settings says what that address is and suggests
+// the person's username as a name, and a new message goes out from their named address when they have one.
+const inbox = fs.readFileSync(path.join(__dirname, '..', 'public', 'js', 'inbox.js'), 'utf8');
+const compose = fs.readFileSync(path.join(__dirname, '..', 'public', 'js', 'compose.js'), 'utf8');
+
+test('settings explain the random mailbox address and suggest a name', () => {
+ assert.match(inbox, /Your mailbox\\'s own address is a random one, so it says nothing about you\./);
+ assert.match(inbox, /You are ' \+ named\.address \+ '\. Another name here becomes an alias of it\./);
+ assert.match(inbox, /fetch\('\/auth\/me'/, 'the suggestion comes from the signed-in username');
+ assert.match(inbox, /if \(u && u\.length >= 3 && !addressName\.value\) addressName\.value = u;/, 'only a suggestion, never over what was typed');
+});
+
+test('the suggestion fits the server rule for names', () => {
+ const toName = (username) => String(username).toLowerCase().replace(/[._\s]+/g, '-').replace(/[^a-z0-9-]+/g, '').replace(/-+/g, '-').slice(0, 32).replace(/^-+|-+$/g, '');
+ assert.ok(inbox.includes(".replace(/[._\\s]+/g, '-').replace(/[^a-z0-9-]+/g, '').replace(/-+/g, '-').slice(0, 32).replace(/^-+|-+$/g, '')"), 'the same mapping as below');
+ assert.strictEqual(toName('ovprobe_merge_keep'), 'ovprobe-merge-keep');
+ assert.strictEqual(toName('JapaneseOldGuy'), 'japaneseoldguy');
+ assert.strictEqual(toName('__x.y__'), 'x-y');
+ ['ovprobe_merge_keep', 'JapaneseOldGuy', 'a.b.c', 'x'.repeat(40) + '_'].map(toName).forEach((n) => assert.match(n, /^[a-z0-9]+(?:-[a-z0-9]+)*$/));
+});
+
+test('a new message goes out from the named address, not the random one', () => {
+ assert.match(compose, /var named = addresses\.find\(function \(a\) \{ return a\.kind === 'name'; \}\); if \(named\) from\.value = named\.address;/);
+ const at = compose.indexOf('var named = addresses.find');
+ assert.ok(at > compose.indexOf('if (p.from) from.value = p.from;'), 'a reply still answers from the address the mail came to');
+});