Merge branch 'staging' into merge2

pull/1/head
yflory 4 years ago
commit 1fa916d88c

@ -4,14 +4,48 @@
## Update notes ## Update notes
* warning about lack of support for internet explorer
* existing support will get worse over time. please update.
To update from 4.10.0 to 4.11.0:
1. Stop your server
2. Get the latest code with git
3. Install the latest dependencies with `bower update` and `npm i`
4. Restart your server
5. Confirm that your instance is passing all the tests included on the `/checkup/` page (on whatever devices you intend to support)
## Features ## Features
* unify unregistered/non-registered/anonymous terminology as 'guest' * unify unregistered/non-registered/anonymous terminology as 'guest'
* prompt users that need support to subscribe * support
* include bar graphs for multiple-answer form questions * prompt users that need support to subscribe
* refactor debugging data generation to easily show users what data is included
* form improvements
* include bar graphs for multiple-answer form questions
* move the tally of empty responses to the top of each question's summary (rather than the bottom)
* bar charts on the admin page's 'Performance' tab
* enhancements for guest users and registered users without names or avatars
* two initials for users with a custom name but no avatar (previously one initial, always capitalized)
* animal avatars as defaults instead of indistinguishable initials (A for Anonymous, G for Guest)
* configurable via `AppConfig.emojiAvatars = []`
* authorship data for guests in rich text comments, code editor author data
* emojis in cursor tooltips for guests (rich text, code, slide, kanban)
* emojis in the share and access modals for contacts with empty names
* script to identify unnecessary duplication of translations
* improvements to upload and media-tag UI
* support for adding descriptive text at upload time
* preview of uploaded media in the upload modal
## Bug fixes ## Bug fixes
* fix empty name fields in various places across the platform where we did not fall back to "anonymous/guest"
* teams
* contacts
* ???
* clarified a comment in the nginx config about _professional support_
* handled an edge case in ICS import to calendars where DTEND was not defined (use duration or consider it an "all-day" event
# 4.10.0 # 4.10.0

@ -30,7 +30,7 @@
"secure-fabric.js": "secure-v1.7.9", "secure-fabric.js": "secure-v1.7.9",
"hyperjson": "~1.4.0", "hyperjson": "~1.4.0",
"chainpad-crypto": "^0.2.0", "chainpad-crypto": "^0.2.0",
"chainpad-listmap": "^0.10.0", "chainpad-listmap": "^1.0.0",
"chainpad": "^5.2.0", "chainpad": "^5.2.0",
"file-saver": "1.3.1", "file-saver": "1.3.1",
"alertifyjs": "1.0.11", "alertifyjs": "1.0.11",

@ -1,6 +1,6 @@
define([ define([
'jquery', 'jquery',
'/bower_components/chainpad-listmap/chainpad-listmap.js', 'chainpad-listmap',
'/bower_components/chainpad-crypto/crypto.js', '/bower_components/chainpad-crypto/crypto.js',
'/common/common-util.js', '/common/common-util.js',
'/common/outer/network-config.js', '/common/outer/network-config.js',

@ -166,6 +166,9 @@
color: @cryptpad_text_col; color: @cryptpad_text_col;
text-decoration: underline; text-decoration: underline;
} }
pre.cp-link-preview {
color: @cryptpad_text_col;
}
.cp-info-menu-container { .cp-info-menu-container {
.logo-block { .logo-block {
text-align: center; text-align: center;

@ -4,7 +4,11 @@
@width: 30px @width: 30px
) { ) {
@avatar-width: @width; @avatar-width: @width;
@avatar-font-size: @width / 1.2; @avatar-font-size: @width / 1.8;
// scale animal avatar to be somewhat larger, because:
// 1. emojis are wider than most latin characters
// 2. they should occupy the width of two average characters
@avatar-font-size-animal: @avatar-font-size * (6/5);
} }
.avatar_main(@width: 30px) { .avatar_main(@width: 30px) {
--LessLoader_require: LessLoader_currentFile(); --LessLoader_require: LessLoader_currentFile();
@ -40,7 +44,9 @@
color: @cp_avatar-fg; color: @cp_avatar-fg;
font-size: @avatar-font-size; font-size: @avatar-font-size;
font-size: var(--avatar-font-size); font-size: var(--avatar-font-size);
text-transform: capitalize; .animal {
font-size: @avatar-font-size-animal;
}
} }
media-tag { media-tag {
min-height: @avatar-width; min-height: @avatar-width;

@ -50,4 +50,56 @@
} }
} }
} }
.cp-charts-cell {
border: 1px solid @cp_form-border;
display: table-cell;
padding: 5px 10px;
background: @cp_form-bg2;
}
.cp-form-results-type-radio {
.cp-form-results-type-multiradio-data {
display: flex;
flex-flow: column;
}
.cp-form-results-type-radio-data {
display: table-row;
border: 1px solid @cp_form-border;
& > span {
.cp-charts-cell();
}
}
}
.cp-charts.cp-bar-table, .cp-charts.cp-text-table {
display: table;
width: 100%;
.cp-charts-row {
display: table-row;
border: 1px solid @cp_form-border;
&.full {
display: flex;
flex-flow: column;
}
& > span {
.cp-charts-cell();
display: table-cell;
&.cp-value {
min-width: 200px;
}
&.cp-bar-container {
width: 99%;
padding: 0px;
position: relative;
.cp-bar {
position: absolute;
background: @cryptpad_color_brand;
height: 100%;
}
}
}
}
}
} }

@ -364,9 +364,9 @@
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
min-height: 20px; min-height: 25px;
height: 20px; height: 25px;
line-height: 20px; line-height: 25px;
max-width: 100%; max-width: 100%;
} }
.fa, .cptools { .fa, .cptools {

@ -273,4 +273,22 @@
} }
} }
} }
#cp-upload-preview-container {
max-width: 100%;
max-height: 300px;
overflow: auto;
// XXX these styles yield weird results for tall, thin images
// maybe img { object-fit: contain; } or scale-down are options
// but they have problems too
media-tag {
max-width: 100%;
iframe {
// pdfs don't take the full width unless we tell them to
width: 100%;
}
& > * {
max-width: 100%;
}
}
}
} }

