Merge branch 'staging' into oo2

pull/1/head
ansuz 6 years ago
commit b181d72727

@ -2,6 +2,7 @@ node_modules/
www/bower_components/
www/common/pdfjs/
www/common/tippy/
www/common/highlight/
www/common/jquery-ui/
www/common/onlyoffice/*
@ -18,6 +19,7 @@ www/pad/mediatag-plugin-dialog.js
www/pad/disable-base64.js
www/kanban/jkanban.js
www/kanban/jscolor.js
www/common/media-tag-nacl.min.js

@ -1,3 +1,85 @@
# Ibis release (v2.8.0)
## Goals
We've been making use of some hidden features for a while, to make sure that they were safe to deploy.
This release, we worked on making _contextual chat_ and _shared folders_ available to everyone.
## Update notes
* run `bower update` to download an updated version of _marked.js_
### Features
* Our kanban application now features a much more consistent and flexible colorpicker, thanks to @MTRNord (https://github.com/MTRNord)
* File upload dialogs now allow you to upload multiple files at once
* Updated German translations thanks to [b3yond](https://github.com/b3yond/)
* An explicit pad storage policy to better suit different privacy constraints
* _import local pads_ at login time is no longer default
* An embedded chat room in every pad, so you can work alongside your fellow editors more easily
* Promotion of our [crowdfunding campaign](https://opencollective.com/cryptpad), including a button on the home page, and a one-time dialog for users
### Bug fixes
* Updating our markdown library resolved an issue which incorrectly rendered links containing parentheses.
* We discovered an issue logging in with _very old_ credentials which were initialized without a public key. We now regenerate your keyring if you do not have public keys stored in association with your account.
* We found another bug in our login process; under certain conditions the terminating function could be called more than once.
# Hedgehog release (v2.7.0)
## Goals
This release overlapped with the publication and presentation of a paper written about CryptPad's architecture.
As such, we didn't plan for any very ambitious new features, and instead focused on bug fixes and some new workflows.
## Update notes
This is a fairly simple release. Just download the latest commits and update your cache-busting string.
### Features
* In order to address some privacy concerns, we've changed CryptPad such that pads are not immediately stored in your CryptDrive as soon as you open them. Instead, users are presented with a prompt in the bottom-right corner which asks them whether they'd like to store it manually. Alternatively, you can use your settings page to revert to the old automatic behaviour, or choose not to store, and to never be asked.
* It was brought to our attention that it was possible to upload base64-encoded images in the rich text editor. These images had a negative performance impact on such pads. From now on, if these images are detected in a pad, users are prompted to run a migration to convert them to uploaded (and encrypted) files.
* We've added a progress bar which is displayed while you are loading a pad, as we found that it was not very clear whether large pads were loading, or if they had become unresponsive due to a bug.
* We've added an option to allow users to right-click uploaded files wherever they appear, and to store that file in their CryptDrive.
* We've improved the dialog which is used to modify the properties of encrypted media embedded within rich text pads.
### Bug fixes
* Due to a particularly disastrous bug in Chrome 68 which was unfortunately beyond our power to fix, we've added a warning for anyone affected by that bug to let them know the cause.
* We've increased the module loading timeout value used by requirejs in our sharedWorker implementation to match the value used by the rest of CryptPad.
# Gibbon release (v2.6.0)
## Goals
For this release we focused on deploying two very large changes in CryptPad.
For one, we'd worked on a large refactoring of the system we use to compile CSS from LESS, so as to make it more efficient.
Secondly, we reworked the architecture we use for implementing the CryptDrive functionality, so as to integrate support for shared folders.
## Update notes
To test the _shared folders_ functionality, users can run the following command in their browser console:
`localStorage.CryptPad_SF = "1";`
Alternatively, if the instance administrator would like to enable shared folders for all users, they can do so via their `/customize/application_config.js` file, by adding the following line:
`config.disableSharedFolders = true;`
### Features
* As mentioned in the _goals_ for this release, we've merged in the work done to drastically improve performance when compiling styles. The system features documentation for anyone interested in understanding how it works.
* We've refactored the APIs used to interact with your CryptDrive, implementing a single interface with which applications can interact, which then manages any number of sub-objects each representing a shared folder. Shared folders are still disabled by default. See the _Update notes_ section for more information.
* The home page now features the same footer which has been displayed on all other information pages until now.
* We've added a slightly nicer spinner icon on loading pages.
* We've created a custom font _cp-tools_ for our custom-designed icons
### Bug fixes
* We've accepted a pull request implementing serverside support for moving files across different drives, for system administrators hosting CryptPad on systems which segregate folders on different partitions.
* We've addressed a report of an edge case in CryptPad's user password change logic which could cause users to delete their accounts.
# Fossa release (v2.5.0)
## Goals

@ -24,7 +24,7 @@
"ckeditor": "4.7.3",
"codemirror": "^5.19.0",
"requirejs": "2.3.5",
"marked": "0.3.5",
"marked": "0.5.0",
"rangy": "rangy-release#~1.3.0",
"json.sortify": "~2.1.0",
"secure-fabric.js": "secure-v1.7.9",

@ -191,7 +191,10 @@ define([
// load the user's object using the legacy credentials
loadUserObject(opt, waitFor(function (err, rt) {
if (err) { return void cb(err); }
if (err) {
waitFor.abort();
return void cb(err);
}
// if a proxy is marked as deprecated, it is because someone had a non-owned drive
// but changed their password, and couldn't delete their old data.
@ -199,6 +202,7 @@ define([
// allow them to proceed. In time, their old drive should get deleted, since
// it will should be pinned by anyone's drive.
if (rt.proxy[Constants.deprecatedKey]) {
waitFor.abort();
return void cb('NO_SUCH_USER', res);
}
@ -514,7 +518,22 @@ define([
if (testing) { return void proceed(result); }
proceed(result);
if (!(proxy.curvePrivate && proxy.curvePublic &&
proxy.edPrivate && proxy.edPublic)) {
console.log("recovering derived public/private keypairs");
// **** reset keys ****
proxy.curvePrivate = result.curvePrivate;
proxy.curvePublic = result.curvePublic;
proxy.edPrivate = result.edPrivate;
proxy.edPublic = result.edPublic;
}
setTimeout(function () {
Realtime.whenRealtimeSyncs(result.realtime, function () {
proceed(result);
});
});
});
}, 500);
}, 200);

@ -94,7 +94,7 @@ define([
])
])
]),
h('div.cp-version-footer', "CryptPad v2.6.0 (Gibbon)")
h('div.cp-version-footer', "CryptPad v2.8.0 (Ibis)")
]);
};
@ -615,6 +615,23 @@ define([
}
]);
var _link = h('a', {
href: "https://opencollective.com/cryptpad/contribute",
target: '_blank',
rel: 'noopener',
});
var crowdFunding = AppConfig.disableCrowdfundingMessages ? undefined : h('button', [
Msg.crowdfunding_home1,
h('br'),
Msg.crowdfunding_home2,
_link
]);
$(crowdFunding).click(function () {
_link.click();
});
return [
h('div#cp-main', [
infopageTopbar(),
@ -629,6 +646,11 @@ define([
icons,
more
])
]),
h('div.row', [
h('div.cp-crowdfunding', [
crowdFunding
])
])
]),
]),
@ -810,7 +832,7 @@ define([
placeholder: Msg.login_password,
}),
h('div.checkbox-container', [
Pages.createCheckbox('import-recent', Msg.register_importRecent, true),
Pages.createCheckbox('import-recent', Msg.register_importRecent),
]),
h('div.extra', [
h('button.login.first.btn', Msg.login_login)

@ -43,6 +43,10 @@
//transform: scale(0.1);
//transform: scale(1);
h1, h2, h3 {
font-size: 1.5em;
}
.cp-corner-filler {
float: left;
clear: left;
@ -94,6 +98,8 @@
.cp-corner-footer {
font-style: italic;
font-size: 0.8em;
}
.cp-corner-footer, .cp-corner-text {
a {
color: @corner-link;
&:hover {
@ -103,10 +109,11 @@
}
button {
color: white;
border: 0px;
padding: 5px;
color: @colortheme_base;
margin-left: 5px;
outline: none;
&.cp-corner-primary {
background-color: @corner-button-ok;
font-weight: bold;

@ -12,6 +12,7 @@
@import (reference) './font.less';
@import (reference) "./app-print.less";
@import (reference) "./app-noscroll.less";
@import (reference) "./messenger.less";
.framework_main(@bg-color, @warn-color, @color) {
--LessLoader_require: LessLoader_currentFile();
@ -36,6 +37,7 @@
.tippy_main();
.checkmark_main(20px);
.password_main();
.messenger_main();
.creation_main(
@bg-color: @bg-color,
@color: @color

@ -0,0 +1,382 @@
@import (reference) './avatar.less';
@import (reference) "./colortheme-all.less";
.messenger_main() {
--LessLoader_require: LessLoader_currentFile();
};
& {
@keyframes example {
0% {
background: rgba(0,0,0,0.1);
}
50% {
background: rgba(0,0,0,0.3);
}
100% {
background: rgba(0,0,0,0.1);
}
}
@button-border: 2px;
@bg-color: @colortheme_friends-bg;
@color: @colortheme_friends-color;
@room-height: 48px;
#cp-app-contacts-container {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
min-height: 0;
&.ready {
background-size: cover;
background-position: center;
}
}
.cp-app-contacts-spinner {
display: none;
}
.cp-app-contacts-initializing {
.cp-app-contacts-spinner {
color: white;
display: block;
}
.cp-app-contacts-info {
display: none;
}
#cp-app-contacts-friendlist,
#cp-app-contacts-messaging {
display: flex;
justify-content: center;
align-items: center;
}
}
#cp-app-contacts-friendlist {
width: 200px;
height: 100%;
background-color: lighten(@bg-color, 10%);
overflow-y: auto;
display: flex;
flex-flow: column;
.cp-app-contacts-friend {
background: rgba(0,0,0,0.1);
padding: 5px;
margin: 10px;
margin-bottom: 0;
cursor: pointer;
position: relative;
height: @room-height;
.cp-app-contacts-right-col {
margin-left: 5px;
display: flex;
flex-flow: column;
flex: 1;
min-width: 0;
.cp-app-contacts-name {
white-space: nowrap;
}
}
&:hover {
background-color: rgba(0,0,0,0.3);
}
&.cp-app-contacts-notify {
animation: example 2s ease-in-out infinite;
}
}
.cp-app-contacts-remove {
cursor: pointer;
width: 20px;
&:hover {
color: darken(@color, 20%);
}
}
.cp-app-contacts-category {
display: flex;
flex-flow: column;
flex-grow: 0;
flex-shrink: 0;
.cp-app-contacts-category-title {
order: 1;
font-size: 18px;
margin: 0px 5px;
text-align: center;
background: rgba(0,0,0,0.1);
font-weight: bold;
height: 22px;
line-height: 22px;
}
.cp-app-contacts-category-content {
order: 2;
display: flex;
flex-flow: column-reverse;
padding-bottom: 10px;
&:empty {
display: none;
& ~ .cp-app-contacts-category-title {
display: none;
}
}
}
}
}
#cp-app-contacts-container.cp-app-contacts-inapp {
#cp-app-contacts-friendlist {
display: none;
/*
transition: width 0.2s ease-in-out 0.2s;
width: 68px;
.cp-app-contacts-friend {
.cp-app-contacts-right-col {
overflow: hidden;
}
}
.cp-app-contacts-category-title {
transition: font-size 0.2s ease-in-out 0.2s;
margin: 0px 2px;
font-size: 16px;
}
&:hover {
transition-delay: 1.5s;
width: 200px !important;
.cp-app-contacts-category-title {
transition-delay: 1.5s;
font-size: 18px;
}
}
*/
}
}
#cp-app-contacts-friendlist .cp-app-contacts-friend, #cp-app-contacts-messaging .cp-avatar {
.avatar_main(30px);
&.cp-avatar {
display: flex;
}
cursor: pointer;
color: @color;
media-tag {
img {
color: #000;
}
}
media-tag, .cp-avatar-default {
//margin-right: 5px;
flex-shrink: 0;
z-index: 1;
margin: 4px;
}
.cp-app-contacts-status {
//width: 5px;
display: inline-block;
position: absolute;
//right: 0;
//top: 0;
//bottom: 0;
//opacity: 0.7;
//background-color: #777;
/* width: (@room-height - 6px);
top: 3px;
bottom: 3px;
left: 3px;
border-radius: 100%;
*/
width: 10px;
height: 10px;
top: 0;
right: 0;
border-bottom-left-radius: 100%;
&.cp-app-contacts-online {
//background-color: green;
//background-color: white;
background-color: #c5ffa8;
}
&.cp-app-contacts-offline {
display: none;
//background-color: red;
}
}
}
.placeholder (@color: #bbb) {
&::-webkit-input-placeholder { /* WebKit, Blink, Edge */
color: @color;
}
&:-moz-placeholder { /* Mozilla Firefox 4 to 18 */
color: @color;
opacity: 1;
}
&::-moz-placeholder { /* Mozilla Firefox 19+ */
color: @color;
opacity: 1;
}
&:-ms-input-placeholder { /* Internet Explorer 10-11 */
color: @color;
}
&::-ms-input-placeholder { /* Microsoft Edge */
color: @color;
}
}
#cp-app-contacts-messaging {
flex: 1;
height: 100%;
background-color: lighten(@bg-color, 20%);
min-width: 0;
.cp-app-contacts-info {
padding: 20px;
}
.cp-app-contacts-header {
background-color: lighten(@bg-color, 15%);
padding: 0;
display: flex;
justify-content: space-between;
align-items: center;
height: 50px;
.hover () {
height: 100%;
line-height: 30px;
padding: 10px;
&:hover {
background-color: rgba(50,50,50,0.3);
}
}
.cp-avatar,
.cp-app-contacts-right-col {
flex: 1 1 auto;
}
.cp-app-contacts-remove-history {
.hover;
}
.cp-avatar {
margin: 10px;
}
.cp-app-contacts-more-history {
//display: none;
.hover;
&.cp-app-contacts-faded {
color: darken(@bg-color, 5%);
}
}
.cp-app-contacts-header-title {
padding: 10px;
flex: 1;
}
}
.cp-app-contacts-tips {
margin: 1em;
background-color: lighten(@bg-color, 15%);
font-size: 14px;
padding: 10px;
position: relative;
.cp-app-contacts-tips-close {
cursor: pointer;
position: absolute;
top: 2px;
right: 2px;
}
}
.cp-app-contacts-chat {
height: 100%;
display: flex;
flex-flow: column;
.cp-app-contacts-messages {
padding: 0 20px;
margin: 10px 0;
flex: 1;
overflow-x: auto;
.cp-app-contacts-message {
display: flex;
flex-wrap: wrap;
& > div {
padding: 0 10px;
}
.cp-app-contacts-content {
overflow: hidden;
word-wrap: break-word;
&> * {
margin: 0;
}
flex: 1;
min-width: 70%;
position: relative;
}
.cp-app-contacts-date {
display: none;
font-style: italic;
}
.cp-app-contacts-sender {
margin-top: 10px;
font-weight: bold;
background-color: rgba(0,0,0,0.1);
display: flex;
justify-content: space-between;
width: 100%;
}
.cp-app-contacts-time {
display: none;
font-size: 0.8em;
align-items: center;
color: @color;
font-weight: bold;
position: absolute;
right: 0;
top: 0;
bottom: 0;
background: rgba(0,0,0,0.3);
border-top-left-radius: 50%;
border-bottom-left-radius: 50%;
padding: 0 10px;
}
&:hover {
.cp-app-contacts-time {
display: flex;
}
}
}
}
}
.cp-app-contacts-input {
background-color: lighten(@bg-color, 15%);
height: auto;
min-height: 50px;
display: flex;
align-items: center;
justify-content: center;
padding: 0 5%;
textarea {
margin: 5px 0;
padding: 5px 10px;
border: none;
height: 54px; // 2 lines (22px height) + 2 margins (5px)
flex: 1;
background-color: darken(@bg-color, 10%);
color: @color;
resize: none;
overflow-y: auto;
.placeholder(#bbb);
&[disabled="true"] {
.placeholder(#999);
}
}
button {
height: 54px;
border-radius: 0;
border: none;
background-color: darken(@bg-color, 15%);
&:hover {
background-color: darken(@bg-color, 20%);
}
}
}
}
}

@ -72,6 +72,18 @@
.modal_main();
};
& {
@keyframes notification {
0% {
background: rgba(0,0,0,0);
}
50% {
background: rgba(0,0,0,0.2);
}
100% {
background: rgba(0,0,0,0);
}
}
.toolbar_vars();
@toolbar_line-height: 32px;
@toolbar_top-height: 64px;
@ -134,9 +146,39 @@
}
}
.cp-toolbar-userlist-drawer {
.cp-toolbar-chat-drawer {
background-color: @toolbar-bg-color;
background-color: var(--toolbar-bg-color);
font: @colortheme_app-font-size @colortheme_font;
width: 20%;
min-width: 200px;
display: block;
overflow-y: auto;
overflow-x: hidden;
padding: 0;
box-sizing: border-box;
position: relative;
order: -2;
resize: horizontal;
#cp-app-contacts-container {
height: 100%;
}
.cp-toolbar-chat-drawer-close {
color: @toolbar-color;
color: var(--toolbar-color);
position: absolute;
top: 0;
right: 1px;
font-size: 15px;
opacity: 0.5;
cursor: pointer;
text-shadow: unset;
&:hover {
opacity: 1;
}
}
}
.cp-toolbar-userlist-drawer {
font: @colortheme_app-font-size @colortheme_font;
min-width: 175px;
width: 175px;
@ -145,6 +187,7 @@
overflow-x: hidden;
padding: 10px;
box-sizing: border-box;
order: -3;
.cp-toolbar-userlist-drawer-close {
position: absolute;
margin-top: -10px;
@ -219,6 +262,14 @@
display: flex;
justify-content: space-between;
align-items: center;
button {
width: 20px;
font-size: 16px;
padding: 0;
border: none;
height: 20px;
cursor: pointer;
}
}
.cp-toolbar-userlist-name-input {
flex: 1;
@ -235,14 +286,6 @@
min-height: 0;
text-overflow: ellipsis;
}
.cp-toolbar-userlist-name-edit {
width: 20px;
font-size: 16px;
padding: 0;
border: none;
height: 20px;
cursor: pointer;
}
.cp-toolbar-userlist-friend {
padding: 0;
}
@ -276,11 +319,15 @@
margin: 50px;
}
&> div {
flex: 1;
display: flex;
flex-wrap: wrap;
justify-content: center;
align-content: center;
overflow-y: auto;
}
&> div:last-child {
flex: unset;
margin: 50px 0;
}
}
@ -299,13 +346,15 @@
margin-left: 5px;
}
@media screen and (max-height: @browser_media-not-big) {
@media screen and (max-height: @browser_media-medium-screen),
screen and (max-width: @browser_media-medium-screen) {
.cp-modal {
& > p {
display: none;
}
& > div {
align-content: unset;
align-items: center;
li {
height: 40px;
width: 200px;
@ -313,9 +362,11 @@
align-items: center;
.fa {
font-size: 32px;
min-width: 50px;
}
.cp-icons-name {
height: auto;
text-align: left;
}
}
}
@ -344,7 +395,7 @@
color: @toolbar-color;
color: var(--toolbar-color);
}
.cp-toolbar-userlist-name-edit {
.cp-toolbar-userlist-button {
color: @toolbar-userlist-name-edit;
color: var(--toolbar-userlist-name-edit);
background: transparent;
@ -961,7 +1012,8 @@
height: @toolbar_line-height;
}
#cp-toolbar-userlist-drawer-open { order: 1; }
#cp-toolbar-userlist-drawer-open { order: 0; }
#cp-toolbar-chat-drawer-open { order: 1; }
.cp-toolbar-share-button { order: 2; }
.cp-toolbar-spinner { order: 3; }
@ -969,6 +1021,11 @@
width: 125px;
text-align: center;
}
#cp-toolbar-chat-drawer-open button {
&.cp-toolbar-notification {
animation: notification 2s ease-in-out infinite;
}
}
.cp-toolbar-share-button {
width: 50px;
text-align: center;

@ -180,6 +180,24 @@
}
}
}
.cp-crowdfunding {
width: 100%;
text-align: center;
button {
outline: none;
background-color: @colortheme_logo-2;
color: @colortheme_base;
border: none;
padding: 10px 20px;
border-radius: 44px;
cursor: pointer;
&:hover {
background-color: lighten(@colortheme_logo-2, 3%);
}
}
}
@media (min-width: 576px) and (max-width: 767px) {
.container {
padding-left: 0;

@ -567,6 +567,14 @@ define(function () {
out.settings_importConfirm = "Bist Du sicher, dass Du die kürzlich besuchte Dokumente in Deinem Konto importieren möchtest??";
out.settings_importDone = "Import erledigt";
out.settings_autostoreTitle = "Automatisches Speichern im CryptDrive";
out.settings_autostoreHint = "<b>Automatisch:</b> Alle Pads werden in deinem CryptDrive gespeichert.<br>" +
"<b>Manuell (immer nachfragen):</b> Wenn du ein Pad noch nicht gespeichert hast, wirst du gefragt, ob du es im CryptDrive speichern willst.<br>" +
"<b>Manuell (nie nachfragen):</b> Pads werden nicht automatisch im CryptDrive gespeichert. Die Option, sie trotzdem zu speichern, ist versteckt.<br>";
out.settings_autostoreYes = "Automatisch";
out.settings_autostoreNo = "Manuell (nie nachfragen)";
out.settings_autostoreMaybe = "Manual (immer nachfragen)";
out.settings_userFeedbackTitle = "Rückmeldung";
out.settings_userFeedbackHint1 = "CryptPad gibt grundlegende Rückmeldungen zum Server, um die Benutzer-Erfahrung zu verbessern können.";
out.settings_userFeedbackHint2 = "Der Inhalt deiner Dokumente wird nie mit dem Server geteilt.";
@ -1107,7 +1115,7 @@ define(function () {
out.readme_cat2_l2 = "Der Titel eines Dokuments kann mit einem Klick auf den Stift geändert werden.";
out.readme_cat3 = "Entdecke CryptPad Apps";
out.readme_cat3_l1 = "Mit dem CryptPad Codeeditor kannst du Code wie JavaScript, Markdown, oder HTML bearbeiten";
out.readme_cat3_l2 = "Mit dem CryptPad Präsentationseditor kannst du schnell Vorträge mit Hilfe von Markdwon gestalten";
out.readme_cat3_l2 = "Mit dem CryptPad Präsentationseditor kannst du schnell Vorträge mit Hilfe von Markdown gestalten";
out.readme_cat3_l3 = "Mit der CryptPad Umfrage kannst du schnell Abstimmungen durchführen, insbesondere, um Meetings zu planen, die in den Kalender von allen passen.";
// Tips
@ -1200,6 +1208,27 @@ define(function () {
out.loading_drive_2 = "Aktualisiere Datenformat";
out.loading_drive_3 = "Verifiziere Datenintegrität";
// Shared folders
out.sharedFolders_forget = "Dieses pad wird nur in einem geteilten Ordner gespeichert, du kannst es nicht in den Papierkorb verschieben. Du kannst es in deinem CryptDrive löschen.";
out.sharedFolders_duplicate = "Einige der pads, die du versucht hast zu verschieben, waren schon im Zielordner geteilt.";
out.sharedFolders_create = "Erstelle einen geteilten Ordner";
out.sharedFolders_create_name = "Neuer Ordner";
out.sharedFolders_create_owned = "Eigener Ordner";
out.sharedFolders_create_password = "Ordnerpasswort";
out.sharedFolders_share = "Teile diese URL mit anderen registrierten Benutzern, um ihnen Zugriff auf den geteilten Ordner zu geben. Sobald sie diese URL öffnen, wird der geteilte Ordner zu ihrem CryptDrive hinzugefügt.";
out.chrome68 = "Anscheinend benutzt du Chrome oder Chromium version 68. Darin ist ein bug, der dafür sorgt, dass nach ein paar Sekunden die Seite komplett weiß ist oder nicht mehr auf Klicks reagiert. Um das Problem zu beheben, wechsle den Tab und komme wieder, oder versuche zu scrollen. Dieser Bug sollte in der nächsten Version deines Browsers gefixt sein.";
// Manual pad storage popup
out.autostore_notstored = "Dieses Pad ist noch nicht in deinem CryptDrive. Willst du es jetzt speichern?";
out.autostore_settings = "Du kannst automatisches Speichern im CryptDrive in deinen <a href=\"/settings/\">Einstellungen</a> aktivieren!";
out.autostore_store = "Speichern";
out.autostore_hide = "Nicht speichern";
out.autostore_error = "Unerwarteter Fehler: wir konnten das Pad nicht speichern, bitte versuche es nochmal.";
out.autostore_saved = "Das Pad wurde erfolgreich in deinem CryptDrive gespeichert!";
out.autostore_forceSave = "Speicher die Datei in deinem CryptDrive"; // File upload modal
out.autostore_notAvailable = "Du musst dieses Pad in deinem CryptDrive speichern, bevor du dieses Feature benutzen kannst."; // Properties/tags/move to trash
return out;
});

@ -141,6 +141,8 @@ define(function () {
out.userListButton = "Liste d'utilisateurs";
out.chatButton = "Chat";
out.userAccountButton = "Votre compte";
out.newButton = 'Nouveau';
@ -226,6 +228,7 @@ define(function () {
out.notifyRenamed = "{0} a changé son nom en {1}";
out.notifyLeft = "{0} a quitté la session collaborative";
out.ok = 'OK';
out.okButton = 'OK (Entrée)';
out.cancel = "Annuler";
@ -252,6 +255,11 @@ define(function () {
out.pad_mediatagTitle = "Options du Media-Tag";
out.pad_mediatagWidth = "Largeur (px)";
out.pad_mediatagHeight = "Hauteur (px)";
out.pad_mediatagRatio = "Préserver les proportions";
out.pad_mediatagBorder = "Éaisseur de la bordure (px)";
out.pad_mediatagPreview = "Aperçu";
out.pad_mediatagImport = 'Sauver dans votre CryptDrive';
out.pad_mediatagOptions = 'Propriétés de l\'image';
// Kanban
out.kanban_newBoard = "Nouveau tableau";
@ -370,7 +378,8 @@ define(function () {
out.contacts_remove = 'Supprimer ce contact';
out.contacts_confirmRemove = 'Êtes-vous sûr de vouloir supprimer <em>{0}</em> de vos contacts ?';
out.contacts_typeHere = "Entrez un message ici...";
out.contacts_warning = "Tout ce que vous tapez ici est permanent et visible par tous les utilisateurs actuels et futurs de ce pad. Soyez prudent avec vos données confidentielles !";
out.contacts_padTitle = "Chat";
out.contacts_info1 = "Voici vos contacts. Ici, vous pouvez :";
out.contacts_info2 = "Cliquer sur le nom d'un contact pour discuter avec lui";
@ -382,6 +391,12 @@ define(function () {
out.contacts_removeHistoryServerError = 'Une erreur est survenue lors de la supprimer de l\'historique du chat. Veuillez réessayer plus tard.';
out.contacts_fetchHistory = "Récupérer l'historique plus ancien";
out.contacts_friends = "Amis";
out.contacts_rooms = "Salons";
out.contacts_leaveRoom = "Quitter ce salon";
out.contacts_online = "Un autre utilisateur est en ligne dans ce salon";
// File manager
out.fm_rootName = "Documents";
@ -414,6 +429,7 @@ define(function () {
out.fm_noname = "Document sans titre";
out.fm_emptyTrashDialog = "Êtes-vous sûr de vouloir vider la corbeille ?";
out.fm_removeSeveralPermanentlyDialog = "Êtes-vous sûr de vouloir supprimer ces {0} éléments de votre CryptDrive de manière permanente ?";
out.fm_removePermanentlyNote = "Les pads dont vous êtes le propriétaire seront supprimés du serveur.";
out.fm_removePermanentlyDialog = "Êtes-vous sûr de vouloir supprimer cet élément de votre CryptDrive de manière permanente ?";
out.fm_deleteOwnedPad = "Êtes-vous sûr de vouloir supprimer définitivement ce pad du serveur ?";
out.fm_deleteOwnedPads = "Êtes-vous sûr de vouloir supprimer définitivement ces pads du serveur ?";
@ -574,6 +590,14 @@ define(function () {
out.settings_importConfirm = "Êtes-vous sûr de vouloir importer les pads récents de ce navigateur dans le CryptDrive de votre compte utilisateur ?";
out.settings_importDone = "Importation terminée";
out.settings_autostoreTitle = "Stockage des pads dans CryptDrive";
out.settings_autostoreHint = "Le stockage <b>Automatique</b> des pads permet de sauver tous les pads que vous visitez dans votre CryptDrive, sans action de votre part.<br>" +
"Le stockage <b>Manuel (toujours demander)</b> permet de ne pas stocker automatiquement les pads, mais d'afficher un message vous demandant s'il faut le faire ou non.<br>" +
"Le stockage <b>Manuel (ne pas demander)</b> permet de ne pas stocker les pads ni d'afficher le message. Une option permettant de les stocker sera toujours disponible, mais cachée.";
out.settings_autostoreYes = "Automatique";
out.settings_autostoreNo = "Manuel (ne pas demander)";
out.settings_autostoreMaybe = "Manuel (toujours demander)";
out.settings_userFeedbackTitle = "Retour d'expérience";
out.settings_userFeedbackHint1 = "CryptPad peut envoyer des retours d'expérience très limités vers le serveur, de manière à nous permettre d'améliorer l'expérience des utilisateurs. ";
out.settings_userFeedbackHint2 = "Le contenu de vos pads et les clés de déchiffrement ne seront jamais partagés avec le serveur.";
@ -659,6 +683,7 @@ define(function () {
// pad
out.pad_showToolbar = "Afficher la barre d'outils";
out.pad_hideToolbar = "Cacher la barre d'outils";
out.pad_base64 = "Ce pad contient des images stockées de manière inefficace. Ces images vont augmenter de manière significative la taille du pad dans votre CryptDrive, et le rendre plus lent à charger. Vous pouvez migrer ces fichiers afin de les stocker séparément dans votre CryptDrive. Voulez-vous commencer la migration maintenant?";
// markdown toolbar
out.mdToolbar_button = "Afficher ou cacher la barre d'outils Markdown";
@ -1190,5 +1215,28 @@ define(function () {
out.sharedFolders_create_password = "Mot de passe du dossier";
out.sharedFolders_share = "Partager cette URL avec d'autres utilisateurs enregistrés leur donne accès au dossier partagé. Une fois l'URL ouverte, le dossier partagé sera ajouté au répertoire racine de leur CryptDrive.";
out.chrome68 = "Il semblerait que vous utilisiez le navigateur Chrome version 68. Ce navigateur contient un bug rendant certaines pages entièrement blanches après quelques secondes ou bloquant les clics. Pour corriger ce problème, vous pouvez vous déplacer vers un nouvel onglet et revenir ou vous pouvez essayer de faire défiler la page. Ce bug devrait être corrigé dans la prochaine version du navigateur.";
// Manual pad storage popup
out.autostore_notstored = "Ce pad n'est pas dans votre CryptDrive. Souhaitez-vous le stocker ?";
out.autostore_settings = "Vous pouvez activer le stockage automatique des pads dans vos <a href=\"/settings/\">Préférences</a> !";
out.autostore_store = "Stocker";
out.autostore_hide = "Ne pas stocker";
out.autostore_error = "Erreur : nous n'avons pas réussi à stocker ce pad, veuillez ré-essayer.";
out.autostore_saved = "Ce pad a été stocké avec succès dans votre CryptDrive !";
out.autostore_forceSave = "Stocker le fichier dans votre CryptDrive"; // File upload modal
out.autostore_notAvailable = "Vous devez stocker ce pad dans votre CryptDrive avant de pouvoir utiliser cette fonctionnalité.";
// Crowdfunding messages
out.crowdfunding_home1 = "CryptPad a besoin d'aide !";
out.crowdfunding_home2 = "Cliquez pour découvrir notre campagne de financement participatif.";
out.crowdfunding_popup_text = "<h3>Aider CryptPad</h3>" +
"Pour vous assurer que CryptPad soit activement développé, nous vous invitons à supporter le projet via la " +
'<a href="https://opencollective.com/cryptpad">page OpenCollective</a>, où vous pouvez trouver notre <b>Roadmap</b> et nos <b>objectifs de financement</b>.';
out.crowdfunding_popup_yes = "Voir la page";
out.crowdfunding_popup_no = "Pas maintenant";
out.crowdfunding_popup_never = "Ne plus demander";
return out;
});

@ -142,6 +142,8 @@ define(function () {
out.userListButton = "User list";
out.chatButton = "Chat";
out.userAccountButton = "Your account";
out.newButton = 'New';
@ -258,7 +260,7 @@ define(function () {
out.pad_mediatagRatio = "Keep ratio";
out.pad_mediatagBorder = "Border width (px)";
out.pad_mediatagPreview = "Preview";
out.pad_mediatagImport = 'Save in CryptDrive';
out.pad_mediatagImport = 'Save in your CryptDrive';
out.pad_mediatagOptions = 'Image properties';
// Kanban
@ -378,6 +380,8 @@ define(function () {
out.contacts_remove = 'Remove this contact';
out.contacts_confirmRemove = 'Are you sure you want to remove <em>{0}</em> from your contacts?';
out.contacts_typeHere = "Type a message here...";
out.contacts_warning = "Everything you type here is persistent and available to all the existing and future users of this pad. Be careful with sensitive information!";
out.contacts_padTitle = "Chat";
out.contacts_info1 = "These are your contacts. From here, you can:";
out.contacts_info2 = "Click your contact's icon to chat with them";
@ -389,6 +393,12 @@ define(function () {
out.contacts_removeHistoryServerError = 'There was an error while removing your chat history. Try again later';
out.contacts_fetchHistory = "Retrieve older history";
out.contacts_friends = "Friends";
out.contacts_rooms = "Rooms";
out.contacts_leaveRoom = "Leave this room";
out.contacts_online = "Another user from this room is online";
// File manager
out.fm_rootName = "Documents";
@ -420,12 +430,13 @@ define(function () {
out.fm_openParent = "Show in folder";
out.fm_noname = "Untitled Document";
out.fm_emptyTrashDialog = "Are you sure you want to empty the trash?";
out.fm_removeSeveralPermanentlyDialog = "Are you sure you want to remove these {0} elements from your CryptDrive permanently?";
out.fm_removePermanentlyDialog = "Are you sure you want to remove that element from your CryptDrive permanently?";
out.fm_removeSeveralPermanentlyDialog = "Are you sure you want to permanently remove these {0} elements from your CryptDrive?";
out.fm_removePermanentlyNote = "Owned pads will be removed from the server if you continue.";
out.fm_removePermanentlyDialog = "Are you sure you want to permanently remove that element from your CryptDrive?";
out.fm_removeSeveralDialog = "Are you sure you want to move these {0} elements to the trash?";
out.fm_removeDialog = "Are you sure you want to move {0} to the trash?";
out.fm_deleteOwnedPad = "Are you sure you want to remove permanently this pad from the server?";
out.fm_deleteOwnedPads = "Are you sure you want to remove permanently these pads from the server?";
out.fm_deleteOwnedPad = "Are you sure you want to permanently remove this pad from the server?";
out.fm_deleteOwnedPads = "Are you sure you want to permanently remove these pads from the server?";
out.fm_restoreDialog = "Are you sure you want to restore {0} to its previous location?";
out.fm_unknownFolderError = "The selected or last visited directory no longer exist. Opening the parent folder...";
out.fm_contextMenuError = "Unable to open the context menu for that element. If the problem persist, try to reload the page.";
@ -585,9 +596,9 @@ define(function () {
out.settings_importDone = "Import completed";
out.settings_autostoreTitle = "Pad storage in CryptDrive";
out.settings_autostoreHint = "<b>Automatic</b> pad storage results in all the pads you visit being stored in your CryptDrive.<br>" +
"<b>Manual (always ask)</b> results in the pads not being stored but a reminder will appear to ask you if you want to store them in CryptDrive.<br>" +
"<b>Manual (never ask)</b> results in the pads not being stored and option to store them will be available but in a hidden way.";
out.settings_autostoreHint = "<b>Automatic</b> All the pads you visit are stored in your CryptDrive.<br>" +
"<b>Manual (always ask)</b> If you have not stored a pad yet, you will be asked if you want to store them in your CryptDrive.<br>" +
"<b>Manual (never ask)</b> Pads are not stored automatically in your Cryptpad. The option to store them will be hidden.";
out.settings_autostoreYes = "Automatic";
out.settings_autostoreNo = "Manual (never ask)";
out.settings_autostoreMaybe = "Manual (always ask)";
@ -682,7 +693,7 @@ define(function () {
// pad
out.pad_showToolbar = "Show toolbar";
out.pad_hideToolbar = "Hide toolbar";
out.pad_base64 = "This pad contains images stored in an inefficient way. These images will increase significantly the size of the pad in your CryptDrive, and they will make it slower to load. Do you want to migrate these images to a better format (they will be stored separately in your drive)?"; // XXX
out.pad_base64 = "This pad contains images stored in an inefficient way. These images will significantly increase the size of the pad in your CryptDrive, and make it slower to load. You can migrate these files to a new format which will be stored separately in your CryptDrive. Do you want to migrate these images now?";
// markdown toolbar
out.mdToolbar_button = "Show or hide the Markdown toolbar";
@ -1257,14 +1268,25 @@ define(function () {
out.chrome68 = "It seems that you're using the browser Chrome or Chromium version 68. It contains a bug resulting in the page turning completely white after a few seconds or the page being unresponsive to clicks. To fix this issue, you can switch to another tab and come back, or try to scroll in the page. This bug should be fixed in the next version of your browser.";
// Manual pad storage popup
out.autostore_notstored = "This pad is not in your CryptDrive. Do you want to store it now?"; // XXX
out.autostore_settings = "You can enable automatic pad storage in your <a href=\"/settings/\">Settings</a> page!"; // XXX
out.autostore_notstored = "This pad is not in your CryptDrive. Do you want to store it now?";
out.autostore_settings = "You can enable automatic pad storage in your <a href=\"/settings/\">Settings</a> page!";
out.autostore_store = "Store";
out.autostore_hide = "Don't store";
out.autostore_error = "Unexpected error: we were unable to store this pad, please try again.";
out.autostore_saved = "The pad was successfully stored in your CryptDrive!";
out.autostore_forceSave = "Store the file in CryptDrive"; // File upload modal
out.autostore_forceSave = "Store the file in your CryptDrive"; // File upload modal
out.autostore_notAvailable = "You must store this pad in your CryptDrive before being able to use this feature."; // Properties/tags/move to trash
// Crowdfunding messages
out.crowdfunding_home1 = "CryptPad needs your help!";
out.crowdfunding_home2 = "Click to learn about our crowdfunding campaign.";
out.crowdfunding_popup_text = "<h3>We need your help!</h3>" +
"To ensure that CryptPad is actively developed, consider supporting the project via the " +
'<a href="https://opencollective.com/cryptpad">OpenCollective page</a>, where you can see our <b>Roadmap</b> and <b>Funding goals</b>.';
out.crowdfunding_popup_yes = "Go to OpenCollective";
out.crowdfunding_popup_no = "Not now";
out.crowdfunding_popup_never = "Don't ask me again";
return out;
});

@ -1,7 +1,7 @@
{
"name": "cryptpad",
"description": "realtime collaborative visual editor with zero knowlege server",
"version": "2.6.0",
"version": "2.8.0",
"license": "AGPL-3.0+",
"repository": {
"type": "git",

@ -19,17 +19,16 @@
flex-flow: column;
height: 100%;
min-height: 100%;
width: 50%;
min-width: 20%;
max-width: 80%;
resize: horizontal;
overflow: hidden;
width: 50%;
&.cp-app-code-fullpage {
max-width: 100%;
resize: none;
flex: 1;
}
}
.CodeMirror {
flex: 1;
@ -51,9 +50,13 @@
#cp-app-code-container { display: none; }
#cp-app-code-preview { border: 0; }
}
&.cp-chat-visible {
#cp-app-code-container {
width: 35%;
}
}
}
#cp-app-code-preview {
flex: 1;
padding: 5px 20px;
overflow: auto;
display: inline-block;
@ -63,6 +66,7 @@
font-family: Calibri,Ubuntu,sans-serif;
word-wrap: break-word;
position: relative;
flex: 1;
media-tag {
* {
max-width:100%;

@ -134,7 +134,7 @@ define(function() {
// spontaneously, resulting in the deletion of the entire folder's content.
// We highly recommend to keep them disabled until they are stable enough to be enabled
// by default by the CryptPad developers.
config.disableSharedFolders = true;
config.disableSharedFolders = false;
return config;
});

@ -13,6 +13,8 @@ define(function () {
storageKey: 'filesData',
tokenKey: 'loginToken',
displayPadCreationScreen: 'displayPadCreationScreen',
deprecatedKey: 'deprecated'
deprecatedKey: 'deprecated',
// Sub
plan: 'CryptPad_plan'
};
});

@ -892,7 +892,7 @@ define([
h('div.cp-corner-filler', { style: "width:60px;" }),
h('div.cp-corner-filler', { style: "width:40px;" }),
h('div.cp-corner-filler', { style: "width:20px;" }),
h('div.cp-corner-text', text),
Pages.setHTML(h('div.cp-corner-text'), text),
h('div.cp-corner-actions', actions),
Pages.setHTML(h('div.cp-corner-footer'), footer)
]);

@ -150,7 +150,8 @@ define([
}
cfg.friendComplete({
logText: Messages.contacts_added,
netfluxId: sender
netfluxId: sender,
friend: msgData
});
var msg = ["FRIEND_REQ_ACK", chan];
var msgStr = Crypto.encrypt(JSON.stringify(msg), key);
@ -163,7 +164,7 @@ define([
if (i !== -1) { pendingRequests.splice(i, 1); }
cfg.friendComplete({
logText: Messages.contacts_rejected,
netfluxId: sender
netfluxId: sender,
});
cfg.updateMetadata();
return;
@ -180,7 +181,8 @@ define([
}
cfg.friendComplete({
logText: Messages.contacts_added,
netfluxId: sender
netfluxId: sender,
friend: data
});
});
return;

@ -5,7 +5,9 @@ define([
'/common/common-util.js',
'/common/common-realtime.js',
'/common/common-constants.js',
], function (Crypto, Curve, Hash, Util, Realtime, Constants) {
'/bower_components/nthen/index.js',
], function (Crypto, Curve, Hash, Util, Realtime, Constants, nThen) {
'use strict';
var Msg = {
inputs: [],
@ -52,7 +54,7 @@ define([
var msgAlreadyKnown = function (channel, sig) {
return channel.messages.some(function (message) {
return message[0] === sig;
return message.sig === sig;
});
};
@ -65,6 +67,7 @@ define([
update: [],
friend: [],
unfriend: [],
event: []
},
range_requests: {},
};
@ -73,6 +76,12 @@ define([
messenger.handlers[type].forEach(g);
};
var emit = function (ev, data) {
eachHandler('event', function (f) {
f(ev, data);
});
};
messenger.on = function (type, f) {
var stack = messenger.handlers[type];
if (!Array.isArray(stack)) {
@ -95,20 +104,26 @@ define([
Msg.hk = network.historyKeeper;
var friends = getFriendList(proxy);
var getChannel = function (curvePublic) {
var friend = friends[curvePublic];
if (!friend) { return; }
var chanId = friend.channel;
if (!chanId) { return; }
var getChannel = function (chanId) {
return channels[chanId];
};
var initRangeRequest = function (txid, curvePublic, sig, cb) {
var getFriendFromChannel = function (id) {
var friend;
for (var k in friends) {
if (friends[k].channel === id) {
friend = friends[k];
break;
}
}
return friend;
};
var initRangeRequest = function (txid, chanId, cb) {
messenger.range_requests[txid] = {
messages: [],
cb: cb,
curvePublic: curvePublic,
sig: sig,
chanId: chanId,
};
};
@ -120,24 +135,22 @@ define([
delete messenger.range_requests[txid];
};
messenger.getMoreHistory = function (curvePublic, hash, count, cb) {
messenger.getMoreHistory = function (chanId, hash, count, cb) {
if (typeof(cb) !== 'function') { return; }
if (typeof(hash) !== 'string') {
// FIXME hash is not necessarily defined.
// What does this mean?
console.error("not sure what to do here");
return;
// Channel is empty!
return void cb(void 0, []);
}
var chan = getChannel(curvePublic);
var chan = getChannel(chanId);
if (typeof(chan) === 'undefined') {
console.error("chan is undefined. we're going to have a problem here");
return;
}
var txid = Util.uid();
initRangeRequest(txid, curvePublic, hash, cb);
initRangeRequest(txid, chanId, cb);
var msg = [ 'GET_HISTORY_RANGE', chan.id, {
from: hash,
count: count,
@ -151,38 +164,80 @@ define([
});
};
var getCurveForChannel = function (id) {
/*var getCurveForChannel = function (id) {
var channel = channels[id];
if (!channel) { return; }
return channel.curve;
};
};*/
/*messenger.getChannelHead = function (id, cb) {
var channel = getChannel(id);
if (channel.isFriendChat) {
var friend;
for (var k in friends) {
if (friends[k].channel === id) {
friend = friends[k];
break;
}
}
if (!friend) { return void cb('NO_SUCH_FRIEND'); }
cb(void 0, friend.lastKnownHash);
} else {
// TODO room
cb('NOT_IMPLEMENTED');
}
};*/
messenger.getChannelHead = function (curvePublic, cb) {
var friend = friends[curvePublic];
if (!friend) { return void cb('NO_SUCH_FRIEND'); }
cb(void 0, friend.lastKnownHash);
messenger.setChannelHead = function (id, hash, cb) {
var channel = getChannel(id);
if (channel.isFriendChat) {
var friend = getFriendFromChannel(id);
if (!friend) { return void cb('NO_SUCH_FRIEND'); }
friend.lastKnownHash = hash;
} else if (channel.isPadChat) {
// Nothing to do
} else {
// TODO room
return void cb('NOT_IMPLEMENTED');
}
cb();
};
messenger.setChannelHead = function (curvePublic, hash, cb) {
var friend = friends[curvePublic];
if (!friend) { return void cb('NO_SUCH_FRIEND'); }
friend.lastKnownHash = hash;
cb();
// Make sure the data we have about our friends are up-to-date when we see them online
var checkFriendData = function (curve, data, channel) {
if (curve === proxy.curvePublic) { return; }
var friend = getFriend(proxy, curve);
if (!friend) { return; }
var types = [];
Object.keys(data).forEach(function (k) {
if (friend[k] !== data[k]) {
types.push(k);
friend[k] = data[k];
}
});
eachHandler('update', function (f) {
f(clone(data), types, channel);
});
};
// Id message allows us to map a netfluxId with a public curve key
var onIdMessage = function (msg, sender) {
var channel;
var isId = Object.keys(channels).some(function (chanId) {
if (channels[chanId].userList.indexOf(sender) !== -1) {
channel = channels[chanId];
return true;
}
});
var channel, parsed0;
if (!isId) { return; }
try {
parsed0 = JSON.parse(msg);
channel = channels[parsed0.channel];
if (!channel) { return; }
if (channel.userList.indexOf(sender) === -1) { return; }
} catch (e) {
console.log(msg);
console.error(e);
// Not an ID message
return;
}
var decryptedMsg = channel.encryptor.decrypt(msg);
var decryptedMsg = channel.encryptor.decrypt(parsed0.msg);
if (decryptedMsg === null) {
return void console.error("Failed to decrypt message");
@ -206,20 +261,26 @@ define([
// the sender field. This is to prevent replay attacks.
if (parsed[2] !== sender || !parsed[1]) { return; }
channel.mapId[sender] = parsed[1];
checkFriendData(parsed[1].curvePublic, parsed[1], channel.id);
eachHandler('join', function (f) {
f(parsed[1], channel.id);
});
if (parsed[0] !== Types.mapId) { return; } // Don't send your key if it's already an ACK
// Answer with your own key
var rMsg = [Types.mapIdAck, proxy.curvePublic, channel.wc.myID];
var myData = createData(proxy);
delete myData.channel;
var rMsg = [Types.mapIdAck, myData, channel.wc.myID];
var rMsgStr = JSON.stringify(rMsg);
var cryptMsg = channel.encryptor.encrypt(rMsgStr);
network.sendto(sender, cryptMsg);
var data = {
channel: channel.id,
msg: cryptMsg
};
network.sendto(sender, JSON.stringify(data));
};
var orderMessages = function (curvePublic, new_messages /*, sig */) {
var channel = getChannel(curvePublic);
var orderMessages = function (channel, new_messages) {
var messages = channel.messages;
// TODO improve performance, guarantee correct ordering
@ -236,9 +297,9 @@ define([
};
var pushMsg = function (channel, cryptMsg) {
var msg = channel.encryptor.decrypt(cryptMsg);
var sig = cryptMsg.slice(0, 64);
if (msgAlreadyKnown(channel, sig)) { return; }
var msg = channel.encryptor.decrypt(cryptMsg);
var parsedMsg = JSON.parse(msg);
var curvePublic;
@ -250,43 +311,38 @@ define([
author: parsedMsg[1],
time: parsedMsg[2],
text: parsedMsg[3],
channel: channel.id,
name: parsedMsg[4] // Display name for multi-user rooms
// this makes debugging a whole lot easier
curve: getCurveForChannel(channel.id),
//curve: getCurveForChannel(channel.id),
};
channel.messages.push(res);
eachHandler('message', function (f) {
f(res);
});
if (!joining[channel.id]) {
// Channel is ready
eachHandler('message', function (f) {
f(res);
});
}
return true;
}
if (parsedMsg[0] === Types.update) {
if (parsedMsg[1] === proxy.curvePublic) { return; }
curvePublic = parsedMsg[1];
var newdata = parsedMsg[3];
var data = getFriend(proxy, parsedMsg[1]);
var types = [];
Object.keys(newdata).forEach(function (k) {
if (data[k] !== newdata[k]) {
types.push(k);
data[k] = newdata[k];
}
});
eachHandler('update', function (f) {
f(clone(newdata), curvePublic);
});
checkFriendData(parsedMsg[1], parsedMsg[3], channel.id);
return;
}
if (parsedMsg[0] === Types.unfriend) {
curvePublic = parsedMsg[1];
delete friends[curvePublic];
removeFromFriendList(parsedMsg[1], function () {
// If this a removal from our part by in another tab, do nothing.
// The channel is already closed in the proxy.on('remove') part
if (curvePublic === proxy.curvePublic) { return; }
removeFromFriendList(curvePublic, function () {
channel.wc.leave(Types.unfriend);
delete channels[channel.id];
eachHandler('unfriend', function (f) {
f(curvePublic);
f(curvePublic, false);
});
});
return;
@ -324,7 +380,7 @@ define([
});
});
eachHandler('update', function (f) {
f(myData, myData.curvePublic);
f(myData, ['displayName', 'profile', 'avatar']);
});
friends.me = myData;
}
@ -352,12 +408,26 @@ define([
return void console.error("received response to unknown request");
}
if (!req.cb) {
// This is the initial history for a pad chat
if (type === 'HISTORY_RANGE') {
if (!getChannel(req.chanId)) { return; }
if (!Array.isArray(parsed[2])) { return; }
pushMsg(getChannel(req.chanId), parsed[2][4]);
} else if (type === 'HISTORY_RANGE_END') {
if (!getChannel(req.chanId)) { return; }
getChannel(req.chanId).ready = true;
onChannelReady(req.chanId);
return;
}
return;
}
if (type === 'HISTORY_RANGE') {
req.messages.push(parsed[2]);
} else if (type === 'HISTORY_RANGE_END') {
// process all the messages (decrypt)
var curvePublic = req.curvePublic;
var channel = getChannel(curvePublic);
var channel = getChannel(req.chanId);
var decrypted = req.messages.map(function (msg) {
if (msg[2] !== 'MSG') { return; }
@ -371,6 +441,8 @@ define([
return null;
}
}).filter(function (decrypted) {
if (!decrypted.d || decrypted.d[0] !== Types.message) { return; }
if (msgAlreadyKnown(channel, decrypted.sig)) { return; }
return decrypted;
}).map(function (O) {
return {
@ -379,11 +451,12 @@ define([
author: O.d[1],
time: O.d[2],
text: O.d[3],
curve: curvePublic,
channel: req.chanId,
name: O.d[4]
};
});
orderMessages(curvePublic, decrypted, req.sig);
orderMessages(channel, decrypted);
req.cb(void 0, decrypted);
return deleteRangeRequest(txid);
} else {
@ -395,20 +468,17 @@ define([
if ((parsed.validateKey || parsed.owners) && parsed.channel) {
return;
}
// End of initial history
if (parsed.state && parsed.state === 1 && parsed.channel) {
if (channels[parsed.channel]) {
// parsed.channel is Ready
// channel[parsed.channel].ready();
channels[parsed.channel].ready = true;
onChannelReady(parsed.channel);
var updateTypes = channels[parsed.channel].updateOnReady;
if (updateTypes) {
//channels[parsed.channel].updateUI(updateTypes);
}
}
return;
}
// Initial history message
var chan = parsed[3];
if (!chan || !channels[chan]) { return; }
pushMsg(channels[chan], parsed[4]);
@ -440,7 +510,7 @@ define([
if (!data) {
// friend is not valid
console.error('friend is not valid');
return;
return void cb('INVALID_FRIEND');
}
var channel = channels[data.channel];
@ -458,12 +528,13 @@ define([
var msgStr = JSON.stringify(msg);
var cryptMsg = channel.encryptor.encrypt(msgStr);
// TODO emit remove_friend event?
try {
channel.wc.bcast(cryptMsg).then(function () {
delete friends[curvePublic];
delete channels[curvePublic];
Realtime.whenRealtimeSyncs(realtime, function () {
removeFromFriendList(curvePublic, function () {
delete channels[channel.id];
eachHandler('unfriend', function (f) {
f(curvePublic, true);
});
cb();
});
}, function (err) {
@ -476,9 +547,27 @@ define([
};
var getChannelMessagesSince = function (chan, data, keys) {
console.log('Fetching [%s] messages since [%s]', data.curvePublic, data.lastKnownHash || '');
console.log('Fetching [%s] messages since [%s]', chan.id, data.lastKnownHash || '');
if (chan.isPadChat) {
// We need to use GET_HISTORY_RANGE to make sure we won't get the full history
var txid = Util.uid();
initRangeRequest(txid, chan.id, undefined);
var msg0 = ['GET_HISTORY_RANGE', chan.id, {
//from: hash,
count: 10,
txid: txid,
}
];
network.sendto(network.historyKeeper, JSON.stringify(msg0)).then(function () {
}, function (err) {
throw new Error(err);
});
return;
}
var cfg = {
validateKey: keys.validateKey,
validateKey: keys ? keys.validateKey : undefined,
owners: [proxy.edPublic, data.edPublic],
lastKnownHash: data.lastKnownHash
};
@ -489,79 +578,88 @@ define([
});
};
var openFriendChannel = function (data, f) {
var keys = Curve.deriveKeys(data.curvePublic, proxy.curvePrivate);
var encryptor = Curve.createEncryptor(keys);
network.join(data.channel).then(function (chan) {
var channel = channels[data.channel] = {
id: data.channel,
sending: false,
friendEd: f,
keys: keys,
curve: data.curvePublic,
encryptor: encryptor,
messages: [],
wc: chan,
userList: [],
mapId: {},
send: function (payload, cb) {
if (!network.webChannels.some(function (wc) {
if (wc.id === channel.wc.id) { return true; }
})) {
return void cb('NO_SUCH_CHANNEL');
}
var openChannel = function (data) {
var keys = data.keys;
var encryptor = data.encryptor || Curve.createEncryptor(keys);
var channel = {
id: data.channel,
isFriendChat: data.isFriendChat,
isPadChat: data.isPadChat,
sending: false,
encryptor: encryptor,
messages: [],
userList: [],
mapId: {},
};
var msg = [Types.message, proxy.curvePublic, +new Date(), payload];
var msgStr = JSON.stringify(msg);
var cryptMsg = channel.encryptor.encrypt(msgStr);
var onJoining = function (peer) {
if (peer === Msg.hk) { return; }
if (channel.userList.indexOf(peer) !== -1) { return; }
channel.userList.push(peer);
channel.wc.bcast(cryptMsg).then(function () {
pushMsg(channel, cryptMsg);
cb();
}, function (err) {
cb(err);
});
}
// Join event will be sent once we are able to ID this peer
var myData = createData(proxy);
delete myData.channel;
var msg = [Types.mapId, myData, channel.wc.myID];
var msgStr = JSON.stringify(msg);
var cryptMsg = channel.encryptor.encrypt(msgStr);
var data = {
channel: channel.id,
msg: cryptMsg
};
network.sendto(peer, JSON.stringify(data));
};
var onLeaving = function (peer) {
var i = channel.userList.indexOf(peer);
while (i !== -1) {
channel.userList.splice(i, 1);
i = channel.userList.indexOf(peer);
}
// update status
var otherData = channel.mapId[peer];
if (!otherData) { return; }
// Make sure the leaving user is not connected with another netflux id
if (channel.userList.some(function (nId) {
return channel.mapId[nId]
&& channel.mapId[nId].curvePublic === otherData.curvePublic;
})) { return; }
// Send the notification
eachHandler('leave', function (f) {
f(otherData, channel.id);
});
};
var onOpen = function (chan) {
channel.wc = chan;
channels[data.channel] = channel;
chan.on('message', function (msg, sender) {
onMessage(msg, sender, chan);
});
var onJoining = function (peer) {
if (peer === Msg.hk) { return; }
if (channel.userList.indexOf(peer) !== -1) { return; }
channel.userList.push(peer);
var msg = [Types.mapId, proxy.curvePublic, chan.myID];
var msgStr = JSON.stringify(msg);
var cryptMsg = channel.encryptor.encrypt(msgStr);
network.sendto(peer, cryptMsg);
};
chan.members.forEach(function (peer) {
if (peer === Msg.hk) { return; }
if (channel.userList.indexOf(peer) !== -1) { return; }
channel.userList.push(peer);
});
chan.on('join', onJoining);
chan.on('leave', function (peer) {
var curvePublic = channel.mapId[peer];
var i = channel.userList.indexOf(peer);
while (i !== -1) {
channel.userList.splice(i, 1);
i = channel.userList.indexOf(peer);
}
// update status
if (!curvePublic) { return; }
eachHandler('leave', function (f) {
f(curvePublic, channel.id);
});
});
chan.on('leave', onLeaving);
// FIXME don't subscribe to the channel implicitly
getChannelMessagesSince(chan, data, keys);
}, function (err) {
getChannelMessagesSince(channel, data, keys);
};
network.join(data.channel).then(onOpen, function (err) {
console.error(err);
});
network.on('reconnect', function () {
if (!channels[data.channel]) { return; }
network.join(data.channel).then(onOpen, function (err) {
console.error(err);
});
});
};
messenger.getFriendList = function (cb) {
@ -573,7 +671,7 @@ define([
}));
};
messenger.openFriendChannel = function (curvePublic, cb) {
/*messenger.openFriendChannel = function (curvePublic, cb) {
if (typeof(curvePublic) !== 'string') { return void cb('INVALID_ID'); }
if (typeof(cb) !== 'function') { throw new Error('expected callback'); }
@ -585,10 +683,10 @@ define([
if (!channel) { return void cb('E_NO_CHANNEL'); }
joining[channel] = cb;
openFriendChannel(friend, curvePublic);
};
};*/
messenger.sendMessage = function (curvePublic, payload, cb) {
var channel = getChannel(curvePublic);
messenger.sendMessage = function (id, payload, cb) {
var channel = getChannel(id);
if (!channel) { return void cb('NO_CHANNEL'); }
if (!network.webChannels.some(function (wc) {
if (wc.id === channel.wc.id) { return true; }
@ -597,6 +695,9 @@ define([
}
var msg = [Types.message, proxy.curvePublic, +new Date(), payload];
if (!channel.isFriendChat) {
msg.push(proxy[Constants.displayNameKey]);
}
var msgStr = JSON.stringify(msg);
var cryptMsg = channel.encryptor.encrypt(msgStr);
@ -608,18 +709,27 @@ define([
});
};
messenger.getStatus = function (curvePublic, cb) {
var channel = getChannel(curvePublic);
messenger.getStatus = function (chanId, cb) {
// Display green status if one member is not me
var channel = getChannel(chanId);
if (!channel) { return void cb('NO_SUCH_CHANNEL'); }
var online = channel.userList.some(function (nId) {
return channel.mapId[nId] === curvePublic;
var data = channel.mapId[nId] || undefined;
if (!data) { return false; }
return data.curvePublic !== proxy.curvePublic;
});
cb(void 0, online);
};
messenger.getFriendInfo = function (curvePublic, cb) {
messenger.getFriendInfo = function (channel, cb) {
setTimeout(function () {
var friend = friends[curvePublic];
var friend;
for (var k in friends) {
if (friends[k].channel === channel) {
friend = friends[k];
break;
}
}
if (!friend) { return void cb('NO_SUCH_FRIEND'); }
// this clone will be redundant when ui uses postmessage
cb(void 0, clone(friend));
@ -633,28 +743,215 @@ define([
});
};
// TODO listen for changes to your friend list
// emit 'update' events for clients
var loadFriend = function (friend, cb) {
var channel = friend.channel;
if (getChannel(channel)) { return void cb(); }
//var update = function (curvePublic
joining[channel] = cb;
var keys = Curve.deriveKeys(friend.curvePublic, proxy.curvePrivate);
var data = {
keys: keys,
channel: friend.channel,
lastKnownHash: friend.lastKnownHash,
owners: [proxy.edPublic, friend.edPublic],
isFriendChat: true
};
openChannel(data);
};
// Detect friends changes made in another worker
proxy.on('change', ['friends'], function (o, n, p) {
var curvePublic;
if (o === undefined) {
// new friend added
curvePublic = p.slice(-1)[0];
eachHandler('friend', function (f) {
f(curvePublic, clone(n));
// Load channel
var friend = friends[curvePublic];
if (typeof(friend) !== 'object') { return; }
var channel = friend.channel;
if (!channel) { return; }
loadFriend(friend, function () {
eachHandler('friend', function (f) {
f(curvePublic);
});
});
return;
}
console.error(o, n, p);
if (typeof(n) === 'undefined') {
// Handled by .on('remove')
return;
}
}).on('remove', ['friends'], function (o, p) {
var curvePublic = p[1];
if (!curvePublic) { return; }
if (p[2] !== 'channel') { return; }
var channel = channels[o];
channel.wc.leave(Types.unfriend);
delete channels[channel.id];
eachHandler('unfriend', function (f) {
f(p[1]); // TODO
f(curvePublic, true);
});
});
// Friend added in our contacts in the current worker
messenger.onFriendAdded = function (friendData) {
var friend = friends[friendData.curvePublic];
if (typeof(friend) !== 'object') { return; }
var channel = friend.channel;
if (!channel) { return; }
loadFriend(friend, function () {
eachHandler('friend', function (f) {
f(friend.curvePublic);
});
});
};
var ready = false;
var initialized = false;
var init = function () {
if (initialized) { return; }
initialized = true;
var friends = getFriendList(proxy);
nThen(function (waitFor) {
Object.keys(friends).forEach(function (key) {
if (key === 'me') { return; }
var friend = clone(friends[key]);
if (typeof(friend) !== 'object') { return; }
var channel = friend.channel;
if (!channel) { return; }
loadFriend(friend, waitFor());
});
// TODO load rooms
}).nThen(function () {
ready = true;
emit('READY');
});
};
//init();
var getRooms = function (data, cb) {
if (data && data.curvePublic) {
var curvePublic = data.curvePublic;
// We need to get data about a new friend's room
var friend = getFriend(proxy, curvePublic);
if (!friend) { return void cb({error: 'NO_SUCH_FRIEND'}); }
var channel = getChannel(friend.channel);
if (!channel) { return void cb({error: 'NO_SUCH_CHANNEL'}); }
return void cb([{
id: channel.id,
isFriendChat: true,
name: friend.displayName,
lastKnownHash: friend.lastKnownHash,
curvePublic: friend.curvePublic,
messages: channel.messages
}]);
}
if (data && data.padChat) {
var pCChannel = getChannel(data.padChat);
if (!pCChannel) { return void cb({error: 'NO_SUCH_CHANNEL'}); }
return void cb([{
id: pCChannel.id,
isPadChat: true,
messages: pCChannel.messages
}]);
}
var rooms = Object.keys(channels).map(function (id) {
var r = getChannel(id);
var name, lastKnownHash, curvePublic;
if (r.isFriendChat) {
var friend = getFriendFromChannel(id);
if (!friend) { return null; }
name = friend.displayName;
lastKnownHash = friend.lastKnownHash;
curvePublic = friend.curvePublic;
} else if (r.isPadChat) {
return;
} else {
// TODO room get metadata (name) && lastKnownHash
}
return {
id: r.id,
isFriendChat: r.isFriendChat,
name: name,
lastKnownHash: lastKnownHash,
curvePublic: curvePublic,
messages: r.messages
};
}).filter(function (x) { return x; });
cb(rooms);
};
var getUserList = function (data, cb) {
var room = getChannel(data.id);
if (!room) { return void cb({error: 'NO_SUCH_CHANNEL'}); }
if (room.isFriendChat) {
var friend = getFriendFromChannel(data.id);
if (!friend) { return void cb({error: 'NO_SUCH_FRIEND'}); }
cb([friend]);
} else {
// TODO room userlist in rooms...
// (this is the static userlist, not the netflux one)
cb([]);
}
};
var openPadChat = function (data, cb) {
var channel = data.channel;
if (getChannel(channel)) {
emit('PADCHAT_READY', channel);
return void cb();
}
var keys = data.secret && data.secret.keys;
var cryptKey = keys.viewKeyStr ? Crypto.b64AddSlashes(keys.viewKeyStr) : data.secret.key;
var encryptor = Crypto.createEncryptor(cryptKey);
var chanData = {
encryptor: encryptor,
channel: data.channel,
isPadChat: true,
//lastKnownHash: friend.lastKnownHash,
//owners: [proxy.edPublic, friend.edPublic],
//isFriendChat: true
};
openChannel(chanData);
joining[channel] = function () {
emit('PADCHAT_READY', channel);
};
cb();
};
network.on('disconnect', function () {
emit('DISCONNECT');
});
network.on('reconnect', function () {
emit('RECONNECT');
});
messenger.execCommand = function (obj, cb) {
var cmd = obj.cmd;
var data = obj.data;
if (cmd === 'INIT_FRIENDS') {
init();
return void cb();
}
if (cmd === 'IS_READY') {
return void cb(ready);
}
if (cmd === 'GET_ROOMS') {
return void getRooms(data, cb);
}
if (cmd === 'GET_USERLIST') {
return void getUserList(data, cb);
}
if (cmd === 'OPEN_PAD_CHAT') {
return void openPadChat(data, cb);
}
};
Object.freeze(messenger);
return messenger;

@ -12,10 +12,11 @@ define([
'/common/clipboard.js',
'/customize/messages.js',
'/customize/application_config.js',
'/customize/pages.js',
'/bower_components/nthen/index.js',
'css!/customize/fonts/cptools/style.css'
], function ($, Config, Util, Hash, Language, UI, Constants, Feedback, h, MediaTag, Clipboard,
Messages, AppConfig, NThen) {
Messages, AppConfig, Pages, NThen) {
var UIElements = {};
// Configure MediaTags to use our local viewer
@ -630,23 +631,25 @@ define([
if (!data.FM) { return; }
var $input = $('<input>', {
'type': 'file',
'style': 'display: none;'
'style': 'display: none;',
'multiple': 'multiple'
}).on('change', function (e) {
var file = e.target.files[0];
var ev = {
target: data.target
};
if (data.filter && !data.filter(file)) {
return;
}
if (data.transformer) {
data.transformer(file, function (newFile) {
data.FM.handleFile(newFile, ev);
if (callback) { callback(); }
});
return;
}
data.FM.handleFile(file, ev);
var files = Util.slice(e.target.files);
files.forEach(function (file) {
var ev = {
target: data.target
};
if (data.filter && !data.filter(file)) {
return;
}
if (data.transformer) {
data.transformer(file, function (newFile) {
data.FM.handleFile(newFile, ev);
});
return;
}
data.FM.handleFile(file, ev);
});
if (callback) { callback(); }
});
if (data.accept) { $input.attr('accept', data.accept); }
@ -1792,13 +1795,16 @@ define([
var $container = $('<div>');
var i = 0;
AppConfig.availablePadTypes.forEach(function (p) {
var types = AppConfig.availablePadTypes.filter(function (p) {
if (p === 'drive') { return; }
if (p === 'contacts') { return; }
if (p === 'todo') { return; }
if (p === 'file') { return; }
if (!common.isLoggedIn() && AppConfig.registeredOnlyTypes &&
AppConfig.registeredOnlyTypes.indexOf(p) !== -1) { return; }
return true;
});
types.forEach(function (p) {
var $element = $('<li>', {
'class': 'cp-icons-element',
'id': 'cp-newpad-icons-'+ (i++)
@ -1822,7 +1828,7 @@ define([
var selected = -1;
var next = function () {
selected = ++selected % 5;
selected = ++selected % types.length;
$container.find('.cp-icons-element-selected').removeClass('cp-icons-element-selected');
$container.find('#cp-newpad-icons-'+selected).addClass('cp-icons-element-selected');
};
@ -2338,10 +2344,57 @@ define([
$(password).find('.cp-password-input').focus();
};
var crowdfundingState = false;
UIElements.displayCrowdfunding = function (common) {
if (crowdfundingState) { return; }
if (AppConfig.disableCrowdfundingMessages) { return; }
var priv = common.getMetadataMgr().getPrivateData();
if (priv.plan) { return; }
crowdfundingState = true;
setTimeout(function () {
common.getAttribute(['general', 'crowdfunding'], function (err, val) {
if (err || val === false) { return; }
// Display the popup
var text = Messages.crowdfunding_popup_text;
var yes = h('button.cp-corner-primary', Messages.crowdfunding_popup_yes);
var no = h('button.cp-corner-primary', Messages.crowdfunding_popup_no);
var never = h('button.cp-corner-cancel', Messages.crowdfunding_popup_never);
var actions = h('div', [yes, no, never]);
var modal = UI.cornerPopup(text, actions, null, {big: true});
$(yes).click(function () {
modal.delete();
common.openURL('https://opencollective.com/cryptpad/contribute');
Feedback.send('CROWDFUNDING_YES');
});
$(modal.popup).find('a').click(function (e) {
e.stopPropagation();
e.preventDefault();
modal.delete();
common.openURL('https://opencollective.com/cryptpad/');
Feedback.send('CROWDFUNDING_LINK');
});
$(no).click(function () {
modal.delete();
Feedback.send('CROWDFUNDING_NO');
});
$(never).click(function () {
modal.delete();
common.setAttribute(['general', 'crowdfunding'], false);
Feedback.send('CROWDFUNDING_NEVER');
});
});
}, 5000);
};
var storePopupState = false;
UIElements.displayStorePadPopup = function (common, data) {
if (storePopupState) { return; }
storePopupState = true;
if (data && data.stored) { return; } // We won't display the popup for dropped files
var text = Messages.autostore_notstored;
var footer = Messages.autostore_settings;
@ -2359,15 +2412,20 @@ define([
});
$(hide).click(function () {
UIElements.displayCrowdfunding(common);
modal.delete();
});
$(store).click(function () {
modal.delete();
common.getSframeChannel().query("Q_AUTOSTORE_STORE", null, function (err, obj) {
if (err || (obj && obj.error)) {
console.error(err || obj.error);
var error = err || (obj && obj.error);
if (error) {
if (error === 'E_OVER_LIMIT') {
return void UI.warn(Messages.pinLimitReached);
}
return void UI.warn(Messages.autostore_error);
}
modal.delete();
UIElements.displayCrowdfunding(common);
UI.log(Messages.autostore_saved);
});
});

@ -345,6 +345,9 @@ define([
};
common.getPadAttribute = function (attr, cb, href) {
href = Hash.getRelativeHref(href || window.location.href);
if (!href) {
return void cb('E404');
}
postMessage("GET_PAD_ATTRIBUTE", {
href: href,
attr: attr,
@ -622,6 +625,12 @@ define([
messenger.setChannelHead = function (data, cb) {
postMessage("CONTACTS_SET_CHANNEL_HEAD", data, cb);
};
messenger.execCommand = function (data, cb) {
postMessage("CHAT_COMMAND", data, cb);
};
messenger.onEvent = Util.mkEvent();
messenger.onMessageEvent = Util.mkEvent();
messenger.onJoinEvent = Util.mkEvent();
messenger.onLeaveEvent = Util.mkEvent();
@ -1059,6 +1068,8 @@ define([
CONTACTS_UPDATE: common.messenger.onUpdateEvent.fire,
CONTACTS_FRIEND: common.messenger.onFriendEvent.fire,
CONTACTS_UNFRIEND: common.messenger.onUnfriendEvent.fire,
// Chat
CHAT_EVENT: common.messenger.onEvent.fire,
// Pad
PAD_READY: common.padRpc.onReadyEvent.fire,
PAD_MESSAGE: common.padRpc.onMessageEvent.fire,
@ -1425,7 +1436,7 @@ define([
postMessage("INIT_RPC", null, waitFor(function (obj) {
console.log('RPC handshake complete');
if (obj.error) { return; }
localStorage.plan = obj.plan;
localStorage[Constants.plan] = obj.plan;
}));
} else if (PINNING_ENABLED) {
console.log('not logged in. pads will not be pinned');

@ -4,16 +4,32 @@ define([
'/common/common-hash.js',
'/common/common-util.js',
'/common/media-tag.js',
'/common/highlight/highlight.pack.js',
'/bower_components/diff-dom/diffDOM.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
],function ($, Marked, Hash, Util, MediaTag) {
'css!/common/highlight/styles/github.css'
],function ($, Marked, Hash, Util, MediaTag, Highlight) {
var DiffMd = {};
var DiffDOM = window.diffDOM;
var renderer = new Marked.Renderer();
var highlighter = function () {
return function(code, lang) {
if (lang) {
try {
return Highlight.highlight(lang, code).value;
} catch (e) {
return code;
}
}
return code;
};
};
Marked.setOptions({
renderer: renderer
renderer: renderer,
highlight: highlighter(),
});
DiffMd.render = function (md) {
@ -25,9 +41,11 @@ define([
// Tasks list
var checkedTaskItemPtn = /^\s*(<p>)?\[[xX]\](<\/p>)?\s*/;
var uncheckedTaskItemPtn = /^\s*(<p>)?\[ ?\](<\/p>)?\s*/;
var bogusCheckPtn = /<input( checked=""){0,1} disabled="" type="checkbox">/;
renderer.listitem = function (text) {
var isCheckedTaskItem = checkedTaskItemPtn.test(text);
var isUncheckedTaskItem = uncheckedTaskItemPtn.test(text);
var hasBogusInput = bogusCheckPtn.test(text);
if (isCheckedTaskItem) {
text = text.replace(checkedTaskItemPtn,
'<i class="fa fa-check-square" aria-hidden="true"></i>&nbsp;') + '\n';
@ -36,6 +54,15 @@ define([
text = text.replace(uncheckedTaskItemPtn,
'<i class="fa fa-square-o" aria-hidden="true"></i>&nbsp;') + '\n';
}
if (!isCheckedTaskItem && !isUncheckedTaskItem && hasBogusInput) {
if (/checked/.test(text)) {
text = text.replace(bogusCheckPtn,
'<i class="fa fa-check-square" aria-hidden="true"></i>&nbsp;') + '\n';
} else if (/disabled/.test(text)) {
text = text.replace(bogusCheckPtn,
'<i class="fa fa-square-o" aria-hidden="true"></i>&nbsp;') + '\n';
}
}
var cls = (isCheckedTaskItem || isUncheckedTaskItem) ? ' class="todo-list-item"' : '';
return '<li'+ cls + '>' + text + '</li>\n';
};

File diff suppressed because one or more lines are too long

@ -0,0 +1,99 @@
/*
Original highlight.js style (c) Ivan Sagalaev <maniac@softwaremaniacs.org>
*/
.hljs {
display: block;
overflow-x: auto;
padding: 0.5em;
background: #F0F0F0;
}
/* Base color: saturation 0; */
.hljs,
.hljs-subst {
color: #444;
}
.hljs-comment {
color: #888888;
}
.hljs-keyword,
.hljs-attribute,
.hljs-selector-tag,
.hljs-meta-keyword,
.hljs-doctag,
.hljs-name {
font-weight: bold;
}
/* User color: hue: 0 */
.hljs-type,
.hljs-string,
.hljs-number,
.hljs-selector-id,
.hljs-selector-class,
.hljs-quote,
.hljs-template-tag,
.hljs-deletion {
color: #880000;
}
.hljs-title,
.hljs-section {
color: #880000;
font-weight: bold;
}
.hljs-regexp,
.hljs-symbol,
.hljs-variable,
.hljs-template-variable,
.hljs-link,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #BC6060;
}
/* Language color: hue: 90; */
.hljs-literal {
color: #78A960;
}
.hljs-built_in,
.hljs-bullet,
.hljs-code,
.hljs-addition {
color: #397300;
}
/* Meta color: hue: 200 */
.hljs-meta {
color: #1f7199;
}
.hljs-meta-string {
color: #4d99bf;
}
/* Misc effects */
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}

@ -0,0 +1,99 @@
/*
github.com style (c) Vasily Polovnyov <vast@whiteants.net>
*/
.hljs {
display: block;
overflow-x: auto;
padding: 0.5em;
color: #333;
background: #f8f8f8;
}
.hljs-comment,
.hljs-quote {
color: #998;
font-style: italic;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-subst {
color: #333;
font-weight: bold;
}
.hljs-number,
.hljs-literal,
.hljs-variable,
.hljs-template-variable,
.hljs-tag .hljs-attr {
color: #008080;
}
.hljs-string,
.hljs-doctag {
color: #d14;
}
.hljs-title,
.hljs-section,
.hljs-selector-id {
color: #900;
font-weight: bold;
}
.hljs-subst {
font-weight: normal;
}
.hljs-type,
.hljs-class .hljs-title {
color: #458;
font-weight: bold;
}
.hljs-tag,
.hljs-name,
.hljs-attribute {
color: #000080;
font-weight: normal;
}
.hljs-regexp,
.hljs-link {
color: #009926;
}
.hljs-symbol,
.hljs-bullet {
color: #990073;
}
.hljs-built_in,
.hljs-builtin-name {
color: #0086b3;
}
.hljs-meta {
color: #999;
font-weight: bold;
}
.hljs-deletion {
background: #fdd;
}
.hljs-addition {
background: #dfd;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}

@ -59,6 +59,9 @@ define([
obj[key] = data.value;
}
broadcast([clientId], "UPDATE_METADATA");
if (Array.isArray(path) && path[0] === 'profile' && store.messenger) {
store.messenger.updateMyData();
}
onSync(cb);
};
@ -462,7 +465,7 @@ define([
if (data.password) { pad.password = data.password; }
if (data.channel) { pad.channel = data.channel; }
store.manager.addPad(data.path, pad, function (e) {
if (e) { return void cb({error: "Error while adding the pad:"+ e}); }
if (e) { return void cb({error: e}); }
sendDriveEvent('DRIVE_CHANGE', {
path: ['drive', UserObject.FILES_DATA]
}, clientId);
@ -597,6 +600,7 @@ define([
Store.setDisplayName = function (clientId, value, cb) {
store.proxy[Constants.displayNameKey] = value;
broadcast([clientId], "UPDATE_METADATA");
if (store.messenger) { store.messenger.updateMyData(); }
onSync(cb);
};
@ -796,12 +800,20 @@ define([
password: data.password,
path: data.path
}, cb);
// Let inner know that dropped files shouldn't trigger the popup
postMessage(clientId, "AUTOSTORE_DISPLAY_POPUP", {
stored: true
});
return;
}
} else {
sendDriveEvent('DRIVE_CHANGE', {
path: ['drive', UserObject.FILES_DATA]
}, clientId);
// Let inner know that dropped files shouldn't trigger the popup
postMessage(clientId, "AUTOSTORE_DISPLAY_POPUP", {
stored: true
});
}
onSync(cb);
};
@ -851,6 +863,9 @@ define([
},
pinPads: function (data, cb) { Store.pinPads(null, data, cb); },
friendComplete: function (data) {
if (data.friend && store.messenger && store.messenger.onFriendAdded) {
store.messenger.onFriendAdded(data.friend);
}
postMessage(clientId, "EV_FRIEND_COMPLETE", data);
},
friendRequest: function (data, cb) {
@ -884,6 +899,7 @@ define([
Store.messenger = {
getFriendList: function (clientId, data, cb) {
if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); }
store.messenger.getFriendList(function (e, keys) {
cb({
error: e,
@ -892,6 +908,7 @@ define([
});
},
getMyInfo: function (clientId, data, cb) {
if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); }
store.messenger.getMyInfo(function (e, info) {
cb({
error: e,
@ -900,6 +917,7 @@ define([
});
},
getFriendInfo: function (clientId, data, cb) {
if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); }
store.messenger.getFriendInfo(data, function (e, info) {
cb({
error: e,
@ -908,6 +926,7 @@ define([
});
},
removeFriend: function (clientId, data, cb) {
if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); }
store.messenger.removeFriend(data, function (e, info) {
cb({
error: e,
@ -916,11 +935,13 @@ define([
});
},
openFriendChannel: function (clientId, data, cb) {
if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); }
store.messenger.openFriendChannel(data, function (e) {
cb({ error: e, });
});
},
getFriendStatus: function (clientId, data, cb) {
if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); }
store.messenger.getStatus(data, function (e, online) {
cb({
error: e,
@ -929,6 +950,7 @@ define([
});
},
getMoreHistory: function (clientId, data, cb) {
if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); }
store.messenger.getMoreHistory(data.curvePublic, data.sig, data.count, function (e, history) {
cb({
error: e,
@ -937,6 +959,7 @@ define([
});
},
sendMessage: function (clientId, data, cb) {
if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); }
store.messenger.sendMessage(data.curvePublic, data.content, function (e) {
cb({
error: e,
@ -944,11 +967,17 @@ define([
});
},
setChannelHead: function (clientId, data, cb) {
if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); }
store.messenger.setChannelHead(data.curvePublic, data.sig, function (e) {
cb({
error: e
});
});
},
execCommand: function (clientId, data, cb) {
if (!store.messenger) { return void cb({error: 'Messenger is disabled'}); }
store.messenger.execCommand(data, cb);
}
};
@ -1309,7 +1338,6 @@ define([
}
};
var messengerEventInit = false;
var sendMessengerEvent = function (q, data) {
messengerEventClients.forEach(function (cId) {
postMessage(cId, q, data);
@ -1319,41 +1347,49 @@ define([
if (messengerEventClients.indexOf(clientId) === -1) {
messengerEventClients.push(clientId);
}
if (!messengerEventInit) {
var messenger = store.messenger = Messenger.messenger(store);
messenger.on('message', function (message) {
sendMessengerEvent('CONTACTS_MESSAGE', message);
};
var loadMessenger = function () {
if (AppConfig.availablePadTypes.indexOf('contacts') === -1) { return; }
var messenger = store.messenger = Messenger.messenger(store);
messenger.on('message', function (message) {
sendMessengerEvent('CONTACTS_MESSAGE', message);
});
messenger.on('join', function (curvePublic, channel) {
sendMessengerEvent('CONTACTS_JOIN', {
curvePublic: curvePublic,
channel: channel,
});
messenger.on('join', function (curvePublic, channel) {
sendMessengerEvent('CONTACTS_JOIN', {
curvePublic: curvePublic,
channel: channel,
});
});
messenger.on('leave', function (curvePublic, channel) {
sendMessengerEvent('CONTACTS_LEAVE', {
curvePublic: curvePublic,
channel: channel,
});
messenger.on('leave', function (curvePublic, channel) {
sendMessengerEvent('CONTACTS_LEAVE', {
curvePublic: curvePublic,
channel: channel,
});
});
messenger.on('update', function (info, types, channel) {
sendMessengerEvent('CONTACTS_UPDATE', {
types: types,
info: info,
channel: channel
});
messenger.on('update', function (info, curvePublic) {
sendMessengerEvent('CONTACTS_UPDATE', {
curvePublic: curvePublic,
info: info,
});
});
messenger.on('friend', function (curvePublic) {
sendMessengerEvent('CONTACTS_FRIEND', {
curvePublic: curvePublic,
});
messenger.on('friend', function (curvePublic) {
sendMessengerEvent('CONTACTS_FRIEND', {
curvePublic: curvePublic,
});
});
messenger.on('unfriend', function (curvePublic, removedByMe) {
sendMessengerEvent('CONTACTS_UNFRIEND', {
curvePublic: curvePublic,
removedByMe: removedByMe
});
messenger.on('unfriend', function (curvePublic) {
sendMessengerEvent('CONTACTS_UNFRIEND', {
curvePublic: curvePublic,
});
});
messenger.on('event', function (ev, data) {
sendMessengerEvent('CHAT_EVENT', {
ev: ev,
data: data
});
messengerEventInit = true;
}
});
};
@ -1442,6 +1478,7 @@ define([
});
userObject.fixFiles();
loadSharedFolders(waitFor);
loadMessenger();
}).nThen(function () {
var requestLogin = function () {
broadcast([], "REQUEST_LOGIN");

@ -69,6 +69,8 @@ define([
CONTACTS_GET_MORE_HISTORY: Store.messenger.getMoreHistory,
CONTACTS_SEND_MESSAGE: Store.messenger.sendMessage,
CONTACTS_SET_CHANNEL_HEAD: Store.messenger.setChannelHead,
// Chat
CHAT_COMMAND: Store.messenger.execCommand,
// Pad
SEND_PAD_MSG: Store.sendPadMsg,
JOIN_PAD: Store.joinPad,

@ -322,6 +322,8 @@ define([
if (!readOnly) { onLocal(); }
evOnReady.fire(newPad);
common.openPadChat(onLocal);
UI.removeLoadingScreen(emitResize);
var privateDat = cpNfInner.metadataMgr.getPrivateData();
@ -559,6 +561,7 @@ define([
}, onLocal);
var configTb = {
displayed: [
'chat',
'userlist',
'title',
'useradmin',

@ -36,7 +36,8 @@ define([
window.addEventListener('message', onMsg);
}).nThen(function (/*waitFor*/) {
SFCommonO.start({
useCreationScreen: true
useCreationScreen: true,
messaging: true
});
});
});

@ -262,6 +262,7 @@ define([
donateURL: Cryptpad.donateURL,
upgradeURL: Cryptpad.upgradeURL
},
plan: localStorage[Utils.Constants.plan],
isNewFile: isNewFile,
isDeleted: isNewFile && window.location.hash.length > 0,
forceCreationScreen: forceCreationScreen,
@ -370,7 +371,7 @@ define([
forceSave: true
};
Cryptpad.setPadTitle(data, function (err) {
cb(err);
cb({error: err});
});
});
sframeChan.on('Q_IS_PAD_STORED', function (data, cb) {
@ -776,6 +777,22 @@ define([
Cryptpad.messenger.setChannelHead(opt, cb);
});
sframeChan.on('Q_CHAT_OPENPADCHAT', function (data, cb) {
Cryptpad.messenger.execCommand({
cmd: 'OPEN_PAD_CHAT',
data: {
channel: data,
secret: secret
}
}, cb);
});
sframeChan.on('Q_CHAT_COMMAND', function (data, cb) {
Cryptpad.messenger.execCommand(data, cb);
});
Cryptpad.messenger.onEvent.reg(function (data) {
sframeChan.event('EV_CHAT_EVENT', data);
});
Cryptpad.messenger.onMessageEvent.reg(function (data) {
sframeChan.event('EV_CONTACTS_MESSAGE', data);
});

@ -161,6 +161,24 @@ define([
});
};
// Chat
var padChatChannel;
funcs.getPadChat = function () {
return padChatChannel;
};
funcs.openPadChat = function (saveChanges) {
var md = JSON.parse(JSON.stringify(ctx.metadataMgr.getMetadata()));
var channel = md.chat || Hash.createChannelId();
if (!md.chat) {
md.chat = channel;
ctx.metadataMgr.updateMetadata(md);
setTimeout(saveChanges);
}
padChatChannel = channel;
ctx.sframeChan.query('Q_CHAT_OPENPADCHAT', channel, function (err, obj) {
if (err || (obj && obj.error)) { console.error(err || (obj && obj.error)); }
});
};
// CodeMirror
funcs.initCodeMirrorApp = callWithCommon(CodeMirror.create);
@ -517,6 +535,11 @@ define([
UI.alert(Messages.chrome68);
});
funcs.isPadStored(function (err, val) {
if (err || !val) { return; }
UIElements.displayCrowdfunding(funcs);
});
ctx.sframeChan.ready();
cb(funcs);
});

@ -35,7 +35,7 @@ define([], function () {
});
sFrameChan.on('EV_CONTACTS_UPDATE', function (data) {
_handlers.update.forEach(function (f) {
f(data.info, data.curvePublic);
f(data.info, data.types, data.channel);
});
});
sFrameChan.on('EV_CONTACTS_FRIEND', function (data) {
@ -45,7 +45,7 @@ define([], function () {
});
sFrameChan.on('EV_CONTACTS_UNFRIEND', function (data) {
_handlers.unfriend.forEach(function (f) {
f(data.curvePublic);
f(data.curvePublic, data.removedByMe);
});
});

@ -171,6 +171,11 @@ define({
'Q_CONTACTS_SET_CHANNEL_HEAD': true,
'Q_CONTACTS_CLEAR_OWNED_CHANNEL': true,
// Chat
'EV_CHAT_EVENT': true,
'Q_CHAT_COMMAND': true,
'Q_CHAT_OPENPADCHAT': true,
// Put one or more entries to the localStore which will go in localStorage.
'EV_LOCALSTORE_PUT': true,
// Put one entry in the parent sessionStorage
@ -218,6 +223,8 @@ define({
// Refresh the drive when the drive has changed ('change' or 'remove' events)
'EV_DRIVE_CHANGE': true,
'EV_DRIVE_REMOVE': true,
// Set shared folder hash in the address bar
'EV_DRIVE_SET_HASH': true,
// Remove an owned pad from the server
'Q_REMOVE_OWNED_CHANNEL': true,

@ -6,8 +6,11 @@ define([
'/common/common-interface.js',
'/common/common-hash.js',
'/common/common-feedback.js',
'/common/sframe-messenger-inner.js',
'/contacts/messenger-ui.js',
'/customize/messages.js',
], function ($, Config, ApiConfig, UIElements, UI, Hash, Feedback, Messages) {
], function ($, Config, ApiConfig, UIElements, UI, Hash, Feedback,
Messenger, MessengerUI, Messages) {
var Common;
var Bar = {
@ -231,16 +234,15 @@ define([
var name = data.name || Messages.anonymous;
var $span = $('<span>', {'class': 'cp-avatar'});
var $rightCol = $('<span>', {'class': 'cp-toolbar-userlist-rightcol'});
var $nameSpan = $('<span>', {'class': 'cp-toolbar-userlist-name'}).text(name).appendTo($rightCol);
var $nameSpan = $('<span>', {'class': 'cp-toolbar-userlist-name'}).appendTo($rightCol);
var $nameValue = $('<span>', {
'class': 'cp-toolbar-userlist-name-value'
}).text(name).appendTo($nameSpan);
var isMe = data.uid === user.uid;
if (isMe && !priv.readOnly) {
$nameSpan.html('');
var $nameValue = $('<span>', {
'class': 'cp-toolbar-userlist-name-value'
}).text(name).appendTo($nameSpan);
if (!Config.disableProfile) {
var $button = $('<button>', {
'class': 'fa fa-pencil cp-toolbar-userlist-name-edit',
'class': 'fa fa-pencil cp-toolbar-userlist-button',
title: Messages.user_rename
}).appendTo($nameSpan);
$button.hover(function (e) { e.preventDefault(); e.stopPropagation(); });
@ -296,16 +298,24 @@ define([
$('<span>', {'class': 'cp-toolbar-userlist-friend'}).text(Messages.userlist_pending)
.appendTo($rightCol);
} else {
$('<span>', {
'class': 'fa fa-user-plus cp-toolbar-userlist-friend',
$('<button>', {
'class': 'fa fa-user-plus cp-toolbar-userlist-button',
'title': Messages._getKey('userlist_addAsFriendTitle', [
name
])
}).appendTo($rightCol).click(function (e) {
}).appendTo($nameSpan).click(function (e) {
e.stopPropagation();
Common.sendFriendRequest(data.netfluxId);
});
}
} else if (Common.isLoggedIn() && data.curvePublic && friends[data.curvePublic]) {
$('<button>', {
'class': 'fa fa-comments-o cp-toolbar-userlist-button',
'title': Messages.userlist_chat
}).appendTo($nameSpan).click(function (e) {
e.stopPropagation();
Common.openURL('/contacts/');
});
}
if (data.profile) {
$span.addClass('cp-userlist-clickable');
@ -410,6 +420,77 @@ define([
return $container;
};
var initChat = function (toolbar) {
var $container = $('<div>', {
id: 'cp-app-contacts-container',
'class': 'cp-app-contacts-inapp'
}).prependTo(toolbar.chatContent);
var sframeChan = Common.getSframeChannel();
var messenger = Messenger.create(sframeChan);
MessengerUI.create(messenger, $container, Common, toolbar);
};
var createChat = function (toolbar, config) {
if (!config.metadataMgr) {
throw new Error("You must provide a `metadataMgr` to display the chat");
}
if (Config.availablePadTypes.indexOf('contacts') === -1) { return; }
var $content = $('<div>', {'class': 'cp-toolbar-chat-drawer'});
$content.on('drop dragover', function (e) {
e.preventDefault();
e.stopPropagation();
});
var $closeIcon = $('<span>', {"class": "fa fa-window-close cp-toolbar-chat-drawer-close"}).appendTo($content);
//$('<h2>').text(Messages.users).appendTo($content);
//$('<p>', {'class': USERLIST_CLS}).appendTo($content);
toolbar.chatContent = $content;
var $container = $('<span>', {id: 'cp-toolbar-chat-drawer-open', title: Messages.chatButton});
var $button = $('<button>', {'class': 'fa fa-comments'}).appendTo($container);
$('<span>',{'class': 'cp-dropdown-button-title'}).appendTo($button);
toolbar.$leftside.prepend($container);
if (config.$contentContainer) {
config.$contentContainer.prepend($content);
}
var hide = function () {
$content.hide();
$button.removeClass('cp-toolbar-button-active');
config.$contentContainer.removeClass('cp-chat-visible');
};
var show = function () {
if (Bar.isEmbed) { $content.hide(); return; }
$content.show();
$button.addClass('cp-toolbar-button-active');
config.$contentContainer.addClass('cp-chat-visible');
$button.removeClass('cp-toolbar-notification');
};
$closeIcon.click(function () {
Common.setAttribute(['toolbar', 'chat-drawer'], false);
hide();
});
$button.click(function () {
var visible = $content.is(':visible');
if (visible) { hide(); }
else { show(); }
visible = !visible;
Common.setAttribute(['toolbar', 'chat-drawer'], visible);
});
show();
Common.getAttribute(['toolbar', 'chat-drawer'], function (err, val) {
if (val === false || ($(window).height() < 800 && $(window).width() < 800)) {
return void hide();
}
show();
});
initChat(toolbar, config);
return $container;
};
var createShare = function (toolbar, config) {
if (!config.metadataMgr) {
throw new Error("You must provide a `metadataMgr` to display the userlist");
@ -997,6 +1078,7 @@ define([
// Create the subelements
var tb = {};
tb['userlist'] = createUserList;
tb['chat'] = createChat;
tb['share'] = createShare;
tb['fileshare'] = createFileShare;
tb['title'] = createTitle;

@ -1,5 +1,5 @@
@import (reference) '../../customize/src/less2/include/avatar.less';
@import (reference) '../../customize/src/less2/include/framework.less';
@import (reference) '../../customize/src/less2/include/messenger.less';
// body
&.cp-app-contacts {
@ -9,238 +9,14 @@
@color: @colortheme_friends-color
);
@keyframes example {
0% {
background: rgba(0,0,0,0.1);
}
50% {
background: rgba(0,0,0,0.3);
}
100% {
background: rgba(0,0,0,0.1);
}
}
display: flex;
flex-flow: column;
background-color: red !important;
@button-border: 2px;
@bg-color: @colortheme_friends-bg;
@color: @colortheme_friends-color;
#cp-app-contacts-container {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
min-height: 0;
&.ready {
background-size: cover;
background-position: center;
}
}
#cp-toolbar {
display: flex; // We need this to remove a 3px border at the bottom of the toolbar
}
#cp-app-contacts-friendlist {
width: 350px;
height: 100%;
background-color: lighten(@bg-color, 10%);
overflow-y: auto;
.cp-app-contacts-friend {
background: rgba(0,0,0,0.1);
padding: 5px;
margin: 10px;
cursor: pointer;
position: relative;
.cp-app-contacts-right-col {
margin-left: 5px;
display: flex;
flex-flow: column;
}
&:hover {
background-color: rgba(0,0,0,0.3);
}
&.cp-app-contacts-notify {
animation: example 2s ease-in-out infinite;
}
}
.cp-app-contacts-remove {
cursor: pointer;
width: 20px;
&:hover {
color: darken(@color, 20%);
}
}
}
#cp-app-contacts-friendlist .cp-app-contacts-friend, #cp-app-contacts-messaging .cp-avatar {
.avatar_main(30px);
&.cp-avatar {
display: flex;
}
cursor: pointer;
color: @color;
media-tag {
img {
color: #000;
}
}
media-tag, .cp-avatar-default {
margin-right: 5px;
}
.cp-app-contacts-status {
width: 5px;
display: inline-block;
position: absolute;
right: 0;
top: 0;
bottom: 0;
opacity: 0.7;
background-color: #777;
&.cp-app-contacts-online {
background-color: green;
}
&.cp-app-contacts-offline {
background-color: red;
}
}
}
.placeholder (@color: #bbb) {
&::-webkit-input-placeholder { /* WebKit, Blink, Edge */
color: @color;
}
&:-moz-placeholder { /* Mozilla Firefox 4 to 18 */
color: @color;
opacity: 1;
}
&::-moz-placeholder { /* Mozilla Firefox 19+ */
color: @color;
opacity: 1;
}
&:-ms-input-placeholder { /* Internet Explorer 10-11 */
color: @color;
}
&::-ms-input-placeholder { /* Microsoft Edge */
color: @color;
}
}
#cp-app-contacts-messaging {
flex: 1;
height: 100%;
background-color: lighten(@bg-color, 20%);
min-width: 0;
.cp-app-contacts-info {
padding: 20px;
}
.cp-app-contacts-header {
background-color: lighten(@bg-color, 15%);
padding: 0;
display: flex;
justify-content: space-between;
align-items: center;
height: 50px;
.hover () {
height: 100%;
line-height: 30px;
padding: 10px;
&:hover {
background-color: rgba(50,50,50,0.3);
}
}
.cp-avatar,
.cp-app-contacts-right-col {
flex:1 1 auto;
}
.cp-app-contacts-remove-history {
.hover;
}
.cp-avatar {
margin: 10px;
}
.cp-app-contacts-more-history {
//display: none;
.hover;
&.cp-app-contacts-faded {
color: darken(@bg-color, 5%);
}
}
}
.cp-app-contacts-chat {
height: 100%;
display: flex;
flex-flow: column;
.cp-app-contacts-messages {
padding: 0 20px;
margin: 10px 0;
flex: 1;
overflow-x: auto;
.cp-app-contacts-message {
& > div {
padding: 0 10px;
}
.cp-app-contacts-content {
overflow: hidden;
word-wrap: break-word;
&> * {
margin: 0;
}
}
.cp-app-contacts-date {
display: none;
font-style: italic;
}
.cp-app-contacts-sender {
margin-top: 10px;
font-weight: bold;
background-color: rgba(0,0,0,0.1);
}
}
}
}
.cp-app-contacts-input {
background-color: lighten(@bg-color, 15%);
height: auto;
min-height: 50px;
display: flex;
align-items: center;
justify-content: center;
padding: 0 75px;
textarea {
margin: 5px 0;
padding: 0 10px;
border: none;
height: 50px;
flex: 1;
background-color: darken(@bg-color, 10%);
color: @color;
resize: none;
line-height: 50px;
overflow-y: auto;
.placeholder(#bbb);
&[disabled=true] {
.placeholder(#999);
}
&:placeholder-shown { line-height: 50px; }
}
button {
height: 50px;
border-radius: 0;
border: none;
background-color: darken(@bg-color, 15%);
&:hover {
background-color: darken(@bg-color, 20%);
}
}
}
}
.messenger_main();
}

@ -41,22 +41,7 @@ define([
document.body.appendChild(toolbarElement);
var messaging = h('div#cp-app-contacts-messaging', [
h('div.cp-app-contacts-info', [
h('h2', Messages.contacts_info1),
h('ul', [
h('li', Messages.contacts_info2),
h('li', Messages.contacts_info3),
])
])
]);
var friendList = h('div#cp-app-contacts-friendlist');
var appElement = h('div#cp-app-contacts-container', [
friendList,
messaging,
]);
var appElement = h('div#cp-app-contacts-container');
document.body.appendChild(appElement);
@ -73,7 +58,7 @@ define([
var messenger = Messenger.create(sFrameChan);
MessengerUI.create(messenger, $(friendList), $(messaging), common);
MessengerUI.create(messenger, $(appElement), common);
UI.removeLoadingScreen();

@ -3,59 +3,141 @@ define([
'/customize/messages.js',
'/common/common-util.js',
'/common/common-interface.js',
'/common/common-notifier.js',
'/common/hyperscript.js',
'/bower_components/marked/marked.min.js',
'/common/media-tag.js',
], function ($, Messages, Util, UI, Notifier, h, Marked, MediaTag) {
], function ($, Messages, Util, UI, h, Marked, MediaTag) {
'use strict';
var debug = console.log;
debug = function () {};
var MessengerUI = {};
var dataQuery = function (curvePublic) {
return '[data-key="' + curvePublic + '"]';
var dataQuery = function (id) {
return '[data-key="' + id + '"]';
};
var userQuery = function (curve) {
return '[data-user="' + curve + '"]';
};
var initChannel = function (state, curvePublic, info) {
console.log('initializing channel for [%s]', curvePublic);
state.channels[curvePublic] = {
messages: [],
HEAD: info.lastKnownHash,
TAIL: null,
var initChannel = function (state, info) {
console.log('initializing channel for [%s]', info.id);
var h, t;
if (Array.isArray(info.messages) && info.messages.length) {
h = info.messages[info.messages.length -1].sig;
t = info.messages[0].sig;
}
state.channels[info.id] = {
messages: info.messages || [],
name: info.name,
isFriendChat: info.isFriendChat,
needMoreHistory: !info.isPadChat,
isPadChat: info.isPadChat,
curvePublic: info.curvePublic,
HEAD: h || info.lastKnownHash,
TAIL: t || null,
};
};
MessengerUI.create = function (messenger, $userlist, $messages, common) {
var origin = common.getMetadataMgr().getPrivateData().origin;
MessengerUI.create = function (messenger, $container, common, toolbar) {
var sframeChan = common.getSframeChannel();
var metadataMgr = common.getMetadataMgr();
var origin = metadataMgr.getPrivateData().origin;
var isApp = typeof(toolbar) !== "undefined";
$container.addClass('cp-app-contacts-initializing');
var messaging = h('div#cp-app-contacts-messaging', [
h('span.fa.fa-spinner.fa-pulse.fa-4x.fa-fw.cp-app-contacts-spinner'),
h('div.cp-app-contacts-info', [
h('h2', Messages.contacts_info1),
h('ul', [
h('li', Messages.contacts_info2),
h('li', Messages.contacts_info3),
h('li', Messages.contacts_info4),
])
])
]);
var friendList = h('div#cp-app-contacts-friendlist', [
h('span.fa.fa-spinner.fa-pulse.fa-4x.fa-fw.cp-app-contacts-spinner'),
h('div.cp-app-contacts-padchat.cp-app-contacts-category', [
h('div.cp-app-contacts-category-content')
]),
h('div.cp-app-contacts-friends.cp-app-contacts-category', [
h('div.cp-app-contacts-category-content'),
h('h2.cp-app-contacts-category-title', Messages.contacts_friends),
]),
h('div.cp-app-contacts-rooms.cp-app-contacts-category', [
h('div.cp-app-contacts-category-content'),
h('h2.cp-app-contacts-category-title', Messages.contacts_rooms),
]),
]);
var $userlist = $(friendList).appendTo($container);
var $messages = $(messaging).appendTo($container);
var state = window.state = {
active: '',
channels: {}
};
state.channels = {};
var displayNames = state.displayNames = {};
var contactsData = state.contactsData = {};
var avatars = state.avatars = {};
var setActive = function (curvePublic) {
state.active = curvePublic;
var setActive = function (id) {
state.active = id;
};
var isActive = function (curvePublic) {
return curvePublic === state.active;
var isActive = function (id) {
return id === state.active;
};
var find = {};
find.inList = function (curvePublic) {
return $userlist.find(dataQuery(curvePublic));
find.inList = function (id) {
return $userlist.find(dataQuery(id));
};
var notifyToolbar = function () {
if (!toolbar || !toolbar['chat']) { return; }
if (toolbar['chat'].find('button').hasClass('cp-toolbar-button-active')) { return; }
toolbar['chat'].find('button').addClass('cp-toolbar-notification');
};
var notify = function (id) {
find.inList(id).addClass('cp-app-contacts-notify');
};
var unnotify = function (id) {
find.inList(id).removeClass('cp-app-contacts-notify');
};
var notify = function (curvePublic) {
find.inList(curvePublic).addClass('cp-app-contacts-notify');
var onResize = function () {
// Don't update the width if we are in the contacts app
if (!isApp) { return; }
var w = $userlist[0].offsetWidth - $userlist[0].clientWidth;
$userlist.css('width', (68 + w)+'px');
};
var unnotify = function (curvePublic) {
find.inList(curvePublic).removeClass('cp-app-contacts-notify');
var reorderRooms = function () {
var channels = Object.keys(state.channels).sort(function (a, b) {
var m1 = state.channels[a].messages.slice(-1)[0];
var m2 = state.channels[b].messages.slice(-1)[0];
if (!m2) { return !m1 ? 0 : 1; }
if (!m1) { return -1; }
return m1.time - m2.time;
});
channels.forEach(function (c, i) {
$userlist.find(dataQuery(c)).css('order', i);
});
// Make sure the width is correct even if there is a scrollbar
onResize();
};
var m = function (md) {
$(window).on('resize', onResize);
var m = function (md, hour) {
var d = h('div.cp-app-contacts-content');
try {
d.innerHTML = Marked(md || '');
@ -73,6 +155,9 @@ define([
// activate media-tags
$d.find('media-tag').each(function (i, e) { MediaTag(e); });
var time = h('div.cp-app-contacts-time', hour);
$d.append(time);
} catch (e) {
console.error(md);
console.error(e);
@ -82,25 +167,35 @@ define([
var markup = {};
markup.message = function (msg) {
if (msg.type !== 'MSG') { return; }
var curvePublic = msg.author;
var name = displayNames[msg.author];
var name = typeof msg.name !== "undefined" ? (msg.name || Messages.anonymous)
: contactsData[msg.author].displayName;
var d = msg.time ? new Date(msg.time) : undefined;
var day = d ? d.toLocaleDateString() : '';
var hour = d ? d.toLocaleTimeString() : '';
return h('div.cp-app-contacts-message', {
title: msg.time? new Date(msg.time).toLocaleString(): '?',
'data-key': curvePublic,
//title: time || '?',
'data-user': curvePublic,
'data-day': day
}, [
name? h('div.cp-app-contacts-sender', name): undefined,
m(msg.text),
name? h('div.cp-app-contacts-sender', [
h('span.cp-app-contacts-sender-name', name),
h('span.cp-app-contacts-sender-time', day)
]): undefined,
m(msg.text, hour),
]);
};
var getChat = function (curvePublic) {
return $messages.find(dataQuery(curvePublic));
var getChat = function (id) {
return $messages.find(dataQuery(id));
};
var normalizeLabels = function ($messagebox) {
$messagebox.find('div.cp-app-contacts-message').toArray().reduce(function (a, b) {
var $b = $(b);
if ($(a).data('key') === $b.data('key')) {
if ($(a).data('user') === $b.data('user') &&
$(a).data('day') === $b.data('day')) {
$b.find('.cp-app-contacts-sender').hide();
return a;
}
@ -108,29 +203,31 @@ define([
}, []);
};
markup.chatbox = function (curvePublic, data) {
markup.chatbox = function (id, data, curvePublic) {
var moreHistory = h('span.cp-app-contacts-more-history.fa.fa-history', {
title: Messages.contacts_fetchHistory,
});
var displayName = data.displayName;
var chan = state.channels[id];
var displayName = chan.name;
var fetching = false;
var $moreHistory = $(moreHistory).click(function () {
if (fetching) { return; }
// get oldest known message...
var channel = state.channels[curvePublic];
var channel = state.channels[id];
if (channel.exhausted) {
return void $moreHistory.addClass('cp-app-contacts-faded');
}
console.log('getting history');
debug('getting history');
var sig = channel.TAIL || channel.HEAD;
fetching = true;
var $messagebox = $(getChat(curvePublic)).find('.cp-app-contacts-messages');
messenger.getMoreHistory(curvePublic, sig, 10, function (e, history) {
var $messagebox = $(getChat(id)).find('.cp-app-contacts-messages');
messenger.getMoreHistory(id, sig, 10, function (e, history) {
fetching = false;
if (e) { return void console.error(e); }
@ -139,13 +236,16 @@ define([
return;
}
history.forEach(function (msg) {
history.forEach(function (msg, i) {
if (channel.exhausted) { return; }
if (msg.sig) {
if (i === 0 && history.length > 1 && msg.sig === channel.TAIL) {
// First message is usually the lastKnownHash, ignore it
return;
}
if (msg.sig === channel.TAIL) {
console.error('No more messages to fetch');
channel.exhausted = true;
console.log(channel);
return void $moreHistory.addClass('cp-app-contacts-faded');
} else {
channel.TAIL = msg.sig;
@ -156,9 +256,11 @@ define([
if (msg.type !== 'MSG') { return; }
// FIXME Schlameil the painter (performance does not scale well)
// XXX trust the server?
/*
if (channel.messages.some(function (old) {
return msg.sig === old.sig;
})) { return; }
})) { return; }*/
channel.messages.unshift(msg);
var el_message = markup.message(msg);
@ -176,22 +278,43 @@ define([
UI.confirm(Messages.contacts_confirmRemoveHistory, function (yes) {
if (!yes) { return; }
messenger.clearOwnedChannel(data.channel, function (e) {
messenger.clearOwnedChannel(id, function (e) {
if (e) {
console.error(e);
UI.alert(Messages.contacts_removeHistoryServerError);
return;
}
// XXX clear the UI?
});
});
});
var avatar = h('div.cp-avatar');
var header = h('div.cp-app-contacts-header', [
avatar,
moreHistory,
removeHistory,
]);
var headerContent = [avatar, moreHistory, data.isFriendCHat ? removeHistory : undefined];
if (isApp) {
headerContent = [
h('div.cp-app-contacts-header-title', Messages.contacts_padTitle),
moreHistory
];
}
var header = h('div.cp-app-contacts-header', headerContent);
var priv = metadataMgr.getPrivateData();
var closeTips = h('span.fa.fa-window-close.cp-app-contacts-tips-close');
var tips;
if (isApp && Util.find(priv.settings, ['general', 'hidetips', 'chat']) !== true) {
tips = h('div.cp-app-contacts-tips', [
closeTips,
Messages.contacts_warning
]);
}
$(closeTips).click(function () {
$(tips).hide();
common.setAttribute(['general', 'hidetips', 'chat'], true);
});
var messages = h('div.cp-app-contacts-messages');
var input = h('textarea', {
placeholder: Messages.contacts_typeHere
@ -205,12 +328,13 @@ define([
]);
var $avatar = $(avatar);
if (data.avatar && avatars[data.avatar]) {
$avatar.append(avatars[data.avatar]).append(rightCol);
var friend = contactsData[curvePublic] || {};
if (friend.avatar && avatars[friend.avatar]) {
$avatar.append(avatars[friend.avatar]).append(rightCol);
} else {
common.displayAvatar($avatar, data.avatar, data.displayName, function ($img) {
if (data.avatar && $img) {
avatars[data.avatar] = $img[0].outerHTML;
common.displayAvatar($avatar, friend.avatar, displayName, function ($img) {
if (friend.avatar && $img) {
avatars[friend.avatar] = $img[0].outerHTML;
}
$(rightCol).insertAfter($avatar);
});
@ -221,14 +345,14 @@ define([
if (typeof(content) !== 'string' || !content.trim()) { return; }
if (sending) { return false; }
sending = true;
messenger.sendMessage(curvePublic, content, function (e) {
messenger.sendMessage(id, content, function (e) {
if (e) {
// failed to send
return void console.error('failed to send');
}
input.value = '';
sending = false;
console.log('sent successfully');
debug('sent successfully');
var $messagebox = $(messages);
var height = $messagebox[0].scrollHeight;
@ -268,9 +392,11 @@ define([
$(sendButton).click(function () { send(input.value); });
return h('div.cp-app-contacts-chat', {
'data-key': curvePublic,
'data-key': id,
'data-user': data.isFriendChat && curvePublic
}, [
header,
tips,
messages,
h('div.cp-app-contacts-input', [
input,
@ -283,16 +409,20 @@ define([
$messages.find('.cp-app-contacts-info').hide();
};
var updateStatus = function (curvePublic) {
var $status = find.inList(curvePublic).find('.cp-app-contacts-status');
// FIXME this stopped working :(
messenger.getStatus(curvePublic, function (e, online) {
var showInfo = function () {
$messages.find('.cp-app-contacts-info').show();
};
var updateStatus = function (id) {
if (!state.channels[id]) { return; }
var $status = find.inList(id).find('.cp-app-contacts-status');
messenger.getStatus(id, function (e, online) {
// if error maybe you shouldn't display this friend...
if (e) {
find.inList(curvePublic).hide();
getChat(curvePublic).hide();
find.inList(id).hide();
getChat(id).hide();
return void console.error(curvePublic, e);
return void console.error(id, e);
}
if (online) {
return void $status
@ -302,72 +432,81 @@ define([
});
};
var display = function (curvePublic) {
var channel = state.channels[curvePublic];
var display = function (chanId) {
var channel = state.channels[chanId];
var lastMsg = channel.messages.slice(-1)[0];
if (lastMsg) {
channel.HEAD = lastMsg.sig;
messenger.setChannelHead(curvePublic, channel.HEAD, function (e) {
messenger.setChannelHead(chanId, channel.HEAD, function (e) {
if (e) { console.error(e); }
});
}
setActive(curvePublic);
unnotify(curvePublic);
var $chat = getChat(curvePublic);
setActive(chanId);
unnotify(chanId);
var $chat = getChat(chanId);
hideInfo();
$messages.find('div.cp-app-contacts-chat[data-key]').hide();
if ($chat.length) {
var $chat_messages = $chat.find('div.cp-app-contacts-message');
if (!$chat_messages.length) {
if ($chat_messages.length < 10) { //|| channel.needMoreHistory) { XXX
delete channel.needMoreHistory;
var $more = $chat.find('.cp-app-contacts-more-history');
$more.click();
}
return void $chat.show();
$chat.show();
return;
} else {
console.error("Chat is missing... Please reload the page and try again.");
}
messenger.getFriendInfo(curvePublic, function (e, info) {
if (e) { return void console.error(e); } // FIXME
var chatbox = markup.chatbox(curvePublic, info);
$messages.append(chatbox);
});
};
var removeFriend = function (curvePublic) {
messenger.removeFriend(curvePublic, function (e /*, removed */) {
if (e) { return void console.error(e); }
find.inList(curvePublic).remove();
//console.log(removed);
});
};
markup.friend = function (data) {
var curvePublic = data.curvePublic;
var friend = h('div.cp-app-contacts-friend.cp-avatar', {
'data-key': curvePublic,
markup.room = function (id, room, userlist) {
var roomEl = h('div.cp-app-contacts-friend.cp-avatar', {
'data-key': id,
'data-user': room.isFriendChat ? userlist[0].curvePublic : '',
title: room.name
});
var remove = h('span.cp-app-contacts-remove.fa.fa-user-times', {
title: Messages.contacts_remove
});
var status = h('span.cp-app-contacts-status');
var leaveRoom = h('span.cp-app-contacts-remove.fa.fa-sign-out', {
title: Messages.contacts_leaveRoom
});
var status = h('span.cp-app-contacts-status', {
title: Messages.contacts_online
});
var rightCol = h('span.cp-app-contacts-right-col', [
h('span.cp-app-contacts-name', [data.displayName]),
remove,
h('span.cp-app-contacts-name', [room.name]),
room.isFriendChat ? remove :
room.isPadChat ? undefined : leaveRoom,
]);
var $friend = $(friend)
.click(function () {
display(curvePublic);
})
.dblclick(function () {
if (data.profile) { window.open(origin + '/profile/#' + data.profile); }
var friendData = room.isFriendChat ? userlist[0] : {};
var $room = $(roomEl).click(function () {
display(id);
}).dblclick(function () {
if (friendData.profile) { window.open(origin + '/profile/#' + friendData.profile); }
});
$(remove).click(function (e) {
e.stopPropagation();
var channel = state.channels[id];
if (!channel.isFriendChat) { return; }
var curvePublic = channel.curvePublic;
var friend = contactsData[curvePublic] || friendData;
UI.confirm(Messages._getKey('contacts_confirmRemove', [
Util.fixHTML(data.displayName)
Util.fixHTML(friend.name)
]), function (yes) {
if (!yes) { return; }
removeFriend(curvePublic, function (e) {
@ -379,41 +518,42 @@ define([
}, undefined, true);
});
if (data.avatar && avatars[data.avatar]) {
$friend.append(avatars[data.avatar]);
$friend.append(rightCol);
if (friendData.avatar && avatars[friendData.avatar]) {
$room.append(avatars[friendData.avatar]);
$room.append(rightCol);
} else {
common.displayAvatar($friend, data.avatar, data.displayName, function ($img) {
if (data.avatar && $img) {
avatars[data.avatar] = $img[0].outerHTML;
common.displayAvatar($room, friendData.avatar, room.name, function ($img) {
if (friendData.avatar && $img) {
avatars[friendData.avatar] = $img[0].outerHTML;
}
$friend.append(rightCol);
$room.append(rightCol);
});
}
$friend.append(status);
return $friend;
$room.append(status);
return $room;
};
var isBottomedOut = function ($elem) {
return ($elem[0].scrollHeight - $elem.scrollTop() === $elem.outerHeight());
};
var initializing = true;
messenger.on('message', function (message) {
if (!initializing) { Notifier.notify(); }
var curvePublic = message.curve;
var chanId = message.channel;
var channel = state.channels[chanId];
if (!channel) { return; }
var name = displayNames[curvePublic];
var chat = getChat(curvePublic, name);
var chat = getChat(chanId);
console.log(message);
debug(message);
var el_message = markup.message(message);
state.channels[curvePublic].messages.push(message);
common.notify();
notifyToolbar();
var $chat = $(chat);
channel.messages.push(message);
var $chat = $(chat);
if (!$chat.length) {
console.error("Got a message but the chat isn't open");
}
@ -427,111 +567,266 @@ define([
$messagebox.scrollTop($messagebox.outerHeight());
}
normalizeLabels($messagebox);
reorderRooms();
var channel = state.channels[curvePublic];
if (!channel) {
console.error('expected channel [%s] to be open', curvePublic);
return;
}
if (isActive(curvePublic)) {
if (isActive(chanId)) {
channel.HEAD = message.sig;
messenger.setChannelHead(curvePublic, message.sig, function (e) {
messenger.setChannelHead(chanId, message.sig, function (e) {
if (e) { return void console.error(e); }
});
return;
}
var lastMsg = channel.messages.slice(-1)[0];
if (lastMsg.sig !== channel.HEAD) {
return void notify(curvePublic);
return void notify(chanId);
}
unnotify(curvePublic);
unnotify(chanId);
});
messenger.on('join', function (curvePublic, channel) {
channel = channel;
updateStatus(curvePublic);
messenger.on('join', function (data, channel) {
if (data.curvePublic) {
contactsData[data.curvePublic] = data;
}
updateStatus(channel);
// TODO room refresh online userlist
});
messenger.on('leave', function (curvePublic, channel) {
channel = channel;
updateStatus(curvePublic);
messenger.on('leave', function (data, channel) {
if (contactsData[data.curvePublic]) {
delete contactsData[data.curvePublic];
}
updateStatus(channel);
// TODO room refresh online userlist
});
// change in your friend list
messenger.on('update', function (info, curvePublic) {
var name = displayNames[curvePublic] = info.displayName;
messenger.on('update', function (info, types, channel) {
if (!info || !info.curvePublic) { return; }
// Make sure we don't store useless data (friends data in pad chat or the other way)
if (channel && !state.channels[channel]) { return; }
var curvePublic = info.curvePublic;
contactsData[curvePublic] = info;
if (types.indexOf('displayName') !== -1) {
var name = info.displayName;
// update label in friend list
$userlist.find(userQuery(curvePublic)).find('.cp-app-contacts-name').text(name);
$userlist.find(userQuery(curvePublic)).attr('title', name);
// update title bar and messages
$messages.find(userQuery(curvePublic) + ' .cp-app-contacts-header ' +
'.cp-app-contacts-name, div.cp-app-contacts-message'+
userQuery(curvePublic) + ' div.cp-app-contacts-sender').text(name);
// TODO room
// Update name in room userlist
}
if (types.indexOf('profile') !== -1) {
// update dblclick event in friend list
$userlist.find(userQuery(curvePublic)).off('dblclick').dblclick(function () {
if (info.profile) { window.open(origin + '/profile/#' + info.profile); }
});
}
if (types.indexOf('avatar') !== -1) {
var $mAvatar = $messages
.find(userQuery(curvePublic) +' .cp-app-contacts-header .cp-avatar');
var $lAvatar = $userlist.find(userQuery(curvePublic));
$lAvatar.find('.cp-avatar-default, media-tag').remove();
// update label in friend list
find.inList(curvePublic).find('.cp-app-contacts-name').text(name);
var $div = $('<div>');
common.displayAvatar($div, info.avatar, info.displayName, function ($img) {
if (info.avatar && $img) {
avatars[info.avatar] = $img[0].outerHTML;
}
$mAvatar.html($div.html());
$lAvatar.find('.cp-app-contacts-right-col').before($div.html());
});
// update title bar and messages
$messages.find(dataQuery(curvePublic) + ' .cp-app-contacts-header ' +
'.cp-app-contacts-name, div.cp-app-contacts-message'+
dataQuery(curvePublic) + ' div.cp-app-contacts-sender').text(name).text(name);
}
});
var connectToFriend = function (curvePublic, cb) {
messenger.getFriendInfo(curvePublic, function (e, info) {
if (e) { return void console.error(e); }
var name = displayNames[curvePublic] = info.displayName;
initChannel(state, curvePublic, info);
var execCommand = function (cmd, data, cb) {
sframeChan.query('Q_CHAT_COMMAND', {cmd: cmd, data: data}, function (err, obj) {
if (err || (obj && obj.error)) { return void cb(err || (obj && obj.error)); }
cb(void 0, obj);
});
};
var initializeRoom = function (room) {
var id = room.id;
initChannel(state, room);
execCommand('GET_USERLIST', {id: id}, function (e, list) {
if (e || list.error) { return void console.error(e || list.error); }
if (!room.isPadChat && (!Array.isArray(list) || !list.length)) {
return void console.error("Empty room!");
}
debug('userlist: ' + JSON.stringify(list), id);
var friend = {};
var chatbox = markup.chatbox(curvePublic, info);
if (room.isFriendChat) {
// This is a friend, the userlist is only one user.
friend = list[0];
contactsData[friend.curvePublic] = friend;
}
var chatbox = markup.chatbox(id, room, friend.curvePublic);
$(chatbox).hide();
$messages.append(chatbox);
var friend = markup.friend(info, name);
$userlist.append(friend);
messenger.openFriendChannel(curvePublic, function (e) {
if (e) { return void console.error(e); }
cb();
updateStatus(curvePublic);
// don't add friends that are already in your userlist
//if (friendExistsInUserList(k)) { return; }
var $messagebox = $(chatbox).find('.cp-app-contacts-messages');
room.messages.forEach(function (msg) {
var el_message = markup.message(msg);
$messagebox.append(el_message);
});
normalizeLabels($messagebox);
var roomEl = markup.room(id, room, list);
var $parentEl;
if (room.isFriendChat) {
$parentEl = $userlist.find('.cp-app-contacts-friends');
} else if (room.isPadChat) {
$parentEl = $userlist.find('.cp-app-contacts-padchat');
} else {
$parentEl = $userlist.find('.cp-app-contacts-rooms');
}
$parentEl.find('.cp-app-contacts-category-content').append(roomEl);
reorderRooms();
updateStatus(id);
if (isApp && room.isPadChat) {
$container.removeClass('cp-app-contacts-initializing');
display(room.id);
}
});
};
messenger.on('friend', function (curvePublic) {
console.log('new friend: ', curvePublic);
//console.error("TODO redraw user list");
//console.error("TODO connect to new friend");
// FIXME this doesn't work right now because the friend hasn't been fully added?
connectToFriend(curvePublic, function () {
//console.error('connected');
if (isApp) { return; }
debug('new friend: ', curvePublic);
execCommand('GET_ROOMS', {curvePublic: curvePublic}, function (err, rooms) {
if (err) { return void console.error(err); }
debug('rooms: ' + JSON.stringify(rooms));
rooms.forEach(initializeRoom);
});
});
messenger.on('unfriend', function (curvePublic) {
console.log('unfriend', curvePublic);
find.inList(curvePublic).remove();
console.error('TODO remove chatbox');
console.error('TODO show something if that chatbox was active');
messenger.on('unfriend', function (curvePublic, removedByMe) {
if (isApp) { return; }
var channel = state.channels[state.active];
$userlist.find(userQuery(curvePublic)).remove();
$messages.find(userQuery(curvePublic)).remove();
if (channel && channel.curvePublic === curvePublic) {
showInfo();
}
if (!removedByMe) {
// TODO UI.alert if this is triggered by the other guy
}
});
common.getMetadataMgr().onChange(function () {
//messenger.checkNewFriends();
messenger.updateMyData();
common.getMetadataMgr().onTitleChange(function () {
var padChat = common.getPadChat();
var md = common.getMetadataMgr().getMetadata();
var name = md.title || md.defaultTitle;
$userlist.find(dataQuery(padChat)).find('.cp-app-contacts-name').text(name);
$userlist.find(dataQuery(padChat)).attr('title', name);
$messages.find(dataQuery(padChat) + ' .cp-app-contacts-header .cp-app-contacts-name')
.text(name);
var $mAvatar = $messages.find(dataQuery(padChat) +' .cp-app-contacts-header .cp-avatar');
var $lAvatar = $userlist.find(dataQuery(padChat));
$lAvatar.find('.cp-avatar-default, media-tag').remove();
var $div = $('<div>');
common.displayAvatar($div, null, name, function () {
$mAvatar.html($div.html());
$lAvatar.find('.cp-app-contacts-right-col').before($div.html());
});
});
// FIXME dirty hack
// TODO room
// messenger.on('joinroom', function (chanid))
// messenger.on('leaveroom', function (chanid))
messenger.getMyInfo(function (e, info) {
displayNames[info.curvePublic] = info.displayName;
contactsData[info.curvePublic] = info;
});
messenger.getFriendList(function (e, keys) {
var count = keys.length + 1;
var ready = function () {
count--;
if (count === 0) {
initializing = false;
UI.removeLoadingScreen();
var ready = false;
var onMessengerReady = function () {
if (isApp) { return; }
if (ready) { return; }
ready = true;
execCommand('GET_ROOMS', null, function (err, rooms) {
if (err) { return void console.error(err); }
debug('rooms: ' + JSON.stringify(rooms));
rooms.forEach(initializeRoom);
});
$container.removeClass('cp-app-contacts-initializing');
};
var onPadChatReady = function (data) {
var padChat = common.getPadChat();
if (data !== padChat) { return; }
if (state.channels[data]) { return; }
execCommand('GET_ROOMS', {padChat: data}, function (err, rooms) {
if (err) { return void console.error(err); }
if (!Array.isArray(rooms) || rooms.length !== 1) {
return void console.error('Invalid pad chat');
}
};
ready();
keys.forEach(function (curvePublic) {
connectToFriend(curvePublic, ready);
var room = rooms[0];
var md = common.getMetadataMgr().getMetadata();
var name = md.title || md.defaultTitle;
room.name = name;
rooms.forEach(initializeRoom);
});
};
var onDisconnect = function () {
debug('disconnected');
$messages.find('.cp-app-contacts-input textarea').prop('disabled', true);
};
var onReconnect = function () {
debug('reconnected');
$messages.find('.cp-app-contacts-input textarea').prop('disabled', false);
};
// Initialize chat when outer is ready (all channels loaded)
// TODO: try again in outer if fail to load a channel
if (!isApp) {
execCommand('INIT_FRIENDS', null, function () {});
execCommand('IS_READY', null, function (err, yes) {
if (yes) { onMessengerReady(); }
});
}
sframeChan.on('EV_CHAT_EVENT', function (obj) {
if (obj.ev === 'READY') {
onMessengerReady();
return;
}
if (obj.ev === 'PADCHAT_READY') {
onPadChatReady(obj.data);
return;
}
if (obj.ev === 'DISCONNECT') {
onDisconnect();
return;
}
if (obj.ev === 'RECONNECT') {
onReconnect();
return;
}
});
};

@ -653,8 +653,10 @@
#cp-app-drive-new-ghost-dialog.cp-modal-container {
.drive_fileIcon;
border: 1px solid @colortheme_modal-fg;
li:not(.cp-app-drive-element-selected):hover {
border: 1px solid white;
background: @colortheme_modal-fg;
color: @colortheme_modal-bg;
}
.cp-modal {
display: flex;
@ -663,14 +665,16 @@
cursor: pointer;
}
&> p {
margin: 50px;
display: flex;
align-items: center;
justify-content: center;
}
&> div {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-content: center;
overflow-y: auto;
flex: 1;
.cp-app-drive-new-upload {
break-after: always;
page-break-after: always;
@ -682,23 +686,27 @@
white-space: nowrap;
}
@media screen and (max-height: @browser_media-not-big) {
@media screen and (max-width: @browser_media-medium-screen),
screen and (max-height: @browser_media-medium-screen) {
.cp-modal {
& > p {
display: none;
}
& > div {
align-content: unset;
li {
height: 40px;
width: 90%;
display: flex;
align-items: center;
align-content: unset;
.fa {
font-size: 32px;
min-width: 50px;
}
.cp-app-drive-new-name {
height: auto;
overflow: hidden;
text-overflow: ellipsis;
}
}
}

@ -1207,15 +1207,23 @@ define([
if (paths) {
paths.forEach(function (p) { pathsList.push(p.path); });
}
var hasOwned = pathsList.some(function (p) {
var el = manager.find(p);
var data = manager.isSharedFolder(el) ? manager.getSharedFolderData(el)
: manager.getFileData(el);
return data.owners && data.owners.indexOf(edPublic) !== -1;
});
var msg = Messages._getKey("fm_removeSeveralPermanentlyDialog", [pathsList.length]);
if (pathsList.length === 1) {
msg = Messages.fm_removePermanentlyDialog;
msg = hasOwned ? Messages.fm_deleteOwnedPad : Messages.fm_removePermanentlyDialog;
} else if (hasOwned) {
msg = msg + '<br><em>' + Messages.fm_removePermanentlyNote + '</em>';
}
UI.confirm(msg, function(res) {
$(window).focus();
if (!res) { return; }
manager.delete(pathsList, refresh);
});
}, null, true);
};
// Drag & drop:
// The data transferred is a stringified JSON containing the path of the dragged element
@ -1290,6 +1298,7 @@ define([
if (!oldPaths) { return; }
// A moved element should be removed from its previous location
var movedPaths = [];
var sharedF = false;
oldPaths.forEach(function (p) {
movedPaths.push(p.path);
@ -1301,9 +1310,25 @@ define([
var newPath = findDropPath(ev.target);
if (!newPath) { return; }
if (sharedF && manager.isPathIn(newPath, [TRASH])) {
deletePaths(null, movedPaths);
return;
return void deletePaths(null, movedPaths);
}
if (manager.isPathIn(newPath, [TRASH])) {
// Filter the selection to remove shared folders.
// Shared folders can't be moved to the trash!
var filteredPaths = movedPaths.filter(function (p) {
var el = manager.find(p);
return !manager.isSharedFolder(el);
});
if (!filteredPaths.length) {
// We only have shared folder, delete them
return void deletePaths(null, movedPaths);
}
movedPaths = filteredPaths;
}
if (movedPaths && movedPaths.length) {
moveElements(movedPaths, newPath, null, refresh);
}
@ -1805,14 +1830,17 @@ define([
.click(function () {
var $input = $('<input>', {
'type': 'file',
'style': 'display: none;'
'style': 'display: none;',
'multiple': 'multiple'
}).on('change', function (e) {
var file = e.target.files[0];
var ev = {
target: $content[0],
path: findDropPath($content[0])
};
APP.FM.handleFile(file, ev);
var files = Util.slice(e.target.files);
files.forEach(function (file) {
var ev = {
target: $content[0],
path: findDropPath($content[0])
};
APP.FM.handleFile(file, ev);
});
});
$input.click();
});
@ -1889,9 +1917,11 @@ define([
var createShareButton = function (id, $container) {
var $shareBlock = $('<button>', {
'class': 'fa fa-shhare-alt cp-toolbar-share-button',
'class': 'cp-toolbar-share-button',
title: Messages.shareButton
});
$sharedIcon.clone().appendTo($shareBlock);
$('<span>').text(Messages.shareButton).appendTo($shareBlock);
var data = manager.getSharedFolderData(id);
var parsed = Hash.parsePadUrl(data.href);
if (!parsed || !parsed.hash) { return void console.error("Invalid href: "+data.href); }
@ -2574,7 +2604,12 @@ define([
createNewButton(isInRoot, $toolbar.find('.cp-app-drive-toolbar-leftside'));
var sfId = manager.isInSharedFolder(currentPath);
if (sfId) {
var sfData = manager.getSharedFolderData(sfId);
var parsed = Hash.parsePadUrl(sfData.href);
sframeChan.event('EV_DRIVE_SET_HASH', parsed.hash || '');
createShareButton(sfId, $toolbar.find('.cp-app-drive-toolbar-leftside'));
} else {
sframeChan.event('EV_DRIVE_SET_HASH', '');
}
createTitle($toolbar.find('.cp-app-drive-path'), path);
@ -2721,7 +2756,7 @@ define([
}
var dataPath = isSharedFolder ? path.slice(0, -1) : path;
$elementRow.data('path', dataPath);
addDragAndDropHandlers($elementRow, path, true, droppable);
addDragAndDropHandlers($elementRow, dataPath, true, droppable);
if (active) {
$elementRow.addClass('cp-app-drive-element-active cp-leftside-active');
}
@ -3221,16 +3256,23 @@ define([
paths.push($(elmt).data('path'));
});
if (!paths.length) { return; }
// Remove shared folders from the selection (they can't be moved to the trash)
// unless the selection is only shared folders
var paths2 = paths.filter(function (p) {
var el = manager.find(p);
return !manager.isSharedFolder(el);
});
// If we are in the trash or anon pad or if we are holding the "shift" key,
// delete permanently
// Or if we are in a shared folder
// Or if the selection is only shared folders
if (!APP.loggedIn || isTrash || manager.isInSharedFolder(currentPath)
|| e.shiftKey) {
|| e.shiftKey || !paths2.length) {
deletePaths(null, paths);
return;
}
// else move to trash
moveElements(paths, [TRASH], false, refresh);
moveElements(paths2, [TRASH], false, refresh);
return;
}
});

@ -41,7 +41,7 @@ define([
var secret = Utils.Hash.getSecrets('drive', hash);
if (hash) {
// Add a shared folder!
// XXX password?
// TODO password?
Cryptpad.addSharedFolder(secret, function (id) {
window.CryptPad_newSharedFolder = id;
// Update the hash in the address bar
@ -87,6 +87,14 @@ define([
cb(obj);
});
});
sframeChan.on('EV_DRIVE_SET_HASH', function (hash) {
// Update the hash in the address bar
var ohc = window.onhashchange;
window.onhashchange = function () {};
window.location.hash = hash || '';
window.onhashchange = ohc;
ohc({reset:true});
});
Cryptpad.onNetworkDisconnect.reg(function () {
sframeChan.event('EV_NETWORK_DISCONNECT');
});

@ -67,7 +67,6 @@
text-overflow: ellipsis;
}
#kanban-edit {
color: black;
font-weight: bold;
}
}
@ -77,6 +76,7 @@
width: 100%;
background: transparent;
border: 1px solid rgba(0,0,0,0.3);
color: inherit;
}
@button-size: 50px;
@ -117,39 +117,39 @@
}
.kanban-header-yellow {
background: #FC3;
background: #FC3 !important;
}
.kanban-header-orange {
background: #F91;
background: #F91 !important;
}
.kanban-header-blue {
background: #0AC;
background: #0AC !important;
}
.kanban-header-red {
background: #E43;
background: #E43 !important;
}
.kanban-header-green {
background: #8C4;
background: #8C4 !important;
}
.kanban-header-purple {
background: #c851ff;
background: #c851ff !important;
}
.kanban-header-cyan {
background: #00ffff;
background: #00ffff !important;
}
.kanban-header-lightgreen {
background: #c3ff5b;
background: #c3ff5b !important;
}
.kanban-header-lightblue {
background: #adeeff;
background: #adeeff !important;
}
@media (max-width: @browser_media-medium-screen) {

@ -10,6 +10,7 @@ define([
'/common/modes.js',
'/customize/messages.js',
'/kanban/jkanban.js',
'/kanban/jscolor.js',
'css!/kanban/jkanban.css',
'less!/kanban/app-kanban.less'
@ -107,7 +108,7 @@ define([
var kanban = new window.jKanban({
element: '#cp-app-kanban-content',
gutter: '15px',
gutter: '5px',
widthBoard: '300px',
buttonContent: '❌',
colors: COLORS,
@ -138,7 +139,7 @@ define([
// Remove the input
$(el).text(name);
// Save the value for the correct board
var board = $(el.parentNode.parentNode).attr("data-id");
var board = $(el.parentNode.parentNode.parentNode).attr("data-id");
var pos = kanban.findElementPosition(el);
kanban.getBoardJSON(board).item[pos].title = name;
kanban.onChange();
@ -206,24 +207,53 @@ define([
}
});
},
colorClick: function (el) {
colorClick: function (el, type) {
if (framework.isReadOnly() || framework.isLocked()) { return; }
verbose("in color click");
var board = $(el.parentNode).attr("data-id");
var boardJSON = kanban.getBoardJSON(board);
verbose("on color click");
var boardJSON;
var board;
if (type === "board") {
verbose("board color click");
board = $(el.parentNode).attr("data-id");
boardJSON = kanban.getBoardJSON(board);
} else {
verbose("item color click");
board = $(el.parentNode.parentNode).attr("data-id");
var pos = kanban.findElementPosition(el);
boardJSON = kanban.getBoardJSON(board).item[pos];
}
var onchange = function (colorL) {
var elL = el;
var typeL = type;
var boardJSONL;
var boardL;
if (typeL === "board") {
verbose("board color change");
boardL = $(elL.parentNode).attr("data-id");
boardJSONL = kanban.getBoardJSON(boardL);
} else {
verbose("item color change");
boardL = $(elL.parentNode.parentNode).attr("data-id");
var pos = kanban.findElementPosition(elL);
boardJSONL = kanban.getBoardJSON(boardL).item[pos];
}
var currentColor = boardJSONL.color;
verbose("Current color " + currentColor);
if (currentColor !== colorL.toString()) {
$(elL).removeClass("kanban-header-" + currentColor);
boardJSONL.color = colorL.toString();
kanban.onChange();
}
};
var jscolorL;
el._jscLinkedInstance = undefined;
jscolorL = new window.jscolor(el,{onFineChange: onchange, valueElement:undefined});
jscolorL.show();
var currentColor = boardJSON.color;
verbose("Current color " + currentColor);
var index = kanban.options.colors.findIndex(function (element) {
return (element === currentColor);
}) + 1;
verbose("Next index " + index);
if (index >= kanban.options.colors.length) { index = 0; }
var nextColor = kanban.options.colors[index];
verbose("Next color " + nextColor);
boardJSON.color = nextColor;
$(el).removeClass("kanban-header-" + currentColor);
$(el).addClass("kanban-header-" + nextColor);
kanban.onChange();
if (currentColor === undefined) {
currentColor = '';
}
jscolorL.fromString(currentColor);
},
buttonClick: function (el, boardId, e) {
e.stopPropagation();

@ -52,6 +52,7 @@
widthBoard: '250px',
responsive: '700',
colors: ["yellow", "green", "blue", "red", "orange"],
responsivePercentage: false,
boards: [],
dragBoards: true,
addItemButton: false,
@ -67,7 +68,7 @@
click: function (el) {},
boardTitleclick: function (el, boardId) {},
buttonClick: function (el, boardId) {},
colorClick: function (el, boardId) {},
colorClick: function (el, type) {},
addItemClick: function (el, boardId) {},
onChange: function () {}
};
@ -85,12 +86,12 @@
//Init Drag Board
self.drakeBoard = self.dragula([self.container], {
moves: function (el, source, handle, sibling) {
if (self.options.readOnly) { return false; }
if (self.options.readOnly) { return false; }
if (!self.options.dragBoards) return false;
return (handle.classList.contains('kanban-board-header') || handle.classList.contains('kanban-title-board'));
},
accepts: function (el, target, source, sibling) {
if (self.options.readOnly) { return false; }
if (self.options.readOnly) { return false; }
return target.classList.contains('kanban-container');
},
revertOnSpill: true,
@ -144,15 +145,18 @@
//Init Drag Item
self.drake = self.dragula(self.boardContainer, {
moves: function (el, source, handle, sibling) {
if (self.options.readOnly) { return false; }
return handle.classList.contains('kanban-item');
},
accepts: function (el, target, source, sibling) {
if (self.options.readOnly) { return false; }
return true;
},
revertOnSpill: true
moves: function (el, source, handle, sibling) {
if (self.options.readOnly) { return false; }
return handle.classList.contains('kanban-item');
},
accepts: function (el, target, source, sibling) {
if (self.options.readOnly) { return false; }
return true;
},
revertOnSpill: true
})
.on('cancel', function(el, container, source) {
self.enableAllBoards();
})
.on('drag', function (el, source) {
// we need to calculate the position before starting to drag
@ -184,7 +188,9 @@
var boardId = source.parentNode.dataset.id;
self.options.dragcancelEl(el, boardId);
})
.on('drop', function (el, target, source, sibling) {
.on('drop', function(el, target, source, sibling) {
self.enableAllBoards();
console.log("In drop");
// TODO: update board object board order
@ -240,6 +246,15 @@
}
};
this.enableAllBoards = function() {
var allB = document.querySelectorAll('.kanban-board');
if (allB.length > 0 && allB !== undefined) {
for (var i = 0; i < allB.length; i++) {
allB[i].classList.remove('disabled-board');
}
}
};
this.addElement = function (boardID, element) {
// add Element to JSON
@ -260,6 +275,7 @@
nodeItem.dragendfn = element.dragend;
nodeItem.dropfn = element.drop;
__onclickHandler(nodeItem);
__onColorClickHandler(nodeItem, "item");
board.appendChild(nodeItem);
// send event that board has changed
self.onChange();
@ -272,8 +288,19 @@
return self;
};
this.addBoards = function (boards) {
var boardWidth = self.options.widthBoard;
this.addBoards = function(boards) {
if (self.options.responsivePercentage) {
self.container.style.width = '100%';
self.options.gutter = '1%';
if (window.innerWidth > self.options.responsive) {
var boardWidth = (100 - boards.length * 2) / boards.length;
} else {
var boardWidth = 100 - (boards.length * 2);
}
} else {
var boardWidth = self.options.widthBoard;
}
var addButton = self.options.addItemButton;
var buttonContent = self.options.buttonContent;
@ -290,7 +317,11 @@
boardNode.dataset.id = board.id;
boardNode.classList.add('kanban-board');
//set style
boardNode.style.width = boardWidth;
if (self.options.responsivePercentage) {
boardNode.style.width = boardWidth + '%';
} else {
boardNode.style.width = boardWidth;
}
boardNode.style.marginLeft = self.options.gutter;
boardNode.style.marginRight = self.options.gutter;
// header board
@ -303,6 +334,10 @@
headerBoard.classList.add(value);
});
if (board.color !== '' && board.color !== undefined) {
headerBoard._jscLinkedInstance = undefined;
jscolorL = new jscolor(headerBoard,{valueElement:undefined});
jscolorL.fromString(board.color);
headerBoard._jscLinkedInstance = undefined;
headerBoard.classList.add("kanban-header-" + board.color);
}
titleBoard = document.createElement('div');
@ -311,7 +346,7 @@
titleBoard.clickfn = board.boardTitleClick;
__onboardTitleClickHandler(titleBoard);
headerBoard.appendChild(titleBoard);
__onColorClickHandler(headerBoard);
__onColorClickHandler(headerBoard, "board");
// if add button is true, add button to the board
if (addButton) {
@ -332,14 +367,24 @@
var nodeItem = document.createElement('div');
nodeItem.classList.add('kanban-item');
nodeItem.dataset.eid = itemKanban.id;
nodeItem.innerHTML = itemKanban.title;
var nodeItemText = document.createElement('div');
nodeItemText.classList.add('kanban-item-text');
nodeItemText.dataset.eid = itemKanban.id;
nodeItemText.innerHTML = itemKanban.title;
nodeItem.appendChild(nodeItemText);
//add function
nodeItem.clickfn = itemKanban.click;
nodeItem.dragfn = itemKanban.drag;
nodeItem.dragendfn = itemKanban.dragend;
nodeItem.dropfn = itemKanban.drop;
nodeItemText.clickfn = itemKanban.click;
nodeItemText.dragfn = itemKanban.drag;
nodeItemText.dragendfn = itemKanban.dragend;
nodeItemText.dropfn = itemKanban.drop;
//add click handler of item
__onclickHandler(nodeItem);
__onclickHandler(nodeItemText);
if (itemKanban.color !== '' && itemKanban.color !== undefined) {
jscolorL = new jscolor(nodeItem,{valueElement:undefined});
jscolorL.fromString(itemKanban.color);
}
__onColorClickHandler(nodeItem, "item");
contentBoard.appendChild(nodeItem);
}
//footer board
@ -487,12 +532,10 @@
});
}
function __onColorClickHandler(nodeItem, clickfn) {
function __onColorClickHandler(nodeItem, type) {
nodeItem.addEventListener('click', function (e) {
e.preventDefault;
self.options.colorClick(this);
if (typeof (this.clickfn) === 'function')
this.clickfn(this);
self.options.colorClick(this, type);
});
}

File diff suppressed because it is too large Load Diff

@ -27,6 +27,7 @@ body.cp-app-pad {
height: 100%;
border: 0;
> .cke_inner {
overflow: hidden;
flex: 1;
position: unset;
display: flex;
@ -47,6 +48,20 @@ body.cp-app-pad {
display: block;
overflow-x: auto;
max-height: 100vh;
.cke_dialog_contents {
#ck-mediatag-preview {
margin: auto;
resize: both;
max-width: 300px;
max-height: 300px;
overflow: auto;
}
media-tag {
display: flex;
border-style: solid;
border-color: black;
}
}
}
.cke_wysiwyg_frame {

@ -43,9 +43,7 @@ CKEDITOR.dialog.add('mediatag', function (editor) {
type: 'html',
id: 'preview',
html: '<label>'+Messages.preview+'</label>'+
'<div id="ck-mediatag-preview"'+
'style="margin:auto;resize:both;max-width:300px;max-height:300px;overflow:auto"'+
'></div>'
'<div id="ck-mediatag-preview"></div>'
},
]
},
@ -77,11 +75,6 @@ CKEDITOR.dialog.add('mediatag', function (editor) {
var $preview = $(dialog).find('#ck-mediatag-preview');
var $clone = $(el.$).clone();
$clone.css({
display: 'flex',
'border-style': 'solid',
'border-color': 'black'
});
$preview.html('').append($clone);
var center = function () {
@ -125,7 +118,7 @@ CKEDITOR.dialog.add('mediatag', function (editor) {
update();
});
setTimeout(center);
setTimeout(update);
},
onOk: function() {
var dialog = this;

@ -1092,6 +1092,11 @@ define([
Test(passIfOk);
}
// No need for onLocal in openPadChat because in poll, we listen for metadata changes
// and save them everytime.
// See `metadataMgr.onChange(function () {`
common.openPadChat(function () {});
UI.removeLoadingScreen();
var privateDat = metadataMgr.getPrivateData();
var skipTemp = Util.find(privateDat,
@ -1160,6 +1165,7 @@ define([
var configTb = {
displayed: [
'chat',
'userlist',
'title',
'useradmin',

@ -37,7 +37,8 @@ define([
window.addEventListener('message', onMsg);
}).nThen(function (/*waitFor*/) {
SFCommonO.start({
useCreationScreen: true
useCreationScreen: true,
messaging: true
});
});
});

@ -236,9 +236,9 @@ define([
label: { class: 'noTitle' }
});
var $div2 = $(h('div.cp-settings-autostore-radio', [
opt1,
opt3,
opt2,
opt3
opt1
])).appendTo($div);
$div.find('input[type="radio"]').on('change', function () {

@ -107,6 +107,7 @@
.cp-app-slide-viewer {
width: 50vw;
overflow: hidden;
z-index: 998;
div#cp-app-slide-modal:not(.cp-app-slide-shown) {
position: relative;
top: auto;

@ -5,7 +5,7 @@
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer" />
<script async data-bootload="main.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<script async data-bootload="/common/sframe-app-outer.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>
html, body {
margin: 0px;

@ -1,43 +0,0 @@
// Load #1, load as little as possible because we are in a race to get the loading screen up.
define([
'/bower_components/nthen/index.js',
'/api/config',
'/common/dom-ready.js',
'/common/requireconfig.js',
'/common/sframe-common-outer.js'
], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
var requireConfig = RequireConfig();
// Loaded in load #2
nThen(function (waitFor) {
DomReady.onReady(waitFor());
}).nThen(function (waitFor) {
var req = {
cfg: requireConfig,
req: [ '/common/loading.js' ],
pfx: window.location.origin
};
window.rc = requireConfig;
window.apiconf = ApiConfig;
document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + '/whiteboard/inner.html?' + requireConfig.urlArgs +
'#' + encodeURIComponent(JSON.stringify(req)));
// This is a cheap trick to avoid loading sframe-channel in parallel with the
// loading screen setup.
var done = waitFor();
var onMsg = function (msg) {
var data = JSON.parse(msg.data);
if (data.q !== 'READY') { return; }
window.removeEventListener('message', onMsg);
var _done = done;
done = function () { };
_done();
};
window.addEventListener('message', onMsg);
}).nThen(function (/*waitFor*/) {
SFCommonO.start({
useCreationScreen: true
});
});
});
Loading…
Cancel
Save