@ -69,7 +69,6 @@
background: @cp_sidebar-right-bg; background: @cp_sidebar-right-bg;
color: @cp_sidebar-right-fg; color: @cp_sidebar-right-fg;
overflow: auto; overflow: auto;
padding-bottom: 200px;
// Following rules are only in settings // Following rules are only in settings
.cp-sidebarlayout-element { .cp-sidebarlayout-element {

@ -532,7 +532,13 @@
&> button { &> button {
height: @toolbar_line-height; height: @toolbar_line-height;
width: @toolbar_line-height; width: @toolbar_line-height;
span { font-size: unset; } span {
.avatar_vars(36px);
font-size: @avatar-font-size;
.animal {
font-size: @avatar-font-size-animal;
}
}
} }
&> button.cp-avatar.cp-avatar { &> button.cp-avatar.cp-avatar {
media-tag { media-tag {
@ -855,10 +861,14 @@
span { span {
text-align: center; text-align: center;
width: 100%; width: 100%;
font-size: 48px; .avatar_vars(72px);
font-size: @avatar-font-size;
display: inline-flex; display: inline-flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
.animal {
font-size: @avatar-font-size-animal;
}
} }
&.cp-avatar { &.cp-avatar {
.avatar_main(64px); .avatar_main(64px);

@ -2,7 +2,7 @@
# to work with CryptPad. This example WILL NOT WORK AS IS. For best results, # to work with CryptPad. This example WILL NOT WORK AS IS. For best results,
# compare the sections of this configuration file against a working CryptPad # compare the sections of this configuration file against a working CryptPad
# installation (http server by the Nodejs process). If you are using CryptPad # installation (http server by the Nodejs process). If you are using CryptPad
# in production, contact sales@cryptpad.fr # in production and require professional support please contact sales@cryptpad.fr
server { server {
listen 443 ssl http2; listen 443 ssl http2;

@ -45,8 +45,8 @@
"lint:js": "jshint --config .jshintrc --exclude-path .jshintignore .", "lint:js": "jshint --config .jshintrc --exclude-path .jshintignore .",
"lint:server": "jshint --config .jshintrc lib", "lint:server": "jshint --config .jshintrc lib",
"lint:less": "./node_modules/lesshint/bin/lesshint -c ./.lesshintrc ./customize.dist/src/less2/", "lint:less": "./node_modules/lesshint/bin/lesshint -c ./.lesshintrc ./customize.dist/src/less2/",
"lint:translations": "node ./scripts/lint-translations.js", "lint:translations": "node ./scripts/translations/lint-translations.js",
"unused-translations": "node ./scripts/unused-translations.js", "unused-translations": "node ./scripts/translations/unused-translations.js",
"test": "node scripts/TestSelenium.js", "test": "node scripts/TestSelenium.js",
"test-rpc": "cd scripts/tests && node test-rpc", "test-rpc": "cd scripts/tests && node test-rpc",
"template": "cd customize.dist/src && for page in ../index.html ../privacy.html ../terms.html ../contact.html ../what-is-cryptpad.html ../features.html ../../www/login/index.html ../../www/register/index.html ../../www/user/index.html;do echo $page; cp template.html $page; done;", "template": "cd customize.dist/src && for page in ../index.html ../privacy.html ../terms.html ../contact.html ../what-is-cryptpad.html ../features.html ../../www/login/index.html ../../www/register/index.html ../../www/user/index.html;do echo $page; cp template.html $page; done;",

@ -0,0 +1,111 @@
var Assert = require("assert");
var Util = require("../../lib/common-util");
var addIfAbsent = function (A, e) {
if (A.includes(e)) { return; }
A.push(e);
};
var findDuplicates = function (map) {
var keys = Object.keys(map);
var duplicates = {};
var markDuplicate = function (value, key1, key2) {
//console.log("[%s] === [%s] (%s)", key1, key2, value);
if (!Array.isArray(duplicates[value])) {
duplicates[value] = [];
}
addIfAbsent(duplicates[value], key1);
addIfAbsent(duplicates[value], key2);
};
keys.forEach(function (key) {
var value = map[key];
//var duplicates = [];
keys.forEach(function (key2) {
if (key === key2) { return; }
var value2 = map[key2];
if (value === value2) {
markDuplicate(value, key, key2);
}
});
});
var temp = {};
// sort keys and construct a new index using the first key in the sorted array
Object.keys(duplicates).forEach(function (key) {
var val = duplicates[key]; // should be an array
val.sort(); // default js sort
var new_key = val[0];
temp[new_key] = val;
});
var canonical = {};
Object.keys(temp).sort().forEach(function (key) {
canonical[key] = temp[key];
});
return canonical;
};
/*
var logDuplicates = function (duplicates) {
// indicate which strings are duplicated and could potentially be changed to use one key
Object.keys(duplicates).forEach(function (val) {
console.log('\"%s\" => %s', val, JSON.stringify(duplicates[val]));
});
};
*/
var FULL_LANGUAGES = {
EN: Util.clone(require("../../www/common/translations/messages.json")),
FR: Util.clone(require("../../www/common/translations/messages.fr.json")),
DE: Util.clone(require("../../www/common/translations/messages.de.json")),
JP: Util.clone(require("../../www/common/translations/messages.ja.json")),
};
var DUPLICATES = {};
Object.keys(FULL_LANGUAGES).forEach(function (code) {
DUPLICATES[code] = findDuplicates(FULL_LANGUAGES[code]);
});
var extraneousKeys = 0;
// 1) check whether the same mapping exists across languages
// ie. English has "Open" (verb) and "Open" (adjective)
// while French has "Ouvrir" and "Ouvert(s)"
// such keys should not be simplified/deduplicated
Object.keys(DUPLICATES.EN).forEach(function (key) {
var reference = DUPLICATES.EN[key];
if (!['FR', 'DE', 'JP'].every(function (code) {
try {
Assert.deepEqual(reference, DUPLICATES[code][key]);
} catch (err) {
return false;
}
return true;
})) {
return;
}
console.log("The key [%s] (\"%s\") is duplicated identically across all fully supported languages", key, FULL_LANGUAGES.EN[key]);
console.log("Values:", JSON.stringify(['EN', 'FR', 'DE', 'JP'].map(function (code) {
return FULL_LANGUAGES[code][key];
})));
console.log("Keys:", JSON.stringify(reference));
console.log();
extraneousKeys += reference.length - 1;
//console.log("\n" + code + "\n==\n");
//logDuplicates(map);
});
console.log("Total extraneous keys: %s", extraneousKeys);
// TODO
// find instances where
// one of the duplicated keys is not translated
// perhaps we could automatically use the translated one everywhere
// and improve the completeness of translations

@ -1,12 +1,14 @@
@import (reference) '../../customize/src/less2/include/framework.less'; @import (reference) '../../customize/src/less2/include/framework.less';
@import (reference) '../../customize/src/less2/include/sidebar-layout.less'; @import (reference) '../../customize/src/less2/include/sidebar-layout.less';
@import (reference) '../../customize/src/less2/include/support.less'; @import (reference) '../../customize/src/less2/include/support.less';
@import (reference) '../../customize/src/less2/include/charts.less';
&.cp-app-admin { &.cp-app-admin {
.framework_min_main(); .framework_min_main();
.sidebar-layout_main(); .sidebar-layout_main();
.support_main(); .support_main();
.charts_main();
.cp-hidden { .cp-hidden {
display: none !important; display: none !important;
@ -294,5 +296,27 @@
} }
} }
} }
span.cp-bar.profiling-percentage {
text-align: center;
padding: 5px;
}
span.profiling-label {
position: absolute;
z-index: 1;
width: 100%;
text-align: center;
padding: 5px;
}
#profiling-chart {
.cp-bar-container {
max-width: 400px;
}
}
.width-constrained {
max-width: 800px;
}
.cp-charts-row.heading {
font-weight: bold;
}
} }

@ -1673,34 +1673,51 @@ define([
var $div = makeBlock('performance-profiling'); // Msg.admin_performanceProfilingHint, .admin_performanceProfilingTitle var $div = makeBlock('performance-profiling'); // Msg.admin_performanceProfilingHint, .admin_performanceProfilingTitle
var onRefresh = function () { var onRefresh = function () {
var body = h('tbody'); var createBody = function () {
return h('div#profiling-chart.cp-charts.cp-bar-table', [
var table = h('table#cp-performance-table', [ h('span.cp-charts-row.heading', [
h('thead', [ h('span', Messages.admin_performanceKeyHeading),
h('th', Messages.admin_performanceKeyHeading), h('span', Messages.admin_performanceTimeHeading),
h('th', Messages.admin_performanceTimeHeading), h('span', Messages.admin_performancePercentHeading),
h('th', Messages.admin_performancePercentHeading), //h('span', ''), //Messages.admin_performancePercentHeading),
]), ]),
body,
]); ]);
var appendRow = function (key, time, percent) { };
console.log("[%s] %ss running time (%s%)", key, time, percent);
body.appendChild(h('tr', [ key, time, percent ].map(function (x) { var body = createBody();
return h('td', x); var appendRow = function (key, time, percent, scaled) {
}))); //console.log("[%s] %ss running time (%s%)", key, time, percent);
body.appendChild(h('span.cp-charts-row', [
h('span', key),
h('span', time),
//h('span', percent),
h('span.cp-bar-container', [
h('span.cp-bar.profiling-percentage', {
style: 'width: ' + scaled + '%',
}, ' ' ),
h('span.profiling-label', percent + '%'),
]),
]));
}; };
var process = function (_o) { var process = function (_o) {
$('#profiling-chart').remove();
body = createBody();
var o = _o[0]; var o = _o[0];
var sorted = Object.keys(o).sort(function (a, b) { var sorted = Object.keys(o).sort(function (a, b) {
if (o[b] - o[a] <= 0) { return -1; } if (o[b] - o[a] <= 0) { return -1; }
return 1; return 1;
}); });
var values = sorted.map(function (k) { return o[k]; });
var total = 0; var total = 0;
sorted.forEach(function (k) { total += o[k]; }); values.forEach(function (value) { total += value; });
var max = Math.max.apply(null, values);
sorted.forEach(function (k) { sorted.forEach(function (k) {
var percent = Math.floor((o[k] / total) * 1000) / 10; var percent = Math.floor((o[k] / total) * 1000) / 10;
appendRow(k, o[k], percent); appendRow(k, o[k], percent, (o[k] / max) * 100);
}); });
$div.append(h('div.width-constrained', body));
}; };
sFrameChan.query('Q_ADMIN_RPC', { sFrameChan.query('Q_ADMIN_RPC', {
@ -1710,10 +1727,7 @@ define([
UI.warn(Messages.error); UI.warn(Messages.error);
return void console.error(e, data); return void console.error(e, data);
} }
//console.info(data);
$div.find("table").remove();
process(data); process(data);
$div.append(table);
}); });
}; };

@ -123,6 +123,7 @@ define([
var jcalData = ICAL.parse(content); var jcalData = ICAL.parse(content);
vcalendar = new ICAL.Component(jcalData); vcalendar = new ICAL.Component(jcalData);
} catch (e) { } catch (e) {
console.error(e);
return void cb(e); return void cb(e);
} }
@ -147,6 +148,18 @@ define([
var isAllDay = false; var isAllDay = false;
var start = ev.getFirstPropertyValue('dtstart'); var start = ev.getFirstPropertyValue('dtstart');
var end = ev.getFirstPropertyValue('dtend'); var end = ev.getFirstPropertyValue('dtend');
var duration = ev.getFirstPropertyValue('duration');
if (!end && !duration) {
if (start.isDate) {
end = start.clone();
end.adjust(1); // Add one day
} else {
end = start.clone();
}
} else if (!end) {
end = start.clone();
end.addDuration(duration);
}
if (start.isDate && end.isDate) { if (start.isDate && end.isDate) {
isAllDay = true; isAllDay = true;
start = String(start); start = String(start);
@ -175,7 +188,7 @@ define([
hidden.push(al.toString()); hidden.push(al.toString());
} }
var trigger = al.getFirstPropertyValue('trigger'); var trigger = al.getFirstPropertyValue('trigger');
var minutes = -trigger.toSeconds() / 60; var minutes = trigger ? (-trigger.toSeconds() / 60) : 0;
if (reminders.indexOf(minutes) === -1) { reminders.push(minutes); } if (reminders.indexOf(minutes) === -1) { reminders.push(minutes); }
}); });

@ -566,7 +566,7 @@ define([
attributes: { attributes: {
'class': 'fa fa-trash-o', 'class': 'fa fa-trash-o',
}, },
content: h('span', Messages.kanban_delete), content: h('span', Messages.poll_remove),
action: function (e) { action: function (e) {
e.stopPropagation(); e.stopPropagation();
var cal = APP.calendars[id]; var cal = APP.calendars[id];
@ -586,8 +586,9 @@ define([
}, function (err) { }, function (err) {
if (err) { if (err) {
console.error(err); console.error(err);
UI.warn(Messages.error); return void UI.warn(Messages.error);
} }
renderCalendar();
}); });
}); });
} }
@ -722,7 +723,7 @@ define([
if (!calendars.length) { return; } if (!calendars.length) { return; }
var team = privateData.teams[teamId]; var team = privateData.teams[teamId];
var avatar = h('span.cp-avatar'); var avatar = h('span.cp-avatar');
common.displayAvatar($(avatar), team.avatar, team.displayName); common.displayAvatar($(avatar), team.avatar, team.displayName || team.name);
APP.$calendars.append(h('div.cp-calendar-team', [ APP.$calendars.append(h('div.cp-calendar-team', [
avatar, avatar,
h('span.cp-name', {title: team.name}, team.name) h('span.cp-name', {title: team.name}, team.name)

@ -3,7 +3,9 @@ define([
'/common/sframe-common-codemirror.js', '/common/sframe-common-codemirror.js',
'/customize/messages.js', '/customize/messages.js',
'/bower_components/chainpad/chainpad.dist.js', '/bower_components/chainpad/chainpad.dist.js',
], function (Util, SFCodeMirror, Messages, ChainPad) { '/common/inner/common-mediatag.js',
'/common/common-interface.js',
], function (Util, SFCodeMirror, Messages, ChainPad, MT, UI) {
var Markers = {}; var Markers = {};
/* TODO Known Issues /* TODO Known Issues
@ -38,7 +40,17 @@ define([
}); });
} }
uid = Number(uid); uid = Number(uid);
var name = Util.fixHTML(author.name || Messages.anonymous); var name = Util.fixHTML(UI.getDisplayName(author.name));
var animal;
if ((!name || name === Messages.anonymous) && typeof(author.uid) === 'string') {
animal = MT.getPseudorandomAnimal(author.uid);
if (animal) {
name = animal + ' ' + Messages.anonymous;
} else {
name = Messages.anonymous;
}
}
var col = Util.hexToRGB(author.color); var col = Util.hexToRGB(author.color);
var rgba = 'rgba('+col[0]+','+col[1]+','+col[2]+','+Env.opacity+');'; var rgba = 'rgba('+col[0]+','+col[1]+','+col[2]+','+Env.opacity+');';
return Env.editor.markText(from, to, { return Env.editor.markText(from, to, {
@ -520,7 +532,8 @@ define([
Env.authormarks.authors[Env.myAuthorId] = { Env.authormarks.authors[Env.myAuthorId] = {
name: userData.name, name: userData.name,
curvePublic: userData.curvePublic, curvePublic: userData.curvePublic,
color: userData.color color: userData.color,
uid: userData.uid,
}; };
if (!old || (old.name === userData.name && old.color === userData.color)) { return; } if (!old || (old.name === userData.name && old.color === userData.color)) { return; }
return true; return true;

@ -208,5 +208,7 @@ define(function() {
// the driveless mode by changing the following value to "false" // the driveless mode by changing the following value to "false"
AppConfig.allowDrivelessMode = true; AppConfig.allowDrivelessMode = true;
AppConfig.emojiAvatars = '🙈 🦀 🐞 🦋 🐬 🐋 🐢 🦉 🦆 🐧 🦡 🦘 🦨 🦦 🦥 🐼 🐻 🦝 🦓 🐄 💮️ 🐙️ 🌸️ 🌻️ 🐝️ 🐐 🦙 🦒 🐘 🦏 🐁 🐹 🐰 🦫 🦔 🐨 🐱 🐺 👺 👹 👽 👾 🤖'.split(/\s+/);
return AppConfig; return AppConfig;
}); });

@ -34,6 +34,7 @@ try {
define([ define([
'/common/requireconfig.js' '/common/requireconfig.js'
], function (RequireConfig) { ], function (RequireConfig) {
require.config(RequireConfig()); require.config(RequireConfig());
// most of CryptPad breaks if you don't support isArray // most of CryptPad breaks if you don't support isArray
@ -91,4 +92,10 @@ define([
} catch (e) { console.error(e); failStore(); } } catch (e) { console.error(e); failStore(); }
require([document.querySelector('script[data-bootload]').getAttribute('data-bootload')]); require([document.querySelector('script[data-bootload]').getAttribute('data-bootload')]);
if (typeof(Promise) !== 'function') {
setTimeout(function () {
var s = "Internet Explorer is not supported anymore, including by Microsoft.\n\nMost of CryptPad's collaborative functionality requires a modern browser to work.\n\nWe recommend Mozilla Firefox.";
window.alert(s);
});
}
}); });

@ -41,6 +41,10 @@ define([
return e; return e;
}; };
UI.getDisplayName = function (name) {
return (typeof(name) === 'string'? name: "").trim() || Messages.anonymous;
};
// FIXME almost everywhere this is used would also be // FIXME almost everywhere this is used would also be
// a good candidate for sframe-common's getMediatagFromHref // a good candidate for sframe-common's getMediatagFromHref
UI.mediaTag = function (src, key) { UI.mediaTag = function (src, key) {

@ -17,7 +17,8 @@ define([
edPublic: proxy.edPublic, edPublic: proxy.edPublic,
curvePublic: proxy.curvePublic, curvePublic: proxy.curvePublic,
notifications: Util.find(proxy, ['mailboxes', 'notifications', 'channel']), notifications: Util.find(proxy, ['mailboxes', 'notifications', 'channel']),
avatar: proxy.profile && proxy.profile.avatar avatar: proxy.profile && proxy.profile.avatar,
uid: proxy.uid,
}; };
if (hash === false) { delete data.channel; } if (hash === false) { delete data.channel; }
return data; return data;

@ -196,7 +196,7 @@ define([
reader.readAsText(blob); reader.readAsText(blob);
}; };
Thumb.fromBlob = function (blob, _cb) { Thumb.fromBlob = function (blob, _cb) {
var cb = Util.once(_cb); var cb = Util.once(Util.mkAsync(_cb));
// The blob is already in memory, it should be super-fast to make a thumbnail // The blob is already in memory, it should be super-fast to make a thumbnail
// ==> 1s timeout // ==> 1s timeout
setTimeout(function () { setTimeout(function () {

@ -156,9 +156,11 @@ define([
var icons = Object.keys(users).map(function (key, i) { var icons = Object.keys(users).map(function (key, i) {
var data = users[key]; var data = users[key];
var name = data.displayName || data.name || Messages.anonymous; var name = UI.getDisplayName(data.displayName || data.name);
var avatar = h('span.cp-usergrid-avatar.cp-avatar'); var avatar = h('span.cp-usergrid-avatar.cp-avatar', {
common.displayAvatar($(avatar), data.avatar, name); 'aria-hidden': true,
});
common.displayAvatar($(avatar), data.avatar, name, Util.noop, data.uid);
var removeBtn, el; var removeBtn, el;
if (config.remove) { if (config.remove) {
removeBtn = h('span.fa.fa-times'); removeBtn = h('span.fa.fa-times');
@ -2005,9 +2007,12 @@ define([
var loadingAvatar; var loadingAvatar;
var to; var to;
var oldUrl = ''; var oldUrl = '';
var oldUid;
var oldName;
var updateButton = function () { var updateButton = function () {
var myData = metadataMgr.getUserData(); var myData = metadataMgr.getUserData();
var privateData = metadataMgr.getPrivateData(); var privateData = metadataMgr.getPrivateData();
var uid = myData.uid;
if (!priv.plan && privateData.plan) { if (!priv.plan && privateData.plan) {
config.$initBlock.empty(); config.$initBlock.empty();
metadataMgr.off('change', updateButton); metadataMgr.off('change', updateButton);
@ -2022,18 +2027,19 @@ define([
return; return;
} }
loadingAvatar = true; loadingAvatar = true;
var newName = myData.name; var newName = UI.getDisplayName(myData.name);
var url = myData.avatar; var url = myData.avatar;
$displayName.text(newName || Messages.anonymous); $displayName.text(newName);
if (accountName && oldUrl !== url) { if ((accountName && oldUrl !== url) || !accountName && uid !== oldUid || oldName !== newName) {
$avatar.html(''); $avatar.html('');
Common.displayAvatar($avatar, url, Common.displayAvatar($avatar, url, newName, function ($img) {
newName || Messages.anonymous, function ($img) {
oldUrl = url; oldUrl = url;
oldUid = uid;
oldName = newName;
$userAdmin.find('> button').removeClass('cp-avatar'); $userAdmin.find('> button').removeClass('cp-avatar');
if ($img) { $userAdmin.find('> button').addClass('cp-avatar'); } if ($img) { $userAdmin.find('> button').addClass('cp-avatar'); }
loadingAvatar = false; loadingAvatar = false;
}); }, uid);
return; return;
} }
loadingAvatar = false; loadingAvatar = false;
@ -2315,6 +2321,7 @@ define([
var teams = Object.keys(privateData.teams).map(function (id) { var teams = Object.keys(privateData.teams).map(function (id) {
var data = privateData.teams[id]; var data = privateData.teams[id];
var avatar = h('span.cp-creation-team-avatar.cp-avatar'); var avatar = h('span.cp-creation-team-avatar.cp-avatar');
// We assume that teams always have a non-empty name, so we don't need a UID
common.displayAvatar($(avatar), data.avatar, data.name); common.displayAvatar($(avatar), data.avatar, data.name);
return h('div.cp-creation-team', { return h('div.cp-creation-team', {
'data-id': id, 'data-id': id,
@ -3049,22 +3056,10 @@ define([
var name = Util.fixHTML(data.title); var name = Util.fixHTML(data.title);
var url = data.href; var url = data.href;
var user = data.name; var user = data.name;
//Messages.link_open = "Open URL";
// openLinkInNewTab ("Open Link in New Tab")
// fc_open ("Open")
// share_linkOpen ("Preview")
// resources_openInNewTab ("Open it in a new tab")
Messages.link_open = Messages.fc_open; // XXX 4.11.0
//Messages.link_store = "Store link in drive";
// toolbar_storeInDrive ? ("Store in CryptDrive")
// autostore_store ? ("Store")
Messages.link_store = Messages.toolbar_storeInDrive; // XXX 4.11.0
var content = h('div', [ var content = h('div', [
UI.setHTML(h('p'), Messages._getKey('notification_openLink', [name, user])), UI.setHTML(h('p'), Messages._getKey('notification_openLink', [name, user])),
h('pre', url), h('pre.cp-link-preview', url),
UIElements.getVerifiedFriend(common, data.curve, user) UIElements.getVerifiedFriend(common, data.curve, user)
]); ]);
var clicked = false; var clicked = false;
@ -3079,7 +3074,7 @@ define([
keys: [27] keys: [27]
}, { }, {
className: 'primary', className: 'primary',
name: Messages.link_open, name: Messages.fc_open,
onClick: function () { onClick: function () {
if (clicked) { return true; } if (clicked) { return true; }
clicked = true; clicked = true;
@ -3089,7 +3084,7 @@ define([
keys: [13] keys: [13]
}, { }, {
className: 'primary', className: 'primary',
name: Messages.link_store, name: Messages.toolbar_storeInDrive,
onClick: function () { onClick: function () {
if (clicked) { return; } if (clicked) { return; }
clicked = true; clicked = true;
@ -3119,7 +3114,7 @@ define([
var sframeChan = common.getSframeChannel(); var sframeChan = common.getSframeChannel();
var msg = data.content.msg; var msg = data.content.msg;
var name = Util.fixHTML(msg.content.user.displayName) || Messages.anonymous; var name = Util.fixHTML(UI.getDisplayName(msg.content.user.displayName));
var title = Util.fixHTML(msg.content.title); var title = Util.fixHTML(msg.content.title);
var text = Messages._getKey('owner_add', [name, title]); var text = Messages._getKey('owner_add', [name, title]);
@ -3251,7 +3246,7 @@ define([
var sframeChan = common.getSframeChannel(); var sframeChan = common.getSframeChannel();
var msg = data.content.msg; var msg = data.content.msg;
var name = Util.fixHTML(msg.content.user.displayName) || Messages.anonymous; var name = Util.fixHTML(UI.getDisplayName(msg.content.user.displayName));
var title = Util.fixHTML(msg.content.title); var title = Util.fixHTML(msg.content.title);
var text = Messages._getKey('owner_team_add', [name, title]); var text = Messages._getKey('owner_team_add', [name, title]);
@ -3366,13 +3361,15 @@ define([
var verified = h('p'); var verified = h('p');
var $verified = $(verified); var $verified = $(verified);
name = UI.getDisplayName(name);
if (priv.friends && priv.friends[curve]) { if (priv.friends && priv.friends[curve]) {
$verified.addClass('cp-notifications-requestedit-verified'); $verified.addClass('cp-notifications-requestedit-verified');
var f = priv.friends[curve]; var f = priv.friends[curve];
$verified.append(h('span.fa.fa-certificate')); $verified.append(h('span.fa.fa-certificate'));
var $avatar = $(h('span.cp-avatar')).appendTo($verified); var $avatar = $(h('span.cp-avatar')).appendTo($verified);
$verified.append(h('p', Messages._getKey('isContact', [f.displayName]))); name = UI.getDisplayName(f.displayName);
common.displayAvatar($avatar, f.avatar, f.displayName); $verified.append(h('p', Messages._getKey('isContact', [name])));
common.displayAvatar($avatar, f.avatar, name, Util.noop, f.uid);
} else { } else {
$verified.append(Messages._getKey('isNotContact', [name])); $verified.append(Messages._getKey('isNotContact', [name]));
} }
@ -3382,7 +3379,7 @@ define([
UIElements.displayInviteTeamModal = function (common, data) { UIElements.displayInviteTeamModal = function (common, data) {
var msg = data.content.msg; var msg = data.content.msg;
var name = Util.fixHTML(msg.content.user.displayName) || Messages.anonymous; var name = Util.fixHTML(UI.getDisplayName(msg.content.user.displayName));
var teamName = Util.fixHTML(Util.find(msg, ['content', 'team', 'metadata', 'name']) || ''); var teamName = Util.fixHTML(Util.find(msg, ['content', 'team', 'metadata', 'name']) || '');
var verified = UIElements.getVerifiedFriend(common, msg.author, name); var verified = UIElements.getVerifiedFriend(common, msg.author, name);
@ -3466,7 +3463,8 @@ define([
name: f.displayName, name: f.displayName,
curvePublic: f.curvePublic, curvePublic: f.curvePublic,
profile: f.profile, profile: f.profile,
notifications: f.notifications notifications: f.notifications,
uid: f.uid,
}; };
}); });
}; };
@ -3565,7 +3563,7 @@ define([
}; };
// Set the value to receive from the autocomplete // Set the value to receive from the autocomplete
var toInsert = function (data, key) { var toInsert = function (data, key) {
var name = data.name.replace(/[^a-zA-Z0-9]+/g, "-"); var name = UI.getDisplayName(data.name.replace(/[^a-zA-Z0-9]+/g, "-"));
return "[@"+name+"|"+key+"]"; return "[@"+name+"|"+key+"]";
}; };
@ -3618,18 +3616,20 @@ define([
var avatar = h('span.cp-avatar', { var avatar = h('span.cp-avatar', {
contenteditable: false contenteditable: false
}); });
common.displayAvatar($(avatar), data.avatar, data.name);
var displayName = UI.getDisplayName(data.name);
common.displayAvatar($(avatar), data.avatar, displayName); // XXX
return h('span.cp-mentions', { return h('span.cp-mentions', {
'data-curve': data.curvePublic, 'data-curve': data.curvePublic,
'data-notifications': data.notifications, 'data-notifications': data.notifications,
'data-profile': data.profile, 'data-profile': data.profile,
'data-name': Util.fixHTML(data.name), 'data-name': Util.fixHTML(displayName),
'data-avatar': data.avatar || "", 'data-avatar': data.avatar || "",
}, [ }, [
avatar, avatar,
h('span.cp-mentions-name', { h('span.cp-mentions-name', {
contenteditable: false contenteditable: false
}, data.name) }, displayName)
]); ]);
}; };
} }
@ -3661,7 +3661,7 @@ define([
}).map(function (key) { }).map(function (key) {
var data = sources[key]; var data = sources[key];
return { return {
label: data.name, label: UI.getDisplayName(data.name),
value: key value: key
}; };
}); });
@ -3696,10 +3696,12 @@ define([
var obj = sources[key]; var obj = sources[key];
if (!obj) { return; } if (!obj) { return; }
var avatar = h('span.cp-avatar'); var avatar = h('span.cp-avatar');
common.displayAvatar($(avatar), obj.avatar, obj.name); var displayName = UI.getDisplayName(obj.name);
common.displayAvatar($(avatar), obj.avatar, displayName, Util.noop, obj.uid);
var li = h('li.cp-autocomplete-value', [ var li = h('li.cp-autocomplete-value', [
avatar, avatar,
h('span', obj.name) h('span', displayName),
]); ]);
return $(li).appendTo(ul); return $(li).appendTo(ul);
}; };

@ -75,7 +75,9 @@
handlers.push(cb); handlers.push(cb);
}, },
unreg: function (cb) { unreg: function (cb) {
if (handlers.indexOf(cb) === -1) { throw new Error("Not registered"); } if (handlers.indexOf(cb) === -1) {
return void console.error("event handler was already unregistered");
}
handlers.splice(handlers.indexOf(cb), 1); handlers.splice(handlers.indexOf(cb), 1);
}, },
fire: function () { fire: function () {

@ -1,7 +1,7 @@
define([ define([
'/bower_components/chainpad-crypto/crypto.js', '/bower_components/chainpad-crypto/crypto.js',
'/bower_components/chainpad-netflux/chainpad-netflux.js', 'chainpad-netflux',
'/bower_components/netflux-websocket/netflux-client.js', 'netflux-client',
'/common/common-util.js', '/common/common-util.js',
'/common/common-hash.js', '/common/common-hash.js',
'/common/common-realtime.js', '/common/common-realtime.js',

@ -161,7 +161,7 @@ define([
common.makeNetwork = function (cb) { common.makeNetwork = function (cb) {
require([ require([
'/bower_components/netflux-websocket/netflux-client.js', 'netflux-client',
'/common/outer/network-config.js' '/common/outer/network-config.js'
], function (Netflux, NetConfig) { ], function (Netflux, NetConfig) {
var wsUrl = NetConfig.getWebsocketURL(); var wsUrl = NetConfig.getWebsocketURL();

@ -171,7 +171,7 @@ define([
if (!Object.keys(_friends).length) { if (!Object.keys(_friends).length) {
var friendText; var friendText;
if (!friendKeys.length) { if (!friendKeys.length) {
console.error(UIElements.noContactsMessage(common)); //console.error(UIElements.noContactsMessage(common));
var findContacts = UIElements.noContactsMessage(common); var findContacts = UIElements.noContactsMessage(common);
friendText = h('span.cp-app-prop-content', friendText = h('span.cp-app-prop-content',
findContacts.content findContacts.content
@ -772,7 +772,8 @@ define([
if (friend.edPublic !== ed || c === 'me') { return; } if (friend.edPublic !== ed || c === 'me') { return; }
_owners[friend.edPublic] = { _owners[friend.edPublic] = {
name: friend.displayName, name: friend.displayName,
avatar: friend.avatar avatar: friend.avatar,
uid: friend.uid,
}; };
return true; return true;
})) { })) {
@ -782,6 +783,11 @@ define([
_owners[ed] = { _owners[ed] = {
avatar: '?', avatar: '?',
name: Messages.owner_unknownUser, name: Messages.owner_unknownUser,
// TODO a possible enhancement is to use data from the context
// ie. if you have opened the access modal from within the pad
// its owner might be present or they might have left some data
// in the pad itself (as is the case of the uid in rich text comments)
// TODO or just implement "Acquaintances"
}; };
strangers++; strangers++;
}); });

@ -6,12 +6,13 @@ define([
'/common/hyperscript.js', '/common/hyperscript.js',
'/common/media-tag.js', '/common/media-tag.js',
'/customize/messages.js', '/customize/messages.js',
'/customize/application_config.js',
'/bower_components/tweetnacl/nacl-fast.min.js', '/bower_components/tweetnacl/nacl-fast.min.js',
'/bower_components/croppie/croppie.min.js', '/bower_components/croppie/croppie.min.js',
'/bower_components/file-saver/FileSaver.min.js', '/bower_components/file-saver/FileSaver.min.js',
'css!/bower_components/croppie/croppie.css', 'css!/bower_components/croppie/croppie.css',
], function ($, Util, Hash, UI, h, MediaTag, Messages) { ], function ($, Util, Hash, UI, h, MediaTag, Messages, AppConfig) {
var MT = {}; var MT = {};
var Nacl = window.nacl; var Nacl = window.nacl;
@ -43,9 +44,16 @@ define([
}); });
}; };
var animal_avatars = {};
MT.getCursorAvatar = function (cursor) { MT.getCursorAvatar = function (cursor) {
var uid = cursor.uid;
// TODO it would be nice to have "{0} is editing" instead of just their name
var html = '<span class="cp-cursor-avatar">'; var html = '<span class="cp-cursor-avatar">';
html += (cursor.avatar && avatars[cursor.avatar]) || ''; if (cursor.avatar && avatars[cursor.avatar]) {
html += avatars[cursor.avatar];
} else if (animal_avatars[uid]) {
html += animal_avatars[uid] + ' ';
}
html += Util.fixHTML(cursor.name) + '</span>'; html += Util.fixHTML(cursor.name) + '</span>';
return html; return html;
}; };
@ -79,12 +87,63 @@ define([
}); });
}; };
MT.displayAvatar = function (common, $container, href, name, _cb) { // https://emojipedia.org/nature/
var ANIMALS = AppConfig.emojiAvatars || [];
var getPseudorandomAnimal = MT.getPseudorandomAnimal = function (seed) {
if (!ANIMALS.length) { return ''; }
if (typeof(seed) !== 'string') { return; }
seed = seed.replace(/\D/g, '').slice(0, 10); // TODO possible optimization for on-wire uid
seed = parseInt(seed);
if (!seed) { return; }
return ANIMALS[seed % ANIMALS.length] || '';
};
var getPrettyInitials = MT.getPrettyInitials = function (name) {
var parts = name.split(/\s+/);
var text;
if (parts.length > 1) {
text = parts.slice(0, 2).map(Util.getFirstCharacter).join('');
} else {
text = Util.getFirstCharacter(name);
var second = Util.getFirstCharacter(name.replace(text, ''));
if (second && second !== '?') {
text += second;
}
}
return text;
};
MT.displayAvatar = function (common, $container, href, name, _cb, uid) {
var cb = Util.once(Util.mkAsync(_cb || function () {})); var cb = Util.once(Util.mkAsync(_cb || function () {}));
var displayDefault = function () { var displayDefault = function () {
var text = Util.getFirstCharacter(name || Messages.anonymous); var animal_avatar;
var $avatar = $('<span>', {'class': 'cp-avatar-default'}).text(text); if (uid && animal_avatars[uid]) {
animal_avatar = animal_avatars[uid];
}
name = UI.getDisplayName(name);
var text;
if (ANIMALS.length && name === Messages.anonymous && uid) {
if (animal_avatar) {
text = animal_avatar;
} else {
text = animal_avatar = getPseudorandomAnimal(uid);
}
} else {
text = getPrettyInitials(name);
}
var $avatar = $('<span>', {
'class': 'cp-avatar-default' + (animal_avatar? ' animal': ''),
// this prevents screenreaders from trying to describe this
alt: '',
'aria-hidden': true,
}).text(text);
$container.append($avatar); $container.append($avatar);
if (uid && animal_avatar) {
animal_avatars[uid] = animal_avatar;
}
if (cb) { cb(); } if (cb) { cb(); }
}; };
if (!window.Symbol) { return void displayDefault(); } // IE doesn't have Symbol if (!window.Symbol) { return void displayDefault(); } // IE doesn't have Symbol
@ -97,6 +156,7 @@ define([
return void cb($el); return void cb($el);
} }
var centerImage = function ($img, $image) { var centerImage = function ($img, $image) {
var img = $image[0]; var img = $image[0];
var w = img.width; var w = img.width;

@ -87,6 +87,7 @@ var factory = function () {
image: function (metadata, url, content, cfg, cb) { image: function (metadata, url, content, cfg, cb) {
var img = document.createElement('img'); var img = document.createElement('img');
img.setAttribute('src', url); img.setAttribute('src', url);
img.setAttribute('alt', metadata.alt || "");
img.blob = content; img.blob = content;
cb(void 0, img); cb(void 0, img);
}, },
@ -94,15 +95,19 @@ var factory = function () {
var video = document.createElement('video'); var video = document.createElement('video');
video.setAttribute('src', url); video.setAttribute('src', url);
video.setAttribute('controls', true); video.setAttribute('controls', true);
// https://discuss.codecademy.com/t/can-we-use-an-alt-attribute-with-the-video-tag/300322/4
video.setAttribute('title', metadata.alt || "");
cb(void 0, video); cb(void 0, video);
}, },
audio: function (metadata, url, content, cfg, cb) { audio: function (metadata, url, content, cfg, cb) {
var audio = document.createElement('audio'); var audio = document.createElement('audio');
audio.setAttribute('src', url); audio.setAttribute('src', url);
audio.setAttribute('controls', true); audio.setAttribute('controls', true);
audio.setAttribute('alt', metadata.alt || "");
cb(void 0, audio); cb(void 0, audio);
}, },
pdf: function (metadata, url, content, cfg, cb) { pdf: function (metadata, url, content, cfg, cb) {
// XXX alt text
var iframe = document.createElement('iframe'); var iframe = document.createElement('iframe');
if (cfg.pdf.viewer) { // PDFJS if (cfg.pdf.viewer) { // PDFJS
var viewerUrl = cfg.pdf.viewer + '?file=' + url; var viewerUrl = cfg.pdf.viewer + '?file=' + url;
@ -115,6 +120,7 @@ var factory = function () {
download: function (metadata, url, content, cfg, cb) { download: function (metadata, url, content, cfg, cb) {
var btn = document.createElement('button'); var btn = document.createElement('button');
btn.setAttribute('class', 'btn btn-default'); btn.setAttribute('class', 'btn btn-default');
btn.setAttribute('alt', metadata.alt || "");
btn.innerHTML = '<i class="fa fa-save"></i>' + cfg.download.text + '<br>' + btn.innerHTML = '<i class="fa fa-save"></i>' + cfg.download.text + '<br>' +
(metadata.name ? '<b>' + fixHTML(metadata.name) + '</b>' : ''); (metadata.name ? '<b>' + fixHTML(metadata.name) + '</b>' : '');
btn.addEventListener('click', function () { btn.addEventListener('click', function () {
@ -542,7 +548,7 @@ var factory = function () {
// Process // Process
var process = function (mediaObject, decrypted, cfg, cb) { var process = function (mediaObject, decrypted, cfg, cb) {
var metadata = decrypted.metadata; var metadata = decrypted.metadata || {};
var blob = decrypted.content; var blob = decrypted.content;
var mediaType = getType(mediaObject, metadata, cfg); var mediaType = getType(mediaObject, metadata, cfg);
@ -596,6 +602,15 @@ var factory = function () {
}); });
}; };
var initHandlers = function () {
return {
'progress': [],
'complete': [],
'metadata': [],
'error': []
};
};
// Initialize a media-tag // Initialize a media-tag
var init = function (el, cfg) { var init = function (el, cfg) {
cfg = cfg || {}; cfg = cfg || {};
@ -613,13 +628,7 @@ var factory = function () {
}; };
} }
var handlers = cfg.handlers || { var handlers = cfg.handlers || initHandlers();
'progress': [],
'complete': [],
'metadata': [],
'error': []
};
var mediaObject = el._mediaObject = { var mediaObject = el._mediaObject = {
handlers: handlers, handlers: handlers,
tag: el tag: el
@ -762,6 +771,24 @@ var factory = function () {
init.fetchDecryptedMetadata = fetchDecryptedMetadata; init.fetchDecryptedMetadata = fetchDecryptedMetadata;
init.preview = function (content, metadata, cfg, cb) {
cfg = cfg || {};
addMissingConfig(cfg, config);
var handlers = cfg.handlers || initHandlers();
var el = document.createElement('media-tag');
var mediaObject = el._mediaObject = {
handlers: handlers,
tag: el,
};
process(mediaObject, {
metadata: metadata,
content: content
}, cfg, function (err) {
if (err) { return void cb(err); }
cb(void 0, el);
});
};
return init; return init;
}; };

@ -190,9 +190,11 @@ define([
markup.message = function (msg) { markup.message = function (msg) {
if (msg.type !== 'MSG') { return; } if (msg.type !== 'MSG') { return; }
var curvePublic = msg.author; var curvePublic = msg.author;
// FIXME this assignment looks like it has some holes in its logic
// but I'm scared to touch it because it looks like it was hacked to fix some bugs
var name = (typeof msg.name !== "undefined" || !contactsData[msg.author]) ? var name = (typeof msg.name !== "undefined" || !contactsData[msg.author]) ?
(msg.name || Messages.anonymous) : (msg.name || Messages.anonymous) :
contactsData[msg.author].displayName; contactsData[msg.author].displayName || Messages.anonymous;
var d = msg.time ? new Date(msg.time) : undefined; var d = msg.time ? new Date(msg.time) : undefined;
var day = d ? d.toLocaleDateString() : ''; var day = d ? d.toLocaleDateString() : '';
var hour = d ? d.toLocaleTimeString() : ''; var hour = d ? d.toLocaleTimeString() : '';
@ -239,7 +241,7 @@ define([
}); });
var chan = state.channels[id]; var chan = state.channels[id];
var displayName = chan.name; var displayName = UI.getDisplayName(chan.name || chan.displayName);
var fetching = false; var fetching = false;
var $moreHistory = $(moreHistory).click(function () { var $moreHistory = $(moreHistory).click(function () {
@ -364,7 +366,7 @@ define([
avatars[friend.avatar] = $img[0].outerHTML; avatars[friend.avatar] = $img[0].outerHTML;
} }
$(rightCol).insertAfter($avatar); $(rightCol).insertAfter($avatar);
}); }, friend.uid);
} }
var sending = false; var sending = false;
@ -544,7 +546,7 @@ define([
title: Messages.contacts_online title: Messages.contacts_online
}); });
var rightCol = h('span.cp-app-contacts-right-col', [ var rightCol = h('span.cp-app-contacts-right-col', [
h('span.cp-app-contacts-name', [room.name]), h('span.cp-app-contacts-name', [room.isFriendChat? UI.getDisplayName(room.name): room.name]),
h('span.cp-app-contacts-icons', [ h('span.cp-app-contacts-icons', [
room.isFriendChat ? mute : undefined, room.isFriendChat ? mute : undefined,
room.isFriendChat ? unmute : undefined, room.isFriendChat ? unmute : undefined,
@ -609,7 +611,7 @@ define([
avatars[friendData.avatar] = $img[0].outerHTML; avatars[friendData.avatar] = $img[0].outerHTML;
} }
$room.append(rightCol); $room.append(rightCol);
}); }, friendData.uid);
} }
$room.append(status); $room.append(status);
return $room; return $room;
@ -631,9 +633,9 @@ define([
var el_message = markup.message(message); var el_message = markup.message(message);
if (message.type === 'MSG') { if (message.type === 'MSG') {
var name = typeof message.name !== "undefined" ? var name = UI.getDisplayName(typeof message.name !== "undefined" ?
(message.name || Messages.anonymous) : message.name:
contactsData[message.author].displayName; contactsData[message.author].displayName);
common.notify({ common.notify({
title: name, title: name,
msg: message.text, msg: message.text,
@ -826,6 +828,11 @@ define([
} }
}; };
/* The following block is for a disabled feature which allows users to switch
between pad chat (when in the context of a pad) and direct chats with their
contacts.
*/
/*
common.getMetadataMgr().onTitleChange(function () { common.getMetadataMgr().onTitleChange(function () {
var padChat = common.getPadChat(); var padChat = common.getPadChat();
var md = common.getMetadataMgr().getMetadata(); var md = common.getMetadataMgr().getMetadata();
@ -839,11 +846,14 @@ define([
$lAvatar.find('.cp-avatar-default, media-tag').remove(); $lAvatar.find('.cp-avatar-default, media-tag').remove();
var $div = $('<div>'); var $div = $('<div>');
// There should always be a title here (defaultTitle if nothing else)
// so we don't ever need to supply a uid for an animal avatar
common.displayAvatar($div, null, name, function () { common.displayAvatar($div, null, name, function () {
$mAvatar.html($div.html()); $mAvatar.html($div.html());
$lAvatar.find('.cp-app-contacts-right-col').before($div.html()); $lAvatar.find('.cp-app-contacts-right-col').before($div.html());
}); });
}); });
*/
// TODO room // TODO room
// var onJoinRoom // var onJoinRoom
@ -878,7 +888,7 @@ define([
h('i.fa.fa-bell'), h('i.fa.fa-bell'),
Messages.contacts_unmute || 'unmute' Messages.contacts_unmute || 'unmute'
]); ]);
common.displayAvatar($(avatar), data.avatar, data.name); common.displayAvatar($(avatar), data.avatar, data.name, Util.noop, data.uid);
$(button).click(function () { $(button).click(function () {
unmuteUser(curve, button); unmuteUser(curve, button);
execCommand('UNMUTE_USER', curve, function (e, data) { execCommand('UNMUTE_USER', curve, function (e, data) {
@ -894,7 +904,7 @@ define([
}); });
return h('div.cp-contacts-muted-user', [ return h('div.cp-contacts-muted-user', [
h('span', avatar), h('span', avatar),
h('span', data.name), h('span', UI.getDisplayName(data.name)),
button button
]); ]);
}); });

@ -26,9 +26,9 @@ define([
'/bower_components/chainpad-crypto/crypto.js', '/bower_components/chainpad-crypto/crypto.js',
'/bower_components/chainpad/chainpad.dist.js', '/bower_components/chainpad/chainpad.dist.js',
'/bower_components/chainpad-netflux/chainpad-netflux.js', 'chainpad-netflux',
'/bower_components/chainpad-listmap/chainpad-listmap.js', 'chainpad-listmap',
'/bower_components/netflux-websocket/netflux-client.js', 'netflux-client',
'/bower_components/nthen/index.js', '/bower_components/nthen/index.js',
'/bower_components/saferphore/index.js', '/bower_components/saferphore/index.js',
], function (ApiConfig, Sortify, UserObject, ProxyManager, Migrate, Hash, Util, Constants, Feedback, ], function (ApiConfig, Sortify, UserObject, ProxyManager, Migrate, Hash, Util, Constants, Feedback,

@ -6,7 +6,7 @@ define([
'/common/outer/cache-store.js', '/common/outer/cache-store.js',
'/customize/messages.js', '/customize/messages.js',
'/bower_components/nthen/index.js', '/bower_components/nthen/index.js',
'/bower_components/chainpad-listmap/chainpad-listmap.js', 'chainpad-listmap',
'/bower_components/chainpad-crypto/crypto.js', '/bower_components/chainpad-crypto/crypto.js',
'/bower_components/chainpad/chainpad.dist.js', '/bower_components/chainpad/chainpad.dist.js',
], function (Util, Hash, Constants, Realtime, Cache, Messages, nThen, Listmap, Crypto, ChainPad) { ], function (Util, Hash, Constants, Realtime, Cache, Messages, nThen, Listmap, Crypto, ChainPad) {

@ -187,6 +187,7 @@ define([
data.color = Util.find(proxy, ['settings', 'general', 'cursor', 'color']); data.color = Util.find(proxy, ['settings', 'general', 'cursor', 'color']);
data.name = proxy[Constants.displayNameKey] || ctx.store.noDriveName || Messages.anonymous; data.name = proxy[Constants.displayNameKey] || ctx.store.noDriveName || Messages.anonymous;
data.avatar = Util.find(proxy, ['profile', 'avatar']); data.avatar = Util.find(proxy, ['profile', 'avatar']);
data.uid = Util.find(proxy, ['uid']) || ctx.store.noDriveUid;
c.cursor = data; c.cursor = data;
sendMyCursor(ctx, client); sendMyCursor(ctx, client);
cb(); cb();

@ -7,7 +7,7 @@ define([
'/common/common-messaging.js', '/common/common-messaging.js',
'/common/notify.js', '/common/notify.js',
'/common/outer/mailbox-handlers.js', '/common/outer/mailbox-handlers.js',
'/bower_components/chainpad-netflux/chainpad-netflux.js', 'chainpad-netflux',
'/bower_components/chainpad-crypto/crypto.js', '/bower_components/chainpad-crypto/crypto.js',
], function (Config, BCast, Util, Hash, Realtime, Messaging, Notify, Handlers, CpNetflux, Crypto) { ], function (Config, BCast, Util, Hash, Realtime, Messaging, Notify, Handlers, CpNetflux, Crypto) {
var Mailbox = {}; var Mailbox = {};

@ -3,7 +3,7 @@ define([
'/common/common-hash.js', '/common/common-hash.js',
'/common/common-constants.js', '/common/common-constants.js',
'/common/common-realtime.js', '/common/common-realtime.js',
'/bower_components/chainpad-listmap/chainpad-listmap.js', 'chainpad-listmap',
'/bower_components/chainpad-crypto/crypto.js', '/bower_components/chainpad-crypto/crypto.js',
'/bower_components/chainpad/chainpad.dist.js', '/bower_components/chainpad/chainpad.dist.js',
], function (Util, Hash, Constants, Realtime, Listmap, Crypto, ChainPad) { ], function (Util, Hash, Constants, Realtime, Listmap, Crypto, ChainPad) {

@ -918,7 +918,7 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto, Feedback)
define([ define([
'/common/common-util.js', '/common/common-util.js',
'/common/common-hash.js', '/common/common-hash.js',
'/bower_components/chainpad-netflux/chainpad-netflux.js', 'chainpad-netflux',
'json.sortify', 'json.sortify',
'/bower_components/nthen/index.js', '/bower_components/nthen/index.js',
'/bower_components/chainpad-crypto/crypto.js', '/bower_components/chainpad-crypto/crypto.js',

@ -6,7 +6,7 @@ define([
'/bower_components/nthen/index.js', '/bower_components/nthen/index.js',
'/bower_components/chainpad-crypto/crypto.js', '/bower_components/chainpad-crypto/crypto.js',
'/bower_components/chainpad-listmap/chainpad-listmap.js', 'chainpad-listmap',
'/bower_components/chainpad/chainpad.dist.js', '/bower_components/chainpad/chainpad.dist.js',
], function (Hash, Util, UserObject, Cache, ], function (Hash, Util, UserObject, Cache,
nThen, Crypto, Listmap, ChainPad) { nThen, Crypto, Listmap, ChainPad) {

@ -14,9 +14,9 @@ define([
'/common/cryptget.js', '/common/cryptget.js',
'/common/outer/cache-store.js', '/common/outer/cache-store.js',
'/bower_components/chainpad-listmap/chainpad-listmap.js', 'chainpad-listmap',
'/bower_components/chainpad-crypto/crypto.js', '/bower_components/chainpad-crypto/crypto.js',
'/bower_components/chainpad-netflux/chainpad-netflux.js', 'chainpad-netflux',
'/bower_components/chainpad/chainpad.dist.js', '/bower_components/chainpad/chainpad.dist.js',
'/bower_components/nthen/index.js', '/bower_components/nthen/index.js',
'/bower_components/saferphore/index.js', '/bower_components/saferphore/index.js',

@ -16,6 +16,9 @@ define([
cm: '/bower_components/codemirror', cm: '/bower_components/codemirror',
'tui-code-snippet': '/lib/calendar/tui-code-snippet.min', 'tui-code-snippet': '/lib/calendar/tui-code-snippet.min',
'tui-date-picker': '/lib/calendar/date-picker', 'tui-date-picker': '/lib/calendar/date-picker',
'netflux-client': '/bower_components/netflux-websocket/netflux-client',
'chainpad-netflux': '/bower_components/chainpad-netflux/chainpad-netflux',
'chainpad-listmap': '/bower_components/chainpad-listmap/chainpad-listmap',
}, },
map: { map: {
'*': { '*': {

@ -11,10 +11,12 @@ define([
'/common/hyperscript.js', '/common/hyperscript.js',
'/customize/messages.js', '/customize/messages.js',
'/customize/pages.js', '/customize/pages.js',
'/bower_components/nthen/index.js',
'/common/media-tag.js',
'/bower_components/file-saver/FileSaver.min.js', '/bower_components/file-saver/FileSaver.min.js',
'/bower_components/tweetnacl/nacl-fast.min.js', '/bower_components/tweetnacl/nacl-fast.min.js',
], function ($, ApiConfig, FileCrypto, MakeBackup, Thumb, UI, UIElements, Util, Hash, h, Messages, Pages) { ], function ($, ApiConfig, FileCrypto, MakeBackup, Thumb, UI, UIElements, Util, Hash, h, Messages, Pages, nThen, MT) {
var Nacl = window.nacl; var Nacl = window.nacl;
var module = {}; var module = {};
@ -312,7 +314,8 @@ define([
}); });
return manualStore; return manualStore;
}; };
var fileUploadModal = function (defaultFileName, cb) {
var fileUploadModal = function (defaultFileName, cb, preview) {
var parsedName = /^(\.?.+?)(\.[^.]+)?$/.exec(defaultFileName) || []; var parsedName = /^(\.?.+?)(\.[^.]+)?$/.exec(defaultFileName) || [];
var ext = parsedName[2] || ""; var ext = parsedName[2] || "";
@ -321,9 +324,14 @@ define([
// Ask for name, password and owner // Ask for name, password and owner
var content = h('div', [ var content = h('div', [
h('h4', Messages.upload_modal_title), h('h4', Messages.upload_modal_title),
(preview? h('div#cp-upload-preview-container', preview): undefined),
UIElements.setHTML(h('label', {for: 'cp-upload-name'}), UIElements.setHTML(h('label', {for: 'cp-upload-name'}),
Messages._getKey('upload_modal_filename', [ext])), Messages._getKey('upload_modal_filename', [ext])),
h('input#cp-upload-name', {type: 'text', placeholder: defaultFileName, value: defaultFileName}), h('input#cp-upload-name', {type: 'text', placeholder: defaultFileName, value: defaultFileName}),
h('label', {for: 'cp-upload-alt'}, Messages.upload_addOptionalAlt),
h('input#cp-upload-alt', {type: 'text', placeholder: Messages.upload_modal_alt}),
h('label', {for: 'cp-upload-password'}, Messages.addOptionalPassword), h('label', {for: 'cp-upload-password'}, Messages.addOptionalPassword),
UI.passwordInput({id: 'cp-upload-password'}), UI.passwordInput({id: 'cp-upload-password'}),
h('span', { h('span', {
@ -335,7 +343,8 @@ define([
manualStore manualStore
]); ]);
$(content).find('#cp-upload-owned').on('change', function () { var $content = $(content);
$content.find('#cp-upload-owned').on('change', function () {
var val = Util.isChecked($(content).find('#cp-upload-owned')); var val = Util.isChecked($(content).find('#cp-upload-owned'));
if (val) { if (val) {
$(content).find('#cp-upload-store').prop('checked', true).prop('disabled', true); $(content).find('#cp-upload-store').prop('checked', true).prop('disabled', true);
@ -348,8 +357,9 @@ define([
if (!yes) { return void cb(); } if (!yes) { return void cb(); }
// Get the values // Get the values
var newName = $(content).find('#cp-upload-name').val(); var newName = $content.find('#cp-upload-name').val();
var password = $(content).find('#cp-upload-password').val() || undefined; var password = $content.find('#cp-upload-password').val() || undefined;
var alt = $content.find('#cp-upload-alt').val() || undefined;
var owned = Util.isChecked($(content).find('#cp-upload-owned')); var owned = Util.isChecked($(content).find('#cp-upload-owned'));
var forceSave = owned || Util.isChecked($(content).find('#cp-upload-store')); var forceSave = owned || Util.isChecked($(content).find('#cp-upload-store'));
@ -366,7 +376,8 @@ define([
name: newName, name: newName,
password: password, password: password,
owned: owned, owned: owned,
forceSave: forceSave forceSave: forceSave,
alt: alt,
}); });
}); });
}; };
@ -437,6 +448,8 @@ define([
} }
var thumb; var thumb;
var preview;
var alt;
var file_arraybuffer; var file_arraybuffer;
var name = file.name; var name = file.name;
var password; var password;
@ -447,6 +460,7 @@ define([
var metadata = { var metadata = {
name: name, name: name,
type: type, type: type,
alt: alt,
}; };
if (thumb) { metadata.thumbnail = thumb; } if (thumb) { metadata.thumbnail = thumb; }
queue.push({ queue.push({
@ -486,8 +500,9 @@ define([
password = obj.password; password = obj.password;
owned = obj.owned; owned = obj.owned;
forceSave = obj.forceSave; forceSave = obj.forceSave;
alt = obj.alt;
finish(); finish();
}); }, preview);
} }
}; };
@ -495,11 +510,20 @@ define([
if (e) { console.error(e); } if (e) { console.error(e); }
file_arraybuffer = buffer; file_arraybuffer = buffer;
if (!Thumb.isSupportedType(file)) { return getName(); } if (!Thumb.isSupportedType(file)) { return getName(); }
nThen(function (w) {
// make a resized thumbnail from the image.. // make a resized thumbnail from the image..
Thumb.fromBlob(file, function (e, thumb64) { Thumb.fromBlob(file, w(function (e, thumb64) {
if (e) { console.error(e); } if (e) { console.error(e); }
if (!thumb64) { return getName(); } if (!thumb64) { return; }
thumb = thumb64; thumb = thumb64;
}));
MT.preview(buffer, {
type: file.type,
}, void 0, w(function (err, el) {
if (err) { return void console.error(err); }
preview = el;
}));
}).nThen(function () {
getName(); getName();
}); });
}); });

@ -235,9 +235,6 @@ define([
}; };
}; };
funcs.getAuthorId = function () {
};
var authorUid = function(existing) { var authorUid = function(existing) {
if (!Array.isArray(existing)) { existing = []; } if (!Array.isArray(existing)) { existing = []; }
var n; var n;
@ -249,11 +246,25 @@ define([
if (existing.indexOf(n) !== -1) { n = 0; } if (existing.indexOf(n) !== -1) { n = 0; }
return n; return n;
}; };
funcs.getAuthorId = function(authors, curve) { funcs.getAuthorId = function(authors, curve, tokenId) {
var existing = Object.keys(authors || {}).map(Number); var existing = Object.keys(authors || {}).map(Number);
if (!funcs.isLoggedIn()) { return authorUid(existing); }
var uid; var uid;
var loggedIn = funcs.isLoggedIn();
if (!loggedIn && !tokenId) { return authorUid(existing); }
if (!loggedIn) {
existing.some(function (id) {
var author = authors[id];
if (!author || author.uid !== tokenId) { return; }
uid = Number(id);
return true;
});
return uid || authorUid(existing);
}
// TODO this should check for a matching curvePublic / uid if:
// 1. you are logged in OR
// 2. you have a token
// so that users that register recognize comments from before
// they registered as their own (same uid)
existing.some(function(id) { existing.some(function(id) {
var author = authors[id] || {}; var author = authors[id] || {};
if (author.curvePublic !== curve) { return; } if (author.curvePublic !== curve) { return; }

@ -53,6 +53,7 @@ MessengerUI, Messages, Pages) {
var USERADMIN_CLS = Bar.constants.user = 'cp-toolbar-user-dropdown'; var USERADMIN_CLS = Bar.constants.user = 'cp-toolbar-user-dropdown';
var USERNAME_CLS = Bar.constants.username = 'cp-toolbar-user-name'; var USERNAME_CLS = Bar.constants.username = 'cp-toolbar-user-name';
/*var READONLY_CLS = */Bar.constants.readonly = 'cp-toolbar-readonly'; /*var READONLY_CLS = */Bar.constants.readonly = 'cp-toolbar-readonly';
var USERBUTTON_CLS = Bar.constants.changeUsername = "cp-toolbar-user-rename";
// Create the toolbar element // Create the toolbar element
@ -250,6 +251,7 @@ MessengerUI, Messages, Pages) {
var friendRequests = Common.getFriendRequests(); // Friend requests received var friendRequests = Common.getFriendRequests(); // Friend requests received
editUsersNames.forEach(function (data) { editUsersNames.forEach(function (data) {
var name = data.name || Messages.anonymous; var name = data.name || Messages.anonymous;
var safeName = Util.fixHTML(name);
var $span = $('<span>', {'class': 'cp-avatar'}); var $span = $('<span>', {'class': 'cp-avatar'});
if (data.color && showColors) { if (data.color && showColors) {
$span.css('border-color', data.color); $span.css('border-color', data.color);
@ -324,7 +326,7 @@ MessengerUI, Messages, Pages) {
$('<button>', { $('<button>', {
'class': 'fa fa-bell cp-toolbar-userlist-button', 'class': 'fa fa-bell cp-toolbar-userlist-button',
'data-cptippy-html': true, 'data-cptippy-html': true,
'title': Messages._getKey('friendRequest_received', [Util.fixHTML(name)]), 'title': Messages._getKey('friendRequest_received', [safeName]),
}).appendTo($nameSpan).click(function (e) { }).appendTo($nameSpan).click(function (e) {
e.stopPropagation(); e.stopPropagation();
UIElements.displayFriendRequestModal(Common, friendRequests[data.curvePublic]); UIElements.displayFriendRequestModal(Common, friendRequests[data.curvePublic]);
@ -335,7 +337,7 @@ MessengerUI, Messages, Pages) {
'class': 'fa fa-user-plus cp-toolbar-userlist-button', 'class': 'fa fa-user-plus cp-toolbar-userlist-button',
'data-cptippy-html': true, 'data-cptippy-html': true,
'title': Messages._getKey('userlist_addAsFriendTitle', [ 'title': Messages._getKey('userlist_addAsFriendTitle', [
Util.fixHTML(name) safeName,
]) ])
}).appendTo($nameSpan).click(function (e) { }).appendTo($nameSpan).click(function (e) {
e.stopPropagation(); e.stopPropagation();
@ -357,14 +359,16 @@ MessengerUI, Messages, Pages) {
}); });
} }
if (data.profile) { if (data.profile) {
// Messages.contacts_info3 "Double-click their icon to view their profile",
$span.addClass('cp-userlist-clickable'); $span.addClass('cp-userlist-clickable');
$span.attr('title', Messages._getKey('userlist_visitProfile', [name]));
$span.click(function () { $span.click(function () {
Common.openURL(origin+'/profile/#' + data.profile); Common.openURL(origin+'/profile/#' + data.profile);
}); });
} }
Common.displayAvatar($span, data.avatar, name, function () { Common.displayAvatar($span, data.avatar, name, function () {
$span.append($rightCol); $span.append($rightCol);
}); }, data.uid);
$span.data('uid', data.uid); $span.data('uid', data.uid);
$editUsersList.append($span); $editUsersList.append($span);
}); });
@ -457,8 +461,8 @@ MessengerUI, Messages, Pages) {
}; };
createCollapse = function (toolbar) { createCollapse = function (toolbar) {
var up = h('i.fa.fa-chevron-up', {title: Messages.ui_collapse}); var up = h('i.fa.fa-chevron-up', {title: Messages.toolbar_collapse});
var down = h('i.fa.fa-chevron-down', {title: Messages.ui_expand}); var down = h('i.fa.fa-chevron-down', {title: Messages.toolbar_expand});
var $button = $(h('button.cp-toolbar-collapse',[ var $button = $(h('button.cp-toolbar-collapse',[
up, up,
@ -1030,12 +1034,21 @@ MessengerUI, Messages, Pages) {
var userMenuCfg = { var userMenuCfg = {
$initBlock: $userAdmin, $initBlock: $userAdmin,
}; };
if (!config.hideDisplayName) {
$.extend(true, userMenuCfg, {
displayNameCls: USERNAME_CLS,
changeNameButtonCls: USERBUTTON_CLS,
});
}
if (config.readOnly !== 1) { if (config.readOnly !== 1) {
userMenuCfg.displayName = 1; userMenuCfg.displayName = 1;
userMenuCfg.displayChangeName = 1; userMenuCfg.displayChangeName = 1;
} }
Common.createUserAdminMenu(userMenuCfg); Common.createUserAdminMenu(userMenuCfg);
$userAdmin.find('> button').attr('title', Messages.userAccountButton); $userAdmin.find('> button').attr({
title: Messages.userAccountButton,
alt: Messages.userAccountButton,
});
return $userAdmin; return $userAdmin;
}; };
@ -1210,18 +1223,31 @@ MessengerUI, Messages, Pages) {
} }
}; };
var getFancyGuestName = function (name, uid) {
name = UI.getDisplayName(name);
if (name === Messages.anonymous && uid) {
var animal = MT.getPseudorandomAnimal(uid);
if (animal) {
name = animal + ' ' + name;
}
}
return name;
};
// Notifications // Notifications
var initNotifications = function (toolbar, config) { var initNotifications = function (toolbar, config) {
// Display notifications when users are joining/leaving the session // Display notifications when users are joining/leaving the session
var oldUserData; var oldUserData;
if (!config.metadataMgr) { return; } if (!config.metadataMgr) { return; }
var metadataMgr = config.metadataMgr; var metadataMgr = config.metadataMgr;
var notify = function(type, name, oldname) { var notify = function(type, name, oldname, uid) {
if (toolbar.isAlone) { return; } if (toolbar.isAlone) { return; }
// type : 1 (+1 user), 0 (rename existing user), -1 (-1 user) // type : 1 (+1 user), 0 (rename existing user), -1 (-1 user)
if (typeof name === "undefined") { return; } if (typeof name === "undefined") { return; }
name = name || Messages.anonymous;
if (Config.disableUserlistNotifications) { return; } if (Config.disableUserlistNotifications) { return; }
name = getFancyGuestName(name, uid);
oldname = getFancyGuestName(oldname, uid);
switch(type) { switch(type) {
case 1: case 1:
UI.log(Messages._getKey("notifyJoined", [name])); UI.log(Messages._getKey("notifyJoined", [name]));
@ -1270,7 +1296,7 @@ MessengerUI, Messages, Pages) {
delete oldUserData[u]; delete oldUserData[u];
if (temp && newdata[userNetfluxId] && temp.uid === newdata[userNetfluxId].uid) { return; } if (temp && newdata[userNetfluxId] && temp.uid === newdata[userNetfluxId].uid) { return; }
if (userPresent(u, temp, newdata || oldUserData) < 1) { if (userPresent(u, temp, newdata || oldUserData) < 1) {
notify(-1, temp.name); notify(-1, temp.name, undefined, temp.uid);
} }
} }
} }
@ -1290,10 +1316,10 @@ MessengerUI, Messages, Pages) {
if (typeof oldUserData[k] === "undefined") { if (typeof oldUserData[k] === "undefined") {
// if the same uid is already present in the userdata, don't notify // if the same uid is already present in the userdata, don't notify
if (!userPresent(k, newdata[k], oldUserData)) { if (!userPresent(k, newdata[k], oldUserData)) {
notify(1, newdata[k].name); notify(1, newdata[k].name, undefined, newdata[k].uid);
} }
} else if (oldUserData[k].name !== newdata[k].name) { } else if (oldUserData[k].name !== newdata[k].name) {
notify(0, newdata[k].name, oldUserData[k].name); notify(0, newdata[k].name, oldUserData[k].name, newdata[k].uid);
} }
} }
} }

@ -26,7 +26,7 @@
"typeError": "Dieses Dokument ist nicht mit der ausgewählten Anwendung kompatibel", "typeError": "Dieses Dokument ist nicht mit der ausgewählten Anwendung kompatibel",
"onLogout": "Du bist ausgeloggt. {0}Klicke hier{1}, um dich wieder einzuloggen,<br>oder drücke <em>Escape</em>, um dein Pad schreibgeschützt zu benutzen.", "onLogout": "Du bist ausgeloggt. {0}Klicke hier{1}, um dich wieder einzuloggen,<br>oder drücke <em>Escape</em>, um dein Pad schreibgeschützt zu benutzen.",
"padNotPinned": "Dieses Pad wird nach 3 Monaten ohne Aktivität auslaufen, {0}logge dich ein{1} oder {2}registriere dich{3}, um das Auslaufen zu verhindern.", "padNotPinned": "Dieses Pad wird nach 3 Monaten ohne Aktivität auslaufen, {0}logge dich ein{1} oder {2}registriere dich{3}, um das Auslaufen zu verhindern.",
"anonymousStoreDisabled": "Der Webmaster dieses CryptPad-Servers hat die anonyme Verwendung des Speichers deaktiviert. Du musst dich einloggen, um CryptDrive zu verwenden.", "anonymousStoreDisabled": "Der Administrator dieser CryptPad-Instanz hat die Verwendung des Speichers für Gäste deaktiviert. Logge dich ein, um dein persönliches CryptDrive zu verwenden.",
"expiredError": "Dieses Pad ist abgelaufen und nicht mehr verfügbar.", "expiredError": "Dieses Pad ist abgelaufen und nicht mehr verfügbar.",
"deletedError": "Dieses Dokument wurde gelöscht und ist nicht mehr verfügbar.", "deletedError": "Dieses Dokument wurde gelöscht und ist nicht mehr verfügbar.",
"inactiveError": "Dieses Pad ist wegen Inaktivität gelöscht worden. Drücke Esc, um ein neues Pad zu erstellen.", "inactiveError": "Dieses Pad ist wegen Inaktivität gelöscht worden. Drücke Esc, um ein neues Pad zu erstellen.",
@ -50,7 +50,7 @@
"forgotten": "In den Papierkorb verschoben", "forgotten": "In den Papierkorb verschoben",
"errorState": "Kritischer Fehler: {0}", "errorState": "Kritischer Fehler: {0}",
"readonly": "schreibgeschützt", "readonly": "schreibgeschützt",
"anonymous": "Anonym", "anonymous": "Gast",
"users": "Nutzer", "users": "Nutzer",
"viewer": "Betrachter", "viewer": "Betrachter",
"viewers": "Betrachter", "viewers": "Betrachter",
@ -88,7 +88,7 @@
"shareSuccess": "Die URL wurde in die Zwischenablage kopiert", "shareSuccess": "Die URL wurde in die Zwischenablage kopiert",
"userListButton": "Benutzerliste", "userListButton": "Benutzerliste",
"chatButton": "Chat", "chatButton": "Chat",
"userAccountButton": "Dein Account", "userAccountButton": "Benutzermenü",
"newButton": "Neu", "newButton": "Neu",
"newButtonTitle": "Neues Pad erstellen", "newButtonTitle": "Neues Pad erstellen",
"uploadButton": "Hochladen", "uploadButton": "Hochladen",
@ -326,7 +326,7 @@
"login_invalUser": "Der Benutzername kann nicht leer sein", "login_invalUser": "Der Benutzername kann nicht leer sein",
"login_invalPass": "Der Passwort kann nicht leer sein", "login_invalPass": "Der Passwort kann nicht leer sein",
"login_unhandledError": "Ein unerwarteter Fehler ist aufgetreten :(", "login_unhandledError": "Ein unerwarteter Fehler ist aufgetreten :(",
"register_importRecent": "Dokumente aus deiner nicht-registrierten Sitzung importieren", "register_importRecent": "Dokumente aus deiner Gast-Sitzung importieren",
"register_acceptTerms": "Ich bin mit den <a>Nutzungsbedingungen</a> einverstanden", "register_acceptTerms": "Ich bin mit den <a>Nutzungsbedingungen</a> einverstanden",
"register_passwordsDontMatch": "Passwörter stimmen nicht überein!", "register_passwordsDontMatch": "Passwörter stimmen nicht überein!",
"register_passwordTooShort": "Passwörter müssen mindestens {0} Zeichen haben.", "register_passwordTooShort": "Passwörter müssen mindestens {0} Zeichen haben.",
@ -498,7 +498,7 @@
"whatis_drive": "Organisieren mit CryptDrive", "whatis_drive": "Organisieren mit CryptDrive",
"features": "Funktionen", "features": "Funktionen",
"features_title": "Funktionen", "features_title": "Funktionen",
"features_anon": "Nicht-registriert", "features_anon": "Gast",
"features_registered": "Registriert", "features_registered": "Registriert",
"features_premium": "Premium", "features_premium": "Premium",
"features_f_apps": "Zugang zu allen Anwendungen", "features_f_apps": "Zugang zu allen Anwendungen",
@ -510,7 +510,7 @@
"features_f_cryptdrive0_note": "Du kannst besuchte Dokumente in deinem Browser speichern, damit du sie später öffnen kannst", "features_f_cryptdrive0_note": "Du kannst besuchte Dokumente in deinem Browser speichern, damit du sie später öffnen kannst",
"features_f_storage0": "Speicherung für eine begrenzte Zeit", "features_f_storage0": "Speicherung für eine begrenzte Zeit",
"features_f_storage0_note": "Dokumente werden nach {0} Tagen ohne Aktivität gelöscht", "features_f_storage0_note": "Dokumente werden nach {0} Tagen ohne Aktivität gelöscht",
"features_f_anon": "Alle Funktionen für anonyme Benutzer", "features_f_anon": "Alle Funktionen für Gäste",
"features_f_anon_note": "Mit zusätzlichen Funktionen", "features_f_anon_note": "Mit zusätzlichen Funktionen",
"features_f_cryptdrive1": "Alle Funktionen des CryptDrives", "features_f_cryptdrive1": "Alle Funktionen des CryptDrives",
"features_f_cryptdrive1_note": "Ordner, geteilte Ordner, Vorlagen, Tags", "features_f_cryptdrive1_note": "Ordner, geteilte Ordner, Vorlagen, Tags",
@ -557,7 +557,7 @@
"creation_expireDays": "Tag(e)", "creation_expireDays": "Tag(e)",
"creation_expireMonths": "Monat(e)", "creation_expireMonths": "Monat(e)",
"creation_password": "Passwort\n", "creation_password": "Passwort\n",
"creation_noTemplate": "Keine Vorlage", "creation_noTemplate": "Leeres Dokument",
"creation_newTemplate": "Neue Vorlage", "creation_newTemplate": "Neue Vorlage",
"creation_create": "Erstellen", "creation_create": "Erstellen",
"creation_owners": "Eigentümer", "creation_owners": "Eigentümer",
@ -584,8 +584,8 @@
"share_linkView": "Ansehen", "share_linkView": "Ansehen",
"share_linkEmbed": "Einbettungsmodus (Werkzeugleiste und Benutzerliste verbergen)", "share_linkEmbed": "Einbettungsmodus (Werkzeugleiste und Benutzerliste verbergen)",
"share_linkPresent": "Anzeigemodus", "share_linkPresent": "Anzeigemodus",
"share_linkOpen": "Vorschau", "share_linkOpen": "Link öffnen",
"share_linkCopy": "Kopieren", "share_linkCopy": "Link kopieren",
"share_embedCategory": "Einbetten", "share_embedCategory": "Einbetten",
"share_mediatagCopy": "Media-Tag in die Zwischenablage kopieren", "share_mediatagCopy": "Media-Tag in die Zwischenablage kopieren",
"sharedFolders_forget": "Dieses Pad ist nur in einem geteilten Ordner gespeichert. Du kannst es nicht in den Papierkorb verschieben. Aber du kannst es in deinem CryptDrive löschen.", "sharedFolders_forget": "Dieses Pad ist nur in einem geteilten Ordner gespeichert. Du kannst es nicht in den Papierkorb verschieben. Aber du kannst es in deinem CryptDrive löschen.",
@ -882,7 +882,7 @@
"oo_exportInProgress": "Export wird durchgeführt", "oo_exportInProgress": "Export wird durchgeführt",
"oo_sheetMigration_loading": "Deine Tabelle wird auf die neueste Version aktualisiert. Bitte warte etwa eine Minute.", "oo_sheetMigration_loading": "Deine Tabelle wird auf die neueste Version aktualisiert. Bitte warte etwa eine Minute.",
"oo_sheetMigration_complete": "Eine aktualisierte Version ist verfügbar. Klicke auf OK, um neu zu laden.", "oo_sheetMigration_complete": "Eine aktualisierte Version ist verfügbar. Klicke auf OK, um neu zu laden.",
"oo_sheetMigration_anonymousEditor": "Die Bearbeitung dieser Tabelle ist für nicht-registrierte Benutzer deaktiviert, bis sie von einem registrierten Benutzer auf die neueste Version aktualisiert wird.", "oo_sheetMigration_anonymousEditor": "Die Bearbeitung dieser Tabelle ist für Gäste deaktiviert, bis sie von einem registrierten Benutzer auf die neueste Version aktualisiert wird.",
"imprint": "Impressum", "imprint": "Impressum",
"isContact": "{0} ist einer deiner Kontakte", "isContact": "{0} ist einer deiner Kontakte",
"isNotContact": "{0} ist <b>nicht</b> einer deiner Kontakte", "isNotContact": "{0} ist <b>nicht</b> einer deiner Kontakte",
@ -1120,7 +1120,7 @@
"whatis_apps": "Eine vollständige Anwendungs-Suite", "whatis_apps": "Eine vollständige Anwendungs-Suite",
"whatis_drive_info": "<p>Speichere und verwalte Dokumente mit CryptDrive. Erstelle Ordner, gemeinsame Ordner und Tags, um Dokumente zu organisieren. Lade Dateien hoch und teile sie (PDFs, Fotos, Video, Audio, etc.). Team-Drives werden zwischen Benutzern geteilt und ermöglichen eine gemeinsame Organisation und detaillierte Zugriffskontrolle.</p>", "whatis_drive_info": "<p>Speichere und verwalte Dokumente mit CryptDrive. Erstelle Ordner, gemeinsame Ordner und Tags, um Dokumente zu organisieren. Lade Dateien hoch und teile sie (PDFs, Fotos, Video, Audio, etc.). Team-Drives werden zwischen Benutzern geteilt und ermöglichen eine gemeinsame Organisation und detaillierte Zugriffskontrolle.</p>",
"whatis_apps_info": "<p>CryptPad bietet eine vollwertige Office-Suite mit allen notwendigen Werkzeugen für eine produktive Zusammenarbeit. Die Anwendungen umfassen: Rich Text, Tabellen, Code/Markdown, Kanban, Präsentationen, Whiteboard und Umfragen.</p><p>Die Anwendungen werden ergänzt durch eine Reihe von Funktionen zur Zusammenarbeit wie Chat, Kontakte, Farbe nach Autor (Code/Markdown) und Kommentare mit Erwähnungen (Rich Text).</p>", "whatis_apps_info": "<p>CryptPad bietet eine vollwertige Office-Suite mit allen notwendigen Werkzeugen für eine produktive Zusammenarbeit. Die Anwendungen umfassen: Rich Text, Tabellen, Code/Markdown, Kanban, Präsentationen, Whiteboard und Umfragen.</p><p>Die Anwendungen werden ergänzt durch eine Reihe von Funktionen zur Zusammenarbeit wie Chat, Kontakte, Farbe nach Autor (Code/Markdown) und Kommentare mit Erwähnungen (Rich Text).</p>",
"register_notes": "<ul class=\"cp-notes-list\"><li>Dein Passwort ist der geheime Schlüssel, der alle deine Dokumente verschlüsselt. <span class=\"red\">Wenn du ihn verlierst, gibt es keine Möglichkeit, deine Daten wiederherzustellen.</span></li><li>Wenn du einen gemeinsam genutzten Computer verwendest, <span class=\"red\">denke daran, dich abzumelden</span>, wenn du fertig bist. Durch bloßes Schließen des Browserfensters bleibt das Konto ungeschützt.</li><li>Um die erstellten und/oder gespeicherten Dokumente zu behalten, ohne eingeloggt zu sein, setze einen Haken bei \"Dokumente aus deiner nicht-registrierten Sitzung importieren\". </li></ul>", "register_notes": "<ul class=\"cp-notes-list\"><li>Dein Passwort ist der geheime Schlüssel, der alle deine Dokumente verschlüsselt. <span class=\"red\">Wenn du ihn verlierst, gibt es keine Möglichkeit, deine Daten wiederherzustellen.</span></li><li>Wenn du einen gemeinsam genutzten Computer verwendest, <span class=\"red\">denke daran, dich abzumelden</span>, wenn du fertig bist. Durch bloßes Schließen des Browserfensters bleibt das Konto ungeschützt.</li><li>Um die Dokumente zu behalten, die du erstellt und/oder gespeichert hast, ohne eingeloggt zu sein, setze einen Haken bei \"Dokumente aus deiner Gast-Sitzung importieren\". </li></ul>",
"settings_cacheTitle": "Cache", "settings_cacheTitle": "Cache",
"settings_cacheButton": "Cache leeren", "settings_cacheButton": "Cache leeren",
"settings_cacheCheckbox": "Cache auf diesem Gerät aktivieren", "settings_cacheCheckbox": "Cache auf diesem Gerät aktivieren",
@ -1243,12 +1243,12 @@
"button_newform": "Neues Formular", "button_newform": "Neues Formular",
"form_page": "Seite {0}/{1}", "form_page": "Seite {0}/{1}",
"form_anonymous_on": "Erlaubt", "form_anonymous_on": "Erlaubt",
"form_anonymous": "Anonyme Antworten", "form_anonymous": "Gastzugriff (nicht eingeloggt)",
"form_removeEnd": "Enddatum entfernen", "form_removeEnd": "Enddatum entfernen",
"form_setEnd": "Enddatum festlegen", "form_setEnd": "Enddatum festlegen",
"form_isPrivate": "Antworten sind privat", "form_isPrivate": "Antworten sind privat",
"form_isPublic": "Antworten sind öffentlich", "form_isPublic": "Antworten sind öffentlich",
"form_makePublicWarning": "Bist du sicher, dass du die Antworten veröffentlichen möchtest? Dies kann nicht rückgängig gemacht werden.", "form_makePublicWarning": "Bist du sicher, dass du die Antworten veröffentlichen möchtest? Vergangene und zukünftige Antworten werden für Teilnehmer sichtbar sein. Dies kann nicht rückgängig gemacht werden.",
"form_makePublic": "Antworten veröffentlichen", "form_makePublic": "Antworten veröffentlichen",
"form_invalidQuestion": "Frage {0}", "form_invalidQuestion": "Frage {0}",
"form_input_ph_url": "https://example.com", "form_input_ph_url": "https://example.com",
@ -1256,7 +1256,7 @@
"form_backButton": "Zurück", "form_backButton": "Zurück",
"form_showSummary": "Zusammenfassung anzeigen", "form_showSummary": "Zusammenfassung anzeigen",
"form_results_empty": "Es gibt keine Antworten", "form_results_empty": "Es gibt keine Antworten",
"form_results": "Antworten", "form_results": "Antworten ({0})",
"form_delete": "Löschen", "form_delete": "Löschen",
"form_reset": "Zurücksetzen", "form_reset": "Zurücksetzen",
"form_maxOptions": "Maximal {0} Antwort(en)", "form_maxOptions": "Maximal {0} Antwort(en)",
@ -1382,5 +1382,19 @@
"fm_link_new": "Neuer Link", "fm_link_new": "Neuer Link",
"form_totalResponses": "Antworten insgesamt: {0}", "form_totalResponses": "Antworten insgesamt: {0}",
"ui_expand": "Ausklappen", "ui_expand": "Ausklappen",
"ui_collapse": "Einklappen" "ui_collapse": "Einklappen",
"support_premiumPriority": "Premium-Benutzer helfen bei der Verbesserung der Benutzerfreundlichkeit von CryptPad und profitieren von priorisierten Antworten auf ihre Support-Tickets.",
"support_premiumLink": "Abonnementoptionen ansehen",
"form_viewAnswer": "Meine Antworten anzeigen",
"form_editAnswer": "Meine Antworten bearbeiten",
"form_preview_button": "Vorschau",
"form_colors": "Farbschema",
"userlist_visitProfile": "Profil aufrufen",
"toolbar_preview": "Vorschau",
"form_geturl": "Link kopieren",
"upload_modal_alt": "Alt-Text",
"upload_addOptionalAlt": "Beschreibungstext hinzufügen (optional)",
"toolbar_expand": "Werkzeugleiste ausklappen",
"toolbar_collapse": "Werkzeugleiste einklappen",
"profile_defaultAlt": "Standard-Profilbild"
} }

@ -607,5 +607,44 @@
"share_linkEdit": "Editar", "share_linkEdit": "Editar",
"share_linkAccess": "Permisos de acceso", "share_linkAccess": "Permisos de acceso",
"properties_passwordWarning": "La contraseña ha sido cambiada con éxito pero fue incapaz de actualizar tu CryptDrive con los nuevos datos. Es posible que tenga que eliminar la versión antigua del pad manualmente.<br>Pulse OK para recargar y actualizar sus derechos de acceso.", "properties_passwordWarning": "La contraseña ha sido cambiada con éxito pero fue incapaz de actualizar tu CryptDrive con los nuevos datos. Es posible que tenga que eliminar la versión antigua del pad manualmente.<br>Pulse OK para recargar y actualizar sus derechos de acceso.",
"properties_passwordSuccess": "La contraseña ha sido cambiada con éxito.<br>Pulsa OK para recargar y actualizar tus derechos de acceso." "properties_passwordSuccess": "La contraseña ha sido cambiada con éxito.<br>Pulsa OK para recargar y actualizar tus derechos de acceso.",
"admin_updateLimitTitle": "Actualizar cuotas de usuarios",
"admin_registeredHint": "Número de usuarios registrados en tu instancia",
"admin_registeredTitle": "Usuarios registrados",
"admin_activePadsHint": "Número de documentos únicos que se están viendo o editando ahora",
"admin_activePadsTitle": "Blocs activos",
"admin_activeSessionsHint": "Número de conexiones websocket activas (y direcciones IP únicas conectadas)",
"admin_activeSessionsTitle": "Conexiones activas",
"adminPage": "Administración",
"admin_cat_stats": "Estadísticas",
"admin_cat_general": "General",
"admin_authError": "Solo los administradores pueden acceder a esta página.",
"markdown_toc": "Contenido",
"survey": "Encuesta de CryptPad",
"crowdfunding_popup_no": "Ahora no",
"crowdfunding_popup_text": "<h3 >¡Necesitamos tu ayuda!</h3>Para garantizar que CryptPad se desarrolle activamente, considera apoyar el proyecto a través de la página OpenCollective, donde puedes ver nuestra <b>Hoja de ruta</b> y <b>Objetivos de financiación</b>.",
"autostore_notAvailable": "Tienes que guardar el bloc en tu CryptDrive antes de usar esta función.",
"autostore_forceSave": "Guarda el fichero en tu CryptDrive",
"autostore_saved": "El bloc se guardó correctamente en tu CryptDrive!",
"autostore_error": "Error extraño: No pudimos guardar este bloc, inténtalo otra vez.",
"autostore_hide": "No guardar",
"autostore_store": "Guardar",
"autostore_settings": "Puedes habilitar el almacenamiento automático de blocs en su <a>Página de</a> configuración!",
"autostore_notstored": "Este {0} no está en su CryptDrive. ¿Quieres guardarlo ahora?",
"autostore_sf": "carpeta",
"chrome68": "Parece que estás usando el navegador Chrome o Chromium versión 68. Contiene un error que hace que la página se vuelva completamente blanca después de unos segundos o que la página no responda a los clics. Para solucionar este problema, puede cambiar a otra pestaña y volver, o tratar de desplazarse en la página. Este error debe corregirse en la próxima versión de su navegador.",
"convertFolderToSF_confirm": "Esta carpeta debe convertirse a carpeta compartida para que otros la puedan ver. ¿Continuar?",
"sharedFolders_create_owned": "Carpeta con propietario",
"sharedFolders_create_name": "Nombre de carpeta",
"share_embedCategory": "Embeber",
"share_linkPresent": "Presentar",
"share_linkEmbed": "Modo de inserción (oculta barra de herramientas y lista de usuarios)",
"settings_padOpenLinkLabel": "Habilita la apertura directa del enlace",
"settings_padOpenLinkHint": "Con esta opción puedes abrir enlaces embebidos haciendo click sin el popup de previsualización",
"properties_changePasswordButton": "Enviar",
"convertFolderToSF_SFChildren": "Esta carpeta no puede ser convertida a una carpeta compartida porque ya contiene carpetas compartidas. Mueva esas carpetas compartidas a otro lugar para continuar.",
"convertFolderToSF_SFParent": "Esta carpeta no puede ser convertida a una carpeta compartida en su actual localización. Mueva la carpeta fuera de la carperta compartida para continuar.",
"sharedFolders_share": "Comparte este enlace con otros usuarios registrados para darles accesso a la carpeta compartida. Una vez que ellos/as abran este enlace, la carpeta compartida será añadida a sus CryptDrive.",
"sharedFolders_create": "Crear una carpeta compartida",
"sharedFolders_duplicate": "Algunos de los documentos que intentaste mover ya estaban compartidos en la carpeta de destino"
} }

@ -26,7 +26,7 @@
"typeError": "Ce pad n'est pas compatible avec l'application sélectionnée", "typeError": "Ce pad n'est pas compatible avec l'application sélectionnée",
"onLogout": "Vous êtes déconnecté de votre compte utilisateur, {0}cliquez ici{1} pour vous authentifier<br>ou appuyez sur <em>Échap</em> pour accéder au pad en mode lecture seule.", "onLogout": "Vous êtes déconnecté de votre compte utilisateur, {0}cliquez ici{1} pour vous authentifier<br>ou appuyez sur <em>Échap</em> pour accéder au pad en mode lecture seule.",
"padNotPinned": "Ce pad va expirer après 3 mois d'inactivité, {0}connectez-vous{1} ou {2}enregistrez-vous{3} pour le préserver.", "padNotPinned": "Ce pad va expirer après 3 mois d'inactivité, {0}connectez-vous{1} ou {2}enregistrez-vous{3} pour le préserver.",
"anonymousStoreDisabled": "L'administrateur de cette instance de CryptPad a désactivé le drive pour les utilisateurs non enregistrés. Vous devez vous connecter pour pouvoir utiliser CryptDrive.", "anonymousStoreDisabled": "L'administrateur de cette instance de CryptPad a désactivé le drive pour les visiteurs. Enregistrez vous ou connectez vous à un compte pour pouvoir utiliser CryptDrive.",
"expiredError": "Ce pad a atteint sa date d'expiration est n'est donc plus disponible.", "expiredError": "Ce pad a atteint sa date d'expiration est n'est donc plus disponible.",
"deletedError": "Ce document a été supprimé et n'est plus disponible.", "deletedError": "Ce document a été supprimé et n'est plus disponible.",
"inactiveError": "Ce pad a été supprimé en raison de son inactivité. Appuyez sur Échap pour créer un nouveau pad.", "inactiveError": "Ce pad a été supprimé en raison de son inactivité. Appuyez sur Échap pour créer un nouveau pad.",
@ -51,7 +51,7 @@
"forgotten": "Déplacé vers la corbeille", "forgotten": "Déplacé vers la corbeille",
"errorState": "Erreur critique : {0}", "errorState": "Erreur critique : {0}",
"readonly": "Lecture seule", "readonly": "Lecture seule",
"anonymous": "Anonyme", "anonymous": "Visiteur",
"users": "Utilisateurs", "users": "Utilisateurs",
"viewer": "lecteur", "viewer": "lecteur",
"viewers": "lecteurs", "viewers": "lecteurs",
@ -89,7 +89,7 @@
"shareSuccess": "Lien copié dans le presse-papiers", "shareSuccess": "Lien copié dans le presse-papiers",
"userListButton": "Liste d'utilisateurs", "userListButton": "Liste d'utilisateurs",
"chatButton": "Chat", "chatButton": "Chat",
"userAccountButton": "Votre compte", "userAccountButton": "Menu utilisateur",
"newButton": "Nouveau", "newButton": "Nouveau",
"newButtonTitle": "Créer un nouveau pad", "newButtonTitle": "Créer un nouveau pad",
"uploadButton": "Importer des fichiers", "uploadButton": "Importer des fichiers",
@ -333,7 +333,7 @@
"login_invalUser": "Nom d'utilisateur requis", "login_invalUser": "Nom d'utilisateur requis",
"login_invalPass": "Mot de passe requis", "login_invalPass": "Mot de passe requis",
"login_unhandledError": "Une erreur inattendue s'est produite :(", "login_unhandledError": "Une erreur inattendue s'est produite :(",
"register_importRecent": "Importer les documents de votre session non-enregistrée", "register_importRecent": "Importer les documents de votre session visiteur",
"register_acceptTerms": "J'accepte <a>les conditions d'utilisation</a>", "register_acceptTerms": "J'accepte <a>les conditions d'utilisation</a>",
"register_passwordsDontMatch": "Les mots de passe doivent être identiques !", "register_passwordsDontMatch": "Les mots de passe doivent être identiques !",
"register_passwordTooShort": "Les mots de passe doivent contenir au moins {0} caractères.", "register_passwordTooShort": "Les mots de passe doivent contenir au moins {0} caractères.",
@ -502,7 +502,7 @@
"whatis_drive": "Organisation avec CryptDrive", "whatis_drive": "Organisation avec CryptDrive",
"features": "Fonctionnalités", "features": "Fonctionnalités",
"features_title": "Fonctionnalités", "features_title": "Fonctionnalités",
"features_anon": "Non-enregistré", "features_anon": "Visiteur",
"features_registered": "Enregistré", "features_registered": "Enregistré",
"features_premium": "Premium", "features_premium": "Premium",
"features_f_apps": "Accès à toutes les applications", "features_f_apps": "Accès à toutes les applications",
@ -514,7 +514,7 @@
"features_f_cryptdrive0_note": "Stockage dans votre navigateur des pads visités afin de pouvoir les retrouver plus tard", "features_f_cryptdrive0_note": "Stockage dans votre navigateur des pads visités afin de pouvoir les retrouver plus tard",
"features_f_storage0": "Durée de stockage limitée", "features_f_storage0": "Durée de stockage limitée",
"features_f_storage0_note": "Les documents sont supprimés après {0} jours d'inactivité", "features_f_storage0_note": "Les documents sont supprimés après {0} jours d'inactivité",
"features_f_anon": "Avantages des utilisateurs anonymes", "features_f_anon": "Avantages des visiteurs",
"features_f_anon_note": "Avec des fonctionnalités supplémentaires", "features_f_anon_note": "Avec des fonctionnalités supplémentaires",
"features_f_cryptdrive1": "Accès complet à CryptDrive", "features_f_cryptdrive1": "Accès complet à CryptDrive",
"features_f_cryptdrive1_note": "Dossiers, dossiers partagés, modèles, tags", "features_f_cryptdrive1_note": "Dossiers, dossiers partagés, modèles, tags",
@ -561,7 +561,7 @@
"creation_expireDays": "Jour(s)", "creation_expireDays": "Jour(s)",
"creation_expireMonths": "Mois", "creation_expireMonths": "Mois",
"creation_password": "Mot de passe\n", "creation_password": "Mot de passe\n",
"creation_noTemplate": "Pas de modèle", "creation_noTemplate": "Document vierge",
"creation_newTemplate": "Nouveau modèle", "creation_newTemplate": "Nouveau modèle",
"creation_create": "Créer", "creation_create": "Créer",
"creation_owners": "Propriétaires", "creation_owners": "Propriétaires",
@ -588,8 +588,8 @@
"share_linkView": "Lecture-seule", "share_linkView": "Lecture-seule",
"share_linkEmbed": "Mode intégration (cache la barre d'outils)", "share_linkEmbed": "Mode intégration (cache la barre d'outils)",
"share_linkPresent": "Présenter", "share_linkPresent": "Présenter",
"share_linkOpen": "Aperçu", "share_linkOpen": "Ouvrir le lien",
"share_linkCopy": "Copier", "share_linkCopy": "Copier le lien",
"share_embedCategory": "Intégration", "share_embedCategory": "Intégration",
"share_mediatagCopy": "Copier le mediatag", "share_mediatagCopy": "Copier le mediatag",
"sharedFolders_forget": "Ce pad est stocké uniquement dans un dossier partagé. Vous ne pouvez pas le déplacer dans votre corbeille. Si vous souhaitez le supprimer, vous pouvez utiliser l'application CryptDrive.", "sharedFolders_forget": "Ce pad est stocké uniquement dans un dossier partagé. Vous ne pouvez pas le déplacer dans votre corbeille. Si vous souhaitez le supprimer, vous pouvez utiliser l'application CryptDrive.",
@ -879,7 +879,7 @@
"oo_exportInProgress": "Exportation en cours", "oo_exportInProgress": "Exportation en cours",
"oo_sheetMigration_loading": "Mise à jour de la feuille de calcul. Merci de patienter environ 1 minute.", "oo_sheetMigration_loading": "Mise à jour de la feuille de calcul. Merci de patienter environ 1 minute.",
"oo_sheetMigration_complete": "Version mise à jour disponible, appuyez sur OK pour recharger.", "oo_sheetMigration_complete": "Version mise à jour disponible, appuyez sur OK pour recharger.",
"oo_sheetMigration_anonymousEditor": "L'édition de cette feuille de calcul est désactivée pour les utilisateurs non-enregistrés jusqu'à ce qu'elle soit mise à jour par un utilisateur enregistré.", "oo_sheetMigration_anonymousEditor": "L'édition de cette feuille de calcul est désactivée pour les visiteurs jusqu'à ce qu'elle soit mise à jour par un utilisateur enregistré.",
"imprint": "Mentions légales", "imprint": "Mentions légales",
"isContact": "{0} est dans vos contacts", "isContact": "{0} est dans vos contacts",
"isNotContact": "{0} n'est <b>pas</b> dans vos contacts", "isNotContact": "{0} n'est <b>pas</b> dans vos contacts",
@ -1108,7 +1108,7 @@
"whatis_apps": "Une suite complète d'applications", "whatis_apps": "Une suite complète d'applications",
"whatis_collaboration_info": "<p>CryptPad est construit pour permettre la collaboration. Les modifications apportées aux documents sont synchronisées en temps réel. Comme toutes les données sont chiffrées, le service et ses administrateurs n'ont aucun moyen de voir le contenu en cours d'édition et de stockage.</p>", "whatis_collaboration_info": "<p>CryptPad est construit pour permettre la collaboration. Les modifications apportées aux documents sont synchronisées en temps réel. Comme toutes les données sont chiffrées, le service et ses administrateurs n'ont aucun moyen de voir le contenu en cours d'édition et de stockage.</p>",
"register_warning_note": "En raison de la nature chiffrée de CryptPad, les administrateurs du service ne seront pas en mesure de récupérer les données au cas où vous oublieriez votre nom d'utilisateur et/ou votre mot de passe. Veuillez les sauvegarder dans un endroit sûr.", "register_warning_note": "En raison de la nature chiffrée de CryptPad, les administrateurs du service ne seront pas en mesure de récupérer les données au cas où vous oublieriez votre nom d'utilisateur et/ou votre mot de passe. Veuillez les sauvegarder dans un endroit sûr.",
"register_notes": "<ul class=\"cp-notes-list\"><li>Votre mot de passe est la clé secrète utilisée pour chiffrer tous vos documents. <span class=\"red\">Si vous le perdez, nous ne pourrons pas récupérer vos données.</span></li><li>Si vous utilisez un ordinateur partagé, <span class=\"red\">n'oubliez pas de vous déconnecter</span> après avoir terminé. La simple fermeture de la fenêtre du navigateur laisse votre compte exposé.</li><li>Pour conserver les documents que vous avez créés et/ou stockés sans être connecté, cochez \"Importer les documents de votre session anonyme\". </li></ul>", "register_notes": "<ul class=\"cp-notes-list\"><li>Votre mot de passe est la clé secrète utilisée pour chiffrer tous vos documents. <span class=\"red\">Si vous le perdez, nous ne pourrons pas récupérer vos données.</span></li><li>Si vous utilisez un ordinateur partagé, <span class=\"red\">n'oubliez pas de vous déconnecter</span> après avoir terminé. La simple fermeture de la fenêtre du navigateur laisse votre compte exposé.</li><li>Pour conserver les documents que vous avez créés et/ou stockés sans être connecté, cochez \"Importer les documents de votre session visiteur\". </li></ul>",
"register_notes_title": "Notes importantes", "register_notes_title": "Notes importantes",
"home_support": "<p>L'équipe de développement ne tire aucun profit des données personnelles des utilisateurs. Cela s'inscrit dans une vision pour des services en ligne qui respectent la vie privée. Contrairement aux grandes plateformes qui prétendent être \"gratuites\" tout en tirant profit des informations personnelles, CryptPad vise à construire un modèle durable financé volontairement par les utilisateurs.</p><p>Vous pouvez soutenir le projet en faisant un don unique ou récurrent par le biais de notre Open Collective. Notre budget est transparent et des mises à jour sont publiées régulièrement. Il existe également un certain nombre de <a>moyens non financiers de contribuer</a>.</p>", "home_support": "<p>L'équipe de développement ne tire aucun profit des données personnelles des utilisateurs. Cela s'inscrit dans une vision pour des services en ligne qui respectent la vie privée. Contrairement aux grandes plateformes qui prétendent être \"gratuites\" tout en tirant profit des informations personnelles, CryptPad vise à construire un modèle durable financé volontairement par les utilisateurs.</p><p>Vous pouvez soutenir le projet en faisant un don unique ou récurrent par le biais de notre Open Collective. Notre budget est transparent et des mises à jour sont publiées régulièrement. Il existe également un certain nombre de <a>moyens non financiers de contribuer</a>.</p>",
"home_support_title": "Soutenez CryptPad", "home_support_title": "Soutenez CryptPad",
@ -1283,7 +1283,7 @@
"form_defaultOption": "Option {0}", "form_defaultOption": "Option {0}",
"form_anonymous_off": "Bloquées", "form_anonymous_off": "Bloquées",
"form_anonymous_on": "Autorisées", "form_anonymous_on": "Autorisées",
"form_anonymous": "Réponses anonymes", "form_anonymous": "Accès visiteur (non connecté)",
"form_willClose": "Ce formulaire fermera le {0}", "form_willClose": "Ce formulaire fermera le {0}",
"form_isClosed": "Ce formulaire a été fermé le {0}", "form_isClosed": "Ce formulaire a été fermé le {0}",
"form_isOpen": "Ce formulaire est ouvert", "form_isOpen": "Ce formulaire est ouvert",
@ -1292,7 +1292,7 @@
"form_open": "Ouvrir", "form_open": "Ouvrir",
"form_isPrivate": "Les réponses sont privées", "form_isPrivate": "Les réponses sont privées",
"form_isPublic": "Les réponses sont publiques", "form_isPublic": "Les réponses sont publiques",
"form_makePublicWarning": "Êtes-vous sûr de vouloir rendre les réponses à ce formulaire publiques ? Cette opération ne peut pas être annulée.", "form_makePublicWarning": "Êtes-vous sûr de vouloir rendre publiques les réponses à ce formulaire ? Les réponses passées et futures seront visibles par les participants. Cette opération ne peut pas être annulée.",
"form_makePublic": "Publier les réponses", "form_makePublic": "Publier les réponses",
"form_invalidQuestion": "Questions {0}", "form_invalidQuestion": "Questions {0}",
"form_invalidWarning": "Certaines résponses contiennent des erreurs :", "form_invalidWarning": "Certaines résponses contiennent des erreurs :",
@ -1309,7 +1309,7 @@
"form_form": "Formulaire", "form_form": "Formulaire",
"form_editor": "Éditeur", "form_editor": "Éditeur",
"form_results_empty": "Il n'y a pas de réponses", "form_results_empty": "Il n'y a pas de réponses",
"form_results": "Réponses", "form_results": "Réponses ({0})",
"form_answered": "Vous avez déjà répondu à ce formulaire", "form_answered": "Vous avez déjà répondu à ce formulaire",
"form_cantFindAnswers": "Vos réponses à ce formulaire n'ont pas pu être récupérées.", "form_cantFindAnswers": "Vos réponses à ce formulaire n'ont pas pu être récupérées.",
"form_updateWarning": "Mettre à jour avec erreurs", "form_updateWarning": "Mettre à jour avec erreurs",
@ -1382,5 +1382,47 @@
"notification_openLink": "Vous avez reçu un lien <b>{0}</b> de {1} :", "notification_openLink": "Vous avez reçu un lien <b>{0}</b> de {1} :",
"ui_expand": "Développer", "ui_expand": "Développer",
"ui_collapse": "Réduire", "ui_collapse": "Réduire",
"form_totalResponses": "Nombre de réponses : {0}" "form_totalResponses": "Nombre de réponses : {0}",
"form_colors": "Thème de couleur",
"userlist_visitProfile": "Voir le profil",
"form_conditional_addAnd": "Ajouter une condition et",
"form_conditional_add": "Ajouter une condition ou",
"form_conditional": "Cette section ne sera affichée que si :",
"form_condition_has": "contient",
"form_condition_hasnot": "ne contient pas",
"form_condition_is": "est",
"form_condition_isnot": "n'est pas",
"form_condition_v": "Choisir une réponse",
"form_condition_q": "Choisir une question",
"form_type_section": "Section conditionelle",
"form_editable": "Modifier les réponses après l'envoi",
"form_responseMsg": "Ce message s'affichera après l'envoi du formulaire par un participant.",
"form_makeAnon": "Anonymiser les résponses",
"form_corruptAnswers": "Ce formulaire a déjà reçu des réponses. La modification du type de question risque d'invalider les données des réponses précédentes.",
"form_geturl": "Copier le lien",
"toolbar_preview": "Aperçu",
"form_updateMsg": "Modifier le message de confirmation",
"form_addMsg": "Ajouter un message de confirmation",
"form_changeTypeConfirm": "Sélectionner le type de question.",
"form_preview": "Aperçu",
"form_required_off": "Optionelle",
"form_required_on": "Requise",
"form_required_answer": "Réponse : ",
"form_requiredWarning": "Une réponse est requise pour les questions suivantes :",
"form_authAnswer": "Ce formulaire ne peut pas être envoyé de manière anonyme",
"form_anonAnswer": "Les réponses à ce formulaire sont anonymisées",
"form_viewAllAnswers": "Voir toutes les réponses ({0})",
"form_viewAnswer": "Voir mes réponses",
"form_editAnswer": "Modifier mes réponses",
"form_alreadyAnswered": "Vous avez repondu à ce formulaire le {0}",
"form_preview_button": "Aperçu",
"form_template_poll": "Sondage de disponibilité",
"upload_addOptionalAlt": "Ajouter un texte descitpif (optionnel)",
"upload_modal_alt": "Texte alternatif",
"profile_defaultAlt": "Image de profil par défaut",
"toolbar_expand": "Afficher la barre d'outils",
"toolbar_collapse": "Cacher la barre d'outils",
"support_premiumLink": "Voir les options d'abonnement",
"support_premiumPriority": "Les utilisateurs abonnés contribuent à l'amélioration des fonctionnalités de CryptPad et bénéficient de réponses prioritaires à leurs tickets de support.",
"form_conditional_hint": "Pour rendre cette section conditionnelle, veuillez ajouter une question choix ou cases à cocher ci-dessus"
} }

@ -123,7 +123,7 @@
"login_hashing": "パスワードをハッシュ化しています。この処理には時間がかかる場合があります。", "login_hashing": "パスワードをハッシュ化しています。この処理には時間がかかる場合があります。",
"login_invalPass": "パスワードを入力してください", "login_invalPass": "パスワードを入力してください",
"login_invalUser": "ユーザー名を入力してください", "login_invalUser": "ユーザー名を入力してください",
"register_importRecent": "匿名セッションのドキュメントをインポート", "register_importRecent": "ゲストセッションのドキュメントをインポート",
"importButton": "インポート", "importButton": "インポート",
"main_catch_phrase": "コラボレーションスイート<br>端末間暗号化とオープンソース", "main_catch_phrase": "コラボレーションスイート<br>端末間暗号化とオープンソース",
"tos_3rdparties": "私たちは、法令に基づく場合を除き、個人情報を第三者に提供しません。", "tos_3rdparties": "私たちは、法令に基づく場合を除き、個人情報を第三者に提供しません。",
@ -196,7 +196,7 @@
"contacts_unmute": "ミュート解除", "contacts_unmute": "ミュート解除",
"contacts_mute": "ミュート", "contacts_mute": "ミュート",
"contacts": "連絡先", "contacts": "連絡先",
"share_linkOpen": "プレビュー", "share_linkOpen": "リンクを開く",
"share_linkView": "表示", "share_linkView": "表示",
"view": "表示", "view": "表示",
"settings_exportError": "表示エラー", "settings_exportError": "表示エラー",
@ -206,7 +206,7 @@
"reconnecting": "再接続中", "reconnecting": "再接続中",
"synchronizing": "同期中", "synchronizing": "同期中",
"initializing": "初期化中...", "initializing": "初期化中...",
"anonymous": "匿名", "anonymous": "ゲスト",
"editor": "編集者", "editor": "編集者",
"typing": "編集中", "typing": "編集中",
"team_infoLabel": "チームについて", "team_infoLabel": "チームについて",
@ -265,7 +265,7 @@
"features_f_cryptdrive1_note": "フォルダ、共有フォルダ、テンプレート、タグ", "features_f_cryptdrive1_note": "フォルダ、共有フォルダ、テンプレート、タグ",
"features_f_cryptdrive1": "CryptDriveの全機能", "features_f_cryptdrive1": "CryptDriveの全機能",
"features_f_anon_note": "追加機能あり", "features_f_anon_note": "追加機能あり",
"features_f_anon": "匿名ユーザーの全機能", "features_f_anon": "ゲストユーザーの全機能",
"features_f_storage0_note": "ドキュメントは{0}日以上利用されないと削除されます", "features_f_storage0_note": "ドキュメントは{0}日以上利用されないと削除されます",
"features_f_storage0": "一時的な保存", "features_f_storage0": "一時的な保存",
"features_f_cryptdrive0_note": "アクセスしたパッドをブラウザに保存して、後で開くことができます", "features_f_cryptdrive0_note": "アクセスしたパッドをブラウザに保存して、後で開くことができます",
@ -278,7 +278,7 @@
"features_premium": "プレミアム", "features_premium": "プレミアム",
"features_registered": "登録済", "features_registered": "登録済",
"features_title": "機能", "features_title": "機能",
"features_anon": "未登録", "features_anon": "ゲスト",
"register_whyRegister": "登録するメリットをご紹介します", "register_whyRegister": "登録するメリットをご紹介します",
"historyText": "履歴", "historyText": "履歴",
"help_button": "ヘルプ", "help_button": "ヘルプ",
@ -481,7 +481,7 @@
"upload_pending": "保留中", "upload_pending": "保留中",
"settings_resetTips": "ヒント", "settings_resetTips": "ヒント",
"chrome68": "バージョン68のChromeもしくはChromiumを使用しているようです。このバージョンには、数秒経過した後でページが白紙になったり、クリックにページが反応しなくなったりするバグがあります。この問題を解決するには、別のタブを表示して改めて表示するか、このページでスクロールを試みてください。このバグは次のバージョンで解決される予定となっています。", "chrome68": "バージョン68のChromeもしくはChromiumを使用しているようです。このバージョンには、数秒経過した後でページが白紙になったり、クリックにページが反応しなくなったりするバグがあります。この問題を解決するには、別のタブを表示して改めて表示するか、このページでスクロールを試みてください。このバグは次のバージョンで解決される予定となっています。",
"register_notes": "<ul class=\"cp-notes-list\"><li>ドキュメントは、パスワードによって暗号化されます。<span class=\"red\">パスワードを紛失すると、データを復元することはできません。</span></li><li>共有のコンピュータを使用している場合は、作業完了時に<span class=\"red\">忘れずログアウトしてください。</span> ブラウザーのウインドウを閉じても、アカウントからはログアウトされません。</li><li>未ログインで作成、共有したファイルを保存するには、 「匿名セッションのドキュメントをインポート」にチェックをしてください。 </li></ul>", "register_notes": "<ul class=\"cp-notes-list\"><li>ドキュメントは、パスワードによって暗号化されます。<span class=\"red\">パスワードを紛失すると、データを復元することはできません。</span></li><li>共有のコンピュータを使用している場合は、作業完了時に<span class=\"red\">忘れずログアウトしてください。</span> ブラウザーのウインドウを閉じても、アカウントからはログアウトされません。</li><li>未ログインで作成、共有したファイルを保存するには、 「ゲストセッションのドキュメントをインポート」にチェックをしてください。 </li></ul>",
"poll_commit": "送信", "poll_commit": "送信",
"admin_removeDonateButtonTitle": "クラウドファンディングへの参加", "admin_removeDonateButtonTitle": "クラウドファンディングへの参加",
"mediatag_notReady": "ダウンロードを完了してください", "mediatag_notReady": "ダウンロードを完了してください",
@ -525,7 +525,7 @@
"crowdfunding_popup_no": "あとで", "crowdfunding_popup_no": "あとで",
"sharedFolders_create_name": "フォルダ名", "sharedFolders_create_name": "フォルダ名",
"creation_newTemplate": "新しいテンプレート", "creation_newTemplate": "新しいテンプレート",
"creation_noTemplate": "テンプレートがありません", "creation_noTemplate": "空のドキュメント",
"creation_expire": "期限切れのパッド", "creation_expire": "期限切れのパッド",
"mdToolbar_list": "箇条書き", "mdToolbar_list": "箇条書き",
"uploadFolder_modal_filesPassword": "ファイルのパスワード", "uploadFolder_modal_filesPassword": "ファイルのパスワード",
@ -626,7 +626,7 @@
"autostore_pad": "パッド", "autostore_pad": "パッド",
"autostore_sf": "フォルダ", "autostore_sf": "フォルダ",
"autostore_file": "ファイル", "autostore_file": "ファイル",
"share_linkCopy": "コピー", "share_linkCopy": "リンクをコピー",
"creation_passwordValue": "パスワード", "creation_passwordValue": "パスワード",
"edit": "編集", "edit": "編集",
"features": "機能", "features": "機能",
@ -746,7 +746,7 @@
"saveTemplatePrompt": "テンプレートのタイトルを入力してください", "saveTemplatePrompt": "テンプレートのタイトルを入力してください",
"newButtonTitle": "新しいパッドを作成", "newButtonTitle": "新しいパッドを作成",
"newButton": "新規", "newButton": "新規",
"userAccountButton": "アカウント", "userAccountButton": "メニュー",
"userListButton": "ユーザーリスト", "userListButton": "ユーザーリスト",
"movedToTrash": "パッドをゴミ箱に移動しました。<br><a>ドライブにアクセス</a>", "movedToTrash": "パッドをゴミ箱に移動しました。<br><a>ドライブにアクセス</a>",
"forgetPrompt": "OKをクリックするとパッドをゴミ箱へと移動します。よろしいですか", "forgetPrompt": "OKをクリックするとパッドをゴミ箱へと移動します。よろしいですか",
@ -774,7 +774,7 @@
"inactiveError": "このパッドは利用されていなかったため削除されました。Escキーを押すと新しいパッドを作成します。", "inactiveError": "このパッドは利用されていなかったため削除されました。Escキーを押すと新しいパッドを作成します。",
"deletedError": "このドキュメントは削除されたため、利用できなくなりました。", "deletedError": "このドキュメントは削除されたため、利用できなくなりました。",
"expiredError": "このパッドは利用期限を過ぎてしまったため、利用できなくなりました。", "expiredError": "このパッドは利用期限を過ぎてしまったため、利用できなくなりました。",
"anonymousStoreDisabled": "このCryptPadのインスタンスの管理者は、匿名ユーザーによる保存を無効に設定しています。CryptDriveを使用するにはログインする必要があります。", "anonymousStoreDisabled": "このCryptPadのインスタンスの管理者は、ゲストによる保存を無効に設定しています。あなたのCryptDriveにアクセスするにはログインが必要です。",
"padNotPinnedVariable": "このパッドは{4}日間利用しないと有効期限が切れます。{0}ログイン{1}するか{2}登録{3}して保存してください。", "padNotPinnedVariable": "このパッドは{4}日間利用しないと有効期限が切れます。{0}ログイン{1}するか{2}登録{3}して保存してください。",
"padNotPinned": "このパッドは3か月間利用しないと有効期限が切れます。{0}ログイン{1}するか{2}登録{3}して保存してください。", "padNotPinned": "このパッドは3か月間利用しないと有効期限が切れます。{0}ログイン{1}するか{2}登録{3}して保存してください。",
"onLogout": "ログアウトしました。{0}ここをクリック{1}するか<br><em>Escape</em>キーを押すと、閲覧モードでパッドにアクセスできます。", "onLogout": "ログアウトしました。{0}ここをクリック{1}するか<br><em>Escape</em>キーを押すと、閲覧モードでパッドにアクセスできます。",
@ -905,7 +905,7 @@
"form_showSummary": "概要を表示", "form_showSummary": "概要を表示",
"form_showIndividual": "個々の回答を表示", "form_showIndividual": "個々の回答を表示",
"form_results_empty": "回答がありません", "form_results_empty": "回答がありません",
"form_results": "回答", "form_results": "回答{0}",
"form_answered": "このフォームは回答済みです", "form_answered": "このフォームは回答済みです",
"form_cantFindAnswers": "このフォームの既存の回答を取得できません。", "form_cantFindAnswers": "このフォームの既存の回答を取得できません。",
"form_duplicates": "重複する項目が削除されました", "form_duplicates": "重複する項目が削除されました",
@ -1095,7 +1095,7 @@
"trimHistory_getSizeError": "ドライブの履歴のサイズを計算している途中でエラーが発生しました", "trimHistory_getSizeError": "ドライブの履歴のサイズを計算している途中でエラーが発生しました",
"profile_login": "このユーザーを連絡先に追加するにはログインする必要があります", "profile_login": "このユーザーを連絡先に追加するにはログインする必要があります",
"dontShowAgain": "再び表示しない", "dontShowAgain": "再び表示しない",
"oo_sheetMigration_anonymousEditor": "登録ユーザーが最新のバージョンに更新するまで、このスプレッドシートを未登録ユーザーが編集することはできません。", "oo_sheetMigration_anonymousEditor": "登録ユーザーが最新のバージョンに更新するまで、このスプレッドシートをゲストが編集することはできません。",
"oo_invalidFormat": "このファイルはインポートできません", "oo_invalidFormat": "このファイルはインポートできません",
"burnAfterReading_warningDeleted": "このパッドは削除されました。ウインドウを閉じた後で再びアクセスすることはできません。", "burnAfterReading_warningDeleted": "このパッドは削除されました。ウインドウを閉じた後で再びアクセスすることはできません。",
"burnAfterReading_proceed": "表示して削除", "burnAfterReading_proceed": "表示して削除",
@ -1382,5 +1382,35 @@
"form_answerAs": "名前を記入してください", "form_answerAs": "名前を記入してください",
"form_totalResponses": "回答数:{0}", "form_totalResponses": "回答数:{0}",
"ui_expand": "広げる", "ui_expand": "広げる",
"ui_collapse": "折りたたむ" "ui_collapse": "折りたたむ",
"support_premiumPriority": "プレミアムユーザーになると、CryptPadの使い勝手を改良する手助けができるほか、サポートチケットに対する優先サポートを受けることができます。",
"support_premiumLink": "定額利用のオプションを表示",
"toolbar_collapse": "ツールバーをたたむ",
"toolbar_expand": "ツールバーを広げる",
"form_preview": "フォームをプレビュー",
"form_geturl": "リンクをコピー",
"form_viewAnswer": "回答を表示",
"form_editAnswer": "回答を編集",
"form_preview_button": "プレビュー",
"upload_addOptionalAlt": "説明文を追加(任意)",
"upload_modal_alt": "代替テキスト",
"form_authAnswer": "このフォームは匿名では送信できません",
"form_anonAnswer": "このフォームへの回答は匿名化されています",
"form_viewAllAnswers": "全ての回答を表示({0}",
"form_condition_v": "値を選択",
"form_condition_q": "質問を選択",
"form_makeAnon": "回答を匿名化",
"toolbar_preview": "プレビュー",
"form_required_off": "任意",
"form_required_on": "必須",
"form_colors": "テーマの色",
"profile_defaultAlt": "既定のプロフィール画像",
"userlist_visitProfile": "プロフィールを表示",
"form_addMsg": "送信時のメッセージを追加",
"form_updateMsg": "送信時のメッセージを変更",
"form_corruptAnswers": "このフォームには既に回答があります。設問の種類を変更すると、回答のデータが無効になる恐れがあります。",
"form_changeTypeConfirm": "新しい設問の種類を選択してください。",
"form_required_answer": "回答: ",
"form_requiredWarning": "以下の設問は回答が必須です:",
"form_alreadyAnswered": "このフォームに{0}に回答しました"
} }

@ -92,7 +92,7 @@
"shareSuccess": "Copied link to clipboard", "shareSuccess": "Copied link to clipboard",
"userListButton": "User list", "userListButton": "User list",
"chatButton": "Chat", "chatButton": "Chat",
"userAccountButton": "Your account", "userAccountButton": "User menu",
"newButton": "New", "newButton": "New",
"newButtonTitle": "Create a new pad", "newButtonTitle": "Create a new pad",
"uploadButton": "Upload files", "uploadButton": "Upload files",
@ -579,7 +579,7 @@
"creation_expireDays": "Day(s)", "creation_expireDays": "Day(s)",
"creation_expireMonths": "Month(s)", "creation_expireMonths": "Month(s)",
"creation_password": "Password\n", "creation_password": "Password\n",
"creation_noTemplate": "No template", "creation_noTemplate": "Blank document",
"creation_newTemplate": "New template", "creation_newTemplate": "New template",
"creation_create": "Create", "creation_create": "Create",
"creation_owners": "Owners", "creation_owners": "Owners",
@ -606,8 +606,8 @@
"share_linkView": "View", "share_linkView": "View",
"share_linkEmbed": "Embed mode (hide toolbar and user list)", "share_linkEmbed": "Embed mode (hide toolbar and user list)",
"share_linkPresent": "Present", "share_linkPresent": "Present",
"share_linkOpen": "Preview", "share_linkOpen": "Open link",
"share_linkCopy": "Copy", "share_linkCopy": "Copy link",
"share_contactCategory": "Contacts", "share_contactCategory": "Contacts",
"share_embedCategory": "Embed", "share_embedCategory": "Embed",
"share_mediatagCopy": "Copy mediatag to clipboard", "share_mediatagCopy": "Copy mediatag to clipboard",
@ -1284,7 +1284,7 @@
"form_updateWarning": "Update anyway", "form_updateWarning": "Update anyway",
"form_cantFindAnswers": "Unable to retrieve your existing answers for this form.", "form_cantFindAnswers": "Unable to retrieve your existing answers for this form.",
"form_answered": "You have already answered this form", "form_answered": "You have already answered this form",
"form_results": "Responses", "form_results": "Responses ({0})",
"form_results_empty": "There are no responses", "form_results_empty": "There are no responses",
"form_editor": "Editor", "form_editor": "Editor",
"form_form": "Form", "form_form": "Form",
@ -1301,7 +1301,7 @@
"form_invalidWarning": "There are errors in some answers:", "form_invalidWarning": "There are errors in some answers:",
"form_invalidQuestion": "Question {0}", "form_invalidQuestion": "Question {0}",
"form_makePublic": "Publish responses", "form_makePublic": "Publish responses",
"form_makePublicWarning": "Are you sure you want to make responses to this form public? This cannot be undone.", "form_makePublicWarning": "Are you sure you want to make responses to this form public? Past and future responses will be visible by participants. This cannot be undone.",
"form_isPublic": "Responses are public", "form_isPublic": "Responses are public",
"form_isPrivate": "Responses are private", "form_isPrivate": "Responses are private",
"form_open": "Open", "form_open": "Open",
@ -1310,7 +1310,7 @@
"form_isOpen": "This form is open", "form_isOpen": "This form is open",
"form_isClosed": "This form was closed on {0}", "form_isClosed": "This form was closed on {0}",
"form_willClose": "This form will close on {0}", "form_willClose": "This form will close on {0}",
"form_anonymous": "Anonymous answers", "form_anonymous": "Guest access (not logged in)",
"form_anonymous_on": "Allowed", "form_anonymous_on": "Allowed",
"form_anonymous_off": "Blocked", "form_anonymous_off": "Blocked",
"form_defaultOption": "Option {0}", "form_defaultOption": "Option {0}",
@ -1384,5 +1384,45 @@
"ui_expand": "Expand", "ui_expand": "Expand",
"form_totalResponses": "Total responses: {0}", "form_totalResponses": "Total responses: {0}",
"support_premiumPriority": "Premium users help support improvements to CryptPad's usability and benefit from prioritized responses to their support tickets.", "support_premiumPriority": "Premium users help support improvements to CryptPad's usability and benefit from prioritized responses to their support tickets.",
"support_premiumLink": "View subscription options" "support_premiumLink": "View subscription options",
"toolbar_collapse": "Collapse toolbar",
"toolbar_expand": "Expand toolbar",
"profile_defaultAlt": "Default profile picture",
"upload_modal_alt": "Alt text",
"upload_addOptionalAlt": "Add descriptive text (optional)",
"form_template_poll": "Quick Scheduling Poll",
"form_preview_button": "Preview",
"form_alreadyAnswered": "You responded to this form on {0}",
"form_editAnswer": "Edit my responses",
"form_viewAnswer": "View my responses",
"form_viewAllAnswers": "View all responses ({0})",
"form_anonAnswer": "Responses to this form are anonymized",
"form_authAnswer": "This form cannot be submitted anonymously",
"form_requiredWarning": "The following questions require an answer:",
"form_required_answer": "Answer: ",
"form_required_on": "Required",
"form_required_off": "Optional",
"form_preview": "Preview form",
"form_changeTypeConfirm": "Select the new question type.",
"form_corruptAnswers": "This form already has responses. Changing this question type may invalidate previous response data.",
"form_geturl": "Copy link",
"toolbar_preview": "Preview",
"form_updateMsg": "Update submit message",
"form_addMsg": "Add submit message",
"form_responseMsg": "This message will be displayed after participants submit the form.",
"form_makeAnon": "Anonymize responses",
"form_editable": "Editing after submission",
"form_type_section": "Conditional section",
"form_condition_q": "Choose a question",
"form_condition_v": "Choose a value",
"form_condition_is": "is",
"form_condition_isnot": "is not",
"form_condition_has": "has",
"form_condition_hasnot": "has not",
"form_conditional": "Only show this section when:",
"form_conditional_add": "Add OR condition",
"form_conditional_addAnd": "Add AND condition",
"userlist_visitProfile": "Visit profile",
"form_colors": "Color theme",
"form_conditional_hint": "To make this section conditional, please add a choice or checkbox question above it"
} }

@ -770,6 +770,10 @@
} }
} }
} }
.cp-form-results-contained {
max-height: 350px; // enough for 10 table entries
overflow: auto;
}
.cp-form-individual { .cp-form-individual {
background: @cp_form-bg1; background: @cp_form-bg1;
padding: 10px; padding: 10px;

@ -895,10 +895,31 @@ define([
return total; return total;
}; };
var getEmpty = function (empty) { // TODO don't include this in the scrollable area var multiAnswerSubHeading = function (content) {
if (empty) { return h('span.cp-charts-row', h('td.cp-charts-cell', {
return UI.setHTML(h('div.cp-form-results-type-text-empty'), Messages._getKey('form_notAnswered', [empty])); colspan: 3,
} style: 'font-weight: bold;',
}, content));
};
var getEmpty = function (empty) {
if (!empty) { return; }
var msg = UI.setHTML(h('span.cp-form-results-empty-text'), Messages._getKey('form_notAnswered', [empty]));
return multiAnswerSubHeading(msg);
};
var barGraphic = function (itemScale) {
return h('span.cp-bar-container', h('div.cp-bar', {
style: 'width: ' + (itemScale * 100) + '%',
}, ' '));
};
var barRow = function (value, count, max, showBar) {
return h('div.cp-charts-row', [
h('span.cp-value', value),
h('span.cp-count', count),
showBar? barGraphic((count / max)): undefined,
]);
}; };
var findItem = function (items, uid) { var findItem = function (items, uid) {
@ -1550,27 +1571,21 @@ define([
return Array.isArray(A)? Math.max.apply(null, A): NaN; return Array.isArray(A)? Math.max.apply(null, A): NaN;
}; };
var barGraphic = function (itemScale) {
return h('span.cp-bar-container', h('div.cp-bar', {
style: 'width: ' + (itemScale * 100) + '%',
}, ' '));
};
var renderTally = function (tally, empty, showBar) { var renderTally = function (tally, empty, showBar) {
var rows = []; var rows = [];
var counts = Util.values(tally); var counts = Util.values(tally);
var max = arrayMax(counts); var max = arrayMax(counts);
if (empty) { rows.push(getEmpty(empty)); }
Object.keys(tally).forEach(function (value) { Object.keys(tally).forEach(function (value) {
var itemCount = tally[value]; var itemCount = tally[value];
var itemScale = (itemCount / max); var itemScale = (itemCount / max);
rows.push(h('div.cp-form-results-type-radio-data', [ rows.push(h('div.cp-charts-row', [
h('span.cp-value', {'title': value}, value), h('span.cp-value', {'title': value}, value),
h('span.cp-count', itemCount), h('span.cp-count', itemCount),
showBar? barGraphic(itemScale): undefined, showBar? barGraphic(itemScale): undefined,
])); ]));
}); });
if (empty) { rows.push(getEmpty(empty)); }
return rows; return rows;
}; };
@ -1613,34 +1628,35 @@ define([
reset: function () { $tag.val(''); } reset: function () { $tag.val(''); }
}; };
}, },
printResults: function (answers, uid) { printResults: function (answers, uid) { // results text
var results = []; var results = [];
var empty = 0; var empty = 0;
var tally = {}; var tally = {};
var isEmpty = function (answer) {
console.error("EMPTY?", JSON.stringify(answer));
return !answer || !answer.trim();
};
Object.keys(answers).forEach(function (author) { Object.keys(answers).forEach(function (author) {
var obj = answers[author]; var obj = answers[author];
var answer = obj.msg[uid]; var answer = obj.msg[uid];
if (!answer || !answer.trim()) { return empty++; } if (isEmpty(answer)) { return empty++; }
Util.inc(tally, answer); Util.inc(tally, answer);
}); });
//var counts = Util.values(tally); //var counts = Util.values(tally);
//var max = arrayMax(counts); //var max = arrayMax(counts);
//if (max < 2) { // there are no duplicates, so just return text //if (max < 2) { // there are no duplicates, so just return text
results.push(getEmpty(empty));
Object.keys(answers).forEach(function (author) { Object.keys(answers).forEach(function (author) {
var obj = answers[author]; var obj = answers[author];
var answer = obj.msg[uid]; var answer = obj.msg[uid];
if (!answer || !answer.trim()) { return empty++; } if (!answer || !answer.trim()) { return empty++; }
results.push(h('div.cp-form-results-type-text-data', answer)); results.push(h('div.cp-charts-row', h('span.cp-value', answer)));
}); });
results.push(getEmpty(empty)); return h('div.cp-form-results-contained', h('div.cp-charts.cp-text-table', results));
return h('div.cp-form-results-type-text', results);
//} //}
/*
var rendered = renderTally(tally, empty);
return h('div.cp-form-results-type-text', rendered);
*/
}, },
icon: h('i.cptools.cptools-form-text') icon: h('i.cptools.cptools-form-text')
}, },
@ -1707,11 +1723,11 @@ define([
var obj = answers[author]; var obj = answers[author];
var answer = obj.msg[uid]; var answer = obj.msg[uid];
if (!answer || !answer.trim()) { return empty++; } if (!answer || !answer.trim()) { return empty++; }
results.push(h('div.cp-form-results-type-textarea-data', answer)); results.push(h('div.cp-charts-row', h('span.cp-value', answer)));
}); });
results.push(getEmpty(empty)); results.unshift(getEmpty(empty));
return h('div.cp-form-results-type-text', results); return h('div.cp-form-results-contained', h('div.cp-charts.cp-text-table', results));
}, },
icon: h('i.cptools.cptools-form-paragraph') icon: h('i.cptools.cptools-form-paragraph')
}, },
@ -1796,7 +1812,7 @@ define([
var showBars = Boolean(content); var showBars = Boolean(content);
var rendered = renderTally(count, empty, showBars); var rendered = renderTally(count, empty, showBars);
return h('div.cp-form-results-type-radio', rendered); return h('div.cp-charts.cp-bar-table', rendered);
}, },
icon: h('i.cptools.cptools-form-list-radio') icon: h('i.cptools.cptools-form-list-radio')
}, },
@ -1924,26 +1940,17 @@ define([
max = arrayMax(counts); max = arrayMax(counts);
}); });
results.push(getEmpty(empty));
count_keys.forEach(function (q_uid) { count_keys.forEach(function (q_uid) {
var q = findItem(opts.items, q_uid); var q = findItem(opts.items, q_uid);
var c = count[q_uid]; var c = count[q_uid];
results.push(multiAnswerSubHeading(q));
var values = Object.keys(c).map(function (res) { Object.keys(c).forEach(function (res) {
var itemCount = c[res]; results.push(barRow(res, c[res], max, showBars));
return h('div.cp-form-results-type-radio-data', [
h('span.cp-value', res),
h('span.cp-count', itemCount),
showBars? barGraphic((itemCount / max)): undefined,
]);
}); });
results.push(h('div.cp-form-results-type-multiradio-data', [
h('span.cp-mr-q', q),
h('span.cp-mr-value', values)
]));
}); });
results.push(getEmpty(empty));
return h('div.cp-form-results-type-radio', results); return h('div.cp-charts.cp-bar-table', results);
}, },
exportCSV: function (answer, form) { exportCSV: function (answer, form) {
var opts = form.opts || {}; var opts = form.opts || {};
@ -2066,7 +2073,7 @@ define([
}); });
var rendered = renderTally(count, empty, showBars); var rendered = renderTally(count, empty, showBars);
return h('div.cp-form-results-type-radio', rendered); return h('div.cp-charts.cp-bar-table', rendered);
}, },
icon: h('i.cptools.cptools-form-list-check') icon: h('i.cptools.cptools-form-list-check')
}, },
@ -2188,10 +2195,19 @@ define([
var empty = 0; var empty = 0;
var count = {}; var count = {};
var showBars = Boolean(content); var showBars = Boolean(content);
var isEmpty = function (answer) {
if (!answer) { return true; }
return !Object.keys(answer).some(function (k) {
var A = answer[k];
return Array.isArray(A) && A.length;
});
};
Object.keys(answers).forEach(function (author) { Object.keys(answers).forEach(function (author) {
var obj = answers[author]; var obj = answers[author];
var answer = obj.msg[uid]; var answer = obj.msg[uid];
if (!answer || !Object.keys(answer).length) { return empty++; } if (isEmpty(answer)) { return empty++; }
Object.keys(answer).forEach(function (q_uid) { Object.keys(answer).forEach(function (q_uid) {
var c = count[q_uid]; var c = count[q_uid];
if (!c) { if (!c) {
@ -2215,26 +2231,17 @@ define([
max = arrayMax(counts); max = arrayMax(counts);
}); });
results.push(getEmpty(empty));
count_keys.forEach(function (q_uid) { count_keys.forEach(function (q_uid) {
var q = findItem(opts.items, q_uid); var q = findItem(opts.items, q_uid);
var c = count[q_uid]; var c = count[q_uid];
results.push(multiAnswerSubHeading(q));
var values = Object.keys(c).map(function (res) { Object.keys(c).forEach(function (res) {
var val = c[res]; results.push(barRow(res, c[res], max, showBars));
return h('div.cp-form-results-type-radio-data', [
h('span.cp-value', res),
h('span.cp-count', val),
showBars? barGraphic(val / max) : undefined,
]);
}); });
results.push(h('div.cp-form-results-type-multiradio-data', [
h('span.cp-mr-q', q),
h('span.cp-mr-value', values),
]));
}); });
results.push(getEmpty(empty));
return h('div.cp-form-results-type-radio', results); return h('div.cp-charts.cp-bar-table', results);
}, },
exportCSV: function (answer, form) { exportCSV: function (answer, form) {
var opts = form.opts || {}; var opts = form.opts || {};
@ -2360,7 +2367,6 @@ define([
// results sort // results sort
var opts = form[uid].opts || TYPES.sort.defaultOpts; var opts = form[uid].opts || TYPES.sort.defaultOpts;
var l = (opts.values || []).length; var l = (opts.values || []).length;
//var results = [];
var empty = 0; var empty = 0;
var count = {}; var count = {};
extractValues(opts.values || []).forEach(function (v) { count[v] = 0; }); extractValues(opts.values || []).forEach(function (v) { count[v] = 0; });
@ -2377,7 +2383,7 @@ define([
}); });
var rendered = renderTally(count, empty, showBars); var rendered = renderTally(count, empty, showBars);
return h('div.cp-form-results-type-radio', rendered); return h('div.cp-charts.cp-bar-table', rendered);
}, },
icon: h('i.cptools.cptools-form-list-ordered') icon: h('i.cptools.cptools-form-list-ordered')
}, },

@ -125,7 +125,7 @@ define([
var noDriveAnswered = false; var noDriveAnswered = false;
nThen(function (w) { nThen(function (w) {
require([ require([
'/bower_components/chainpad-netflux/chainpad-netflux.js', 'chainpad-netflux',
'/common/pinpad.js', '/common/pinpad.js',
], w(function (_CPNetflux, _Pinpad) { ], w(function (_CPNetflux, _Pinpad) {
CPNetflux = _CPNetflux; CPNetflux = _CPNetflux;

@ -159,6 +159,14 @@
margin-right: 5px; margin-right: 5px;
.tools_unselectable(); .tools_unselectable();
cursor: default; cursor: default;
&.cp-cursor.cp-tippy-html {
.avatar_vars(20px);
background-color: var(--red);
font-size: @avatar-font-size;
&.animal {
font-size: @avatar-font-size-animal;
}
}
} }
} }
.kanban-item { .kanban-item {

@ -97,16 +97,28 @@ define([
// Tippy // Tippy
var html = MT.getCursorAvatar(cursor); var html = MT.getCursorAvatar(cursor);
var l = Util.getFirstCharacter(cursor.name || Messages.anonymous); var name = UI.getDisplayName(cursor.name);
var l; // label?
var animal = '';
if (cursor.name === Messages.anonymous && typeof(cursor.uid) === 'string') {
l = MT.getPseudorandomAnimal(cursor.uid);
if (l) {
animal = '.animal';
}
}
if (!l) {
l = MT.getPrettyInitials(name);
}
var text = ''; var text = '';
if (cursor.color) { if (cursor.color) {
text = 'color:'+getTextColor(cursor.color)+';'; text = 'background-color:' + cursor.color + '; color:'+getTextColor(cursor.color)+';';
} }
var avatar = h('span.cp-cursor.cp-tippy-html', { var avatar = h('span.cp-cursor.cp-tippy-html' + animal, {
style: "background-color: " + (cursor.color || 'red') + ";"+text, style: text,
'data-cptippy-html': true, 'data-cptippy-html': true,
title: html title: html,
}, l); }, l);
if (!noClear) { if (!noClear) {
cursor.clear = function () { cursor.clear = function () {
@ -563,12 +575,12 @@ define([
"12": { "12": {
"id": 12, "id": 12,
"title": Messages.kanban_working, "title": Messages.kanban_working,
"item": [3, 4] "item": [],
}, },
"13": { "13": {
"id": 13, "id": 13,
"title": Messages.kanban_done, "title": Messages.kanban_done,
"item": [5, 6] "item": [],
} }
}, },
items: items items: items
@ -1009,8 +1021,8 @@ define([
var common = framework._.sfCommon; var common = framework._.sfCommon;
var $button = common.createButton('toggle', true, { var $button = common.createButton('toggle', true, {
element: $(container), element: $(container),
//icon: 'fa-tags', // FIXME icon: 'fa-tags',
//text: Messages.fm_tagsName, // FIXME text: Messages.fm_tagsName,
}, function () { }, function () {
$button.toggleClass('cp-toolbar-button-active'); $button.toggleClass('cp-toolbar-button-active');
@ -1295,12 +1307,12 @@ define([
// Add new cursor // Add new cursor
var avatar = getAvatar(cursor); var avatar = getAvatar(cursor);
var $item = $('.kanban-item[data-eid="'+cursor.item+'"]'); var $item = $('.kanban-item[data-eid="'+cursor.item+'"]');
var $board = $('.kanban-board[data-id="'+cursor.board+'"]');
if ($item.length) { if ($item.length) {
remoteCursors[id] = cursor; remoteCursors[id] = cursor;
$item.find('.cp-kanban-cursors').append(avatar); $item.find('.cp-kanban-cursors').append(avatar);
return; return;
} }
var $board = $('.kanban-board[data-id="'+cursor.board+'"]');
if ($board.length) { if ($board.length) {
remoteCursors[id] = cursor; remoteCursors[id] = cursor;
$board.find('header .cp-kanban-cursors').append(avatar); $board.find('header .cp-kanban-cursors').append(avatar);

@ -43,18 +43,21 @@ define([
var canonicalize = function(t) { return t.replace(/\r\n/g, '\n'); }; var canonicalize = function(t) { return t.replace(/\r\n/g, '\n'); };
var getAuthorId = function(Env, curve) { var getAuthorId = function(Env, curve, uid) {
return Env.common.getAuthorId(Env.comments.authors, curve); return Env.common.getAuthorId(Env.comments.authors, curve, uid);
}; };
// Return the author ID and add/update the data for registered users // Return the author ID and add/update user data
// Return the username for unregistered users // associate data with a curvePublic for registered users and the uid otherwise
var updateAuthorData = function(Env, onChange) { var updateAuthorData = function(Env, onChange) {
var userData = Env.metadataMgr.getUserData(); var userData = Env.metadataMgr.getUserData();
var myAuthorId;
if (!Env.common.isLoggedIn()) { if (!Env.common.isLoggedIn()) {
return userData.name; myAuthorId = getAuthorId(Env, undefined, userData.uid);
} else {
myAuthorId = getAuthorId(Env, userData.curvePublic);
} }
var myAuthorId = getAuthorId(Env, userData.curvePublic);
var data = Env.comments.authors[myAuthorId] = Env.comments.authors[myAuthorId] || {}; var data = Env.comments.authors[myAuthorId] = Env.comments.authors[myAuthorId] || {};
var old = Sortify(data); var old = Sortify(data);
data.name = userData.name; data.name = userData.name;
@ -62,6 +65,8 @@ define([
data.profile = userData.profile; data.profile = userData.profile;
data.curvePublic = userData.curvePublic; data.curvePublic = userData.curvePublic;
data.notifications = userData.notifications; data.notifications = userData.notifications;
data.uid = userData.uid;
if (typeof(onChange) === "function" && Sortify(data) !== old) { if (typeof(onChange) === "function" && Sortify(data) !== old) {
onChange(); onChange();
} }
@ -82,6 +87,9 @@ define([
var userData = Env.metadataMgr.getUserData(); var userData = Env.metadataMgr.getUserData();
var privateData = Env.metadataMgr.getPrivateData(); var privateData = Env.metadataMgr.getPrivateData();
var others = {}; var others = {};
// XXX mentioned users should be excluded from the list of notified recipients to avoid notifying them twice
// Get all the other registered users with a mailbox // Get all the other registered users with a mailbox
thread.m.forEach(function(obj) { thread.m.forEach(function(obj) {
var u = obj.u; var u = obj.u;
@ -93,7 +101,8 @@ define([
curvePublic: author.curvePublic, curvePublic: author.curvePublic,
comment: obj.m, comment: obj.m,
content: obj.v, content: obj.v,
notifications: author.notifications notifications: author.notifications,
uid: author.uid,
}; };
}); });
// Send the notification // Send the notification
@ -146,7 +155,7 @@ define([
'aria-required': true, 'aria-required': true,
contenteditable: true, contenteditable: true,
}); });
Env.common.displayAvatar($(avatar), userData.avatar, name); Env.common.displayAvatar($(avatar), userData.avatar, name, Util.noop, userData.uid);
var cancel = h('button.btn.btn-cancel', { var cancel = h('button.btn.btn-cancel', {
tabindex: 1 tabindex: 1
@ -224,7 +233,9 @@ define([
if (Env.common.isLoggedIn()) { if (Env.common.isLoggedIn()) {
var authors = {}; var authors = {};
Object.keys((Env.comments && Env.comments.authors) ||  {}).forEach(function(id) { Object.keys((Env.comments && Env.comments.authors) ||  {})
.filter(function (id) { return Util.find(Env, ['commments', 'authors', id, 'curvePublic']); })
.forEach(function(id) {
var obj = Util.clone(Env.comments.authors[id]); var obj = Util.clone(Env.comments.authors[id]);
authors[obj.curvePublic] = obj; authors[obj.curvePublic] = obj;
}); });
@ -369,7 +380,7 @@ define([
var name = Util.fixHTML(author.name || Messages.anonymous); var name = Util.fixHTML(author.name || Messages.anonymous);
var date = new Date(msg.t); var date = new Date(msg.t);
var avatar = h('span.cp-avatar'); var avatar = h('span.cp-avatar');
Env.common.displayAvatar($(avatar), author.avatar, name); Env.common.displayAvatar($(avatar), author.avatar, name, Util.noop, author.uid);
if (author.profile) { if (author.profile) {
$(avatar).click(function(e) { $(avatar).click(function(e) {
Env.common.openURL(Hash.hashToHref(author.profile, 'profile')); Env.common.openURL(Hash.hashToHref(author.profile, 'profile'));
@ -393,7 +404,7 @@ define([
} }
cleanMentions($el); cleanMentions($el);
var avatar = h('span.cp-avatar'); var avatar = h('span.cp-avatar');
Env.common.displayAvatar($(avatar), avatarUrl, name); Env.common.displayAvatar($(avatar), avatarUrl, name, Util.noop, author.uid);
$el.append([ $el.append([
avatar, avatar,
h('span.cp-mentions-name', name) h('span.cp-mentions-name', name)

@ -3,7 +3,9 @@ define([
'/common/common-ui-elements.js', '/common/common-ui-elements.js',
'/common/common-interface.js', '/common/common-interface.js',
'/bower_components/chainpad/chainpad.dist.js', '/bower_components/chainpad/chainpad.dist.js',
], function ($, UIElements, UI, ChainPad) { '/customize/messages.js',
'/common/inner/common-mediatag.js',
], function ($, UIElements, UI, ChainPad, Messages, MT) {
var Cursor = {}; var Cursor = {};
Cursor.isCursor = function (el) { Cursor.isCursor = function (el) {
@ -40,8 +42,17 @@ define([
var cursors = {}; var cursors = {};
// FIXME despite the name of this function this doesn't actually render as a tippy tooltip
// that means that emojis will use the system font that shows up in native tooltips
// so this might be of limited value/aesthetic appeal compared to other apps' cursors
var makeTippy = function (cursor) { var makeTippy = function (cursor) {
return cursor.name; if (typeof(cursor.uid) === 'string' && (!cursor.name || cursor.name === Messages.anonymous)) {
var animal = MT.getPseudorandomAnimal(cursor.uid);
if (animal) {
return animal + ' ' + Messages.anonymous;
}
}
return cursor.name || Messages.anonymous;
}; };
var makeCursor = function (id, cursor) { var makeCursor = function (id, cursor) {

@ -7,7 +7,7 @@ define([
'/common/sframe-common.js', '/common/sframe-common.js',
'/common/common-realtime.js', '/common/common-realtime.js',
'/customize/application_config.js', '/customize/application_config.js',
'/bower_components/chainpad-listmap/chainpad-listmap.js', 'chainpad-listmap',
'/poll/render.js', '/poll/render.js',
'/poll/export.js', '/poll/export.js',
'/common/diffMarked.js', '/common/diffMarked.js',

@ -1,7 +1,7 @@
define([ define([
'jquery', 'jquery',
'/bower_components/chainpad-crypto/crypto.js', '/bower_components/chainpad-crypto/crypto.js',
'/bower_components/chainpad-listmap/chainpad-listmap.js', 'chainpad-listmap',
'/common/toolbar.js', '/common/toolbar.js',
'/bower_components/nthen/index.js', '/bower_components/nthen/index.js',
'/common/sframe-common.js', '/common/sframe-common.js',
@ -348,8 +348,8 @@ define([
if (!val) { if (!val) {
$('<img>', { $('<img>', {
src: '/customize/images/avatar.png', src: '/customize/images/avatar.png',
title: Messages.profile_avatar, title: Messages.profile_avatar, // XXX
alt: 'Avatar' alt: Messages.profile_defaultAlt,
}).appendTo($span); }).appendTo($span);
return; return;
} }
@ -391,7 +391,7 @@ define([
}, function () { }, function () {
sframeChan.query("Q_PROFILE_AVATAR_ADD", data.url, function (err, err2) { sframeChan.query("Q_PROFILE_AVATAR_ADD", data.url, function (err, err2) {
if (err || err2) { return void UI.log(err || err2); } if (err || err2) { return void UI.log(err || err2); }
displayAvatar(data.url); displayAvatar(data.url); // XXX add "Profile picture"
}); });
}); });
}; };

@ -10,14 +10,12 @@ define([
'/customize/messages.js', '/customize/messages.js',
], function ($, ApiConfig, h, UI, Hash, Util, Clipboard, UIElements, Messages) { ], function ($, ApiConfig, h, UI, Hash, Util, Clipboard, UIElements, Messages) {
var send = function (ctx, id, type, data, dest) { var getDebuggingData = function (ctx, data) {
var common = ctx.common; var common = ctx.common;
var supportKey = ApiConfig.supportMailbox;
var supportChannel = Hash.getChannelIdFromKey(supportKey);
var metadataMgr = common.getMetadataMgr(); var metadataMgr = common.getMetadataMgr();
var user = metadataMgr.getUserData();
var privateData = metadataMgr.getPrivateData(); var privateData = metadataMgr.getPrivateData();
var user = metadataMgr.getUserData();
var teams = privateData.teams || {};
data = data || {}; data = data || {};
data.sender = { data.sender = {
@ -34,16 +32,12 @@ define([
data.sender.quota = ctx.pinUsage; data.sender.quota = ctx.pinUsage;
} }
data.id = id;
data.time = +new Date();
var teams = privateData.teams || {};
if (!ctx.isAdmin) { if (!ctx.isAdmin) {
data.sender.userAgent = Util.find(window, ['navigator', 'userAgent']); data.sender.userAgent = Util.find(window, ['navigator', 'userAgent']);
data.sender.vendor = Util.find(window, ['navigator', 'vendor']); data.sender.vendor = Util.find(window, ['navigator', 'vendor']);
data.sender.appVersion = Util.find(window, ['navigator', 'appVersion']); data.sender.appVersion = Util.find(window, ['navigator', 'appVersion']);
data.sender.appVersion = Util.find(window, ['screen', 'width']); data.sender.screenWidth = Util.find(window, ['screen', 'width']);
data.sender.appVersion = Util.find(window, ['screen', 'height']); data.sender.screenHeight = Util.find(window, ['screen', 'height']);
data.sender.blockLocation = privateData.blockLocation || ''; data.sender.blockLocation = privateData.blockLocation || '';
data.sender.teams = Object.keys(teams).map(function (key) { data.sender.teams = Object.keys(teams).map(function (key) {
var team = teams[key]; var team = teams[key];
@ -57,7 +51,25 @@ define([
} }
return ret; return ret;
}).filter(Boolean); }).filter(Boolean);
}
return data;
};
var send = function (ctx, id, type, data, dest) {
var common = ctx.common;
var supportKey = ApiConfig.supportMailbox;
var supportChannel = Hash.getChannelIdFromKey(supportKey);
var metadataMgr = common.getMetadataMgr();
var user = metadataMgr.getUserData();
var privateData = metadataMgr.getPrivateData();
data = getDebuggingData(ctx, data);
data.id = id;
data.time = +new Date();
if (!ctx.isAdmin) {
// "dest" is the recipient that is not the admin support mailbox. // "dest" is the recipient that is not the admin support mailbox.
// In the support page, make sure dest is always ourselves. // In the support page, make sure dest is always ourselves.
dest.channel = privateData.support; dest.channel = privateData.support;
@ -474,6 +486,10 @@ define([
ui.makeCloseMessage = function (content, hash) { ui.makeCloseMessage = function (content, hash) {
return makeCloseMessage(ctx, content, hash); return makeCloseMessage(ctx, content, hash);
}; };
ui.getDebuggingData = function (data) {
return getDebuggingData(ctx, data);
};
return ui; return ui;
}; };

@ -693,6 +693,8 @@ define([
redrawRoster(common); redrawRoster(common);
}); });
}; };
var getDisplayName = UI.getDisplayName;
var makeMember = function (common, data, me, roster) { var makeMember = function (common, data, me, roster) {
if (!data.curvePublic) { return; } if (!data.curvePublic) { return; }
@ -701,11 +703,12 @@ define([
return user.role === "OWNER" && user.curvePublic !== me.curvePublic && !user.pendingOwner; return user.role === "OWNER" && user.curvePublic !== me.curvePublic && !user.pendingOwner;
}); });
var displayName = getDisplayName(data.displayName);
// Avatar // Avatar
var avatar = h('span.cp-avatar.cp-team-member-avatar'); var avatar = h('span.cp-avatar.cp-team-member-avatar');
common.displayAvatar($(avatar), data.avatar, data.displayName); common.displayAvatar($(avatar), data.avatar, displayName, Util.noop, data.uid);
// Name // Name
var name = h('span.cp-team-member-name', data.displayName); var name = h('span.cp-team-member-name', displayName);
if (data.pendingOwner) { if (data.pendingOwner) {
$(name).append(h('em', { $(name).append(h('em', {
title: Messages.team_pendingOwnerTitle title: Messages.team_pendingOwnerTitle
@ -789,7 +792,7 @@ define([
title: Messages.team_rosterKick title: Messages.team_rosterKick
}); });
$(remove).click(function () { $(remove).click(function () {
UI.confirm(Messages._getKey('team_kickConfirm', [Util.fixHTML(data.displayName)]), function (yes) { UI.confirm(Messages._getKey('team_kickConfirm', [Util.fixHTML(displayName)]), function (yes) {
if (!yes) { return; } if (!yes) { return; }
APP.module.execCommand('REMOVE_USER', { APP.module.execCommand('REMOVE_USER', {
pending: data.pending, pending: data.pending,
@ -1073,6 +1076,9 @@ define([
metadata: obj metadata: obj
}, function () { }, function () {
$avatar.empty(); $avatar.empty();
// the UI is not supposed to allow admins to remove team names
// so we expect that it will be there. Failing that the initials
// from the default name will be displayed
common.displayAvatar($avatar, data.url); common.displayAvatar($avatar, data.url);
}); });
}); });
@ -1093,7 +1099,7 @@ define([
if (!val) { if (!val) {
var $img = $('<img>', { var $img = $('<img>', {
src: '/customize/images/avatar.png', src: '/customize/images/avatar.png',
title: Messages.profile_avatar, title: Messages.profile_avatar, // XXX
alt: 'Avatar' alt: 'Avatar'
}); });
var mt = h('media-tag', $img[0]); var mt = h('media-tag', $img[0]);
@ -1191,10 +1197,11 @@ define([
var displayUser = function (common, data) { var displayUser = function (common, data) {
var avatar = h('span.cp-teams-invite-from-avatar.cp-avatar'); var avatar = h('span.cp-teams-invite-from-avatar.cp-avatar');
common.displayAvatar($(avatar), data.avatar, data.displayName); var name = getDisplayName(data.displayName);
common.displayAvatar($(avatar), data.avatar, name);
return h('div.cp-teams-invite-from-author', [ return h('div.cp-teams-invite-from-author', [
avatar, avatar,
h('span.cp-teams-invite-from-name', data.displayName) h('span.cp-teams-invite-from-name', name)
]); ]);
}; };
@ -1319,20 +1326,21 @@ define([
nThen(function (waitFor) { nThen(function (waitFor) {
// Get preview content. // Get preview content.
sframeChan.query('Q_ANON_GET_PREVIEW_CONTENT', { seeds: seeds }, waitFor(function (err, json) { sframeChan.query('Q_ANON_GET_PREVIEW_CONTENT', { seeds: seeds }, waitFor(function (err, json) {
if (json && (json.error || !Object.keys(json).length)) { if (json && (json.error || !Object.keys(json).length)) { // XXX team invite links are triggering this every time for me?
$(errorBlock).text(Messages.team_inviteInvalidLinkError).show(); $(errorBlock).text(Messages.team_inviteInvalidLinkError).show();
waitFor.abort(); waitFor.abort();
$div.empty(); $div.empty();
return; return;
} }
// XXX nothing guarantees that author, teamName, or message exist in json
$div.empty(); $div.empty();
$div.append(h('div.cp-teams-invite-from', [ $div.append(h('div.cp-teams-invite-from', [
Messages.team_inviteFrom || 'From:', Messages.team_inviteFrom,
displayUser(common, json.author) displayUser(common, json.author)
])); ]));
$div.append(UI.setHTML(h('p.cp-teams-invite-to'), $div.append(UI.setHTML(h('p.cp-teams-invite-to'),
Messages._getKey('team_inviteFromMsg', Messages._getKey('team_inviteFromMsg',
[Util.fixHTML(json.author.displayName), [Util.fixHTML(getDisplayName(json.author.displayName)),
Util.fixHTML(json.teamName)]))); Util.fixHTML(json.teamName)])));
if (json.message) { if (json.message) {
$div.append(h('div.cp-teams-invite-message', json.message)); $div.append(h('div.cp-teams-invite-message', json.message));
@ -1449,10 +1457,10 @@ define([
// Update the name in the user menu // Update the name in the user menu
var $displayName = $bar.find('.' + Toolbar.constants.username); var $displayName = $bar.find('.' + Toolbar.constants.username);
metadataMgr.onChange(function () { metadataMgr.onChange(function () {
var name = metadataMgr.getUserData().name || Messages.anonymous; var name = getDisplayName(metadataMgr.getUserData().name);
$displayName.text(name); $displayName.text(name);
}); });
$displayName.text(user.name || Messages.anonymous); $displayName.text(getDisplayName(user.name));
// Load the Team module // Load the Team module
var onEvent = function (obj) { var onEvent = function (obj) {

@ -1,7 +1,7 @@
define([ define([
'jquery', 'jquery',
'/bower_components/chainpad-crypto/crypto.js', '/bower_components/chainpad-crypto/crypto.js',
'/bower_components/chainpad-listmap/chainpad-listmap.js', 'chainpad-listmap',
'/common/toolbar.js', '/common/toolbar.js',
'/bower_components/nthen/index.js', '/bower_components/nthen/index.js',
'/common/sframe-common.js', '/common/sframe-common.js',

Loading…
Cancel
Save