Merge branch 'soon'

pull/1/head
ansuz 8 years ago
commit 9818c562fb

1
.gitignore vendored

@ -10,3 +10,4 @@ messages.log
.DS_Store .DS_Store
www/scratch www/scratch
data data
npm-debug.log

@ -9,7 +9,7 @@ branches:
- soon - soon
- staging - staging
node_js: node_js:
- "4.2.1" - "6.6.0"
before_script: before_script:
- npm run-script lint - npm run-script lint
- cp config.js.dist config.js - cp config.js.dist config.js

@ -24,23 +24,19 @@
"ckeditor": "~4.5.6", "ckeditor": "~4.5.6",
"codemirror": "^5.19.0", "codemirror": "^5.19.0",
"requirejs": "~2.1.15", "requirejs": "~2.1.15",
"reconnectingWebsocket": "",
"marked": "~0.3.5", "marked": "~0.3.5",
"rangy": "rangy-release#~1.3.0", "rangy": "rangy-release#~1.3.0",
"json.sortify": "~2.1.0", "json.sortify": "~2.1.0",
"fabric.js": "fabric#~1.6.0", "fabric.js": "fabric#~1.6.0",
"hyperjson": "~1.4.0", "hyperjson": "~1.4.0",
"textpatcher": "^1.3.0", "textpatcher": "^1.3.0",
"proxy-polyfill": "^0.1.5",
"chainpad": "^0.3.0", "chainpad": "^0.3.0",
"chainpad-json-validator": "^0.2.0", "chainpad-json-validator": "^0.2.0",
"chainpad-crypto": "^0.1.3", "chainpad-crypto": "^0.1.3",
"chainpad-listmap": "^0.3.0", "chainpad-listmap": "^0.3.0",
"lil-uri": "^0.2.1",
"file-saver": "^1.3.1", "file-saver": "^1.3.1",
"diff-dom": "^2.1.1", "diff-dom": "^2.1.1",
"alertifyjs": "^1.0.11", "alertifyjs": "^1.0.11",
"spin.js": "^2.3.2",
"scrypt-async": "^1.2.0", "scrypt-async": "^1.2.0",
"bootstrap": "#v4.0.0-alpha.6" "bootstrap": "#v4.0.0-alpha.6"
} }

@ -115,6 +115,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="version-footer">CryptPad v1.2.0 (Chupacabra)</div>
</footer> </footer>
</body> </body>

@ -112,6 +112,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="version-footer">CryptPad v1.2.0 (Chupacabra)</div>
</footer> </footer>
</body> </body>

@ -234,6 +234,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="version-footer">CryptPad v1.2.0 (Chupacabra)</div>
</footer> </footer>
</body> </body>

@ -1,6 +1,11 @@
/* Logs are shown to inform the user that something has happened /* Logs are shown to inform the user that something has happened
They are only displayed briefly They are only displayed briefly
*/ */
@media print {
.alertify-logs {
visibility: hidden;
}
}
.alertify-logs > * { .alertify-logs > * {
padding: 12px 48px; padding: 12px 48px;
color: #fafafa; color: #fafafa;
@ -56,11 +61,25 @@
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
} }
.alertify .dialog .bright,
.alertify .alert .bright {
color: #ffffff;
}
.alertify .dialog > div, .alertify .dialog > div,
.alertify .alert > div { .alertify .alert > div {
background-color: #444; background-color: #444;
border-radius: 5px; border-radius: 5px;
} }
.alertify .dialog > div.half,
.alertify .alert > div.half {
width: 50%;
}
@media (max-width: 600px) {
.alertify .dialog > div.half,
.alertify .alert > div.half {
width: 100%;
}
}
.alertify .dialog > *, .alertify .dialog > *,
.alertify .alert > * { .alertify .alert > * {
width: 30%; width: 30%;
@ -118,6 +137,34 @@
border: 1px solid #302B28; border: 1px solid #302B28;
border-radius: 5px; border-radius: 5px;
} }
.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button).safe,
.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button).safe,
.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button).danger,
.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button).danger {
color: #302B28;
white-space: normal;
font-weight: bold;
}
.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button).danger,
.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button).danger {
background-color: #FA5858;
}
.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button).danger:hover,
.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button).danger:hover,
.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button).danger:active,
.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button).danger:active {
background-color: #fb7171;
}
.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button).safe,
.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button).safe {
background-color: #46E981;
}
.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button).safe:hover,
.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button).safe:hover,
.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button).safe:active,
.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button).safe:active {
background-color: #74eea0;
}
.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):hover, .alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):hover,
.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):hover, .alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):hover,
.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):active, .alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):active,
@ -319,6 +366,7 @@
.cp #loading .cryptofist { .cp #loading .cryptofist {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
height: 300px;
} }
@media screen and (max-height: 450px) { @media screen and (max-height: 450px) {
.cp #loading .cryptofist { .cp #loading .cryptofist {
@ -332,6 +380,25 @@
.cp #loading .spinnerContainer > div { .cp #loading .spinnerContainer > div {
height: 100px; height: 100px;
} }
.cp #loadingTip {
position: fixed;
z-index: 99999;
top: 80%;
left: 0;
right: 0;
text-align: center;
}
.cp #loadingTip span {
background-color: #302B28;
color: #fafafa;
text-align: center;
font-size: 1.5em;
opacity: 0.7;
font-family: lato, Helvetica, sans-serif;
padding: 15px;
max-width: 60%;
display: inline-block;
}
/* The container <div> - needed to position the dropdown content */ /* The container <div> - needed to position the dropdown content */
.dropdown-bar { .dropdown-bar {
position: relative; position: relative;
@ -376,6 +443,10 @@
background-color: #f1f1f1; background-color: #f1f1f1;
color: black !important; color: black !important;
} }
.dropdown-bar .dropdown-bar-content a.active {
background-color: #e8e8e8;
color: black !important;
}
.dropdown-bar .dropdown-bar-content hr { .dropdown-bar .dropdown-bar-content hr {
margin: 5px 0px; margin: 5px 0px;
height: 1px; height: 1px;
@ -487,6 +558,14 @@
font-size: 1.2em; font-size: 1.2em;
font-weight: bold; font-weight: bold;
} }
.cp footer div.version-footer {
background-color: #302B28;
color: #fafafa;
text-align: center;
width: 100%;
padding-top: 10px;
padding-bottom: 10px;
}
html.cp, html.cp,
.cp body { .cp body {
font-size: .875em; font-size: .875em;
@ -977,11 +1056,6 @@ html.cp,
.cp #main_other .buttons { .cp #main_other .buttons {
margin-top: 15px; margin-top: 15px;
} }
.cp #fileManagerIframe {
width: 100%;
height: 500px;
margin-top: 15px;
}
.cp .create, .cp .create,
.cp .action { .cp .action {
display: inline-block; display: inline-block;
@ -1271,196 +1345,6 @@ html.cp,
.cp div.realtime #addoption { .cp div.realtime #addoption {
border-bottom-left-radius: 5px; border-bottom-left-radius: 5px;
} }
.cp.slide #modal .button {
position: absolute;
cursor: pointer;
font-size: 30px;
opacity: 0.6;
display: none;
}
.cp.slide #modal .button:hover {
opacity: 1;
display: block !important;
}
.cp.slide #modal #button_exit {
left: 20px;
top: 20px;
z-index: 9001;
}
.cp.slide #modal #button_left {
left: 6vw;
bottom: 10vh;
}
.cp.slide #modal #button_right {
right: 6vw;
bottom: 10vh;
}
.cp.slide #modal #content p,
.cp.slide #modal #content ul,
.cp.slide #modal #content ol {
font-size: 26px;
}
.cp.slide #modal #content img {
position: relative;
min-width: 1%;
max-width: 90%;
max-height: 90%;
margin: auto;
}
.cp div.modal,
.cp div#modal {
box-sizing: border-box;
z-index: 9001;
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100vh;
display: none;
background-color: #000;
}
.cp div.modal #content,
.cp div#modal #content {
box-sizing: border-box;
border: 1px solid white;
vertical-align: middle;
padding: 2.5vw;
/* center things as much as possible
margin-top: 50vh;
margin-bottom: 50vh;
transform: translateY(-50%);
*/
width: 100vw;
height: 56.25vw;
max-height: 100vh;
max-width: 177.78vh;
margin: auto;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.cp div.modal #content p,
.cp div#modal #content p,
.cp div.modal #content li,
.cp div#modal #content li,
.cp div.modal #content pre,
.cp div#modal #content pre,
.cp div.modal #content code,
.cp div#modal #content code {
font-size: 2.75vw;
line-height: 3.025vw;
}
.cp div.modal #content h1,
.cp div#modal #content h1 {
font-size: 5vw;
line-height: 5.5vw;
}
.cp div.modal #content h2,
.cp div#modal #content h2 {
font-size: 4.2vw;
line-height: 4.62vw;
}
.cp div.modal #content h3,
.cp div#modal #content h3 {
font-size: 3.6vw;
line-height: 3.96vw;
}
.cp div.modal #content h4,
.cp div#modal #content h4 {
font-size: 3vw;
line-height: 3.3vw;
}
.cp div.modal #content h5,
.cp div#modal #content h5 {
font-size: 2.2vw;
line-height: 2.42vw;
}
.cp div.modal #content h6,
.cp div#modal #content h6 {
font-size: 1.6vw;
line-height: 1.76vw;
}
.cp div.modal #content h1,
.cp div#modal #content h1,
.cp div.modal #content h2,
.cp div#modal #content h2,
.cp div.modal #content h3,
.cp div#modal #content h3,
.cp div.modal #content h4,
.cp div#modal #content h4,
.cp div.modal #content h5,
.cp div#modal #content h5,
.cp div.modal #content h6,
.cp div#modal #content h6 {
color: inherit;
}
.cp div.modal #content pre > code,
.cp div#modal #content pre > code {
display: block;
position: relative;
border: 1px solid #333;
width: 90%;
margin: auto;
padding-left: .25vw;
}
.cp div.modal #content ul,
.cp div#modal #content ul,
.cp div.modal #content ol,
.cp div#modal #content ol {
min-width: 50%;
max-width: 100%;
display: table;
margin: 0 auto;
}
.cp div.modal .center,
.cp div#modal .center {
position: relative;
width: 80%;
height: 80%;
margin: auto;
border: 1px solid #ffffff;
text-align: center;
}
.cp div.modal.shown,
.cp div#modal.shown {
display: block;
}
.cp div.modal table,
.cp div#modal table {
margin: 30px;
border-collapse: collapse;
}
.cp div.modal table input,
.cp div#modal table input {
height: 100%;
width: 90%;
border: 3px solid #fff;
}
.cp div.modal table tfoot tr td,
.cp div#modal table tfoot tr td {
z-index: 4000;
cursor: pointer;
}
.cp div.modal #addtime,
.cp div#modal #addtime,
.cp div.modal #adddate,
.cp div#modal #adddate {
color: #46E981;
border: 1px solid #46E981;
padding: 15px;
}
.cp div.modal #adddate,
.cp div#modal #adddate {
border-top-left-radius: 5px;
}
.cp div.modal #addtime,
.cp div#modal #addtime {
border-bottom-left-radius: 5px;
}
#cors-store { #cors-store {
display: none; display: none;
} }

@ -1,9 +1,8 @@
define([ define([
'/customize/application_config.js', '/customize/application_config.js',
'/common/cryptpad-common.js', '/common/cryptpad-common.js',
'/bower_components/lil-uri/uri.min.js',
'/bower_components/jquery/dist/jquery.min.js', '/bower_components/jquery/dist/jquery.min.js',
], function (Config, Cryptpad, LilUri) { ], function (Config, Cryptpad) {
var $ = window.$; var $ = window.$;
var APP = window.APP = { var APP = window.APP = {
@ -133,8 +132,10 @@ define([
if (result.proxy && !result.proxy.login_name) { if (result.proxy && !result.proxy.login_name) {
result.proxy.login_name = result.userName; result.proxy.login_name = result.userName;
} }
Cryptpad.login(result.userHash, result.userName, function () { Cryptpad.whenRealtimeSyncs(result.realtime, function () {
document.location.href = '/drive/'; Cryptpad.login(result.userHash, result.userName, function () {
document.location.href = '/drive/';
});
}); });
return; return;
} }

@ -1,11 +1,6 @@
(function () { (function () {
var LS_LANG = "CRYPTPAD_LANG"; var LS_LANG = "CRYPTPAD_LANG";
var getStoredLanguage = function () { return localStorage.getItem(LS_LANG); };
var getBrowserLanguage = function () { return navigator.language || navigator.userLanguage; };
var getLanguage = function () { return getStoredLanguage() || getBrowserLanguage(); };
var language = getLanguage();
// add your module to this map so it gets used // add your module to this map so it gets used
var map = { var map = {
'fr': 'Français', 'fr': 'Français',
@ -15,6 +10,19 @@ var map = {
'pt-br': 'Português do Brasil' 'pt-br': 'Português do Brasil'
}; };
var getStoredLanguage = function () { return localStorage.getItem(LS_LANG); };
var getBrowserLanguage = function () { return navigator.language || navigator.userLanguage; };
var getLanguage = function () {
if (getStoredLanguage()) { return getStoredLanguage(); }
var l = getBrowserLanguage() || '';
if (Object.keys(map).indexOf(l) !== -1) {
return l;
}
// Edge returns 'fr-FR' --> transform it to 'fr' and check again
return Object.keys(map).indexOf(l.split('-')[0]) !== -1 ? l.split('-')[0] : 'en';
};
var language = getLanguage();
var req = ['/customize/translations/messages.js']; var req = ['/customize/translations/messages.js'];
if (language && map[language]) { req.push('/customize/translations/messages.' + language + '.js'); } if (language && map[language]) { req.push('/customize/translations/messages.' + language + '.js'); }
req.push('/bower_components/jquery/dist/jquery.min.js'); req.push('/bower_components/jquery/dist/jquery.min.js');
@ -109,12 +117,7 @@ define(req, function(Default, Language) {
var $button = $(selector).find('button .buttonTitle'); var $button = $(selector).find('button .buttonTitle');
// Select the current language in the list // Select the current language in the list
var option = $(selector).find('[data-value="' + language + '"]'); var option = $(selector).find('[data-value="' + language + '"]');
if ($(option).length) { selector.setValue(language || 'English');
$button.text($(option).text());
}
else {
$button.text('English');
}
// Listen for language change // Listen for language change
$(selector).find('a.languageValue').on('click', function () { $(selector).find('a.languageValue').on('click', function () {

@ -133,6 +133,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="version-footer">CryptPad v1.2.0 (Chupacabra)</div>
</footer> </footer>
</body> </body>

@ -39,4 +39,5 @@
</div> </div>
</div> </div>
</div> </div>
<div class="version-footer">CryptPad v1.2.0 (Chupacabra)</div>
</footer> </footer>

@ -4,6 +4,9 @@
They are only displayed briefly They are only displayed briefly
*/ */
.alertify-logs { .alertify-logs {
@media print {
visibility: hidden;
}
> * { > * {
padding: @padding-base @padding-base * 4; padding: @padding-base @padding-base * 4;
color: @alertify-fore; color: @alertify-fore;
@ -58,10 +61,19 @@
} }
.dialog, .alert { .dialog, .alert {
.bright {
color: @light-base;
}
& > div { & > div {
background-color: @alertify-dialog-bg; background-color: @alertify-dialog-bg;
border-radius: 5px; border-radius: 5px;
&.half {
width: 50%;
@media (max-width: @media-medium-screen) {
width: 100%;
}
}
} }
width: 100%; width: 100%;
@ -133,6 +145,25 @@
border: 1px solid @alertify-base; border: 1px solid @alertify-base;
border-radius: 5px; border-radius: 5px;
&.safe, &.danger {
color: @old-base;
white-space: normal;
font-weight: bold;
}
&.danger {
background-color: @cp-red;
&:hover, &:active {
background-color: lighten(@cp-red, 5%);
}
}
&.safe {
background-color: @cp-green;
&:hover, &:active {
background-color: lighten(@cp-green, 10%);
}
}
&:hover, &:active { &:hover, &:active {
background-color: @alertify-btn-bg-hover; background-color: @alertify-btn-bg-hover;
} }

@ -494,12 +494,6 @@ noscript {
} }
} }
#fileManagerIframe {
width: 100%;
height: 500px;
margin-top: 15px;
}
/* buttons */ /* buttons */
.create, .action { .create, .action {
@ -816,192 +810,6 @@ form.realtime, div.realtime {
#adduser { .top-left; } #adduser { .top-left; }
#addoption { .bottom-left; } #addoption { .bottom-left; }
} }
// used for slides
.viewportRatio (@x, @y, @p: 100) {
width: @p * 100vw;
height: @y * (@p * 100vw) / @x;
max-width: @x / @y * (@p * 100vh);
max-height: (@p * 100vh);
}
&.slide {
#modal {
.button {
position: absolute;
cursor: pointer;
font-size: 30px;
opacity: 0.6;
display: none;
}
.button:hover {
opacity: 1;
display: block !important;
}
#button_exit {
left: 20px;
top: 20px;
z-index: 9001;
}
#button_left {
left: 6vw;
bottom: 10vh;
}
#button_right {
right: 6vw;
bottom: 10vh;
}
}
#modal #content {
p, ul, ol { font-size: 26px; }
img {
position: relative;
min-width: 1%;
max-width: 90%;
max-height: 90%;
margin: auto;
}
}
}
div.modal, div#modal {
display: none;
#content {
box-sizing: border-box;
border: 1px solid white;
vertical-align: middle;
padding: 2.5vw;
/* center things as much as possible
margin-top: 50vh;
margin-bottom: 50vh;
transform: translateY(-50%);
*/
width: 100vw;
height: 56.25vw; // height:width ratio = 9/16 = .5625
max-height: 100vh;
max-width: 177.78vh; // 16/9 = 1.778
margin: auto;
position: absolute;
top:0;bottom:0; // vertical center
left:0;right:0; // horizontal center
p, li, pre, code {
.size(2.75);
}
h1 { .size(5); }
h2 { .size(4.2); }
h3 { .size(3.6); }
h4 { .size (3); }
h5 { .size(2.2); }
h6 { .size(1.6); }
h1, h2, h3, h4, h5, h6 {
color: inherit;
}
pre > code {
display: block;
position: relative;
border: 1px solid #333;
width: 90%;
margin: auto;
padding-left: .25vw;
}
ul, ol {
min-width: 50%;
max-width: 100%;
display: table;
margin: 0 auto;
}
}
box-sizing: border-box;
z-index: 9001;
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100vh;
display: none;
background-color: @slide-default-bg;
.center {
position: relative;
width: 80%;
height: 80%;
margin: auto;
border: 1px solid @light-base;
text-align: center;
}
&.shown {
display: block;
}
table {
margin: 30px;
border-collapse: collapse;
tr {
td {
}
}
input {
height: 100%;
width: 90%;
border: 3px solid @base;
}
thead {
tr {
th {
span.remove {
}
}
}
}
tbody {
tr {
td {
}
}
}
tfoot {
tr {
td {
z-index: 4000;
cursor: pointer;
}
}
}
}
#addtime,
#adddate {
color: @cp-green;
border: 1px solid @cp-green;
padding: 15px;
}
#adddate { .top-left; }
#addtime { .bottom-left; }
}
} }
// hack for our cross-origin iframe // hack for our cross-origin iframe

@ -57,6 +57,11 @@
background-color: #f1f1f1; background-color: #f1f1f1;
color: black !important; color: black !important;
} }
&.active {
background-color: #e8e8e8;
color: black !important;
}
} }
hr { hr {

@ -18,4 +18,12 @@
font-size: 1.2em; font-size: 1.2em;
font-weight: bold; font-weight: bold;
} }
div.version-footer {
background-color: @old-base;
color: @old-fore;
text-align: center;
width: 100%;
padding-top: 10px;
padding-bottom: 10px;
}
} }

@ -16,6 +16,7 @@
.cryptofist { .cryptofist {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
height: 300px;
@media screen and (max-height: @media-short-screen) { @media screen and (max-height: @media-short-screen) {
display: none; display: none;
} }
@ -28,4 +29,22 @@
} }
} }
} }
.cp #loadingTip {
position: fixed;
z-index: 99999;
top: 80%;
left: 0;
right: 0;
text-align: center;
span {
background-color: @bg-loading;
color: @color-loading;
text-align: center;
font-size: 1.5em;
opacity: 0.7;
font-family: lato, Helvetica, sans-serif;
padding: 15px;
max-width: 60%;
display: inline-block;
}
}

@ -95,6 +95,7 @@
} }
button { button {
color: #000;
background-color: inherit; background-color: inherit;
background-image: linear-gradient(to bottom,#fff,#e4e4e4); background-image: linear-gradient(to bottom,#fff,#e4e4e4);
border: 1px solid #A6A6A6; border: 1px solid #A6A6A6;
@ -253,13 +254,12 @@
input { input {
font-size: 1.5em; font-size: 1.5em;
vertical-align: middle; vertical-align: middle;
height: 100%;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid black; border: 1px solid black;
background: #fff; background: #fff;
cursor: auto; cursor: auto;
width: 300px; width: 300px;
padding: 0px 5px; padding: 5px 5px;
} }
} }
.cryptpad-link { .cryptpad-link {
@ -312,6 +312,7 @@
//float: right; //float: right;
pre { pre {
white-space: pre; white-space: pre;
margin: 0;
} }
} }
button { button {

@ -116,6 +116,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="version-footer">CryptPad v1.2.0 (Chupacabra)</div>
</footer> </footer>
</body> </body>

@ -42,6 +42,10 @@
background-color: #f1f1f1; background-color: #f1f1f1;
color: black !important; color: black !important;
} }
.dropdown-bar .dropdown-bar-content a.active {
background-color: #e8e8e8;
color: black !important;
}
.dropdown-bar .dropdown-bar-content hr { .dropdown-bar .dropdown-bar-content hr {
margin: 5px 0px; margin: 5px 0px;
height: 1px; height: 1px;
@ -164,6 +168,7 @@
margin-right: 2px; margin-right: 2px;
} }
.cryptpad-toolbar button { .cryptpad-toolbar button {
color: #000;
background-color: inherit; background-color: inherit;
background-image: linear-gradient(to bottom, #fff, #e4e4e4); background-image: linear-gradient(to bottom, #fff, #e4e4e4);
border: 1px solid #A6A6A6; border: 1px solid #A6A6A6;
@ -324,13 +329,12 @@
.cryptpad-toolbar-top .cryptpad-title input { .cryptpad-toolbar-top .cryptpad-title input {
font-size: 1.5em; font-size: 1.5em;
vertical-align: middle; vertical-align: middle;
height: 100%;
box-sizing: border-box; box-sizing: border-box;
border: 1px solid black; border: 1px solid black;
background: #fff; background: #fff;
cursor: auto; cursor: auto;
width: 300px; width: 300px;
padding: 0px 5px; padding: 5px 5px;
} }
.cryptpad-toolbar-top .cryptpad-link { .cryptpad-toolbar-top .cryptpad-link {
position: absolute; position: absolute;
@ -374,6 +378,7 @@
} }
.cryptpad-toolbar-leftside .cryptpad-user-list pre { .cryptpad-toolbar-leftside .cryptpad-user-list pre {
white-space: pre; white-space: pre;
margin: 0;
} }
.cryptpad-toolbar-leftside button { .cryptpad-toolbar-leftside button {
margin: 2px 4px 2px 0px; margin: 2px 4px 2px 0px;

@ -35,10 +35,8 @@ define(function () {
out.orangeLight = "La conexión es lenta y podria impactar la experiencia"; out.orangeLight = "La conexión es lenta y podria impactar la experiencia";
out.redLight = "Has sido desconectado de la sesión"; out.redLight = "Has sido desconectado de la sesión";
out.importButton = 'Importar';
out.importButtonTitle = 'Importar un documento de tus archivos locales'; out.importButtonTitle = 'Importar un documento de tus archivos locales';
out.exportButton = 'Exportar';
out.exportButtonTitle = 'Exportar este documento a un archivo local'; out.exportButtonTitle = 'Exportar este documento a un archivo local';
out.exportPrompt = '¿Cómo te gustaría llamar a este archivo?'; out.exportPrompt = '¿Cómo te gustaría llamar a este archivo?';
@ -46,22 +44,16 @@ define(function () {
out.clickToEdit = "Haz clic para cambiar"; out.clickToEdit = "Haz clic para cambiar";
out.forgetButton = 'Olvidar';
out.forgetButtonTitle = 'Eliminar este documento de la lista en la pagina de inicio'; out.forgetButtonTitle = 'Eliminar este documento de la lista en la pagina de inicio';
out.forgetPrompt = 'Pulser OK eliminará este documento del almacenamiento local (localStorage), ¿estás seguro?'; out.forgetPrompt = 'Pulser OK eliminará este documento del almacenamiento local (localStorage), ¿estás seguro?';
out.shareButton = 'Compartir'; out.shareButton = 'Compartir';
out.shareSuccess = 'URL copiada al portapapeles'; out.shareSuccess = 'URL copiada al portapapeles';
out.presentButton = 'Presentar';
out.presentButtonTitle = "Entrar en el modo presentación"; out.presentButtonTitle = "Entrar en el modo presentación";
out.presentSuccess = 'ESC para salir del modo presentación'; out.presentSuccess = 'ESC para salir del modo presentación';
out.sourceButton = 'Ver código fuente';
out.sourceButtonTitle = "Abandonar modo presentación";
out.backgroundButton = 'Color de fondo';
out.backgroundButtonTitle = 'Cambiar el color de fondo en el modo presentación'; out.backgroundButtonTitle = 'Cambiar el color de fondo en el modo presentación';
out.colorButton = 'Color de texto';
out.colorButtonTitle = 'Cambiar el color de texto en el modo presentación'; out.colorButtonTitle = 'Cambiar el color de texto en el modo presentación';
out.editShare = "URL de edición compartida"; out.editShare = "URL de edición compartida";
@ -88,7 +80,6 @@ define(function () {
out.poll_p_save = "Tus configuraciones se actualizan instantaneamente, no es necesario guardar cambios."; out.poll_p_save = "Tus configuraciones se actualizan instantaneamente, no es necesario guardar cambios.";
out.poll_p_encryption = "Todos los datos entrados son cifrados, solo las personas que poseen el enlace tienen acceso. Incluso el servidor no puede ver el contenido."; out.poll_p_encryption = "Todos los datos entrados son cifrados, solo las personas que poseen el enlace tienen acceso. Incluso el servidor no puede ver el contenido.";
out.wizardButton = 'Asistente';
out.wizardLog = "Presiona el boton en la parte superior izquierda para volver a la encuesta"; out.wizardLog = "Presiona el boton en la parte superior izquierda para volver a la encuesta";
out.wizardTitle = "Utiliza el asistente para crear tu encuesta"; out.wizardTitle = "Utiliza el asistente para crear tu encuesta";
out.wizardConfirm = "¿Estás realmente seguro de agregar estas opciones a tu encuesta?"; out.wizardConfirm = "¿Estás realmente seguro de agregar estas opciones a tu encuesta?";
@ -326,5 +317,35 @@ define(function () {
out.readme_cat3_l2 = "Con los slides CryptPad, puedes hacer presentaciones rápidas con Markdown"; out.readme_cat3_l2 = "Con los slides CryptPad, puedes hacer presentaciones rápidas con Markdown";
out.readme_cat3_l3 = "Con CryptPoll puedes tomar votos rápidos, especialmente utíl para programar un horario que conviene a todo el mundo"; out.readme_cat3_l3 = "Con CryptPoll puedes tomar votos rápidos, especialmente utíl para programar un horario que conviene a todo el mundo";
return out; // 1.2.0 - Chupacabra
out.settings_resetError = "Verificación no válida. Tu CryptDrive no fue cambiado.";
out.saved = "Guardado";
out.printButton = "Imprimir";
out.printButtonTitle = "Imprimir tu presentación o exportar a PDF";
out.printOptions = "Opciones de impresión";
out.printSlideNumber = "Mostrar el número de diapositiva";
out.printDate = "Mostrar la fecha";
out.printTitle = "Mostrar el título";
out.printCSS = "CSS personalizado:";
out.editOpen = "Abrir enlances de edición en pestaña nueva";
out.editOpenTitle = "Abrir en modo edición en pestaña nueva";
out.settings_importTitle = "Importar pads recientes locales en CryptDrive";
out.settings_import = "Importar";
out.settings_importConfirm = "¿Seguro qué quieres importar tus pads recientes a tu cuenta CryptDrive?";
out.settings_importDone = "Importación terminada";
out.tips = {};
out.tips.lag = "El icono verde en la parte superior derecha muestra la calidad de tu connexión a CryptPad.";
out.tips.shortcuts = "`ctrl+b`, `ctrl+i`, y `ctrl+u` son accesos rápidos para negrita, itálica y subrayado.";
out.tips.indent = "Cuando editas listas, puedes usar tab o shift+tab para icrementar o decrementar indentación.";
out.tips.title = "Puedes cambiar el título de tus pads en la parte superior de la pantalla.";
out.tips.store = "Cada vez que visitas un pad con una sesión iniciada se guardará a tu CryptDrive.";
out.tips.marker = "Puedes resaltar texto en un pad utilizando el \"marcador\" en el menú de estílo.";
out.feedback_about = "Si estas leyendo esto, quizas estés curioso de saber porqué CryptPad solicita esta página cuando haces algunas acciones";
out.feedback_privacy = "Nos importa tu privacidad, y al mismo tiempo queremos que CryptPad sea muy fácil de usar. Utilizamos esta página para conocer las funcionalidades que importan a nuestros usuarios, pidiendolo con un parametro que nos dice que accion fue realizada.";
out.feedback_optout = "Si quieres darte de baja, visita <a href='/settings/'>tus preferencias</a>, donde podrás activar o desactivar feedback";
return out;
}); });

@ -25,6 +25,7 @@ define(function () {
out.loading = "Chargement..."; out.loading = "Chargement...";
out.error = "Erreur"; out.error = "Erreur";
out.saved = "Enregistré";
out.disconnected = 'Déconnecté'; out.disconnected = 'Déconnecté';
out.synchronizing = 'Synchronisation'; out.synchronizing = 'Synchronisation';
@ -48,10 +49,8 @@ define(function () {
out.orangeLight = "Votre connexion est lente, ce qui réduit la qualité de l'éditeur"; out.orangeLight = "Votre connexion est lente, ce qui réduit la qualité de l'éditeur";
out.redLight = "Vous êtes déconnectés de la session"; out.redLight = "Vous êtes déconnectés de la session";
out.importButton = 'Import';
out.importButtonTitle = 'Importer un pad depuis un fichier local'; out.importButtonTitle = 'Importer un pad depuis un fichier local';
out.exportButton = 'Exporter';
out.exportButtonTitle = 'Exporter ce pad vers un fichier local'; out.exportButtonTitle = 'Exporter ce pad vers un fichier local';
out.exportPrompt = 'Comment souhaitez-vous nommer ce fichier ?'; out.exportPrompt = 'Comment souhaitez-vous nommer ce fichier ?';
@ -62,7 +61,6 @@ define(function () {
out.clickToEdit = 'Cliquer pour modifier'; out.clickToEdit = 'Cliquer pour modifier';
out.forgetButton = 'Supprimer';
out.forgetButtonTitle = 'Déplacer ce pad vers la corbeille'; out.forgetButtonTitle = 'Déplacer ce pad vers la corbeille';
out.forgetPrompt = 'Cliquer sur OK déplacera ce pad vers la corbeille de votre CryptDrive, êtes-vous sûr ?'; out.forgetPrompt = 'Cliquer sur OK déplacera ce pad vers la corbeille de votre CryptDrive, êtes-vous sûr ?';
out.movedToTrash = 'Ce pad a été déplacé vers la corbeille.<br><a href="/drive/">Accéder à mon Drive</a>'; out.movedToTrash = 'Ce pad a été déplacé vers la corbeille.<br><a href="/drive/">Accéder à mon Drive</a>';
@ -73,20 +71,25 @@ define(function () {
out.newButton = 'Nouveau'; out.newButton = 'Nouveau';
out.newButtonTitle = 'Créer un nouveau pad'; out.newButtonTitle = 'Créer un nouveau pad';
out.presentButton = 'Present';
out.presentButtonTitle = "Entrer en mode présentation"; out.presentButtonTitle = "Entrer en mode présentation";
out.presentSuccess = 'Appuyer sur Échap pour quitter le mode présentation'; out.presentSuccess = 'Appuyer sur Échap pour quitter le mode présentation';
out.sourceButton = 'Voir la source';
out.sourceButtonTitle = "Quitter le mode présentation";
out.backgroundButton = 'Couleur de fond';
out.backgroundButtonTitle = 'Changer la couleur de fond de la présentation'; out.backgroundButtonTitle = 'Changer la couleur de fond de la présentation';
out.colorButton = 'Couleur du texte';
out.colorButtonTitle = 'Changer la couleur du texte en mode présentation'; out.colorButtonTitle = 'Changer la couleur du texte en mode présentation';
out.editShare = "Partager le lien d'édition"; out.printButton = "Imprimer";
out.printButtonTitle = "Imprimer votre présentation ou l'enregistrer au format PDF";
out.printOptions = "Options d'impression";
out.printSlideNumber = "Afficher le numéro des slides";
out.printDate = "Afficher la date";
out.printTitle = "Afficher le titre du pad";
out.printCSS = "Personnaliser l'apparence (CSS):";
out.editShare = "Lien d'édition";
out.editShareTitle = "Copier le lien d'édition dans le presse-papiers"; out.editShareTitle = "Copier le lien d'édition dans le presse-papiers";
out.viewShare = "Partager lien de lecture-seule"; out.editOpen = "Éditer dans un nouvel onglet";
out.editOpenTitle = "Ouvrir le lien d'édition dans un nouvel onglet";
out.viewShare = "Lien de lecture-seule";
out.viewShareTitle = "Copier lien d'accès en lecture seule dans le presse-papiers"; out.viewShareTitle = "Copier lien d'accès en lecture seule dans le presse-papiers";
out.viewOpen = "Voir dans un nouvel onglet"; out.viewOpen = "Voir dans un nouvel onglet";
out.viewOpenTitle = "Ouvrir le lien en lecture seule dans un nouvel onglet"; out.viewOpenTitle = "Ouvrir le lien en lecture seule dans un nouvel onglet";
@ -108,7 +111,6 @@ define(function () {
out.poll_p_save = "Vos modifications sont mises à jour instantanément, donc vous n'avez jamais besoin de sauver le contenu."; out.poll_p_save = "Vos modifications sont mises à jour instantanément, donc vous n'avez jamais besoin de sauver le contenu.";
out.poll_p_encryption = "Tout ce que vous entrez est chiffré donc seules les personnes possédant le lien du sondage y ont accès. Même le serveur ne peut pas voir le contenu."; out.poll_p_encryption = "Tout ce que vous entrez est chiffré donc seules les personnes possédant le lien du sondage y ont accès. Même le serveur ne peut pas voir le contenu.";
out.wizardButton = 'Assistant';
out.wizardLog = "Cliquez sur le bouton dans le coin supérieur gauche pour retourner au sondage"; out.wizardLog = "Cliquez sur le bouton dans le coin supérieur gauche pour retourner au sondage";
out.wizardTitle = "Utiliser l'assistant pour créer votre sondage"; out.wizardTitle = "Utiliser l'assistant pour créer votre sondage";
out.wizardConfirm = "Êtes-vous vraiment prêt à ajouter ces options au sondage ?"; out.wizardConfirm = "Êtes-vous vraiment prêt à ajouter ces options au sondage ?";
@ -223,10 +225,13 @@ define(function () {
out.register_importRecent = "Importer l'historique (Recommendé)"; out.register_importRecent = "Importer l'historique (Recommendé)";
out.register_acceptTerms = "J'accepte <a href='/terms.html'>les conditions d'utilisation</a>"; out.register_acceptTerms = "J'accepte <a href='/terms.html'>les conditions d'utilisation</a>";
out.register_rememberPassword = "Je vais me souvenir de mes identifiants";
out.register_passwordsDontMatch = "Les mots de passe doivent être identiques!"; out.register_passwordsDontMatch = "Les mots de passe doivent être identiques!";
out.register_mustAcceptTerms = "Vous devez accepter les conditions d'utilisation."; out.register_mustAcceptTerms = "Vous devez accepter les conditions d'utilisation.";
out.register_mustRememberPass = "Nous ne pouvons pas réinitialiser votre mot de passe si vous l'oubliez. C'est important que vous vous en souveniez! Veuillez cocher la case pour confirmer."; out.register_mustRememberPass = "Nous ne pouvons pas réinitialiser votre mot de passe si vous l'oubliez. C'est important que vous vous en souveniez! Veuillez cocher la case pour confirmer.";
out.register_writtenPassword = "J'ai bien noté mon nom d'utilisateur et mon mot de passe, continuer";
out.register_cancel = "Retour";
out.register_warning = "Zero Knowledge signifie que nous ne pouvons pas récupérer vos données si vous perdez vos identifiants.";
out.register_alreadyRegistered = "Cet utilisateur existe déjà, souhaitez-vous vous connecter ?";
out.register_header = "Bienvenue dans CryptPad"; out.register_header = "Bienvenue dans CryptPad";
out.register_explanation = [ out.register_explanation = [
@ -250,10 +255,16 @@ define(function () {
"Êtes-vous sûr de vouloir continuer ?<br>" + "Êtes-vous sûr de vouloir continuer ?<br>" +
"Tapez “<em>I love CryptPad</em>” pour confirmer."; "Tapez “<em>I love CryptPad</em>” pour confirmer.";
out.settings_resetDone = "Votre drive est désormais vide!"; out.settings_resetDone = "Votre drive est désormais vide!";
out.settings_resetError = "Texte de vérification incorrect. Votre CryptDrive n'a pas été modifié.";
out.settings_resetTips = "Astuces et informations dans CryptDrive"; out.settings_resetTips = "Astuces et informations dans CryptDrive";
out.settings_resetTipsButton = "Réinitialiser les astuces visibles dans CryptDrive"; out.settings_resetTipsButton = "Réinitialiser les astuces visibles dans CryptDrive";
out.settings_resetTipsDone = "Toutes les astuces sont de nouveau visibles."; out.settings_resetTipsDone = "Toutes les astuces sont de nouveau visibles.";
out.settings_importTitle = "Importer les pads récents de ce navigateur dans mon CryptDrive";
out.settings_import = "Importer";
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_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_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."; out.settings_userFeedbackHint2 = "Le contenu de vos pads et les clés de déchiffrement ne seront jamais partagés avec le serveur.";
out.settings_userFeedback = "Activer l'envoi de retours d'expérience"; out.settings_userFeedback = "Activer l'envoi de retours d'expérience";
@ -306,7 +317,6 @@ define(function () {
out.policy_whatweknow = 'Ce que nous savons de vous'; out.policy_whatweknow = 'Ce que nous savons de vous';
out.policy_whatweknow_p1 = 'En tant qu\'application hébergée sur le web, CryptPad a accès aux meta-données exposées par le protocole HTTP. Ceci inclus votre adresse IP et d\'autres en-têtes HTTP qui peuvent être utilisées pour identifier votre propre navigateur. Vous pouvez voir quelles informations votre navigateur partage en visitant <a target="_blank" rel="noopener noreferrer" href="https://www.whatismybrowser.com/detect/what-http-headers-is-my-browser-sending" title="what http headers is my browser sending">WhatIsMyBrowser.com</a>.'; out.policy_whatweknow_p1 = 'En tant qu\'application hébergée sur le web, CryptPad a accès aux meta-données exposées par le protocole HTTP. Ceci inclus votre adresse IP et d\'autres en-têtes HTTP qui peuvent être utilisées pour identifier votre propre navigateur. Vous pouvez voir quelles informations votre navigateur partage en visitant <a target="_blank" rel="noopener noreferrer" href="https://www.whatismybrowser.com/detect/what-http-headers-is-my-browser-sending" title="what http headers is my browser sending">WhatIsMyBrowser.com</a>.';
out.policy_whatweknow_p2 = 'Nous utilisons <a href="https://piwik.org/" target="_blank" rel="noopener noreferrer" title="open source analytics platform">Piwik</a>, une plateforme open source d\'analytique, afin d\'en apprendre plus sur nos utilisateurs. Piwik nous indique comment vous avez trouvé CryptPad, que ce soit par une entrée directe, par un moteur de recherche ou depuis un lien provenant d\'un autre site web tel que Reddit ou Twitter. Nous savons également quand vous visitez le site, sur quels liens vous cliquez dans les pages informatives et combien de temps vous restez sur une page donnée.'; out.policy_whatweknow_p2 = 'Nous utilisons <a href="https://piwik.org/" target="_blank" rel="noopener noreferrer" title="open source analytics platform">Piwik</a>, une plateforme open source d\'analytique, afin d\'en apprendre plus sur nos utilisateurs. Piwik nous indique comment vous avez trouvé CryptPad, que ce soit par une entrée directe, par un moteur de recherche ou depuis un lien provenant d\'un autre site web tel que Reddit ou Twitter. Nous savons également quand vous visitez le site, sur quels liens vous cliquez dans les pages informatives et combien de temps vous restez sur une page donnée.';
out.policy_whatweknow_p3 = 'Ces outils d\'analytique sont utilisés uniquement sur les pages informatives. Nous ne collectons aucune information concernant votre utilisation de nos applications "zero knowledge".';
out.policy_howweuse = 'Comment nous utilisons ce que nous apprenons'; out.policy_howweuse = 'Comment nous utilisons ce que nous apprenons';
out.policy_howweuse_p1 = 'Nous utilisons ces informations pour prendre de meilleures décisions concernant la communication autour de CryptPad, en évaluant le succès de ce qui a été realisé par le passé. Les informations concernant votre localisation nous permettent de savoir si nous devons considérer l\'ajout de traductions de CryptPad dans d\'autres langues que l\'anglais.'; out.policy_howweuse_p1 = 'Nous utilisons ces informations pour prendre de meilleures décisions concernant la communication autour de CryptPad, en évaluant le succès de ce qui a été realisé par le passé. Les informations concernant votre localisation nous permettent de savoir si nous devons considérer l\'ajout de traductions de CryptPad dans d\'autres langues que l\'anglais.';
out.policy_howweuse_p2 = "Les informations concernant votre navigateur (que ce soit un système d\'exploitation de bureau ou d\'appareil portable) nous aident à prendre des décisions lors de la priorisation des ajouts et améliorations de fonctionnalités. Notre équipe de développement est petite, et nous essayons de prendre des décisions qui amélioreront l\'expérience du plus grand nombre d\'utilisateurs possible."; out.policy_howweuse_p2 = "Les informations concernant votre navigateur (que ce soit un système d\'exploitation de bureau ou d\'appareil portable) nous aident à prendre des décisions lors de la priorisation des ajouts et améliorations de fonctionnalités. Notre équipe de développement est petite, et nous essayons de prendre des décisions qui amélioreront l\'expérience du plus grand nombre d\'utilisateurs possible.";
@ -357,13 +367,23 @@ define(function () {
'</small>', '</small>',
'</p>', '</p>',
].join(''); ].join('');
out.initialState = [
'<span style="font-size:18px;"><p>',
'Voici <strong>CryptPad</strong>, l\'éditeur collaboratif en temps-réel Zero Knowledge. Tout est sauvegardé dés que vous le tapez.',
'<br>',
'Partagez le lien vers ce pad avec des amis ou utilisez le bouton <span style="background-color:#449d44;color:#ffffff;">&nbsp;Partager&nbsp;</span> pour obtenir le <em>lien de lecture-seule</em>, qui permet la lecture mais non la modification.',
'</p>',
'<p><span style="color:#808080; font-size: 18px;">',
'<em>',
'Lancez-vous, commencez à taper...',
'</em></span></p></span>'
].join('');
out.codeInitialState = [ out.codeInitialState = [
'/*\n', '/*\n',
' Voici CryptPad, l\'éditeur collaboratif en temps-réel Zero Knowledge.\n', ' Voici l\'éditeur de code collaboratif et Zero Knowledge de CryptPad.\n',
' Ce que vous tapez ici est chiffré de manière que seules les personnes avec le lien peuvent y accéder.\n', ' Ce que vous tapez ici est chiffré de manière que seules les personnes avec le lien peuvent y accéder.\n',
' Même le serveur est incapable de voir ce que vous tapez.\n', ' Vous pouvez choisir le langage de programmation pour la coloration syntaxique, ainsi que le thème de couleurs, dans le coin supérieur droit.\n',
' Ce que vous voyez ici, ce que vous entendez, quand vous partez, ça reste ici.\n',
'*/' '*/'
].join(''); ].join('');
@ -405,5 +425,18 @@ define(function () {
out.readme_cat3_l2 = "Avec l'éditeur de présentations de CryptPad, vous pouvez réaliser des présentations rapides en utilisant Markdown"; out.readme_cat3_l2 = "Avec l'éditeur de présentations de CryptPad, vous pouvez réaliser des présentations rapides en utilisant Markdown";
out.readme_cat3_l3 = "Avec CryptPoll vous pouvez créer rapidement des sondages, et en particulier plannifier des meetings qui rentrent dans l'agenda de tout ceux qui souhaitent participer."; out.readme_cat3_l3 = "Avec CryptPoll vous pouvez créer rapidement des sondages, et en particulier plannifier des meetings qui rentrent dans l'agenda de tout ceux qui souhaitent participer.";
// Tips
out.tips = {};
out.tips.lag = "L'icône verte dans le coin supérieur droit montre la qualité de votre connexion Internet vers le serveur CryptPad.";
out.tips.shortcuts = "`ctrl+b`, `ctrl+i` et `ctrl+u` sont des raccourcis rapides pour mettre en gras, en italique ou souligner.";
out.tips.indent = "Dans les listes à puces ou numérotées, vous pouvez utiliser `Tab` ou `Maj+Tab` pour augmenter ou réduire rapidement l'indentation.";
out.tips.title = "Vous pouvez changer le titre de votre pad en cliquant au centre en haut de la page.";
out.tips.store = "Dés que vous ouvrez un nouveau pad, il est automatiquement stocké dans votre CryptDrive si vous êtes connectés.";
out.tips.marker = "Vous pouvez surligner du texte dans un pad en utilisant l'option \"marker\" dans le menu déroulant des styles.";
out.feedback_about = "Si vous lisez ceci, vous vous demandez probablement pourquoi CryptPad envoie des requêtes vers des pages web quand vous realisez certaines actions.";
out.feedback_privacy = "Nous prenons au sérieux le respect de votre vie privée, et en même temps nous souhaitons rendre CryptPad très simple à utiliser. Nous utilisons cette page pour comprendre quelles foncitonnalités dans l'interface comptent le plus pour les utilisateurs, en l'appelant avec un paramètre spécifiant quelle action a été réalisée.";
out.feedback_optout = "Si vous le souhaitez, vous pouvez désactiver ces requêtes en vous rendant dans <a href='/settings/'>votre page de préférences</a>, où vous trouverez une case à cocher pour désactiver le retour d'expérience.";
return out; return out;
}); });

@ -27,6 +27,7 @@ define(function () {
out.loading = "Loading..."; out.loading = "Loading...";
out.error = "Error"; out.error = "Error";
out.saved = "Saved";
out.disconnected = 'Disconnected'; out.disconnected = 'Disconnected';
out.synchronizing = 'Synchronizing'; out.synchronizing = 'Synchronizing';
@ -50,10 +51,8 @@ define(function () {
out.orangeLight = "Your slow connection may impact your experience"; out.orangeLight = "Your slow connection may impact your experience";
out.redLight = "You are disconnected from the session"; out.redLight = "You are disconnected from the session";
out.importButton = 'IMPORT';
out.importButtonTitle = 'Import a pad from a local file'; out.importButtonTitle = 'Import a pad from a local file';
out.exportButton = 'EXPORT';
out.exportButtonTitle = 'Export this pad to a local file'; out.exportButtonTitle = 'Export this pad to a local file';
out.exportPrompt = 'What would you like to name your file?'; out.exportPrompt = 'What would you like to name your file?';
@ -64,7 +63,6 @@ define(function () {
out.clickToEdit = "Click to edit"; out.clickToEdit = "Click to edit";
out.forgetButton = 'FORGET';
out.forgetButtonTitle = 'Move this pad to the trash'; out.forgetButtonTitle = 'Move this pad to the trash';
out.forgetPrompt = 'Clicking OK will move this pad to your trash. Are you sure?'; out.forgetPrompt = 'Clicking OK will move this pad to your trash. Are you sure?';
out.movedToTrash = 'That pad has been moved to the trash.<br><a href="/drive/">Access my Drive</a>'; out.movedToTrash = 'That pad has been moved to the trash.<br><a href="/drive/">Access my Drive</a>';
@ -75,22 +73,27 @@ define(function () {
out.newButton = 'New'; out.newButton = 'New';
out.newButtonTitle = 'Create a new pad'; out.newButtonTitle = 'Create a new pad';
out.presentButton = 'PRESENT';
out.presentButtonTitle = "Enter presentation mode"; out.presentButtonTitle = "Enter presentation mode";
out.presentSuccess = 'Hit ESC to exit presentation mode'; out.presentSuccess = 'Hit ESC to exit presentation mode';
out.sourceButton = 'VIEW SOURCE'; //TODO remove? hidden behind the present mode
out.sourceButtonTitle = "Leave presentation mode";
out.backgroundButton = 'BACKGROUND COLOR';
out.backgroundButtonTitle = 'Change the background color in the presentation'; out.backgroundButtonTitle = 'Change the background color in the presentation';
out.colorButton = 'TEXT COLOR';
out.colorButtonTitle = 'Change the text color in presentation mode'; out.colorButtonTitle = 'Change the text color in presentation mode';
out.printButton = "Print";
out.printButtonTitle = "Print your slides or export them as a PDF file";
out.printOptions = "Print options";
out.printSlideNumber = "Display the slide number";
out.printDate = "Display the date";
out.printTitle = "Display the pad title";
out.printCSS = "Custom style rules (CSS):";
out.editShare = "Editing link"; out.editShare = "Editing link";
out.editShareTitle = "Copy the edit link to clipboard"; out.editShareTitle = "Copy the editing link to clipboard";
out.editOpen = "Open editing link in a new tab";
out.editOpenTitle = "Open this pad in editing mode in a new tab";
out.viewShare = "Read-only link"; out.viewShare = "Read-only link";
out.viewShareTitle = "Copy the read-only link to clipboard"; out.viewShareTitle = "Copy the read-only link to clipboard";
out.viewOpen = "Open read-only link in new tab"; out.viewOpen = "Open read-only link in a new tab";
out.viewOpenTitle = "Open this pad in read-only mode in a new tab"; out.viewOpenTitle = "Open this pad in read-only mode in a new tab";
out.notifyJoined = "{0} has joined the collaborative session"; out.notifyJoined = "{0} has joined the collaborative session";
@ -99,7 +102,7 @@ define(function () {
out.okButton = 'OK (enter)'; out.okButton = 'OK (enter)';
out.cancel = "Cancel"; // Not used? out.cancel = "Cancel";
out.cancelButton = 'Cancel (esc)'; out.cancelButton = 'Cancel (esc)';
// Polls // Polls
@ -110,7 +113,6 @@ define(function () {
out.poll_p_save = "Your settings are updated instantly, so you never need to save."; out.poll_p_save = "Your settings are updated instantly, so you never need to save.";
out.poll_p_encryption = "All your input is encrypted so only people who have the link can access it. Even the server cannot see what you change."; out.poll_p_encryption = "All your input is encrypted so only people who have the link can access it. Even the server cannot see what you change.";
out.wizardButton = 'WIZARD';
out.wizardLog = "Click the button in the top left to return to your poll"; out.wizardLog = "Click the button in the top left to return to your poll";
out.wizardTitle = "Use the wizard to create your poll"; out.wizardTitle = "Use the wizard to create your poll";
out.wizardConfirm = "Are you really ready to add these options to your poll?"; out.wizardConfirm = "Are you really ready to add these options to your poll?";
@ -225,7 +227,6 @@ define(function () {
out.register_importRecent = "Import pad history (Recommended)"; out.register_importRecent = "Import pad history (Recommended)";
out.register_acceptTerms = "I accept <a href='/terms.html'>the terms of service</a>"; out.register_acceptTerms = "I accept <a href='/terms.html'>the terms of service</a>";
out.register_rememberPassword = "I will remember my login name and password";
out.register_passwordsDontMatch = "Passwords do not match!"; out.register_passwordsDontMatch = "Passwords do not match!";
out.register_mustAcceptTerms = "You must accept the terms of service."; out.register_mustAcceptTerms = "You must accept the terms of service.";
out.register_mustRememberPass = "We cannot reset your password if you forget it. It's very important that you remember it! Please check the checkbox to confirm."; out.register_mustRememberPass = "We cannot reset your password if you forget it. It's very important that you remember it! Please check the checkbox to confirm.";
@ -240,6 +241,13 @@ define(function () {
"</ul>" "</ul>"
].join(''); ].join('');
out.register_writtenPassword = "I have written down my username and password, proceed";
out.register_cancel = "Go back";
out.register_warning = "Zero Knowledge means that we can't recover your data if you lose your password.";
out.register_alreadyRegistered = "This user already exists, do you want to log in?";
// Settings // Settings
out.settings_title = "Settings"; out.settings_title = "Settings";
out.settings_save = "Save"; out.settings_save = "Save";
@ -252,10 +260,16 @@ define(function () {
"Are you sure you want to continue?<br>" + "Are you sure you want to continue?<br>" +
"Type “<em>I love CryptPad</em>” to confirm."; "Type “<em>I love CryptPad</em>” to confirm.";
out.settings_resetDone = "Your drive is now empty!"; out.settings_resetDone = "Your drive is now empty!";
out.settings_resetError = "Incorrect verification text. Your CryptDrive has not been changed.";
out.settings_resetTips = "Tips in CryptDrive"; out.settings_resetTips = "Tips in CryptDrive";
out.settings_resetTipsButton = "Reset the available tips in CryptDrive"; out.settings_resetTipsButton = "Reset the available tips in CryptDrive";
out.settings_resetTipsDone = "All the tips are now visible again."; out.settings_resetTipsDone = "All the tips are now visible again.";
out.settings_importTitle = "Import this browser's recent pads in my CryptDrive";
out.settings_import = "Import";
out.settings_importConfirm = "Are you sure you want to import recent pads from this browser to your user account's CryptDrive?";
out.settings_importDone = "Import completed";
out.settings_userFeedbackHint1 = "CryptPad provides some very basic feedback to the server, to let us know how to improve your experience."; out.settings_userFeedbackHint1 = "CryptPad provides some very basic feedback to the server, to let us know how to improve your experience.";
out.settings_userFeedbackHint2 = "Your pad's content will never be shared with the server."; out.settings_userFeedbackHint2 = "Your pad's content will never be shared with the server.";
out.settings_userFeedback = "Enable user feedback"; out.settings_userFeedback = "Enable user feedback";
@ -351,26 +365,22 @@ define(function () {
// Initial states // Initial states
out.initialState = [ out.initialState = [
'<p>', '<span style="font-size:18px;"><p>',
'This is <strong>CryptPad</strong>, the zero knowledge realtime collaborative editor.', 'This is&nbsp;<strong>CryptPad</strong>, the Zero Knowledge realtime collaborative editor. Everything is saved as you type.',
'<br>', '<br>',
'What you type here is encrypted so only people who have the link can access it.', 'Share the link to this pad to edit with friends or use the <span style="background-color:#449d44;color:#ffffff;">&nbsp;Share&nbsp;</span> button to share a <em>read-only link</em>&nbsp;which allows viewing but not editing.',
'<br>',
'Even the server cannot see what you type.',
'</p>',
'<p>',
'<small>',
'<i>What you see here, what you hear here, when you leave here, let it stay here</i>',
'</small>',
'</p>', '</p>',
'<p><span style="color:#808080;"><em>',
'Go ahead, just start typing...',
'</em></span></p></span>'
].join(''); ].join('');
out.codeInitialState = [ out.codeInitialState = [
'/*\n', '/*\n',
' This is CryptPad, the zero knowledge realtime collaborative editor.\n', ' This is the CryptPad Zero Knowledge collaborative code editor.\n',
' What you type here is encrypted so only people who have the link can access it.\n', ' What you type here is encrypted so only people who have the link can access it.\n',
' Even the server cannot see what you type.\n', ' You can choose the programming language to highlight and the UI color scheme in the upper right.\n',
' What you see here, what you hear here, when you leave here, let it stay here.\n',
'*/' '*/'
].join(''); ].join('');
@ -391,6 +401,8 @@ define(function () {
' - Your slides are updated in realtime' ' - Your slides are updated in realtime'
].join(''); ].join('');
// Readme
out.driveReadmeTitle = "What is CryptDrive?"; out.driveReadmeTitle = "What is CryptDrive?";
out.readme_welcome = "Welcome to CryptPad !"; out.readme_welcome = "Welcome to CryptPad !";
out.readme_p1 = "Welcome to CryptPad, this is where you can take note of things alone and with friends."; out.readme_p1 = "Welcome to CryptPad, this is where you can take note of things alone and with friends.";
@ -412,5 +424,18 @@ define(function () {
out.readme_cat3_l2 = "With CryptPad slide editor, you can make quick presentations using Markdown"; out.readme_cat3_l2 = "With CryptPad slide editor, you can make quick presentations using Markdown";
out.readme_cat3_l3 = "With CryptPoll you can take quick votes, especially for scheduling meetings which fit with everybody's calendar"; out.readme_cat3_l3 = "With CryptPoll you can take quick votes, especially for scheduling meetings which fit with everybody's calendar";
// Tips
out.tips = {};
out.tips.lag = "The green icon in the upper right shows the quality of your internet connection to the CryptPad server.";
out.tips.shortcuts = "`ctrl+b`, `ctrl+i` and `ctrl+u` are quick shortcuts for bold, italic and underline.";
out.tips.indent = "In numbered and bulleted lists, you can use tab or shift+tab to quickly increase or decrease indentation.";
out.tips.title = "You can set the title of your pad by clicking the top center.";
out.tips.store = "Every time you visit a pad, if you're logged in it will be saved to your CryptDrive.";
out.tips.marker = "You can highlight text in a pad using the \"marker\" item in the styles dropdown menu.";
out.feedback_about = "If you're reading this, you were probably curious why CryptPad is requesting web pages when you perform certain actions";
out.feedback_privacy = "We care about your privacy, and at the same time we want CryptPad to be very easy to use. We use this file to figure out which UI features matter to our users, by requesting it along with a parameter specifying which action was taken.";
out.feedback_optout = "If you would like to opt out, visit <a href='/settings/'>your user settings page</a>, where you'll find a checkbox to enable or disable user feedback";
return out; return out;
}); });

@ -1,7 +1,7 @@
{ {
"name": "cryptpad", "name": "cryptpad",
"description": "realtime collaborative visual editor with zero knowlege server", "description": "realtime collaborative visual editor with zero knowlege server",
"version": "1.1.1", "version": "1.2.0",
"dependencies": { "dependencies": {
"express": "~4.10.1", "express": "~4.10.1",
"ws": "^1.0.1", "ws": "^1.0.1",
@ -16,7 +16,7 @@
"scripts": { "scripts": {
"lint": "jshint --config .jshintrc --exclude-path .jshintignore .", "lint": "jshint --config .jshintrc --exclude-path .jshintignore .",
"test": "node TestSelenium.js", "test": "node TestSelenium.js",
"style": "lessc ./customize.dist/src/less/cryptpad.less > ./customize.dist/main.css && lessc ./customize.dist/src/less/toolbar.less > ./customize.dist/toolbar.css && lessc ./www/drive/file.less > ./www/drive/file.css && lessc ./www/settings/main.less > ./www/settings/main.css", "style": "lessc ./customize.dist/src/less/cryptpad.less > ./customize.dist/main.css && lessc ./customize.dist/src/less/toolbar.less > ./customize.dist/toolbar.css && lessc ./www/drive/file.less > ./www/drive/file.css && lessc ./www/settings/main.less > ./www/settings/main.css && lessc ./www/slide/slide.less > ./www/slide/slide.css",
"template": "cd customize.dist/src && node build.js" "template": "cd customize.dist/src && node build.js"
} }
} }

@ -35,13 +35,18 @@ bower install
## copy config.js.dist to config.js ## copy config.js.dist to config.js
cp config.js.dist config.js cp config.js.dist config.js
## modify configuration to use your own mongodb instance
## for example aon the default mongodb port `mongodb://localhost:27017/demo_database`
$EDITOR config.js
node ./server.js node ./server.js
``` ```
## Configuration
CryptPad _should_ work with an unmodified configuration file, though there are many things which you may want to customize.
Attributes in the config should have comments indicating how they are used.
```
$EDITOR config.js
```
## Maintenance ## Maintenance
To get access to the most recent codebase: To get access to the most recent codebase:
@ -63,18 +68,20 @@ npm update;
To reset your instance of Cryptpad and remove all the data that is being stored: To reset your instance of Cryptpad and remove all the data that is being stored:
If you are using the leveldb adaptor, this is as simple as deleting the folder which contains your leveldb datastore:
``` ```
# change into your cryptpade directory # change into your cryptpad directory
cd /your/cryptpad/instance/location; cd /your/cryptpad/instance/location;
# delete the datastore # delete the datastore
rm -rf ./cryptpad.db rm -rf ./datastore
``` ```
If you are using the mongodb adaptor, [drop the relevant collection](https://docs.mongodb.org/manual/reference/method/db.collection.drop/#db.collection.drop). If you are using the mongodb adaptor, [drop the relevant collection](https://docs.mongodb.org/manual/reference/method/db.collection.drop/#db.collection.drop).
If you are using the [leveldb adaptor](https://github.com/xwiki-labs/cryptpad-level-store), delete the datastore directory you have configured.
## Testing ## Testing
To test CryptPad, go to http://your.server:3000/assert/ To test CryptPad, go to http://your.server:3000/assert/
@ -82,6 +89,16 @@ To test CryptPad, go to http://your.server:3000/assert/
You can use WebDriver to run this test automatically by running TestSelenium.js but you will need chromedriver installed. You can use WebDriver to run this test automatically by running TestSelenium.js but you will need chromedriver installed.
If you use Mac, you can `brew install chromedriver`. If you use Mac, you can `brew install chromedriver`.
## Developing CryptPad
CryptPad is built with a lot of small javascript libraries.
To make js files load faster, we apply an aggressive caching policy.
If you want to add new features to CryptPad, you'll want to turn off caching.
You can do so by launching your server in _dev mode_, like so:
`DEV=1 node server.js`
# Setup using Docker # Setup using Docker
See [Cryptpad-Docker](cryptpad-docker.md) See [Cryptpad-Docker](cryptpad-docker.md)
@ -108,14 +125,14 @@ Still there are other low-lives in the world so using CryptPad over HTTPS is pro
## Translations ## Translations
We'd like to make it easy for more people to use encryption in their routine activities. We'd like to make it easy for more people to use encryption in their routine activities.
As such, we've tried to make language-specific parts of Cryptpad translatable. If you're As such, we've tried to make language-specific parts of CryptPad translatable. If you're
able to translate Cryptpad's interface, and would like to help, please contact us! able to translate CryptPad's interface, and would like to help, please contact us!
You can also see [our translation guide](/customize.dist/translations/README.md). You can also see [our translation guide](/customize.dist/translations/README.md).
## Contacting Us ## Contacting Us
You can reach members of the Cryptpad development team on [twitter](https://twitter.com/cryptpad), You can reach members of the CryptPad development team on [twitter](https://twitter.com/cryptpad),
via our [github issue tracker](https://github.com/xwiki-labs/cryptpad/issues/), on the via our [github issue tracker](https://github.com/xwiki-labs/cryptpad/issues/), on the
[freenode](http://webchat.freenode.net/?channels=%23cryptpad&uio=MT1mYWxzZSY5PXRydWUmMTE9Mjg3JjE1PXRydWUe7) [freenode](http://webchat.freenode.net/?channels=%23cryptpad&uio=MT1mYWxzZSY5PXRydWUmMTE9Mjg3JjE1PXRydWUe7)
irc network, or by [email](mailto:research@xwiki.com). irc network, or by [email](mailto:research@xwiki.com).
@ -137,11 +154,9 @@ published by the Free Software Foundation, either version 3 of the License, or (
any later version. If you wish to use this technology in a proprietary product, please contact any later version. If you wish to use this technology in a proprietary product, please contact
sales@xwiki.com sales@xwiki.com
* Icons thanks to http://www.famfamfam.com/ licensed [Creative Commons Attribution 2.5 License]
[ChainPad]: https://github.com/xwiki-contrib/chainpad [ChainPad]: https://github.com/xwiki-contrib/chainpad
[CKEditor]: http://ckeditor.com/ [CKEditor]: http://ckeditor.com/
[fragment identifier]: https://en.wikipedia.org/wiki/Fragment_identifier [fragment identifier]: https://en.wikipedia.org/wiki/Fragment_identifier
[active attack]: https://en.wikipedia.org/wiki/Attack_(computing)#Types_of_attacks [active attack]: https://en.wikipedia.org/wiki/Attack_(computing)#Types_of_attacks
[Creative Commons Attribution 2.5 License]: http://creativecommons.org/licenses/by/2.5/ [Creative Commons Attribution 2.5 License]: http://creativecommons.org/licenses/by/2.5/

@ -20,6 +20,11 @@ var app = Express();
var httpsOpts; var httpsOpts;
var DEV_MODE = !!process.env.DEV
if (DEV_MODE) {
console.log("DEV MODE ENABLED");
}
const clone = (x) => (JSON.parse(JSON.stringify(x))); const clone = (x) => (JSON.parse(JSON.stringify(x)));
var setHeaders = (function () { var setHeaders = (function () {
@ -96,7 +101,7 @@ app.get('/api/config', function(req, res){
res.send('define(' + JSON.stringify({ res.send('define(' + JSON.stringify({
requireConf: { requireConf: {
waitSeconds: 60, waitSeconds: 60,
urlArgs: 'ver=' + Package.version urlArgs: 'ver=' + Package.version + (DEV_MODE? '-' + (+new Date()): ''),
}, },
websocketPath: config.useExternalWebsocket ? undefined : config.websocketPath, websocketPath: config.useExternalWebsocket ? undefined : config.websocketPath,
websocketURL:'ws' + ((useSecureWebsockets) ? 's' : '') + '://' + host + ':' + websocketURL:'ws' + ((useSecureWebsockets) ? 's' : '') + '://' + host + ':' +

@ -11,6 +11,7 @@
data-main-favicon="/customize/main-favicon.png" data-main-favicon="/customize/main-favicon.png"
data-alt-favicon="/customize/alt-favicon.png" data-alt-favicon="/customize/alt-favicon.png"
id="favicon" /> id="favicon" />
<link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css">
<link rel="stylesheet" href="/customize/main.css" /> <link rel="stylesheet" href="/customize/main.css" />
<style> <style>
html, body { html, body {
@ -45,5 +46,14 @@
<div id="iframe-container"> <div id="iframe-container">
<iframe id="pad-iframe"></iframe><script src="/common/noscriptfix.js"></script> <iframe id="pad-iframe"></iframe><script src="/common/noscriptfix.js"></script>
</div> </div>
<div id="loading">
<div class="loadingContainer">
<img class="cryptofist" src="/customize/cryptofist_small.png" />
<div class="spinnerContainer">
<span class="fa fa-spinner fa-pulse fa-4x fa-fw"></span>
</div>
<p data-localization="loading"></p>
</div>
</div>
</body> </body>
</html> </html>

@ -81,7 +81,7 @@ define([
editor.setOption('mode', mode); editor.setOption('mode', mode);
if ($select) { if ($select) {
var name = $select.find('a[data-value="' + mode + '"]').text() || 'Mode'; var name = $select.find('a[data-value="' + mode + '"]').text() || 'Mode';
$select.find('.buttonTitle').text(name); $select.setValue(name);
} }
}; };
@ -110,7 +110,9 @@ define([
} }
editor.setOption('theme', theme); editor.setOption('theme', theme);
} }
if ($select) { $select.find('.buttonTitle').text(theme || 'Theme'); } if ($select) {
$select.setValue(theme || 'Theme');
}
}; };
}()); }());
@ -374,6 +376,10 @@ define([
userData: userData, userData: userData,
readOnly: readOnly, readOnly: readOnly,
ifrw: ifrw, ifrw: ifrw,
share: {
secret: secret,
channel: info.channel
},
title: { title: {
onRename: renameCb, onRename: renameCb,
defaultName: defaultName, defaultName: defaultName,
@ -386,8 +392,6 @@ define([
var $rightside = $bar.find('.' + Toolbar.constants.rightside); var $rightside = $bar.find('.' + Toolbar.constants.rightside);
var $userBlock = $bar.find('.' + Toolbar.constants.username); var $userBlock = $bar.find('.' + Toolbar.constants.username);
var $editShare = $bar.find('.' + Toolbar.constants.editShare);
var $viewShare = $bar.find('.' + Toolbar.constants.viewShare);
var $usernameButton = module.$userNameButton = $($bar.find('.' + Toolbar.constants.changeUsername)); var $usernameButton = module.$userNameButton = $($bar.find('.' + Toolbar.constants.changeUsername));
var editHash; var editHash;
@ -419,17 +423,6 @@ define([
var $forgetPad = Cryptpad.createButton('forget', true, {}, forgetCb); var $forgetPad = Cryptpad.createButton('forget', true, {}, forgetCb);
$rightside.append($forgetPad); $rightside.append($forgetPad);
if (!readOnly) {
$editShare.append(Cryptpad.createButton('editshare', false, {editHash: editHash}));
}
if (viewHash) {
/* add a 'links' button */
$viewShare.append(Cryptpad.createButton('viewshare', false, {viewHash: viewHash}));
if (!readOnly) {
$viewShare.append(Cryptpad.createButton('viewopen', false, {viewHash: viewHash}));
}
}
var configureLanguage = function (cb) { var configureLanguage = function (cb) {
// FIXME this is async so make it happen as early as possible // FIXME this is async so make it happen as early as possible
var options = []; var options = [];
@ -447,13 +440,14 @@ define([
text: 'Mode', // Button initial text text: 'Mode', // Button initial text
options: options, // Entries displayed in the menu options: options, // Entries displayed in the menu
left: true, // Open to the left of the button left: true, // Open to the left of the button
isSelect: true,
}; };
var $block = module.$language = Cryptpad.createDropdown(dropdownConfig); var $block = module.$language = Cryptpad.createDropdown(dropdownConfig);
var $button = $block.find('.buttonTitle'); var $button = $block.find('.buttonTitle');
$block.find('a').click(function (e) { $block.find('a').click(function (e) {
setMode($(this).attr('data-value')); setMode($(this).attr('data-value'), $block);
$button.text($(this).text()); onLocal();
}); });
$rightside.append($block); $rightside.append($block);
@ -480,6 +474,8 @@ define([
text: 'Theme', // Button initial text text: 'Theme', // Button initial text
options: options, // Entries displayed in the menu options: options, // Entries displayed in the menu
left: true, // Open to the left of the button left: true, // Open to the left of the button
isSelect: true,
initialValue: lastTheme
}; };
var $block = module.$theme = Cryptpad.createDropdown(dropdownConfig); var $block = module.$theme = Cryptpad.createDropdown(dropdownConfig);
var $button = $block.find('.buttonTitle'); var $button = $block.find('.buttonTitle');
@ -489,7 +485,6 @@ define([
$block.find('a').click(function (e) { $block.find('a').click(function (e) {
var theme = $(this).attr('data-value'); var theme = $(this).attr('data-value');
setTheme(theme, $block); setTheme(theme, $block);
$button.text($(this).text());
localStorage.setItem(themeKey, theme); localStorage.setItem(themeKey, theme);
}); });

@ -12,6 +12,8 @@ define([
S.cb(err, doc); S.cb(err, doc);
S.done = true; S.done = true;
var disconnect = Cryptpad.find(S, ['network', 'disconnect']);
if (typeof(disconnect) === 'function') { disconnect(); }
var abort = Cryptpad.find(S, ['realtime', 'realtime', 'abort']); var abort = Cryptpad.find(S, ['realtime', 'realtime', 'abort']);
if (typeof(abort) === 'function') { if (typeof(abort) === 'function') {
S.realtime.realtime.sync(); S.realtime.realtime.sync();
@ -50,6 +52,7 @@ define([
var onReady = config.onReady = function (info) { var onReady = config.onReady = function (info) {
var rt = Session.session = info.realtime; var rt = Session.session = info.realtime;
Session.network = info.network;
finish(Session, void 0, rt.getUserDoc()); finish(Session, void 0, rt.getUserDoc());
}; };
overwrite(config, opt); overwrite(config, opt);
@ -66,6 +69,7 @@ define([
var Session = { cb: cb, }; var Session = { cb: cb, };
config.onReady = function (info) { config.onReady = function (info) {
var realtime = Session.session = info.realtime; var realtime = Session.session = info.realtime;
Session.network = info.network;
TextPatcher.create({ TextPatcher.create({
realtime: realtime, realtime: realtime,

@ -1,16 +1,14 @@
define([ define([
'/api/config', '/api/config',
'/customize/messages.js?app=' + window.location.pathname.split('/').filter(function (x) { return x; }).join('.'), '/customize/messages.js?app=' + window.location.pathname.split('/').filter(function (x) { return x; }).join('.'),
'/customize/fsStore.js', '/common/fsStore.js',
'/bower_components/chainpad-crypto/crypto.js?v=0.1.5', '/bower_components/chainpad-crypto/crypto.js?v=0.1.5',
'/bower_components/alertifyjs/dist/js/alertify.js', '/bower_components/alertifyjs/dist/js/alertify.js',
'/bower_components/spin.js/spin.min.js',
'/common/clipboard.js', '/common/clipboard.js',
'/customize/fsStore.js',
'/customize/application_config.js', '/customize/application_config.js',
'/bower_components/jquery/dist/jquery.min.js', '/bower_components/jquery/dist/jquery.min.js',
], function (Config, Messages, Store, Crypto, Alertify, Spinner, Clipboard, AppConfig) { ], function (Config, Messages, Store, Crypto, Alertify, Clipboard, AppConfig) {
/* This file exposes functionality which is specific to Cryptpad, but not to /* This file exposes functionality which is specific to Cryptpad, but not to
any particular pad type. This includes functions for committing metadata any particular pad type. This includes functions for committing metadata
about pads to your local storage for future use and improved usability. about pads to your local storage for future use and improved usability.
@ -22,6 +20,7 @@ define([
var common = window.Cryptpad = { var common = window.Cryptpad = {
Messages: Messages, Messages: Messages,
Alertify: Alertify, Alertify: Alertify,
Clipboard: Clipboard
}; };
var store; var store;
@ -223,6 +222,7 @@ define([
if (typeof keys === 'string') { if (typeof keys === 'string') {
return chanKey + keys; return chanKey + keys;
} }
if (!keys.editKeyStr) { return; }
return '/1/edit/' + hexToBase64(chanKey) + '/' + Crypto.b64RemoveSlashes(keys.editKeyStr); return '/1/edit/' + hexToBase64(chanKey) + '/' + Crypto.b64RemoveSlashes(keys.editKeyStr);
}; };
var getViewHashFromKeys = common.getViewHashFromKeys = function (chanKey, keys) { var getViewHashFromKeys = common.getViewHashFromKeys = function (chanKey, keys) {
@ -304,6 +304,18 @@ define([
return secret; return secret;
}; };
var getHashes = common.getHashes = function (channel, secret) {
var hashes = {};
if (secret.keys.editKeyStr) {
hashes.editHash = getEditHashFromKeys(channel, secret.keys);
}
if (secret.keys.viewKeyStr) {
hashes.viewHash = getViewHashFromKeys(channel, secret.keys);
}
return hashes;
};
var uint8ArrayToHex = common.uint8ArrayToHex = function (a) { var uint8ArrayToHex = common.uint8ArrayToHex = function (a) {
// call slice so Uint8Arrays work as expected // call slice so Uint8Arrays work as expected
return Array.prototype.slice.call(a).map(function (e, i) { return Array.prototype.slice.call(a).map(function (e, i) {
@ -427,6 +439,8 @@ define([
var ret = {}; var ret = {};
if (!href) { return ret; }
if (!/^https*:\/\//.test(href)) { if (!/^https*:\/\//.test(href)) {
var idx = href.indexOf('/#'); var idx = href.indexOf('/#');
ret.type = href.slice(1, idx); ret.type = href.slice(1, idx);
@ -605,22 +619,52 @@ define([
}; };
// STORAGE // STORAGE
var isNotStrongestStored = common.isNotStrongestStored = function (href, recents) { var findWeaker = common.findWeaker = function (href, recents) {
var parsed = parsePadUrl(href); var rHref = href || getRelativeHref(window.location.href);
var parsed = parsePadUrl(rHref);
if (!parsed.hash) { return false; } if (!parsed.hash) { return false; }
return recents.some(function (pad) { var weaker;
recents.some(function (pad) {
var p = parsePadUrl(pad.href); var p = parsePadUrl(pad.href);
if (p.type !== parsed.type) { return false; } // Not the same type if (p.type !== parsed.type) { return; } // Not the same type
if (p.hash === parsed.hash) { return false; } // Same hash, not stronger if (p.hash === parsed.hash) { return; } // Same hash, not stronger
var pHash = parseHash(p.hash); var pHash = parseHash(p.hash);
var parsedHash = parseHash(parsed.hash); var parsedHash = parseHash(parsed.hash);
if (!parsedHash || !pHash) { return; } if (!parsedHash || !pHash) { return; }
if (pHash.version !== parsedHash.version) { return false; } if (pHash.version !== parsedHash.version) { return; }
if (pHash.channel !== parsedHash.channel) { return false; } if (pHash.channel !== parsedHash.channel) { return; }
if (pHash.mode === 'edit' && parsedHash.mode === 'view') { return true; } if (pHash.mode === 'view' && parsedHash.mode === 'edit') {
if (pHash.mode === parsedHash.mode && parsedHash.present) { return true; } weaker = pad.href;
return false; return true;
}
return;
}); });
return weaker;
};
var findStronger = common.findStronger = function (href, recents) {
var rHref = href || getRelativeHref(window.location.href);
var parsed = parsePadUrl(rHref);
if (!parsed.hash) { return false; }
var stronger;
recents.some(function (pad) {
var p = parsePadUrl(pad.href);
if (p.type !== parsed.type) { return; } // Not the same type
if (p.hash === parsed.hash) { return; } // Same hash, not stronger
var pHash = parseHash(p.hash);
var parsedHash = parseHash(parsed.hash);
if (!parsedHash || !pHash) { return; }
if (pHash.version !== parsedHash.version) { return; }
if (pHash.channel !== parsedHash.channel) { return; }
if (pHash.mode === 'edit' && parsedHash.mode === 'view') {
stronger = pad.href;
return true;
}
return;
});
return stronger;
};
var isNotStrongestStored = common.isNotStrongestStored = function (href, recents) {
return findStronger(href, recents);
}; };
var setPadTitle = common.setPadTitle = function (name, cb) { var setPadTitle = common.setPadTitle = function (name, cb) {
var href = window.location.href; var href = window.location.href;
@ -632,6 +676,7 @@ define([
return; return;
} }
var updateWeaker = [];
var contains; var contains;
var renamed = recent.map(function (pad) { var renamed = recent.map(function (pad) {
var p = parsePadUrl(pad.href); var p = parsePadUrl(pad.href);
@ -665,6 +710,14 @@ define([
// set the name // set the name
pad.title = name; pad.title = name;
// If we now have a stronger version of a stored href, replace the weaker one by the strong one
if (pad && pad.href && href !== pad.href) {
updateWeaker.push({
o: pad.href,
n: href
});
}
pad.href = href; pad.href = href;
} }
return pad; return pad;
@ -679,6 +732,11 @@ define([
} }
setRecentPads(renamed, function (err, data) { setRecentPads(renamed, function (err, data) {
if (updateWeaker.length > 0) {
updateWeaker.forEach(function (obj) {
getStore().replaceHref(obj.o, obj.n);
});
}
cb(err, data); cb(err, data);
}); });
}); });
@ -817,23 +875,46 @@ define([
}; };
var LOADING = 'loading'; var LOADING = 'loading';
var getRandomTip = function () {
if (!Messages.tips || !Object.keys(Messages.tips).length) { return ''; }
var keys = Object.keys(Messages.tips);
var rdm = Math.floor(Math.random() * keys.length);
return Messages.tips[keys[rdm]];
};
common.addLoadingScreen = function (loadingText) { common.addLoadingScreen = function (loadingText) {
var $loading, $container;
if ($('#' + LOADING).length) { if ($('#' + LOADING).length) {
$('#' + LOADING).show(); $loading = $('#' + LOADING).show();
return; if (loadingText) {
$('#' + LOADING).find('p').text(loadingText);
}
$container = $loading.find('.loadingContainer');
} else {
$loading = $('<div>', {id: LOADING});
$container = $('<div>', {'class': 'loadingContainer'});
$container.append('<img class="cryptofist" src="/customize/cryptofist_small.png" />');
var $spinner = $('<div>', {'class': 'spinnerContainer'});
common.spinner($spinner).show();
var $text = $('<p>').text(loadingText || Messages.loading);
$container.append($spinner).append($text);
$loading.append($container);
$('body').append($loading);
}
if (Messages.tips) {
var $loadingTip = $('<div>', {'id': 'loadingTip'});
var $tip = $('<span>', {'class': 'tips'}).text(getRandomTip()).appendTo($loadingTip);
$loadingTip.css({
'top': $('body').height()/2 + $container.height()/2 + 20 + 'px'
});
$('body').append($loadingTip);
} }
var $loading = $('<div>', {id: LOADING});
var $container = $('<div>', {'class': 'loadingContainer'});
$container.append('<img class="cryptofist" src="/customize/cryptofist_small.png" />');
var $spinner = $('<div>', {'class': 'spinnerContainer'});
var loadingSpinner = common.spinner($spinner).show();
var $text = $('<p>').text(loadingText || Messages.loading);
$container.append($spinner).append($text);
$loading.append($container);
$('body').append($loading);
}; };
common.removeLoadingScreen = function (cb) { common.removeLoadingScreen = function (cb) {
$('#' + LOADING).fadeOut(750, cb); $('#' + LOADING).fadeOut(750, cb);
$('#loadingTip').css('top', '');
window.setTimeout(function () {
$('#loadingTip').fadeOut(750);
}, 3000);
}; };
common.errorLoadingScreen = function (error, transparent) { common.errorLoadingScreen = function (error, transparent) {
if (!$('#' + LOADING).is(':visible')) { common.addLoadingScreen(); } if (!$('#' + LOADING).is(':visible')) { common.addLoadingScreen(); }
@ -912,7 +993,7 @@ define([
switch (type) { switch (type) {
case 'export': case 'export':
button = $('<button>', { button = $('<button>', {
title: Messages.exportButton + '\n' + Messages.exportButtonTitle, title: Messages.exportButtonTitle,
}).append($('<span>', {'class':'fa fa-download', style: 'font:'+size+' FontAwesome'})); }).append($('<span>', {'class':'fa fa-download', style: 'font:'+size+' FontAwesome'}));
if (callback) { if (callback) {
button.click(callback); button.click(callback);
@ -920,7 +1001,7 @@ define([
break; break;
case 'import': case 'import':
button = $('<button>', { button = $('<button>', {
title: Messages.importButton + '\n' + Messages.importButtonTitle, title: Messages.importButtonTitle,
}).append($('<span>', {'class':'fa fa-upload', style: 'font:'+size+' FontAwesome'})); }).append($('<span>', {'class':'fa fa-upload', style: 'font:'+size+' FontAwesome'}));
if (callback) { if (callback) {
button.click(common.importContent('text/plain', function (content, file) { button.click(common.importContent('text/plain', function (content, file) {
@ -931,7 +1012,7 @@ define([
case 'forget': case 'forget':
button = $('<button>', { button = $('<button>', {
id: 'cryptpad-forget', id: 'cryptpad-forget',
title: Messages.forgetButton + '\n' + Messages.forgetButtonTitle, title: Messages.forgetButtonTitle,
'class': "fa fa-trash cryptpad-forget", 'class': "fa fa-trash cryptpad-forget",
style: 'font:'+size+' FontAwesome' style: 'font:'+size+' FontAwesome'
}); });
@ -970,6 +1051,7 @@ define([
}); });
} }
break; break;
// TODO remove editshare, viewshare, and viewopen
case 'editshare': case 'editshare':
button = $('<a>', { button = $('<a>', {
title: Messages.editShareTitle, title: Messages.editShareTitle,
@ -1020,14 +1102,14 @@ define([
break; break;
case 'present': case 'present':
button = $('<button>', { button = $('<button>', {
title: Messages.presentButton + '\n' + Messages.presentButtonTitle, title: Messages.presentButtonTitle,
'class': "fa fa-play-circle cryptpad-present-button", // class used in slide.js 'class': "fa fa-play-circle cryptpad-present-button", // class used in slide.js
style: 'font:'+size+' FontAwesome' style: 'font:'+size+' FontAwesome'
}); });
break; break;
case 'source': case 'source':
button = $('<button>', { button = $('<button>', {
title: Messages.sourceButton + '\n' + Messages.sourceButtonTitle, title: Messages.sourceButtonTitle,
'class': "fa fa-stop-circle cryptpad-source-button", // class used in slide.js 'class': "fa fa-stop-circle cryptpad-source-button", // class used in slide.js
style: 'font:'+size+' FontAwesome' style: 'font:'+size+' FontAwesome'
}); });
@ -1063,10 +1145,15 @@ define([
// Container // Container
var $container = $(config.container); var $container = $(config.container);
var containerConfig = {
'class': 'dropdown-bar'
};
if (config.buttonTitle) {
containerConfig.title = config.buttonTitle;
}
if (!config.container) { if (!config.container) {
$container = $('<span>', { $container = $('<span>', containerConfig);
'class': 'dropdown-bar'
});
} }
// Button // Button
@ -1088,6 +1175,34 @@ define([
$container.append($button).append($innerblock); $container.append($button).append($innerblock);
var value = config.initialValue || '';
var setActive = function ($el) {
if ($el.length !== 1) { return; }
$innerblock.find('.active').removeClass('active');
$el.addClass('active');
var scroll = $el.position().top + $innerblock.scrollTop();
if (scroll < $innerblock.scrollTop()) {
$innerblock.scrollTop(scroll);
} else if (scroll > ($innerblock.scrollTop() + 280)) {
$innerblock.scrollTop(scroll-270);
}
};
var hide = function () {
window.setTimeout(function () { $innerblock.hide(); }, 0);
};
var show = function () {
$innerblock.show();
$innerblock.find('.active').removeClass('active');
if (config.isSelect && value) {
var $val = $innerblock.find('[data-value="'+value+'"]');
setActive($val);
$innerblock.scrollTop($val.position().top + $innerblock.scrollTop());
}
};
$button.click(function (e) { $button.click(function (e) {
e.stopPropagation(); e.stopPropagation();
var state = $innerblock.is(':visible'); var state = $innerblock.is(':visible');
@ -1100,12 +1215,64 @@ define([
// empty try catch in case this iframe is problematic (cross-origin) // empty try catch in case this iframe is problematic (cross-origin)
} }
if (state) { if (state) {
$innerblock.hide(); hide();
return; return;
} }
$innerblock.show(); show();
}); });
if (config.isSelect) {
var pressed = '';
var to;
$container.keydown(function (e) {
var $value = $innerblock.find('[data-value].active');
if (e.which === 38) { // Up
if ($value.length) {
var $prev = $value.prev();
setActive($prev);
}
}
if (e.which === 40) { // Down
if ($value.length) {
var $next = $value.next();
setActive($next);
}
}
if (e.which === 13) { //Enter
if ($value.length) {
$value.click();
hide();
}
}
if (e.which === 27) { // Esc
hide();
}
});
$container.keypress(function (e) {
window.clearTimeout(to);
var c = String.fromCharCode(e.which);
pressed += c;
var $value = $innerblock.find('[data-value^="'+pressed+'"]:first');
if ($value.length) {
setActive($value);
$innerblock.scrollTop($value.position().top + $innerblock.scrollTop());
}
to = window.setTimeout(function () {
pressed = '';
}, 1000);
});
$container.setValue = function (val) {
value = val;
var $val = $innerblock.find('[data-value="'+val+'"]');
var textValue = $val.html() || val;
$button.find('.buttonTitle').html(textValue);
};
$container.getValue = function () {
return value || '';
};
}
return $container; return $container;
}; };
@ -1130,7 +1297,8 @@ define([
text: Messages.language, // Button initial text text: Messages.language, // Button initial text
options: options, // Entries displayed in the menu options: options, // Entries displayed in the menu
left: true, // Open to the left of the button left: true, // Open to the left of the button
container: $initBlock // optional container: $initBlock, // optional
isSelect: true
}; };
var $block = createDropdown(dropdownConfig); var $block = createDropdown(dropdownConfig);
$block.attr('id', 'language-selector'); $block.attr('id', 'language-selector');
@ -1187,7 +1355,7 @@ define([
content: Messages.user_rename content: Messages.user_rename
}); });
} }
if (parsed && parsed.type && parsed.type !== 'settings') { if (parsed && (!parsed.type || parsed.type !== 'settings')) {
options.push({ options.push({
tag: 'a', tag: 'a',
attributes: {'class': 'settings'}, attributes: {'class': 'settings'},
@ -1332,7 +1500,7 @@ define([
}); });
}; };
common.confirm = function (msg, cb, opt, force) { common.confirm = function (msg, cb, opt, force, styleCB) {
opt = opt || {}; opt = opt || {};
cb = cb || function () {}; cb = cb || function () {};
if (force !== true) { msg = fixHTML(msg); } if (force !== true) { msg = fixHTML(msg); }
@ -1353,6 +1521,19 @@ define([
cb(false); cb(false);
stopListening(keyHandler); stopListening(keyHandler);
}); });
window.setTimeout(function () {
var $ok = findOKButton();
var $cancel = findCancelButton();
if (opt.okClass) { $ok.addClass(opt.okClass); }
if (opt.cancelClass) { $cancel.addClass(opt.cancelClass); }
if (opt.reverseOrder) {
$ok.insertBefore($ok.prev());
}
if (typeof(styleCB) === 'function') {
styleCB($ok.closest('.dialog'));
}
}, 0);
}; };
common.log = function (msg) { common.log = function (msg) {
@ -1367,37 +1548,12 @@ define([
* spinner * spinner
*/ */
common.spinner = function (parent) { common.spinner = function (parent) {
var $target = $('<div>', { var $target = $('<span>', {
// 'class': 'fa fa-spinner fa-pulse fa-4x fa-fw'
}).hide(); }).hide();
$(parent).append($target); $(parent).append($target);
var opts = {
lines: 20, // The number of lines to draw
length: 5, // The length of each line
width: 2, // The line thickness
radius: 15, // The radius of the inner circle
scale: 2, // Scales overall size of the spinner
corners: 1, // Corner roundness (0..1)
color: '#ddd', // #rgb or #rrggbb or array of colors
opacity: 0.3, // Opacity of the lines
rotate: 31, // The rotation offset
direction: 1, // 1: clockwise, -1: counterclockwise
speed: 1, // Rounds per second
trail: 49, // Afterglow percentage
fps: 20, // Frames per second when using setTimeout() as a fallback for CSS
zIndex: 2e9, // The z-index (defaults to 2000000000)
className: 'spinner', // The CSS class to assign to the spinner
top: '50%', // Top position relative to parent
left: '50%', // Left position relative to parent
shadow: false, // Whether to render a shadow
hwaccel: false, // Whether to use hardware acceleration
position: 'relative', // Element positioning
height: '100px'
};
var spinner = new Spinner(opts).spin($target[0]);
return { return {
show: function () { show: function () {
$target.show(); $target.show();
@ -1408,7 +1564,7 @@ define([
return this; return this;
}, },
get: function () { get: function () {
return spinner; return $target;
}, },
}; };
}; };

@ -0,0 +1,5 @@
define([
'/customize/messages.js',
], function (Messages) {
Messages._applyTranslation();
});

@ -1,5 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<head> <head>
<title>About our feedback api</title>
<script data-main="feedback-main.js" src="/bower_components/requirejs/require.js"></script>
<style> <style>
body { body {
max-width: 60vw; max-width: 60vw;
@ -9,9 +11,9 @@ body {
</style> </style>
</head> </head>
<body> <body>
<p>If you're reading this, you were probably curious why CryptPad is requesting web pages when you perform certain actions.</p> <p data-localization="feedback_about">If you're reading this, you were probably curious why CryptPad is requesting web pages when you perform certain actions.</p>
<p>We care about your privacy, and at the same time we want CryptPad to be very easy to use. <p data-localization="feedback_privacy">We care about your privacy, and at the same time we want CryptPad to be very easy to use.
We use this file to figure out which UI features matter to our users, by requesting it along with a parameter specifying which action was taken.</p> We use this file to figure out which UI features matter to our users, by requesting it along with a parameter specifying which action was taken.</p>
<p>If you would like to opt out, visit <a href="/settings/">your user settings page</a>, where you'll find a checkbox to enable or disable user feedback</p> <p data-localization="feedback_optout">If you would like to opt out, visit <a href="/settings/">your user settings page</a>, where you'll find a checkbox to enable or disable user feedback</p>

@ -6,10 +6,10 @@ define([
var Messages = {}; var Messages = {};
var ROOT = "root"; var ROOT = module.ROOT = "root";
var UNSORTED = "unsorted"; var UNSORTED = module.UNSORTED = "unsorted";
var TRASH = "trash"; var TRASH = module.TRASH = "trash";
var TEMPLATE = "template"; var TEMPLATE = module.TEMPLATE = "template";
var init = module.init = function (files, config) { var init = module.init = function (files, config) {
var Cryptpad = config.Cryptpad; var Cryptpad = config.Cryptpad;
@ -232,15 +232,95 @@ define([
return ret; return ret;
}; };
var getFilesDataFiles = function () { var getFilesDataFiles = exp.getFilesDataFiles = function () {
var ret = []; var ret = [];
for (var el in files[FILES_DATA]) { files[FILES_DATA].forEach(function (el) {
if (el.href && ret.indexOf(el.href) === -1) { if (el.href && ret.indexOf(el.href) === -1) {
ret.push(el.href); ret.push(el.href);
} }
});
return ret;
};
var _findFileInRoot = function (path, href) {
if (path[0] !== ROOT && path[0] !== TRASH) { return []; }
var paths = [];
var root = exp.findElement(files, path);
var addPaths = function (p) {
if (paths.indexOf(p) === -1) {
paths.push(p);
}
};
if (isFile(root)) {
if (compareFiles(href, root)) {
if (paths.indexOf(path) === -1) {
paths.push(path);
}
}
return paths;
}
for (var e in root) {
var nPath = path.slice();
nPath.push(e);
_findFileInRoot(nPath, href).forEach(addPaths);
}
return paths;
};
var _findFileInHrefArray = function (rootName, href) {
var unsorted = files[rootName].slice();
var ret = [];
var i = -1;
while ((i = unsorted.indexOf(href, i+1)) !== -1){
ret.push([rootName, i]);
} }
return ret; return ret;
}; };
var _findFileInTrash = function (path, href) {
var root = exp.findElement(files, path);
var paths = [];
var addPaths = function (p) {
if (paths.indexOf(p) === -1) {
paths.push(p);
}
};
if (path.length === 1) {
Object.keys(root).forEach(function (key) {
var arr = root[key];
if (!Array.isArray(arr)) { return; }
var nPath = path.slice();
nPath.push(key);
_findFileInTrash(nPath, href).forEach(addPaths);
});
}
if (path.length === 2) {
if (!Array.isArray(root)) { return []; }
root.forEach(function (el, i) {
var nPath = path.slice();
nPath.push(i);
nPath.push('element');
if (isFile(el.element)) {
if (compareFiles(href, el.element)) {
addPaths(nPath);
}
return;
}
_findFileInTrash(nPath, href).forEach(addPaths);
});
}
if (path.length >= 4) {
_findFileInRoot(path, href).forEach(addPaths);
}
return paths;
};
var findFile = exp.findFile = function (href) {
var rootpaths = _findFileInRoot([ROOT], href);
var unsortedpaths = _findFileInHrefArray(UNSORTED, href);
var templatepaths = _findFileInHrefArray(TEMPLATE, href);
var trashpaths = _findFileInTrash([TRASH], href);
return rootpaths.concat(unsortedpaths, templatepaths, trashpaths);
};
// Remove the selected 'href' from the tree located at 'path', and push its locations to the 'paths' array // Remove the selected 'href' from the tree located at 'path', and push its locations to the 'paths' array
var removeFileFromRoot = function (path, href) { var removeFileFromRoot = function (path, href) {
@ -374,7 +454,6 @@ define([
var parentEl = exp.findElement(files, parentPath); var parentEl = exp.findElement(files, parentPath);
// Trash root: we have array here, we can't just splice with the path otherwise we might break the path // Trash root: we have array here, we can't just splice with the path otherwise we might break the path
// of another element in the loop // of another element in the loop
console.log(path);
if (path.length === 4) { if (path.length === 4) {
trashRoot.push({ trashRoot.push({
name: path[1], name: path[1],
@ -573,7 +652,6 @@ define([
// Import elements in the file manager // Import elements in the file manager
var importElements = exp.importElements = function (elements, path, cb) { var importElements = exp.importElements = function (elements, path, cb) {
if (!elements || elements.length === 0) { return; } if (!elements || elements.length === 0) { return; }
console.log(elements);
var newParent = findElement(files, path); var newParent = findElement(files, path);
if (!newParent) { debug("Trying to import elements into a non-existing folder"); return; } if (!newParent) { debug("Trying to import elements into a non-existing folder"); return; }
elements.forEach(function (e) { elements.forEach(function (e) {
@ -670,7 +748,7 @@ define([
}; };
// Delete permanently (remove from the trash root and from filesData) // Delete permanently (remove from the trash root and from filesData)
var removeFromTrash = exp.removeFromTrash = function (path, cb) { var removeFromTrash = exp.removeFromTrash = function (path, cb, nocheck) {
if (!path || path.length < 4 || path[0] !== TRASH) { return; } if (!path || path.length < 4 || path[0] !== TRASH) { return; }
// Remove the last element from the path to get the parent path and the element name // Remove the last element from the path to get the parent path and the element name
var parentPath = path.slice(); var parentPath = path.slice();
@ -691,7 +769,9 @@ define([
parentEl[name] = undefined; parentEl[name] = undefined;
delete parentEl[name]; delete parentEl[name];
} }
checkDeletedFiles(); if (!nocheck) {
checkDeletedFiles();
}
if(cb) { cb(); } if(cb) { cb(); }
}; };
@ -773,7 +853,7 @@ define([
pushToTrash(key, href, path); pushToTrash(key, href, path);
}; };
var addUnsortedPad = exp.addPad = function (href, path, name) { var addPad = exp.addPad = function (href, path, name) {
if (workgroup) { return; } if (workgroup) { return; }
if (!href) { return; } if (!href) { return; }
var unsortedFiles = getUnsortedFiles(); var unsortedFiles = getUnsortedFiles();
@ -798,10 +878,54 @@ define([
} }
} }
if (unsortedFiles.indexOf(href) === -1 && rootFiles.indexOf(href) === -1 && templateFiles.indexOf(href) === -1 && trashFiles.indexOf(href) === -1) { if (unsortedFiles.indexOf(href) === -1 && rootFiles.indexOf(href) === -1 && templateFiles.indexOf(href) === -1 && trashFiles.indexOf(href) === -1) {
console.log('push', href);
files[UNSORTED].push(href); files[UNSORTED].push(href);
} }
}; };
var replaceFile = function (path, o, n) {
var root = exp.findElement(files, path);
if (isFile(root)) { return; }
for (var e in root) {
if (isFile(root[e])) {
if (compareFiles(o, root[e])) {
root[e] = n;
}
} else {
var nPath = path.slice();
nPath.push(e);
replaceFile(nPath, o, n);
}
}
};
// Replace a href by a stronger one everywhere in the drive (except FILES_DATA)
var replaceHref = exp.replaceHref = function (o, n) {
if (!isFile(o) || !isFile(n)) { return; }
var paths = findFile(o);
// Remove all the occurences in the trash
// Replace all the occurences not in the trash
// If all the occurences are in the trash or no occurence, add the pad to unsorted
var allInTrash = true;
paths.forEach(function (p) {
if (p[0] === TRASH) {
removeFromTrash(p, null, true); // 3rd parameter means skip "checkDeletedFiles"
return;
} else {
allInTrash = false;
var parentPath = p.slice();
var key = parentPath.pop();
var parentEl = findElement(files, parentPath);
parentEl[key] = n;
}
});
if (allInTrash) {
addPad(n);
}
};
// addTemplate is called when we want to add a new pad, never visited, to the templates list // addTemplate is called when we want to add a new pad, never visited, to the templates list
// first, we must add it to FILES_DATA, so the input has to be an fileDAta object // first, we must add it to FILES_DATA, so the input has to be an fileDAta object
var addTemplate = exp.addTemplate = function (fileData) { var addTemplate = exp.addTemplate = function (fileData) {

@ -119,6 +119,10 @@ define([
return filesOp.getStructure(); return filesOp.getStructure();
}; };
ret.replaceHref = function (o, n) {
return filesOp.replaceHref(o, n);
};
var changeHandlers = ret.changeHandlers = []; var changeHandlers = ret.changeHandlers = [];
ret.change = function (f) {}; ret.change = function (f) {};
@ -127,9 +131,10 @@ define([
}; };
var onReady = function (f, proxy, Cryptpad, exp) { var onReady = function (f, proxy, Cryptpad, exp) {
var fo = FO.init(proxy.drive, { var fo = exp.fo = FO.init(proxy.drive, {
Cryptpad: Cryptpad Cryptpad: Cryptpad
}); });
//storeObj = proxy; //storeObj = proxy;
store = initStore(fo, proxy, exp); store = initStore(fo, proxy, exp);
if (typeof(f) === 'function') { if (typeof(f) === 'function') {

@ -94,17 +94,21 @@ define([
res.realtime = rt.realtime; res.realtime = rt.realtime;
res.network = rt.network; res.network = rt.network;
// they're registering...
res.userHash = opt.userHash;
res.userName = uname;
// they tried to just log in but there's no such user // they tried to just log in but there's no such user
if (!isRegister && isProxyEmpty(rt.proxy)) { if (!isRegister && isProxyEmpty(rt.proxy)) {
rt.network.disconnect(); // clean up after yourself rt.network.disconnect(); // clean up after yourself
return void cb('NO_SUCH_USER', res); return void cb('NO_SUCH_USER', res);
} }
// they're registering... // they tried to register, but those exact credentials exist
if (isRegister && !isProxyEmpty(rt.proxy)) {
res.userHash = opt.userHash; rt.network.disconnect();
res.userName = uname; return void cb('ALREADY_REGISTERED', res);
//res.displayName // TODO }
cb(void 0, res); cb(void 0, res);
}); });

@ -0,0 +1,198 @@
require.config({ paths: { 'json.sortify': '/bower_components/json.sortify/dist/JSON.sortify' } });
define([
'/common/cryptpad-common.js',
'/common/cryptget.js',
'/common/fileObject.js',
'json.sortify'
], function (Cryptpad, Crypt, FO, Sortify) {
var exp = {};
var getType = function (el) {
if (el === null) { return "null"; }
return Array.isArray(el) ? "array" : typeof(el);
};
var findAvailableKey = function (obj, key) {
if (typeof (obj[key]) === "undefined") { return key; }
var i = 1;
var nkey = key;
while (typeof (obj[nkey]) !== "undefined") {
nkey = key + '_' + i;
i++;
}
return nkey;
};
var copy = function (el) {
if (typeof (el) !== "object") { return el; }
return JSON.parse(JSON.stringify(el));
};
var deduplicate = function (array) {
var a = array.slice();
for(var i=0; i<a.length; i++) {
for(var j=i+1; j<a.length; j++) {
if(a[i] === a[j] || (
typeof(a[i]) === "object" && Sortify(a[i]) === Sortify(a[j]))) {
a.splice(j--, 1);
}
}
}
return a;
};
// Merge obj2 into obj1
// If keepOld is true, obj1 values are kept in case of conflicti
// Not used ATM
var merge = function (obj1, obj2, keepOld) {
if (typeof (obj1) !== "object" || typeof (obj2) !== "object") { return; }
Object.keys(obj2).forEach(function (k) {
var v = obj2[k];
// If one of them is not an object or if we have a map and a array, don't override, create a new key
if (!obj1[k] || typeof(obj1[k]) !== "object" || typeof(obj2[k]) !== "object" ||
(getType(obj1[k]) !== getType(obj2[k]))) {
// We don't want to override the values in the object (username, preferences)
// These values should be the ones stored in the first object
if (keepOld) { return; }
if (obj1[k] === obj2[k]) { return; }
var nkey = findAvailableKey(obj1, k);
obj1[nkey] = copy(obj2[k]);
return;
}
// Else, they're both maps or both arrays
if (getType(obj1[k]) === "array" && getType(obj2[k]) === "array") {
var c = obj1[k].concat(obj2[k]);
obj1[k] = deduplicate(c);
return;
}
merge(obj1[k], obj2[k], keepOld);
});
};
var createFromPath = function (proxy, oldFo, path, href) {
var root = proxy.drive;
var error = function (msg) {
console.error(msg || "Unable to find that path", path);
};
if (path[0] === FO.TRASH && path.length === 4) {
href = oldFo.getTrashElementData(path);
path.pop();
}
var p, next, nextRoot;
path.forEach(function (p, i) {
if (!root) { return; }
if (typeof(p) === "string") {
if (getType(root) !== "object") { root = undefined; error(); return; }
if (i === path.length - 1) {
root[findAvailableKey(root, p)] = href;
return;
}
next = getType(path[i+1]);
nextRoot = getType(root[p]);
if (nextRoot !== "undefined") {
if (next === "string" && nextRoot === "object" || next === "number" && nextRoot === "array") {
root = root[p];
return;
}
p = findAvailableKey(root, p);
}
if (next === "number") {
root[p] = [];
root = root[p];
return;
}
root[p] = {};
root = root[p];
return;
}
// Path contains a non-string element: it's an array index
if (typeof(p) !== "number") { root = undefined; error(); return; }
if (getType(root) !== "array") { root = undefined; error(); return; }
if (i === path.length - 1) {
if (root.indexOf(href) === -1) { root.push(href); }
return;
}
next = getType(path[i+1]);
if (next === "number") {
error('2 consecutives arrays in the user object');
root = undefined;
//root.push([]);
//root = root[root.length - 1];
return;
}
root.push({});
root = root[root.length - 1];
return;
});
};
var mergeAnonDrive = exp.anonDriveIntoUser = function (proxy, cb) {
// Make sure we have an FS_hash and we don't use it, otherwise just stop the migration and cb
if (!localStorage.FS_hash || !Cryptpad.isLoggedIn()) {
if (typeof(cb) === "function") { cb(); }
}
// Get the content of FS_hash and then merge the objects, remove the migration key and cb
var todo = function (err, doc) {
if (err) { console.error("Cannot migrate recent pads", err); return; }
var parsed;
if (!doc) {
if (typeof(cb) === "function") { cb(); }
return;
}
try { parsed = JSON.parse(doc); } catch (e) {
if (typeof(cb) === "function") { cb(); }
console.error("Cannot parsed recent pads", e);
return;
}
if (parsed) {
//merge(proxy, parsed, true);
var oldFo = FO.init(parsed.drive, {
Cryptpad: Cryptpad
});
var newData = Cryptpad.getStore().getProxy();
var newFo = newData.fo;
var newRecentPads = proxy.drive[Cryptpad.storageKey];
var newFiles = newFo.getFilesDataFiles();
var oldFiles = oldFo.getFilesDataFiles();
oldFiles.forEach(function (href) {
// Do not migrate a pad if we already have it, it would create a duplicate in the drive
if (newFiles.indexOf(href) !== -1) { return; }
// If we have a stronger version, do not add the current href
if (Cryptpad.findStronger(href, newRecentPads)) { return; }
// If we have a weaker version, replace the href by the new one
// NOTE: if that weaker version is in the trash, the strong one will be put in unsorted
var weaker = Cryptpad.findWeaker(href, newRecentPads);
if (weaker) {
// Update RECENTPADS
newRecentPads.some(function (pad) {
if (pad.href === weaker) {
pad.href = href;
return true;
}
return;
});
// Update the file in the drive
newFo.replaceHref(weaker, href);
return;
}
// Here it means we have a new href, so we should add it to the drive at its old location
var paths = oldFo.findFile(href);
if (paths.length === 0) { return; }
createFromPath(proxy, oldFo, paths[0], href);
// Also, push the file data in our array
var data = oldFo.getFileData(href);
if (data) {
newRecentPads.push(data);
}
});
}
if (typeof(cb) === "function") { cb(); }
};
Crypt.get(localStorage.FS_hash, todo);
};
return exp;
});

@ -123,16 +123,89 @@ define([
// Share button // Share button
if (config.displayed.indexOf('share') !== -1) { if (config.displayed.indexOf('share') !== -1) {
var $shareIcon = $('<span>', {'class': 'fa fa-share-alt'}); var secret = Cryptpad.find(config, ['share', 'secret']);
var $span = $('<span>', {'class': 'large'}).append(' ' +Messages.shareButton); var channel = Cryptpad.find(config, ['share', 'channel']);
var dropdownConfigShare = { if (!secret || !channel) {
text: $('<div>').append($shareIcon).append($span).html(), throw new Error("Unable to display the share button: share.secret and share.channel required");
options: [] }
}; Cryptpad.getRecentPads(function (err, recent) {
var $shareBlock = Cryptpad.createDropdown(dropdownConfigShare); var $shareIcon = $('<span>', {'class': 'fa fa-share-alt'});
$shareBlock.find('button').attr('id', 'shareButton'); var $span = $('<span>', {'class': 'large'}).append(' ' +Messages.shareButton);
$shareBlock.find('.dropdown-bar-content').addClass(SHARE_CLS).addClass(EDITSHARE_CLS).addClass(VIEWSHARE_CLS); var hashes = Cryptpad.getHashes(channel, secret);
$userlistElement.append($shareBlock); var options = [];
// If we have a stronger version in drive, add it and add a redirect button
var stronger = recent && Cryptpad.findStronger(null, recent);
if (stronger) {
var parsed = Cryptpad.parsePadUrl(stronger);
hashes.editHash = parsed.hash;
}
if (hashes.editHash) {
options.push({
tag: 'a',
attributes: {title: Messages.editShareTitle, 'class': 'editShare'},
content: '<span class="fa fa-users"></span> ' + Messages.editShare
});
if (stronger) {
// We're in view mode, display the "open editing link" button
options.push({
tag: 'a',
attributes: {
title: Messages.editOpenTitle,
'class': 'editOpen',
href: window.location.pathname + '#' + hashes.editHash,
target: '_blank'
},
content: '<span class="fa fa-users"></span> ' + Messages.editOpen
});
}
options.push({tag: 'hr'});
}
if (hashes.viewHash) {
options.push({
tag: 'a',
attributes: {title: Messages.viewShareTitle, 'class': 'viewShare'},
content: '<span class="fa fa-eye"></span> ' + Messages.viewShare
});
if (hashes.editHash && !stronger) {
// We're in edit mode, display the "open readonly" button
options.push({
tag: 'a',
attributes: {
title: Messages.viewOpenTitle,
'class': 'viewOpen',
href: window.location.pathname + '#' + hashes.viewHash,
target: '_blank'
},
content: '<span class="fa fa-eye"></span> ' + Messages.viewOpen
});
}
}
var dropdownConfigShare = {
text: $('<div>').append($shareIcon).append($span).html(),
options: options
};
var $shareBlock = Cryptpad.createDropdown(dropdownConfigShare);
$shareBlock.find('button').attr('id', 'shareButton');
$shareBlock.find('.dropdown-bar-content').addClass(SHARE_CLS).addClass(EDITSHARE_CLS).addClass(VIEWSHARE_CLS);
$userlistElement.append($shareBlock);
if (hashes.editHash) {
$shareBlock.find('a.editShare').click(function () {
var url = window.location.origin + window.location.pathname + '#' + hashes.editHash;
var success = Cryptpad.Clipboard.copy(url);
if (success) { Cryptpad.log(Messages.shareSuccess); }
});
}
if (hashes.viewHash) {
$shareBlock.find('a.viewShare').click(function () {
var url = window.location.origin + window.location.pathname + '#' + hashes.viewHash;
var success = Cryptpad.Clipboard.copy(url);
if (success) { Cryptpad.log(Messages.shareSuccess); }
});
}
});
} }
}; };
@ -223,7 +296,7 @@ define([
} }
if (anonymous > 0) { if (anonymous > 0) {
var text = anonymous === 1 ? Messages.anonymousUser : Messages.anonymousUsers; var text = anonymous === 1 ? Messages.anonymousUser : Messages.anonymousUsers;
$editUsers.push('<span class="anonymous">' + anonymous + ' ' + text + '</span>'); $editUsers.append('<span class="anonymous">' + anonymous + ' ' + text + '</span>');
} }
if (numberOfViewUsers > 0) { if (numberOfViewUsers > 0) {
var viewText = '<span class="viewer">'; var viewText = '<span class="viewer">';

@ -66,6 +66,7 @@ li {
.contextMenu { .contextMenu {
display: none; display: none;
position: absolute; position: absolute;
z-index: 50;
} }
.contextMenu li { .contextMenu li {
padding: 0; padding: 0;
@ -81,6 +82,10 @@ li {
color: #eee; color: #eee;
margin: -1px; margin: -1px;
} }
.selected .fa-minus-square-o,
.selected .fa-plus-square-o {
color: #000;
}
span.fa-folder, span.fa-folder,
span.fa-folder-open { span.fa-folder-open {
color: #FEDE8B; color: #FEDE8B;
@ -101,17 +106,24 @@ span.fa-folder-open {
color: #000; color: #000;
} }
#tree li { #tree li {
/*cursor: pointer;*/ padding: 0 0 0 5px;
cursor: auto; cursor: auto;
} }
#tree li:hover > span.element {
text-decoration: underline;
}
#tree li.collapsed ul { #tree li.collapsed ul {
display: none; display: none;
} }
#tree li input { #tree li input {
width: calc(70%); width: calc(100% - 30px);
}
#tree li > span.element-row {
width: calc(100% + 5px);
display: inline-block;
cursor: pointer;
margin-left: -5px;
padding-left: 5px;
}
#tree li > span.element-row:not(.selected):hover {
background-color: #eee;
} }
#tree span.element { #tree span.element {
cursor: pointer; cursor: pointer;
@ -220,6 +232,9 @@ span.fa-folder-open {
#content li:not(.header) *:not(input) { #content li:not(.header) *:not(input) {
/*pointer-events: none;*/ /*pointer-events: none;*/
} }
#content li:not(.header):hover:not(.selected) {
background-color: #eee;
}
#content li:not(.header):hover .name { #content li:not(.header):hover .name {
/*text-decoration: underline;*/ /*text-decoration: underline;*/
} }
@ -233,12 +248,20 @@ span.fa-folder-open {
text-align: center; text-align: center;
vertical-align: top; vertical-align: top;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis;
padding-top: 10px;
padding-bottom: 5px;
max-height: 145px;
}
#content div.grid li:not(.selected) {
border: transparent 1px;
} }
#content div.grid li .name { #content div.grid li .name {
width: 100%; width: 100%;
} }
#content div.grid li input { #content div.grid li input {
width: 100%; width: 100%;
margin-top: 5px;
} }
#content div.grid li .fa { #content div.grid li .fa {
display: block; display: block;

@ -2,6 +2,8 @@
@tree-fg: #000; @tree-fg: #000;
@tree-lines-col: #888; @tree-lines-col: #888;
@drive-hover: #eee;
@content-bg: @tree-bg; @content-bg: @tree-bg;
@content-bg-ro: darken(@content-bg, 10%); @content-bg-ro: darken(@content-bg, 10%);
@content-fg: @tree-fg; @content-fg: @tree-fg;
@ -93,6 +95,7 @@ li {
.contextMenu { .contextMenu {
display: none; display: none;
position: absolute; position: absolute;
z-index: 50;
li { li {
padding: 0; padding: 0;
font-size: 16px; font-size: 16px;
@ -109,6 +112,9 @@ li {
background: #666; background: #666;
color: #eee; color: #eee;
margin: -1px; margin: -1px;
.fa-minus-square-o, .fa-plus-square-o {
color: @tree-fg;
}
} }
span { span {
@ -134,16 +140,23 @@ span {
padding: 10px 0px; padding: 10px 0px;
color: @tree-fg; color: @tree-fg;
li { li {
/*cursor: pointer;*/ padding: 0 0 0 5px;
cursor: auto; cursor: auto;
&:hover > span.element {
text-decoration: underline;
}
&.collapsed ul { &.collapsed ul {
display: none; display: none;
} }
input { input {
width: calc(100% - 30px); width: ~"calc(100% - 30px)";
}
& > span.element-row {
width: ~"calc(100% + 5px)";
display: inline-block;
cursor: pointer;
margin-left: -5px;
padding-left: 5px;
}
& > span.element-row:not(.selected):hover {
background-color: @drive-hover;
} }
} }
span.element { span.element {
@ -261,6 +274,9 @@ span {
/*pointer-events: none;*/ /*pointer-events: none;*/
} }
&:hover { &:hover {
&:not(.selected) {
background-color: @drive-hover;
}
.name { .name {
/*text-decoration: underline;*/ /*text-decoration: underline;*/
} }
@ -276,12 +292,20 @@ span {
text-align: center; text-align: center;
vertical-align: top; vertical-align: top;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis;
padding-top: 10px;
padding-bottom: 5px;
max-height: 145px;
&:not(.selected) {
border: transparent 1px;
}
.name { .name {
width: 100%; width: 100%;
} }
input { input {
width: 100%; width: 100%;
margin-top: 5px;
} }
.fa { .fa {
display: block; display: block;

@ -9,6 +9,7 @@
data-main-favicon="/customize/main-favicon.png" data-main-favicon="/customize/main-favicon.png"
data-alt-favicon="/customize/alt-favicon.png" data-alt-favicon="/customize/alt-favicon.png"
id="favicon" /> id="favicon" />
<link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css">
<link rel="stylesheet" href="/customize/main.css" /> <link rel="stylesheet" href="/customize/main.css" />
<script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script> <script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script>
<style> <style>
@ -33,5 +34,14 @@
</head> </head>
<body> <body>
<iframe id="pad-iframe"></iframe><script src="/common/noscriptfix.js"></script> <iframe id="pad-iframe"></iframe><script src="/common/noscriptfix.js"></script>
<div id="loading">
<div class="loadingContainer">
<img class="cryptofist" src="/customize/cryptofist_small.png" />
<div class="spinnerContainer">
<span class="fa fa-spinner fa-pulse fa-4x fa-fw"></span>
</div>
<p data-localization="loading"></p>
</div>
</div>
</body> </body>
</html> </html>

@ -8,8 +8,9 @@ define([
'/common/fileObject.js', '/common/fileObject.js',
'/common/toolbar.js', '/common/toolbar.js',
'/customize/application_config.js', '/customize/application_config.js',
'/common/cryptget.js' '/common/cryptget.js',
], function (Listmap, Crypto, TextPatcher, JSONSortify, Cryptpad, FO, Toolbar, AppConfig, Get) { '/common/mergeDrive.js'
], function (Listmap, Crypto, TextPatcher, JSONSortify, Cryptpad, FO, Toolbar, AppConfig, Get, Merge) {
var module = window.MODULE = {}; var module = window.MODULE = {};
var Messages = Cryptpad.Messages; var Messages = Cryptpad.Messages;
@ -235,15 +236,25 @@ define([
} }
}; };
var findDataHolder = function ($el) {
return $el.is('.element-row') ? $el : $el.closest('.element-row');
};
var removeSelected = function () { var removeSelected = function () {
$iframe.find('.selected').removeClass("selected"); $iframe.find('.selected').removeClass("selected");
var $container = $driveToolbar.find('#contextButtonsContainer'); var $container = $driveToolbar.find('#contextButtonsContainer');
if (!$container.length) { return; } if (!$container.length) { return; }
$container.html(''); $container.html('');
}; };
var removeInput = function () { var removeInput = function (cancel) {
$iframe.find('li > span:hidden').removeAttr('style'); if (!cancel && $iframe.find('.element-row > input').length === 1) {
$iframe.find('li > input').remove(); var $input = $iframe.find('.element-row > input');
filesOp.renameElement($input.data('path'), $input.val(), function () {
APP.refresh();
});
}
$iframe.find('.element-row > input').remove();
$iframe.find('.element-row > span:hidden').removeAttr('style');
}; };
var compareDays = function (date1, date2) { var compareDays = function (date1, date2) {
@ -302,13 +313,17 @@ define([
var $input = $('<input>', { var $input = $('<input>', {
placeholder: name, placeholder: name,
value: name value: name
}); }).data('path', path);
$input.on('keyup', function (e) { $input.on('keyup', function (e) {
if (e.which === 13) { if (e.which === 13) {
removeInput(); removeInput(true);
filesOp.renameElement(path, $input.val(), function () { filesOp.renameElement(path, $input.val(), function () {
refresh(); refresh();
}); });
return;
}
if (e.which === 27) {
removeInput(true);
} }
}); });
//$element.parent().append($input); //$element.parent().append($input);
@ -330,33 +345,53 @@ define([
// since it would remove the input // since it would remove the input
$input.on('mousedown', function (e) { $input.on('mousedown', function (e) {
e.stopPropagation(); e.stopPropagation();
$input.parents('li').attr("draggable", false); $input.parents('.element-row').attr("draggable", false);
}); });
$input.on('mouseup', function (e) { $input.on('mouseup', function (e) {
e.stopPropagation(); e.stopPropagation();
$input.parents('li').attr("draggable", true); $input.parents('.element-row').attr("draggable", true);
}); });
},0); },0);
}; };
var filterContextMenu = function ($menu, $element) { var filterContextMenu = function ($menu, paths) {
var path = $element.data('path'); //var path = $element.data('path');
var hide = []; var hide = [];
if (!APP.editable) { var hasFolder = false;
hide.push($menu.find('a.editable')); paths.forEach(function (p, i) {
} var path = p.path;
if (!isOwnDrive()) { var $element = p.element;
hide.push($menu.find('a.own')); if (!APP.editable) {
} hide.push($menu.find('a.editable'));
if ($element.is('.file-element')) { }
hide.push($menu.find('a.newfolder')); if (!isOwnDrive()) {
} else { hide.push($menu.find('a.own'));
hide.push($menu.find('a.open_ro')); }
} if ($element.is('.file-element')) {
if (path && path.length > 4) { // No folder in files
hide.push($menu.find('a.newfolder'));
} else {
if (hasFolder) {
// More than 1 folder selected: cannot create a new subfolder
hide.push($menu.find('a.newfolder'));
}
hasFolder = true;
hide.push($menu.find('a.open_ro'));
}
if (path && path.length > 4) {
hide.push($menu.find('a.restore'));
hide.push($menu.find('a.properties'));
}
});
if (paths.length > 1) {
hide.push($menu.find('a.restore')); hide.push($menu.find('a.restore'));
hide.push($menu.find('a.properties')); hide.push($menu.find('a.properties'));
hide.push($menu.find('a.rename'));
}
if (hasFolder && paths.length > 1) {
// Cannot open multiple folders
hide.push($menu.find('a.open'));
} }
return hide; return hide;
}; };
@ -370,10 +405,36 @@ define([
$driveToolbar.find('.path').css('max-width', 'calc(100vw - '+$tree.width()+'px - '+l+'px)'); $driveToolbar.find('.path').css('max-width', 'calc(100vw - '+$tree.width()+'px - '+l+'px)');
}; };
var getSelectedPaths = function ($element) {
var paths = [];
if ($iframe.find('.selected').length > 1) {
var $selected = $iframe.find('.selected');
$selected.each(function (idx, elmt) {
var ePath = $(elmt).data('path');
if (ePath) {
paths.push({
path: ePath,
element: $(elmt)
});
}
});
}
if (!paths.length) {
var path = $element.data('path');
if (!path) { return false; }
paths.push({
path: path,
element: $element
});
}
return paths;
};
var updateContextButton = function () { var updateContextButton = function () {
var $li = $content.find('.selected'); var $li = $content.find('.selected');
if ($li.length !== 1) { if ($li.length === 0) {
$li = $tree.find('.element.active').closest('li'); $li = findDataHolder($tree.find('.element.active'));
} }
var $button = $driveToolbar.find('#contextButton'); var $button = $driveToolbar.find('#contextButton');
if ($button.length) { // mobile if ($button.length) { // mobile
@ -398,13 +459,13 @@ define([
var $container = $driveToolbar.find('#contextButtonsContainer'); var $container = $driveToolbar.find('#contextButtonsContainer');
if (!$container.length) { return; } if (!$container.length) { return; }
$container.html(''); $container.html('');
var $element = $li; var $element = $li.length === 1 ? $li : $($li[0]);
var paths = getSelectedPaths($element);
var $menu = $element.data('context'); var $menu = $element.data('context');
var path = $element.data('path'); if (!$menu) { return; }
if (!$menu || !path) { return; }
var actions = []; var actions = [];
var $actions = $menu.find('a'); var $actions = $menu.find('a');
var toHide = filterContextMenu($menu, $element); var toHide = filterContextMenu($menu, paths);
$actions = $actions.filter(function (i, el) { $actions = $actions.filter(function (i, el) {
for (var j = 0; j < toHide.length; j++) { for (var j = 0; j < toHide.length; j++) {
if ($(el).is(toHide[j])) { return false; } if ($(el).is(toHide[j])) { return false; }
@ -419,8 +480,9 @@ define([
} else { } else {
$a.text($(el).text()); $a.text($(el).text());
} }
$(el).data('path', path); $(el).data('paths', paths);
$(el).data('element', $element); //$(el).data('path', path);
//:$(el).data('element', $element);
$container.append($a); $container.append($a);
$a.click(function() { $(el).click(); }); $a.click(function() { $(el).click(); });
}); });
@ -434,9 +496,7 @@ define([
if (!e || !e.ctrlKey) { if (!e || !e.ctrlKey) {
removeSelected(); removeSelected();
} }
if (!$element.is('li')) { $element = findDataHolder($element);
$element = $element.closest('li');
}
if (!$element.length) { if (!$element.length) {
log(Messages.fm_selectError); log(Messages.fm_selectError);
return; return;
@ -448,6 +508,9 @@ define([
$element.removeClass("selected"); $element.removeClass("selected");
} }
updateContextButton(); updateContextButton();
if ($iframe.find('.selected').length > 1) {
module.hideMenu();
}
}; };
// Open the selected context menu on the closest "li" element // Open the selected context menu on the closest "li" element
@ -455,7 +518,7 @@ define([
module.hideMenu(); module.hideMenu();
e.stopPropagation(); e.stopPropagation();
var $element = $(e.target).closest('li'); var $element = findDataHolder($(e.target));
if (!$element.length) { if (!$element.length) {
logError("Unable to locate the .element tag", e.target); logError("Unable to locate the .element tag", e.target);
$menu.hide(); $menu.hide();
@ -463,10 +526,9 @@ define([
return false; return false;
} }
var path = $element.data('path'); var paths = getSelectedPaths($element);
if (!path) { return false; }
var toHide = filterContextMenu($menu, $element); var toHide = filterContextMenu($menu, paths);
toHide.forEach(function ($a) { toHide.forEach(function ($a) {
$a.parent('li').hide(); $a.parent('li').hide();
}); });
@ -483,10 +545,13 @@ define([
return true; return true;
} }
onElementClick(undefined, $element); if (paths.length === 1) {
onElementClick(undefined, $element);
}
$menu.find('a').data('path', path); $menu.find('a').data('paths', paths);
$menu.find('a').data('element', $element); //$menu.find('a').data('path', path);
//$menu.find('a').data('element', $element);
return false; return false;
}; };
@ -503,13 +568,14 @@ define([
}; };
var openTrashTreeContextMenu = function (e) { var openTrashTreeContextMenu = function (e) {
removeSelected();
$trashTreeContextMenu.find('li').show(); $trashTreeContextMenu.find('li').show();
openContextMenu(e, $trashTreeContextMenu); openContextMenu(e, $trashTreeContextMenu);
return false; return false;
}; };
var openTrashContextMenu = function (e) { var openTrashContextMenu = function (e) {
var path = $(e.target).closest('li').data('path'); var path = findDataHolder($(e.target)).data('path');
if (!path) { return; } if (!path) { return; }
$trashContextMenu.find('li').show(); $trashContextMenu.find('li').show();
openContextMenu(e, $trashContextMenu); openContextMenu(e, $trashContextMenu);
@ -595,6 +661,7 @@ define([
msg = Messages._getKey('fm_removeDialog', [name]); msg = Messages._getKey('fm_removeDialog', [name]);
} }
Cryptpad.confirm(msg, function (res) { Cryptpad.confirm(msg, function (res) {
$(ifrw).focus();
if (!res) { return; } if (!res) { return; }
andThen(); andThen();
}); });
@ -603,7 +670,7 @@ define([
// The data transferred is a stringified JSON containing the path of the dragged element // The data transferred is a stringified JSON containing the path of the dragged element
var onDrag = function (ev, path) { var onDrag = function (ev, path) {
var paths = []; var paths = [];
var $element = $(ev.target).closest('li'); var $element = findDataHolder($(ev.target));
if ($element.hasClass('selected')) { if ($element.hasClass('selected')) {
var $selected = $iframe.find('.selected'); var $selected = $iframe.find('.selected');
$selected.each(function (idx, elmt) { $selected.each(function (idx, elmt) {
@ -659,7 +726,8 @@ define([
} }
}); });
var newPath = $(ev.target).data('path') || $(ev.target).parent('li').data('path'); var $el = findDataHolder($(ev.target));
var newPath = $el.data('path');
if (!newPath) { return; } if (!newPath) { return; }
if (movedPaths && movedPaths.length) { if (movedPaths && movedPaths.length) {
moveElements(movedPaths, newPath, null, refresh); moveElements(movedPaths, newPath, null, refresh);
@ -790,7 +858,8 @@ define([
$icon = filesOp.isFolderEmpty(root[key]) ? $folderEmptyIcon.clone() : $folderIcon.clone(); $icon = filesOp.isFolderEmpty(root[key]) ? $folderEmptyIcon.clone() : $folderIcon.clone();
} }
var $element = $('<li>', { var $element = $('<li>', {
draggable: true draggable: true,
'class': 'element-row'
}); });
if (isFolder) { if (isFolder) {
addFolderData(element, key, $element); addFolderData(element, key, $element);
@ -1159,7 +1228,7 @@ define([
var e = useData ? element : filesOp.getFileData(element); var e = useData ? element : filesOp.getFileData(element);
if (!e) { if (!e) {
e = { e = {
href : el, href : element,
title : Messages.fm_noname, title : Messages.fm_noname,
atime : 0, atime : 0,
ctime : 0 ctime : 0
@ -1255,7 +1324,7 @@ define([
var idx = files[rootName].indexOf(href); var idx = files[rootName].indexOf(href);
var $icon = getFileIcon(href); var $icon = getFileIcon(href);
var $element = $('<li>', { var $element = $('<li>', {
'class': 'file-element element', 'class': 'file-element element element-row',
draggable: draggable draggable: draggable
}); });
addFileData(href, file.title, $element, false); addFileData(href, file.title, $element, false);
@ -1287,7 +1356,7 @@ define([
var sortedFiles = sortElements(false, [FILES_DATA], keys, Cryptpad.getLSAttribute(SORT_FILE_BY), !getSortFileDesc(), false, true); var sortedFiles = sortElements(false, [FILES_DATA], keys, Cryptpad.getLSAttribute(SORT_FILE_BY), !getSortFileDesc(), false, true);
sortedFiles.forEach(function (file) { sortedFiles.forEach(function (file) {
var $icon = getFileIcon(file.href); var $icon = getFileIcon(file.href);
var $element = $('<li>', { 'class': 'file-element element' }); var $element = $('<li>', { 'class': 'file-element element element-row' });
addFileData(file.href, file.title, $element, false); addFileData(file.href, file.title, $element, false);
$element.data('path', [FILES_DATA, allfiles.indexOf(file)]); $element.data('path', [FILES_DATA, allfiles.indexOf(file)]);
$element.data('element', file.href); $element.data('element', file.href);
@ -1347,6 +1416,7 @@ define([
// NOTE: Elements in the trash are not using the same storage structure as the others // NOTE: Elements in the trash are not using the same storage structure as the others
// _WORKGROUP_ : do not change the lastOpenedFolder value in localStorage // _WORKGROUP_ : do not change the lastOpenedFolder value in localStorage
var displayDirectory = module.displayDirectory = function (path, force) { var displayDirectory = module.displayDirectory = function (path, force) {
module.hideMenu();
if (!APP.editable) { debug("Read-only mode"); } if (!APP.editable) { debug("Read-only mode"); }
if (!appStatus.isReady && !force) { return; } if (!appStatus.isReady && !force) { return; }
// Only Trash and Root are available in not-owned files manager // Only Trash and Root are available in not-owned files manager
@ -1408,7 +1478,7 @@ define([
e.stopPropagation(); e.stopPropagation();
var $li = $content.find('.selected'); var $li = $content.find('.selected');
if ($li.length !== 1) { if ($li.length !== 1) {
$li = $tree.find('.element.active').closest('li'); $li = findDataHolder($tree.find('.element.active'));
} }
// Close if already opened // Close if already opened
if ($iframe.find('.contextMenu:visible').length) { if ($iframe.find('.contextMenu:visible').length) {
@ -1418,7 +1488,8 @@ define([
// Open the menu // Open the menu
$iframe.find('.contextMenu').css({ $iframe.find('.contextMenu').css({
top: ($context.offset().top + 32) + 'px', top: ($context.offset().top + 32) + 'px',
right: '0px' right: '0px',
left: ''
}); });
$li.contextmenu(); $li.contextmenu();
}); });
@ -1467,7 +1538,7 @@ define([
}; };
var refreshFilesData = function () { var refreshFilesData = function () {
$content.find('li').each(function (i, e) { $content.find('.element-row').each(function (i, e) {
var $el = $(e); var $el = $(e);
if ($el.data('path')) { if ($el.data('path')) {
var path = $el.data('path'); var path = $el.data('path');
@ -1488,10 +1559,12 @@ define([
if (collapsable) { if (collapsable) {
$collapse = $expandIcon.clone(); $collapse = $expandIcon.clone();
} }
var $element = $('<li>').append($collapse).append($icon).append($name).click(function (e) { var $elementRow = $('<span>', {'class': 'element-row'}).append($collapse).append($icon).append($name).click(function (e) {
e.stopPropagation();
module.displayDirectory(path); module.displayDirectory(path);
}); });
if (draggable) { $element.attr('draggable', true); } var $element = $('<li>').append($elementRow);
if (draggable) { $elementRow.attr('draggable', true); }
if (collapsable) { if (collapsable) {
$element.addClass('collapsed'); $element.addClass('collapsed');
$collapse.click(function(e) { $collapse.click(function(e) {
@ -1519,8 +1592,8 @@ define([
$collapse.click(); $collapse.click();
} }
} }
$element.data('path', path); $elementRow.data('path', path);
addDragAndDropHandlers($element, path, true, droppable); addDragAndDropHandlers($elementRow, path, true, droppable);
if (active) { $name.addClass('active'); } if (active) { $name.addClass('active'); }
return $element; return $element;
}; };
@ -1559,7 +1632,7 @@ define([
(isCurrentFolder ? $folderOpenedIcon : $folderIcon); (isCurrentFolder ? $folderOpenedIcon : $folderIcon);
var $element = createTreeElement(key, $icon.clone(), newPath, true, true, subfolder, isCurrentFolder); var $element = createTreeElement(key, $icon.clone(), newPath, true, true, subfolder, isCurrentFolder);
$element.appendTo($list); $element.appendTo($list);
$element.contextmenu(openDirectoryContextMenu); $element.find('>.element-row').contextmenu(openDirectoryContextMenu);
createTree($element, newPath); createTree($element, newPath);
}); });
}; };
@ -1672,61 +1745,76 @@ define([
$contextMenu.on("click", "a", function(e) { $contextMenu.on("click", "a", function(e) {
e.stopPropagation(); e.stopPropagation();
var path = $(this).data('path'); var paths = $(this).data('paths');
var $element = $(this).data('element'); //var path = $(this).data('path');
if (!$element || !path || path.length < 2) { //var $element = $(this).data('element');
if (paths.length === 0) {
log(Messages.fm_forbidden); log(Messages.fm_forbidden);
debug("Directory context menu on a forbidden or unexisting element. ", $element, path); debug("Directory context menu on a forbidden or unexisting element. ", paths);
return; return;
} }
if ($(this).hasClass("rename")) { if ($(this).hasClass("rename")) {
displayRenameInput($element, path); if (paths.length !== 1) { return; }
displayRenameInput(paths[0].element, paths[0].path);
} }
else if($(this).hasClass("delete")) { else if($(this).hasClass("delete")) {
moveElements([path], [TRASH], false, refresh); var pathsList = [];
paths.forEach(function (p) { pathsList.push(p.path); });
moveElements(pathsList, [TRASH], false, refresh);
} }
else if ($(this).hasClass('open')) { else if ($(this).hasClass('open')) {
$element.dblclick(); paths.forEach(function (p) {
var $element = p.element;
$element.click();
$element.dblclick();
});
} }
else if ($(this).hasClass('open_ro')) { else if ($(this).hasClass('open_ro')) {
var el = filesOp.findElement(files, path); paths.forEach(function (p) {
if (filesOp.isFolder(el)) { return; } var el = filesOp.findElement(files, p.path);
var roUrl = getReadOnlyUrl(el); if (filesOp.isFolder(el)) { return; }
openFile(roUrl); var roUrl = getReadOnlyUrl(el);
openFile(roUrl, false);
});
} }
else if ($(this).hasClass('newfolder')) { else if ($(this).hasClass('newfolder')) {
if (paths.length !== 1) { return; }
var onCreated = function (info) { var onCreated = function (info) {
module.newFolder = info.newPath; module.newFolder = info.newPath;
module.displayDirectory(path); module.displayDirectory(paths[0].path);
}; };
filesOp.createNewFolder(path, null, onCreated); filesOp.createNewFolder(paths[0].path, null, onCreated);
} }
module.hideMenu(); module.hideMenu();
}); });
$defaultContextMenu.on("click", "a", function(e) { $defaultContextMenu.on("click", "a", function(e) {
e.stopPropagation(); e.stopPropagation();
var path = $(this).data('path'); var paths = $(this).data('paths');
var $element = $(this).data('element'); if (paths.length === 0) {
if (!$element || !path || path.length < 2) {
log(Messages.fm_forbidden); log(Messages.fm_forbidden);
debug("Directory context menu on a forbidden or unexisting element. ", $element, path); debug("Context menu on a forbidden or unexisting element. ", paths);
return; return;
} }
if ($(this).hasClass('open')) { if ($(this).hasClass('open')) {
$element.dblclick(); paths.forEach(function (p) {
var $element = p.element;
$element.dblclick();
});
} }
else if ($(this).hasClass('open_ro')) { else if ($(this).hasClass('open_ro')) {
var el = filesOp.findElement(files, path); paths.forEach(function (p) {
if (filesOp.isPathInFilesData(path)) { var el = filesOp.findElement(files, p.path);
el = el.href; if (filesOp.isPathInFilesData(p.path)) { el = el.href; }
} if (!el || filesOp.isFolder(el)) { return; }
if (!el || filesOp.isFolder(el)) { return; } var roUrl = getReadOnlyUrl(el);
var roUrl = getReadOnlyUrl(el); openFile(roUrl, false);
openFile(roUrl); });
} }
else if ($(this).hasClass('delete')) { else if ($(this).hasClass('delete')) {
moveElements([path], [TRASH], false, refresh); var pathsList = [];
paths.forEach(function (p) { pathsList.push(p.path); });
moveElements(pathsList, [TRASH], false, refresh);
} }
module.hideMenu(); module.hideMenu();
}); });
@ -1751,11 +1839,10 @@ define([
$trashTreeContextMenu.on('click', 'a', function (e) { $trashTreeContextMenu.on('click', 'a', function (e) {
e.stopPropagation(); e.stopPropagation();
var path = $(this).data('path'); var paths = $(this).data('paths');
var $element = $(this).data('element'); if (paths.length !== 1 || !paths[0].element || !filesOp.comparePath(paths[0].path, [TRASH])) {
if (!$element || !filesOp.comparePath(path, [TRASH])) {
log(Messages.fm_forbidden); log(Messages.fm_forbidden);
debug("Trash tree context menu on a forbidden or unexisting element. ", $element, path); debug("Trash tree context menu on a forbidden or unexisting element. ", paths);
return; return;
} }
if ($(this).hasClass("empty")) { if ($(this).hasClass("empty")) {
@ -1769,22 +1856,34 @@ define([
$trashContextMenu.on('click', 'a', function (e) { $trashContextMenu.on('click', 'a', function (e) {
e.stopPropagation(); e.stopPropagation();
var path = $(this).data('path'); var paths = $(this).data('paths');
var $element = $(this).data('element'); if (paths.length === 0) {
if (!$element || !path || path.length < 2) {
log(Messages.fm_forbidden); log(Messages.fm_forbidden);
debug("Trash context menu on a forbidden or unexisting element. ", $element, path); debug("Trash context menu on a forbidden or unexisting element. ", paths);
return; return;
} }
var name = path[path.length - 1]; var path = paths[0].path;
var name = paths[0].path[paths[0].path.length - 1];
if ($(this).hasClass("remove")) { if ($(this).hasClass("remove")) {
if (path.length === 4) { name = path[1]; } if (paths.length === 1) {
Cryptpad.confirm(Messages._getKey("fm_removePermanentlyDialog", [name]), function(res) { if (path.length === 4) { name = path[1]; }
Cryptpad.confirm(Messages._getKey("fm_removePermanentlyDialog", [name]), function(res) {
if (!res) { return; }
filesOp.removeFromTrash(path, refresh);
});
return;
}
var pathsList = [];
paths.forEach(function (p) { pathsList.push(p.path); });
var msg = Messages._getKey("fm_removeSeveralPermanentlyDialog", [paths.length]);
Cryptpad.confirm(msg, function(res) {
if (!res) { return; } if (!res) { return; }
filesOp.removeFromTrash(path, refresh); filesOp.deletePathsPermanently(pathsList);
refresh();
}); });
} }
else if ($(this).hasClass("restore")) { else if ($(this).hasClass("restore")) {
if (paths.length !== 1) { return; }
if (path.length === 4) { name = path[1]; } if (path.length === 4) { name = path[1]; }
Cryptpad.confirm(Messages._getKey("fm_restoreDialog", [name]), function(res) { Cryptpad.confirm(Messages._getKey("fm_restoreDialog", [name]), function(res) {
if (!res) { return; } if (!res) { return; }
@ -1792,6 +1891,7 @@ define([
}); });
} }
else if ($(this).hasClass("properties")) { else if ($(this).hasClass("properties")) {
if (paths.length !== 1) { return; }
if (path.length !== 4) { return; } if (path.length !== 4) { return; }
var element = filesOp.getTrashElementData(path); var element = filesOp.getTrashElementData(path);
var sPath = stringifyPath(element.path); var sPath = stringifyPath(element.path);
@ -1803,12 +1903,12 @@ define([
$(ifrw).on('click', function (e) { $(ifrw).on('click', function (e) {
if (e.which !== 1) { return ; } if (e.which !== 1) { return ; }
removeSelected(e); removeSelected(e);
removeInput(e); removeInput();
module.hideMenu(e); module.hideMenu(e);
hideNewButton(); hideNewButton();
}); });
$(ifrw).on('drag drop', function (e) { $(ifrw).on('drag drop', function (e) {
removeInput(e); removeInput();
module.hideMenu(e); module.hideMenu(e);
}); });
$(ifrw).on('mouseup drop', function (e) { $(ifrw).on('mouseup drop', function (e) {
@ -1837,6 +1937,7 @@ define([
} }
Cryptpad.confirm(msg, function(res) { Cryptpad.confirm(msg, function(res) {
$(ifrw).focus();
if (!res) { return; } if (!res) { return; }
filesOp.deletePathsPermanently(paths); filesOp.deletePathsPermanently(paths);
refresh(); refresh();
@ -1949,30 +2050,19 @@ define([
logError("Couldn't set username", err); logError("Couldn't set username", err);
return; return;
} }
if (myUserName === "") {
myUserName = Messages.anonymous;
}
APP.$displayName.text(myUserName); APP.$displayName.text(myUserName);
}); });
}; };
// TODO: move that function and use a more generic API?
var migrateAnonDrive = function (proxy, cb) { var migrateAnonDrive = function (proxy, cb) {
if (sessionStorage.migrateAnonDrive) { if (sessionStorage.migrateAnonDrive) {
// Make sure we have an FS_hash and we don't use it, otherwise just stop the migration and cb Merge.anonDriveIntoUser(proxy, function () {
if (!localStorage.FS_hash || !APP.loggedIn) {
delete sessionStorage.migrateAnonDrive; delete sessionStorage.migrateAnonDrive;
if (typeof(cb) === "function") { cb(); } if (typeof(cb) === "function") { cb(); }
} });
// Get the content of FS_hash and then merge the objects, remove the migration key and cb
var todo = function (err, doc) {
if (err) { logError("Cannot migrate recent pads", err); return; }
var parsed;
try { parsed = JSON.parse(doc); } catch (e) { logError("Cannot parsed recent pads", e); }
if (parsed) {
$.extend(true, proxy, parsed);
}
delete sessionStorage.migrateAnonDrive;
if (typeof(cb) === "function") { cb(); }
};
Get.get(localStorage.FS_hash, todo);
} else { } else {
if (typeof(cb) === "function") { cb(); } if (typeof(cb) === "function") { cb(); }
} }

@ -76,18 +76,21 @@ define([
if (result.proxy && !result.proxy.login_name) { if (result.proxy && !result.proxy.login_name) {
result.proxy.login_name = result.userName; result.proxy.login_name = result.userName;
} }
Cryptpad.login(result.userHash, result.userName, function () {
if (sessionStorage.redirectTo) { Cryptpad.whenRealtimeSyncs(result.realtime, function() {
var h = sessionStorage.redirectTo; Cryptpad.login(result.userHash, result.userName, function () {
var parser = document.createElement('a'); if (sessionStorage.redirectTo) {
parser.href = h; var h = sessionStorage.redirectTo;
if (parser.origin === window.location.origin) { var parser = document.createElement('a');
delete sessionStorage.redirectTo; parser.href = h;
window.location.href = h; if (parser.origin === window.location.origin) {
return; delete sessionStorage.redirectTo;
window.location.href = h;
return;
}
} }
} window.location.href = '/drive/';
window.location.href = '/drive/'; });
}); });
return; return;
} }

@ -4,6 +4,7 @@
<title>CryptPad</title> <title>CryptPad</title>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/> <meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css">
<link rel="icon" type="image/png" <link rel="icon" type="image/png"
href="/customize/main-favicon.png" href="/customize/main-favicon.png"
data-main-favicon="/customize/main-favicon.png" data-main-favicon="/customize/main-favicon.png"
@ -56,5 +57,14 @@
</head> </head>
<body> <body>
<iframe id="pad-iframe"></iframe><script src="/common/noscriptfix.js"></script> <iframe id="pad-iframe"></iframe><script src="/common/noscriptfix.js"></script>
<div id="loading">
<div class="loadingContainer">
<img class="cryptofist" src="/customize/cryptofist_small.png" />
<div class="spinnerContainer">
<span class="fa fa-spinner fa-pulse fa-4x fa-fw"></span>
</div>
<p data-localization="loading"></p>
</div>
</div>
</body> </body>
</html> </html>

@ -110,7 +110,7 @@ define([
var defaultName = Cryptpad.getDefaultName(parsedHash); var defaultName = Cryptpad.getDefaultName(parsedHash);
if (readOnly) { if (readOnly) {
$('#pad-iframe')[0].contentWindow.$('#cke_1_toolbox > .cke_toolbar').hide(); $('#pad-iframe')[0].contentWindow.$('#cke_1_toolbox > .cke_toolbox_main').hide();
} }
/* add a class to the magicline plugin so we can pick it out more easily */ /* add a class to the magicline plugin so we can pick it out more easily */
@ -164,6 +164,16 @@ define([
send scripts over the wire. send scripts over the wire.
*/ */
if (['addAttribute', 'modifyAttribute'].indexOf(info.diff.action) !== -1) { if (['addAttribute', 'modifyAttribute'].indexOf(info.diff.action) !== -1) {
if (info.diff.name === 'href') {
// console.log(info.diff);
var href = info.diff.newValue;
// TODO normalize HTML entities
if (/javascript *: */.test(info.diff.newValue)) {
// TODO remove javascript: links
}
}
if (/^on/.test(info.diff.name)) { if (/^on/.test(info.diff.name)) {
console.log("Rejecting forbidden element attribute with name (%s)", info.diff.name); console.log("Rejecting forbidden element attribute with name (%s)", info.diff.name);
return true; return true;
@ -561,6 +571,10 @@ define([
userData: userData, userData: userData,
readOnly: readOnly, readOnly: readOnly,
ifrw: ifrw, ifrw: ifrw,
share: {
secret: secret,
channel: info.channel
},
title: { title: {
onRename: renameCb, onRename: renameCb,
defaultName: defaultName, defaultName: defaultName,
@ -573,8 +587,6 @@ define([
var $rightside = $bar.find('.' + Toolbar.constants.rightside); var $rightside = $bar.find('.' + Toolbar.constants.rightside);
var $userBlock = $bar.find('.' + Toolbar.constants.username); var $userBlock = $bar.find('.' + Toolbar.constants.username);
var $editShare = $bar.find('.' + Toolbar.constants.editShare);
var $viewShare = $bar.find('.' + Toolbar.constants.viewShare);
var $usernameButton = module.$userNameButton = $($bar.find('.' + Toolbar.constants.changeUsername)); var $usernameButton = module.$userNameButton = $($bar.find('.' + Toolbar.constants.changeUsername));
var editHash; var editHash;
@ -584,23 +596,24 @@ define([
editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys); editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys);
} }
// Expand / collapse the toolbar var $existingButton = $bar.find('#cke_1_toolbar_collapser').hide();
var $existingButton = $bar.find('#cke_1_toolbar_collapser'); if (!readOnly) {
var $collapse = Cryptpad.createButton(null, true); // Expand / collapse the toolbar
$existingButton.hide(); var $collapse = Cryptpad.createButton(null, true);
$collapse.removeClass('fa-question'); $collapse.removeClass('fa-question');
var updateIcon = function () { var updateIcon = function () {
$collapse.removeClass('fa-caret-down').removeClass('fa-caret-up'); $collapse.removeClass('fa-caret-down').removeClass('fa-caret-up');
var isCollapsed = !$bar.find('.cke_toolbox_main').is(':visible'); var isCollapsed = !$bar.find('.cke_toolbox_main').is(':visible');
if (isCollapsed) { $collapse.addClass('fa-caret-down'); } if (isCollapsed) { $collapse.addClass('fa-caret-down'); }
else { $collapse.addClass('fa-caret-up'); } else { $collapse.addClass('fa-caret-up'); }
}; };
updateIcon();
$collapse.click(function () {
$existingButton.click();
updateIcon(); updateIcon();
}); $collapse.click(function () {
$rightside.append($collapse); $existingButton.click();
updateIcon();
});
$rightside.append($collapse);
}
/* add an export button */ /* add an export button */
var $export = Cryptpad.createButton('export', true, {}, exportFile); var $export = Cryptpad.createButton('export', true, {}, exportFile);
@ -624,20 +637,6 @@ define([
var $forgetPad = Cryptpad.createButton('forget', true, {}, forgetCb); var $forgetPad = Cryptpad.createButton('forget', true, {}, forgetCb);
$rightside.append($forgetPad); $rightside.append($forgetPad);
if (!readOnly) {
$editShare.append(Cryptpad.createButton('editshare', false, {editHash: editHash}));
if (viewHash) {
$editShare.append($('<hr>'));
}
}
if (viewHash) {
/* add a 'links' button */
$viewShare.append(Cryptpad.createButton('viewshare', false, {viewHash: viewHash}));
if (!readOnly) {
$viewShare.append(Cryptpad.createButton('viewopen', false, {viewHash: viewHash}));
}
}
// set the hash // set the hash
if (!readOnly) { Cryptpad.replaceHash(editHash); } if (!readOnly) { Cryptpad.replaceHash(editHash); }

@ -88,6 +88,7 @@
margin: auto; margin: auto;
min-width: 80%; min-width: 80%;
width: 80%;
min-height: 5em; min-height: 5em;
font-size: 20px; font-size: 20px;
font-weight: bold; font-weight: bold;
@ -142,3 +143,13 @@
<button data-localization-title="poll_commit" id="commit"><span class="fa fa-check"></span></button> <button data-localization-title="poll_commit" id="commit"><span class="fa fa-check"></span></button>
</div> </div>
</div> </div>
<div id="loading">
<div class="loadingContainer">
<img class="cryptofist" src="/customize/cryptofist_small.png" />
<div class="spinnerContainer">
<span class="fa fa-spinner fa-pulse fa-4x fa-fw"></span>
</div>
<p data-localization="loading"></p>
</div>
</div>

@ -647,7 +647,7 @@ define([
// Update the toolbar list: // Update the toolbar list:
// Add the current user in the metadata if he has edit rights // Add the current user in the metadata if he has edit rights
if (readOnly) { return; } if (readOnly) { return; }
if (typeof(lastName) === 'string' && lastName.length) { if (typeof(lastName) === 'string') {
setName(lastName); setName(lastName);
} else { } else {
var myData = {}; var myData = {};
@ -687,6 +687,10 @@ define([
displayed: ['useradmin', 'language', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad'], displayed: ['useradmin', 'language', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad'],
userData: userData, userData: userData,
readOnly: readOnly, readOnly: readOnly,
share: {
secret: secret,
channel: info.channel
},
title: { title: {
onRename: renameCb, onRename: renameCb,
defaultName: defaultName, defaultName: defaultName,
@ -712,17 +716,6 @@ define([
var $forgetPad = Cryptpad.createButton('forget', true, {}, forgetCb); var $forgetPad = Cryptpad.createButton('forget', true, {}, forgetCb);
$rightside.append($forgetPad); $rightside.append($forgetPad);
if (!readOnly) {
$editShare.append(Cryptpad.createButton('editshare', false, {editHash: editHash}));
}
if (viewHash) {
/* add a 'links' button */
$viewShare.append(Cryptpad.createButton('viewshare', false, {viewHash: viewHash}));
if (!readOnly) {
$viewShare.append(Cryptpad.createButton('viewopen', false, {viewHash: viewHash}));
}
}
// set the hash // set the hash
if (!readOnly) { Cryptpad.replaceHash(editHash); } if (!readOnly) { Cryptpad.replaceHash(editHash); }

@ -306,6 +306,7 @@ var Renderer = function (Cryptpad) {
labelClass += ' yes'; labelClass += ' yes';
} }
// TODO implement Yes/No/Maybe/Undecided
return ['TD', {class:"checkbox-cell"}, [ return ['TD', {class:"checkbox-cell"}, [
['DIV', {class: 'checkbox-contain'}, [ ['DIV', {class: 'checkbox-contain'}, [
['INPUT', attrs, []], ['INPUT', attrs, []],
@ -397,6 +398,7 @@ var Renderer = function (Cryptpad) {
info.node.selectionEnd = info.selection[1]; info.node.selectionEnd = info.selection[1];
} }
} catch (err) { } catch (err) {
// FIXME LOL empty try-catch?
//console.log(info.node); //console.log(info.node);
//console.error(err); //console.error(err);
} }

@ -71,9 +71,6 @@
<input id="accept-terms" type="checkbox" /> <input id="accept-terms" type="checkbox" />
<label for="accept-terms" data-localization="register_acceptTerms"></label><br /> <label for="accept-terms" data-localization="register_acceptTerms"></label><br />
<input id="promise" type="checkbox" />
<label for="promise" data-localization="register_rememberPassword"></label><br />
<button id="register" class="btn btn-primary" data-localization="login_register"></button> <button id="register" class="btn btn-primary" data-localization="login_register"></button>
</div> </div>
</div> </div>

@ -58,10 +58,28 @@ define([
// checkboxes // checkboxes
var $checkImport = $('#import-recent'); var $checkImport = $('#import-recent');
var $checkAcceptTerms = $('#accept-terms'); var $checkAcceptTerms = $('#accept-terms');
var $checkPromise = $('#promise');
var $register = $('button#register'); var $register = $('button#register');
var logMeIn = function (result) {
localStorage.User_hash = result.userHash;
Cryptpad.whenRealtimeSyncs(result.realtime, function () {
Cryptpad.login(result.userHash, result.userName, function () {
if (sessionStorage.redirectTo) {
var h = sessionStorage.redirectTo;
var parser = document.createElement('a');
parser.href = h;
if (parser.origin === window.location.origin) {
delete sessionStorage.redirectTo;
window.location.href = h;
return;
}
}
window.location.href = '/drive/';
});
});
};
$register.click(function () { $register.click(function () {
var uname = $uname.val(); var uname = $uname.val();
var passwd = $passwd.val(); var passwd = $passwd.val();
@ -69,7 +87,6 @@ define([
var shouldImport = $checkImport[0].checked; var shouldImport = $checkImport[0].checked;
var doesAccept = $checkAcceptTerms[0].checked; var doesAccept = $checkAcceptTerms[0].checked;
var doesPromise = $checkPromise[0].checked;
/* basic validation */ /* basic validation */
if (passwd !== confirmPassword) { // do their passwords match? if (passwd !== confirmPassword) { // do their passwords match?
@ -80,61 +97,69 @@ define([
return void Cryptpad.alert(Messages.register_mustAcceptTerms); return void Cryptpad.alert(Messages.register_mustAcceptTerms);
} }
if (!doesPromise) { // do they promise to remember their password? Cryptpad.confirm("<h2 class='bright'>" + Messages.register_warning + "</h2>",
return void Cryptpad.alert(Messages.register_mustRememberPass); function (yes) {
} if (!yes) { return; }
Cryptpad.addLoadingScreen(Messages.login_hashing);
Login.loginOrRegister(uname, passwd, true, function (err, result) {
if (err) {
switch (err) {
case 'NO_SUCH_USER':
Cryptpad.removeLoadingScreen(function () {
Cryptpad.alert(Messages.login_noSuchUser);
});
break;
case 'INVAL_USER':
Cryptpad.removeLoadingScreen(function () {
Cryptpad.alert(Messages.login_invalUser);
});
break;
case 'INVAL_PASS':
Cryptpad.removeLoadingScreen(function () {
Cryptpad.alert(Messages.login_invalPass);
});
break;
case 'ALREADY_REGISTERED':
Cryptpad.removeLoadingScreen(function () {
Cryptpad.confirm(Messages.register_alreadyRegistered, function (yes) {
if (!yes) { return; }
result.proxy.login_name = uname;
if (!result.proxy[Cryptpad.displayNameKey]) {
result.proxy[Cryptpad.displayNameKey] = uname;
}
Cryptpad.eraseTempSessionValues();
logMeIn(result);
});
});
break;
default: // UNHANDLED ERROR
Cryptpad.errorLoadingScreen(Messages.login_unhandledError);
}
return;
}
var proxy = result.proxy;
Cryptpad.addLoadingScreen(Messages.login_hashing); Cryptpad.eraseTempSessionValues();
Login.loginOrRegister(uname, passwd, true, function (err, result) { if (shouldImport) {
if (err) { sessionStorage.migrateAnonDrive = 1;
switch (err) {
case 'NO_SUCH_USER':
Cryptpad.removeLoadingScreen(function () {
Cryptpad.alert(Messages.login_noSuchUser);
});
break;
case 'INVAL_USER':
Cryptpad.removeLoadingScreen(function () {
Cryptpad.alert(Messages.login_invalUser);
});
break;
case 'INVAL_PASS':
Cryptpad.removeLoadingScreen(function () {
Cryptpad.alert(Messages.login_invalPass);
});
break;
default: // UNHANDLED ERROR
Cryptpad.errorLoadingScreen(Messages.login_unhandledError);
} }
}
var proxy = result.proxy; proxy.login_name = uname;
proxy[Cryptpad.displayNameKey] = uname;
localStorage.User_hash = result.userHash; sessionStorage.createReadme = 1;
Cryptpad.eraseTempSessionValues(); logMeIn(result);
if (shouldImport) {
sessionStorage.migrateAnonDrive = 1;
}
proxy.login_name = uname;
proxy[Cryptpad.displayNameKey] = uname;
sessionStorage.createReadme = 1;
Cryptpad.whenRealtimeSyncs(result.realtime, function () {
Cryptpad.login(result.userHash, result.userName, function () {
if (sessionStorage.redirectTo) {
var h = sessionStorage.redirectTo;
var parser = document.createElement('a');
parser.href = h;
if (parser.origin === window.location.origin) {
delete sessionStorage.redirectTo;
window.location.href = h;
return;
}
}
window.location.href = '/drive/';
});
}); });
}, {
ok: Messages.register_writtenPassword, //'I have written down my password, proceed',
cancel: Messages.register_cancel, // 'Go back',
cancelClass: 'safe',
okClass: 'danger',
reverseOrder: true,
}, true, function ($dialog) {
$dialog.find('> div').addClass('half');
}); });
}); });
}); });

@ -106,6 +106,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="version-footer">CryptPad v1.2.0 (Chupacabra)</div>
</footer> </footer>
</body> </body>

@ -1,9 +1,10 @@
define([ define([
'/common/cryptpad-common.js', '/common/cryptpad-common.js',
'/common/cryptget.js', '/common/cryptget.js',
'/common/mergeDrive.js',
'/bower_components/file-saver/FileSaver.min.js', '/bower_components/file-saver/FileSaver.min.js',
'/bower_components/jquery/dist/jquery.min.js', '/bower_components/jquery/dist/jquery.min.js',
], function (Cryptpad, Crypt) { ], function (Cryptpad, Crypt, Merge) {
var $ = window.jQuery; var $ = window.jQuery;
var saveAs = window.saveAs; var saveAs = window.saveAs;
@ -41,7 +42,7 @@ define([
var obj = store.proxy; var obj = store.proxy;
var $div = $('<div>', {'class': 'infoBlock'}); var $div = $('<div>', {'class': 'infoBlock'});
var accountName = obj.login_name; var accountName = obj.login_name || localStorage[Cryptpad.userNameKey];
var $label = $('<span>', {'class': 'label'}).text(Messages.user_accountName + ':'); var $label = $('<span>', {'class': 'label'}).text(Messages.user_accountName + ':');
var $name = $('<span>').text(accountName || ''); var $name = $('<span>').text(accountName || '');
if (!accountName) { if (!accountName) {
@ -65,10 +66,9 @@ define([
'id': 'displayName', 'id': 'displayName',
'placeholder': Messages.anonymous}).appendTo($div); 'placeholder': Messages.anonymous}).appendTo($div);
var $save = $('<button>', {'class': 'btn btn-primary'}).text(Messages.settings_save).appendTo($div); var $save = $('<button>', {'class': 'btn btn-primary'}).text(Messages.settings_save).appendTo($div);
var $ok = $('<span>', {'class': 'fa fa-check'}).appendTo($div); var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved}).hide().appendTo($div);
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'}).appendTo($div); var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'}).hide().appendTo($div);
$spinner.hide();
var displayName = obj[USERNAME_KEY] || ''; var displayName = obj[USERNAME_KEY] || '';
$input.val(displayName); $input.val(displayName);
@ -124,7 +124,8 @@ define([
var exportFile = function () { var exportFile = function () {
var sjson = JSON.stringify(obj); var sjson = JSON.stringify(obj);
var suggestion = obj.login_name + '-' + new Date().toDateString(); var name = obj.login_name || obj[USERNAME_KEY] || Messages.anonymous;
var suggestion = name + '-' + new Date().toDateString();
Cryptpad.prompt(Cryptpad.Messages.exportPrompt, Cryptpad.prompt(Cryptpad.Messages.exportPrompt,
Cryptpad.fixFileName(suggestion) + '.json', function (filename) { Cryptpad.fixFileName(suggestion) + '.json', function (filename) {
if (!(typeof(filename) === 'string' && filename)) { return; } if (!(typeof(filename) === 'string' && filename)) { return; }
@ -134,7 +135,7 @@ define([
}; };
var importFile = function (content, file) { var importFile = function (content, file) {
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'}).appendTo($div); var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'}).appendTo($div);
Crypt.put(Cryptpad.getUserHash(), content, function (e) { Crypt.put(Cryptpad.getUserHash() || localStorage[Cryptpad.fileHashKey], content, function (e) {
if (e) { console.error(e); } if (e) { console.error(e); }
$spinner.remove(); $spinner.remove();
}); });
@ -164,7 +165,10 @@ define([
$button.click(function () { $button.click(function () {
Cryptpad.prompt(Messages.settings_resetPrompt, "", function (val) { Cryptpad.prompt(Messages.settings_resetPrompt, "", function (val) {
if (val !== "I love CryptPad") { return; } if (val !== "I love CryptPad") {
Cryptpad.alert(Messages.settings_resetError);
return;
}
obj.proxy.drive = Cryptpad.getStore().getEmptyObject(); obj.proxy.drive = Cryptpad.getStore().getEmptyObject();
Cryptpad.alert(Messages.settings_resetDone); Cryptpad.alert(Messages.settings_resetDone);
}, undefined, true); }, undefined, true);
@ -183,29 +187,66 @@ define([
$('<br>').appendTo($div); $('<br>').appendTo($div);
var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved});
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'});
var $checkbox = $('<input>', { var $checkbox = $('<input>', {
'type': 'checkbox', 'type': 'checkbox',
}).on('change', function () { }).on('change', function () {
$spinner.show();
$ok.hide();
obj.proxy.allowUserFeedback = $checkbox.is(':checked') || false; obj.proxy.allowUserFeedback = $checkbox.is(':checked') || false;
// TODO provide feedback to show if this is synced Cryptpad.whenRealtimeSyncs(obj.info.realtime, function () {
// Cryptpad.whenRealtimeSyncs... $spinner.hide();
$ok.show();
});
}); });
$checkbox.appendTo($div); $checkbox.appendTo($div);
$label.appendTo($div); $label.appendTo($div);
$ok.hide().appendTo($div);
$spinner.hide().appendTo($div);
if (obj.proxy.allowUserFeedback) { if (obj.proxy.allowUserFeedback) {
$checkbox[0].checked = true; $checkbox[0].checked = true;
} }
return $div; return $div;
}; };
var createImportLocalPads = function (obj) {
if (!Cryptpad.isLoggedIn()) { return; }
var $div = $('<div>', {'class': 'importLocalPads'});
var $label = $('<label>', {'for' : 'importLocalPads'}).text(Messages.settings_importTitle).appendTo($div);
$('<br>').appendTo($div);
var $button = $('<button>', {'id': 'importLocalPads', 'class': 'btn btn-primary'})
.text(Messages.settings_import).appendTo($div);
var $ok = $('<span>', {'class': 'fa fa-check', title: Messages.saved}).hide().appendTo($div);
var $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'}).hide().appendTo($div);
$button.click(function () {
Cryptpad.confirm(Messages.settings_importConfirm, function (yes) {
if (!yes) { return; }
$spinner.show();
$ok.hide();
Merge.anonDriveIntoUser(obj.proxy, function () {
$spinner.hide();
$ok.show();
Cryptpad.alert(Messages.settings_importDone);
});
}, undefined, true);
});
return $div;
};
var andThen = function (obj) { var andThen = function (obj) {
APP.$container.append(createTitle()); APP.$container.append(createTitle());
APP.$container.append(createInfoBlock(obj)); APP.$container.append(createInfoBlock(obj));
APP.$container.append(createDisplayNameInput(obj)); APP.$container.append(createDisplayNameInput(obj));
APP.$container.append(createResetTips()); APP.$container.append(createResetTips());
APP.$container.append(createBackupDrive(obj)); APP.$container.append(createBackupDrive(obj));
APP.$container.append(createImportLocalPads(obj));
APP.$container.append(createResetDrive(obj)); APP.$container.append(createResetDrive(obj));
APP.$container.append(createUserFeedbackToggle(obj)); APP.$container.append(createUserFeedbackToggle(obj));
obj.proxy.on('change', [], refresh); obj.proxy.on('change', [], refresh);

@ -10,6 +10,7 @@
data-main-favicon="/customize/main-favicon.png" data-main-favicon="/customize/main-favicon.png"
data-alt-favicon="/customize/alt-favicon.png" data-alt-favicon="/customize/alt-favicon.png"
id="favicon" /> id="favicon" />
<link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css">
<link rel="stylesheet" href="/customize/main.css" /> <link rel="stylesheet" href="/customize/main.css" />
<style> <style>
html, body { html, body {
@ -34,19 +35,30 @@
} }
/* We use !important here to override the 96% set to the element in DecorateToolbar.js /* We use !important here to override the 96% set to the element in DecorateToolbar.js
when we enter fullscreen mode. It allows us to avoid changing the iframe's size in JS */ when we enter fullscreen mode. It allows us to avoid changing the iframe's size in JS */
#pad-iframe.fullscreen { body #pad-iframe.fullscreen {
top: 0px; top: 0px;
height: 100% !important; height: 100%;
} }
#iframe-container.fullscreen { body #iframe-container.fullscreen {
top: 0px; top: 0px;
height: 100% !important; height: 100%;
} }
</style> </style>
</head> </head>
<body> <body>
<div id="iframe-container"> <div id="iframe-container">
<iframe id="pad-iframe"></iframe><script src="/common/noscriptfix.js"></script> <iframe id="pad-iframe", name="pad-iframe"></iframe><script src="/common/noscriptfix.js"></script>
</div>
<div id="loading">
<div class="loadingContainer">
<img class="cryptofist" src="/customize/cryptofist_small.png" />
<div class="spinnerContainer">
<span class="fa fa-spinner fa-pulse fa-4x fa-fw"></span>
</div>
<p data-localization="loading"></p>
</div>
</div> </div>
</body> </body>
</html> </html>

@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="stylesheet" href="/customize/main.css"> <link rel="stylesheet" href="/customize/main.css">
<link rel="stylesheet" href="./slide.css">
<link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css"> <link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css">
<script src="/bower_components/jquery/dist/jquery.min.js"></script> <script src="/bower_components/jquery/dist/jquery.min.js"></script>
<script src="/bower_components/codemirror/lib/codemirror.js"></script> <script src="/bower_components/codemirror/lib/codemirror.js"></script>
@ -34,55 +35,6 @@
<script src="/bower_components/codemirror/addon/fold/markdown-fold.js"></script> <script src="/bower_components/codemirror/addon/fold/markdown-fold.js"></script>
<script src="/bower_components/codemirror/addon/fold/comment-fold.js"></script> <script src="/bower_components/codemirror/addon/fold/comment-fold.js"></script>
<script src="/bower_components/codemirror/addon/display/placeholder.js"></script> <script src="/bower_components/codemirror/addon/display/placeholder.js"></script>
<style>
html, body{
height: 100%;
width: 100%;
padding: 0px;
margin: 0px;
overflow: hidden;
box-sizing: border-box;
position: relative;
}
body {
display: flex;
flex-flow: column;
}
#modal.shown {
display: block;
position: fixed;
top: 0px;
left: 0px;
z-index: 100;
background-color: black;
color: white;
height: 100vh;
width: 100%;
}
#content h1, h2, h3, h4, h5, h6 {
text-align: center;
}
h1 { font-size: 40px; }
h2 { font-size: 37px; }
h3 { font-size: 34px; }
h4 { font-size: 31px; }
h5 { font-size: 27px; }
h6 { font-size: 24px; }
.CodeMirror {
height: 100%;
}
.CodeMirror-focused .cm-matchhighlight {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==);
background-position: bottom;
background-repeat: repeat-x;
}
#colorPicker_check {
display: block;
}
</style>
</head> </head>
<body> <body>
<div id="bar"></div> <div id="bar"></div>
@ -98,6 +50,7 @@
<div id="button_right" class="button"><span class="fa fa-chevron-right"></span></div> <div id="button_right" class="button"><span class="fa fa-chevron-right"></span></div>
<div id="content"></div> <div id="content"></div>
</div> </div>
<div id="print"></div>
</span> </span>
<div id="nope"></div> <div id="nope"></div>
<div id="colorPicker_check"></div> <div id="colorPicker_check"></div>

@ -128,12 +128,15 @@ define([
} }
editor.setOption('theme', theme); editor.setOption('theme', theme);
} }
if ($select) { $select.find('.buttonTitle').text(theme || 'Theme'); } if ($select) {
$select.setValue(theme || 'Theme');
}
}; };
}()); }());
var $modal = $pad.contents().find('#modal'); var $modal = $pad.contents().find('#modal');
var $content = $pad.contents().find('#content'); var $content = $pad.contents().find('#content');
var $print = $pad.contents().find('#print');
Slide.setModal($modal, $content, $pad, ifrw, initialState); Slide.setModal($modal, $content, $pad, ifrw, initialState);
@ -409,6 +412,79 @@ define([
onLocal(); onLocal();
}; };
var createPrintDialog = function () {
var printOptions = {
title: true,
slide: true,
date: true
};
var $container = $('<div class="alertify">');
var $container2 = $('<div class="dialog">').appendTo($container);
var $div = $('<div id="printOptions">').appendTo($container2);
var $p = $('<p>', {'class': 'msg'}).appendTo($div);
$('<b>').text(Messages.printOptions).appendTo($p);
$p.append($('<br>'));
// Slide number
$('<input>', {type: 'checkbox', id: 'checkNumber', checked: 'checked'}).on('change', function () {
var c = this.checked;
console.log(c);
printOptions.slide = c;
}).appendTo($p).css('width', 'auto');
$('<label>', {'for': 'checkNumber'}).text(Messages.printSlideNumber).appendTo($p);
$p.append($('<br>'));
// Date
$('<input>', {type: 'checkbox', id: 'checkDate', checked: 'checked'}).on('change', function () {
var c = this.checked;
printOptions.date = c;
}).appendTo($p).css('width', 'auto');
$('<label>', {'for': 'checkDate'}).text(Messages.printDate).appendTo($p);
$p.append($('<br>'));
// Title
$('<input>', {type: 'checkbox', id: 'checkTitle', checked: 'checked'}).on('change', function () {
var c = this.checked;
printOptions.title = c;
}).appendTo($p).css('width', 'auto');
$('<label>', {'for': 'checkTitle'}).text(Messages.printTitle).appendTo($p);
$p.append($('<br>'));
// CSS
$('<label>', {'for': 'cssPrint'}).text(Messages.printCSS).appendTo($p);
$p.append($('<br>'));
var $textarea = $('<textarea>', {'id':'cssPrint'}).css({'width':'100%', 'height':'100px'}).appendTo($p);
var fixCSS = function (css) {
var append = '.cp #print ';
return css.replace(/(\n*)([^\n]+)\s*\{/g, '$1' + append + '$2 {');
};
var todo = function () {
var $style = $('<style>').text(fixCSS($textarea.val()));
$print.prepend($style);
var length = $print.find('.slide-frame').length;
$print.find('.slide-frame').each(function (i, el) {
if (printOptions.slide) {
$('<div>', {'class': 'slideNumber'}).text((i+1)+'/'+length).appendTo($(el));
}
if (printOptions.date) {
$('<div>', {'class': 'slideDate'}).text(new Date().toLocaleDateString()).appendTo($(el));
}
if (printOptions.title) {
$('<div>', {'class': 'slideTitle'}).text(APP.title).appendTo($(el));
}
});
window.frames["pad-iframe"].focus();
window.frames["pad-iframe"].print();
$container.remove();
};
var $nav = $('<nav>').appendTo($div);
var $ok = $('<button>', {'class': 'ok'}).text(Messages.printButton).appendTo($nav).click(todo);
var $cancel = $('<button>', {'class': 'cancel'}).text(Messages.cancel).appendTo($nav).click(function () {
$container.remove();
});
return $container;
};
var onInit = config.onInit = function (info) { var onInit = config.onInit = function (info) {
userList = info.userList; userList = info.userList;
@ -417,6 +493,10 @@ define([
userData: userData, userData: userData,
readOnly: readOnly, readOnly: readOnly,
ifrw: ifrw, ifrw: ifrw,
share: {
secret: secret,
channel: info.channel
},
title: { title: {
onRename: renameCb, onRename: renameCb,
defaultName: defaultName, defaultName: defaultName,
@ -429,8 +509,6 @@ define([
var $rightside = $bar.find('.' + Toolbar.constants.rightside); var $rightside = $bar.find('.' + Toolbar.constants.rightside);
var $userBlock = $bar.find('.' + Toolbar.constants.username); var $userBlock = $bar.find('.' + Toolbar.constants.username);
var $editShare = $bar.find('.' + Toolbar.constants.editShare);
var $viewShare = $bar.find('.' + Toolbar.constants.viewShare);
var $usernameButton = module.$userNameButton = $($bar.find('.' + Toolbar.constants.changeUsername)); var $usernameButton = module.$userNameButton = $($bar.find('.' + Toolbar.constants.changeUsername));
var editHash; var editHash;
@ -462,16 +540,15 @@ define([
var $forgetPad = Cryptpad.createButton('forget', true, {}, forgetCb); var $forgetPad = Cryptpad.createButton('forget', true, {}, forgetCb);
$rightside.append($forgetPad); $rightside.append($forgetPad);
if (!readOnly) { var $printButton = $('<button>', {
$editShare.append(Cryptpad.createButton('editshare', false, {editHash: editHash})); title: Messages.printButtonTitle,
} 'class': 'rightside-button fa fa-print',
if (viewHash) { style: 'font-size: 17px'
/* add a 'links' button */ }).click(function () {
$viewShare.append(Cryptpad.createButton('viewshare', false, {viewHash: viewHash + '/present'})); $print.html($content.html());
if (!readOnly) { $('body').append(createPrintDialog());
$viewShare.append(Cryptpad.createButton('viewopen', false, {viewHash: viewHash + '/present'})); });
} $rightside.append($printButton);
}
var $present = Cryptpad.createButton('present', true) var $present = Cryptpad.createButton('present', true)
.click(function () { .click(function () {
@ -509,6 +586,8 @@ define([
text: 'Theme', // Button initial text text: 'Theme', // Button initial text
options: options, // Entries displayed in the menu options: options, // Entries displayed in the menu
left: true, // Open to the left of the button left: true, // Open to the left of the button
isSelect: true,
initialValue: lastTheme
}; };
var $block = module.$theme = Cryptpad.createDropdown(dropdownConfig); var $block = module.$theme = Cryptpad.createDropdown(dropdownConfig);
var $button = $block.find('.buttonTitle'); var $button = $block.find('.buttonTitle');
@ -518,7 +597,6 @@ define([
$block.find('a').click(function (e) { $block.find('a').click(function (e) {
var theme = $(this).attr('data-value'); var theme = $(this).attr('data-value');
setTheme(theme, $block); setTheme(theme, $block);
$button.text($(this).text());
localStorage.setItem(themeKey, theme); localStorage.setItem(themeKey, theme);
}); });
@ -530,13 +608,13 @@ define([
id: SLIDE_BACKCOLOR_ID, id: SLIDE_BACKCOLOR_ID,
'class': 'fa fa-square rightside-button', 'class': 'fa fa-square rightside-button',
'style': 'font-family: FontAwesome; color: #000;', 'style': 'font-family: FontAwesome; color: #000;',
title: Messages.backgroundButton + '\n' + Messages.backgroundButtonTitle title: Messages.backgroundButtonTitle
}); });
var $text = $('<button>', { var $text = $('<button>', {
id: SLIDE_COLOR_ID, id: SLIDE_COLOR_ID,
'class': 'fa fa-i-cursor rightside-button', 'class': 'fa fa-i-cursor rightside-button',
'style': 'font-family: FontAwesome; font-weight: bold; color: #fff; background: #000;', 'style': 'font-family: FontAwesome; font-weight: bold; color: #fff; background: #000;',
title: Messages.colorButton + '\n' + Messages.colorButtonTitle title: Messages.colorButtonTitle
}); });
var $testColor = $('<input>', { type: 'color', value: '!' }); var $testColor = $('<input>', { type: 'color', value: '!' });
var $check = $pad.contents().find("#colorPicker_check"); var $check = $pad.contents().find("#colorPicker_check");
@ -634,7 +712,8 @@ define([
updateMetadata(userDoc); updateMetadata(userDoc);
editor.setValue(newDoc || initialState); editor.setValue(newDoc || initialState);
Slide.update(newDoc); Slide.update(newDoc, true);
Slide.draw();
if (Cryptpad.initialName && APP.title === defaultName) { if (Cryptpad.initialName && APP.title === defaultName) {
updateTitle(Cryptpad.initialName); updateTitle(Cryptpad.initialName);

@ -0,0 +1,322 @@
html,
body {
height: 100%;
width: 100%;
padding: 0px;
margin: 0px;
overflow: hidden;
box-sizing: border-box;
position: relative;
}
body {
display: flex;
flex-flow: column;
}
h1 {
font-size: 40px;
}
h2 {
font-size: 37px;
}
h3 {
font-size: 34px;
}
h4 {
font-size: 31px;
}
h5 {
font-size: 27px;
}
h6 {
font-size: 24px;
}
body .CodeMirror {
height: 100%;
}
body .CodeMirror-focused .cm-matchhighlight {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==);
background-position: bottom;
background-repeat: repeat-x;
}
#colorPicker_check {
display: block;
}
@media print {
@page {
margin: 0;
size: auto;
}
body {
display: block;
}
body .CodeMirror,
body #cme_toolbox {
display: none;
}
body * {
visibility: hidden;
height: auto;
max-height: none;
}
html,
body {
max-height: none;
overflow: visible;
}
html #print {
display: block;
visibility: visible;
}
html #print * {
visibility: visible;
}
}
#print {
position: relative;
display: none;
}
#print .slide-frame {
display: flex !important;
justify-content: center;
align-items: center;
flex-flow: column;
padding: 5vh 0;
height: 100vh;
width: 100%;
page-break-after: always;
position: relative;
box-sizing: border-box;
}
#print .slide-frame li {
min-width: 50vw;
}
#print .slide-frame h1 {
padding-top: 0;
}
#print .slide-frame .slideNumber {
position: absolute;
right: 5vh;
bottom: 5vh;
}
#print .slide-frame .slideDate {
position: absolute;
left: 5vh;
bottom: 5vh;
}
#print .slide-frame .slideTitle {
position: absolute;
top: 5vh;
left: 0px;
right: 0px;
text-align: center;
}
.cp.slide #modal .button {
position: absolute;
cursor: pointer;
font-size: 30px;
opacity: 0.6;
display: none;
}
.cp.slide #modal .button:hover {
opacity: 1;
display: block !important;
}
.cp.slide #modal #button_exit {
left: 20px;
top: 20px;
z-index: 9001;
}
.cp.slide #modal #button_left {
left: 6vw;
bottom: 10vh;
}
.cp.slide #modal #button_right {
right: 6vw;
bottom: 10vh;
}
.cp.slide #modal #content h1,
.cp.slide #modal #content h2,
.cp.slide #modal #content h3,
.cp.slide #modal #content h4,
.cp.slide #modal #content h5,
.cp.slide #modal #content h6 {
text-align: center;
}
.cp.slide #modal.shown {
display: block;
position: fixed;
top: 0px;
left: 0px;
z-index: 100;
background-color: black;
color: white;
height: 100vh;
width: 100%;
}
.cp.slide #modal #content p,
.cp.slide #modal #content ul,
.cp.slide #modal #content ol {
font-size: 26px;
}
.cp.slide #modal #content img {
position: relative;
min-width: 1%;
max-width: 90%;
max-height: 90%;
margin: auto;
}
.cp div.modal,
.cp div#modal {
box-sizing: border-box;
z-index: 9001;
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100vh;
display: none;
background-color: #000;
}
.cp div.modal #content,
.cp div#modal #content {
box-sizing: border-box;
border: 1px solid white;
vertical-align: middle;
padding: 2.5vw;
/* center things as much as possible
margin-top: 50vh;
margin-bottom: 50vh;
transform: translateY(-50%);
*/
width: 100vw;
height: 56.25vw;
max-height: 100vh;
max-width: 177.78vh;
margin: auto;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.cp div.modal #content p,
.cp div#modal #content p,
.cp div.modal #content li,
.cp div#modal #content li,
.cp div.modal #content pre,
.cp div#modal #content pre,
.cp div.modal #content code,
.cp div#modal #content code {
font-size: 2.75vw;
line-height: 3.025vw;
}
.cp div.modal #content h1,
.cp div#modal #content h1 {
font-size: 5vw;
line-height: 5.5vw;
}
.cp div.modal #content h2,
.cp div#modal #content h2 {
font-size: 4.2vw;
line-height: 4.62vw;
}
.cp div.modal #content h3,
.cp div#modal #content h3 {
font-size: 3.6vw;
line-height: 3.96vw;
}
.cp div.modal #content h4,
.cp div#modal #content h4 {
font-size: 3vw;
line-height: 3.3vw;
}
.cp div.modal #content h5,
.cp div#modal #content h5 {
font-size: 2.2vw;
line-height: 2.42vw;
}
.cp div.modal #content h6,
.cp div#modal #content h6 {
font-size: 1.6vw;
line-height: 1.76vw;
}
.cp div.modal #content h1,
.cp div#modal #content h1,
.cp div.modal #content h2,
.cp div#modal #content h2,
.cp div.modal #content h3,
.cp div#modal #content h3,
.cp div.modal #content h4,
.cp div#modal #content h4,
.cp div.modal #content h5,
.cp div#modal #content h5,
.cp div.modal #content h6,
.cp div#modal #content h6 {
color: inherit;
text-align: center;
}
.cp div.modal #content pre > code,
.cp div#modal #content pre > code {
display: block;
position: relative;
border: 1px solid #333;
width: 90%;
margin: auto;
padding-left: .25vw;
}
.cp div.modal #content ul,
.cp div#modal #content ul,
.cp div.modal #content ol,
.cp div#modal #content ol {
min-width: 50%;
max-width: 100%;
display: table;
margin: 0 auto;
}
.cp div.modal .center,
.cp div#modal .center {
position: relative;
width: 80%;
height: 80%;
margin: auto;
border: 1px solid #ffffff;
text-align: center;
}
.cp div.modal.shown,
.cp div#modal.shown {
display: block;
}
.cp div.modal table,
.cp div#modal table {
margin: 30px;
border-collapse: collapse;
}
.cp div.modal table input,
.cp div#modal table input {
height: 100%;
width: 90%;
border: 3px solid #fff;
}
.cp div.modal table tfoot tr td,
.cp div#modal table tfoot tr td {
z-index: 4000;
cursor: pointer;
}
.cp div.modal #addtime,
.cp div#modal #addtime,
.cp div.modal #adddate,
.cp div#modal #adddate {
color: #46E981;
border: 1px solid #46E981;
padding: 15px;
}
.cp div.modal #adddate,
.cp div#modal #adddate {
border-top-left-radius: 5px;
}
.cp div.modal #addtime,
.cp div#modal #addtime {
border-bottom-left-radius: 5px;
}

@ -19,6 +19,9 @@ define([
var $content; var $content;
var $pad; var $pad;
var placeholder; var placeholder;
var separator = '<hr data-pewpew="pezpez">';
var separatorReg = /<hr data\-pewpew="pezpez">/g;
var slideClass = 'slide-frame';
Slide.onChange = function (f) { Slide.onChange = function (f) {
if (typeof(f) === 'function') { if (typeof(f) === 'function') {
@ -26,11 +29,15 @@ define([
} }
}; };
var getNumberOfSlides = Slide.getNumberOfSlides = function () {
return $content.find('.' + slideClass).length;
};
var change = function (oldIndex, newIndex) { var change = function (oldIndex, newIndex) {
if (Slide.changeHandlers.length) { if (Slide.changeHandlers.length) {
Slide.changeHandlers.some(function (f, i) { Slide.changeHandlers.some(function (f, i) {
// HERE // HERE
f(oldIndex, newIndex, Slide.content.length); f(oldIndex, newIndex, getNumberOfSlides());
}); });
} }
}; };
@ -76,7 +83,7 @@ define([
var Err; var Err;
var Els = [A, B].map(function (frag) { var Els = [A, B].map(function (frag) {
if (typeof(frag) === 'object') { if (typeof(frag) === 'object') {
if (!frag && frag.body) { if (!frag || (frag && !frag.body)) {
Err = "No body"; Err = "No body";
return; return;
} }
@ -108,19 +115,23 @@ define([
}; };
var draw = Slide.draw = function (i) { var draw = Slide.draw = function (i) {
console.log("Trying to draw slide #%s", i); i = i || 0;
if (typeof(Slide.content[i]) !== 'string') { return; } if (typeof(Slide.content) !== 'string') { return; }
var c = Slide.content;
var m = '<span class="'+slideClass+'">'+Marked(c).replace(separatorReg, '</span><span class="'+slideClass+'">')+'</span>';
var c = Slide.content[i]; var Dom = domFromHTML('<div id="content">' + m + '</div>');
var Dom = domFromHTML('<div id="content">' + Marked(c) + '</div>');
removeListeners(Dom.body); removeListeners(Dom.body);
var patch = makeDiff(domFromHTML($content[0].outerHTML), Dom); var patch = makeDiff(domFromHTML($content[0].outerHTML), Dom);
if (typeof(patch) === 'string') { if (typeof(patch) === 'string') {
$content.html(Marked(c)); $content.html(m);
} else { } else {
DD.apply($content[0], patch); DD.apply($content[0], patch);
} }
$content.find('.' + slideClass).hide();
$content.find('.' + slideClass + ':eq( ' + i + ' )').show();
change(Slide.lastIndex, Slide.index); change(Slide.lastIndex, Slide.index);
}; };
@ -159,13 +170,13 @@ define([
$modal.removeClass('shown'); $modal.removeClass('shown');
}; };
var update = Slide.update = function (content) { var update = Slide.update = function (content, init) {
if (!Slide.shown) { return; } if (!Slide.shown && !init) { return; }
if (!content) { content = placeholder; } if (!content) { content = ''; }
var old = Slide.content[Slide.index]; var old = Slide.content;
Slide.content = content.split(/\n\s*\-\-\-\s*\n/).filter(truthy); Slide.content = content.replace(/\n\s*\-\-\-\s*\n/g, '\n\n'+separator+'\n\n');
if (old !== Slide.content[Slide.index]) { if (old !== Slide.content) {
draw(Slide.index); draw();
return; return;
} }
change(Slide.lastIndex, Slide.index); change(Slide.lastIndex, Slide.index);
@ -183,7 +194,7 @@ define([
console.log('right'); console.log('right');
Slide.lastIndex = Slide.index; Slide.lastIndex = Slide.index;
var i = Slide.index = Math.min(Slide.content.length -1, Slide.index + 1); var i = Slide.index = Math.min(getNumberOfSlides() -1, Slide.index + 1);
Slide.draw(i); Slide.draw(i);
}; };
@ -199,7 +210,7 @@ define([
console.log('end'); console.log('end');
Slide.lastIndex = Slide.index; Slide.lastIndex = Slide.index;
var i = Slide.index = Slide.content.length - 1; var i = Slide.index = getNumberOfSlides() - 1;
Slide.draw(i); Slide.draw(i);
}; };

@ -0,0 +1,314 @@
@import "../../customize.dist/src/less/variables.less";
@import "../../customize.dist/src/less/mixins.less";
// used for slides
.viewportRatio (@x, @y, @p: 100) {
width: @p * 100vw;
height: @y * (@p * 100vw) / @x;
max-width: @x / @y * (@p * 100vh);
max-height: (@p * 100vh);
}
html, body{
height: 100%;
width: 100%;
padding: 0px;
margin: 0px;
overflow: hidden;
box-sizing: border-box;
position: relative;
}
body {
display: flex;
flex-flow: column;
}
//.cp {
h1 { font-size: 40px; }
h2 { font-size: 37px; }
h3 { font-size: 34px; }
h4 { font-size: 31px; }
h5 { font-size: 27px; }
h6 { font-size: 24px; }
body {
.CodeMirror {
height: 100%;
}
.CodeMirror-focused .cm-matchhighlight {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==);
background-position: bottom;
background-repeat: repeat-x;
}
}
#colorPicker_check {
display: block;
}
@media print {
@page {
margin: 0;
size: auto;
}
body {
.CodeMirror, #cme_toolbox {
display: none;
}
* {
visibility: hidden;
height: auto;
max-height: none;
}
display:block;
}
html, body {
max-height: none;
overflow: visible;
}
html {
#print {
display: block;
visibility: visible;
* {
visibility: visible;
}
}
}
}
#print {
position: relative;
display: none;
.slide-frame {
display: flex !important;
justify-content: center;
align-items: center;
flex-flow: column;
padding: 5vh 0;
height: 100vh;
width: 100%;
page-break-after: always;
position: relative;
box-sizing: border-box;
li {
min-width: 50vw;
}
h1 {
padding-top: 0;
}
.slideNumber {
position: absolute;
right: 5vh;
bottom: 5vh;
}
.slideDate {
position: absolute;
left: 5vh;
bottom: 5vh;
}
.slideTitle {
position: absolute;
top: 5vh;
left: 0px; right: 0px;
text-align: center;
}
}
}
.cp {
&.slide {
#modal {
.button {
position: absolute;
cursor: pointer;
font-size: 30px;
opacity: 0.6;
display: none;
}
.button:hover {
opacity: 1;
display: block !important;
}
#button_exit {
left: 20px;
top: 20px;
z-index: 9001;
}
#button_left {
left: 6vw;
bottom: 10vh;
}
#button_right {
right: 6vw;
bottom: 10vh;
}
#content {
h1, h2, h3, h4, h5, h6 {
text-align: center;
}
}
&.shown {
display: block;
position: fixed;
top: 0px;
left: 0px;
z-index: 100;
background-color: black;
color: white;
height: 100vh;
width: 100%;
}
}
#modal #content {
p, ul, ol { font-size: 26px; }
img {
position: relative;
min-width: 1%;
max-width: 90%;
max-height: 90%;
margin: auto;
}
}
}
div.modal, div#modal {
display: none;
#content {
box-sizing: border-box;
border: 1px solid white;
vertical-align: middle;
padding: 2.5vw;
/* center things as much as possible
margin-top: 50vh;
margin-bottom: 50vh;
transform: translateY(-50%);
*/
width: 100vw;
height: 56.25vw; // height:width ratio = 9/16 = .5625
max-height: 100vh;
max-width: 177.78vh; // 16/9 = 1.778
margin: auto;
position: absolute;
top:0;bottom:0; // vertical center
left:0;right:0; // horizontal center
p, li, pre, code {
.size(2.75);
}
h1 { .size(5); }
h2 { .size(4.2); }
h3 { .size(3.6); }
h4 { .size (3); }
h5 { .size(2.2); }
h6 { .size(1.6); }
h1, h2, h3, h4, h5, h6 {
color: inherit;
text-align: center;
}
pre > code {
display: block;
position: relative;
border: 1px solid #333;
width: 90%;
margin: auto;
padding-left: .25vw;
}
ul, ol {
min-width: 50%;
max-width: 100%;
display: table;
margin: 0 auto;
}
}
box-sizing: border-box;
z-index: 9001;
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100vh;
display: none;
background-color: @slide-default-bg;
.center {
position: relative;
width: 80%;
height: 80%;
margin: auto;
border: 1px solid @light-base;
text-align: center;
}
&.shown {
display: block;
}
table {
margin: 30px;
border-collapse: collapse;
tr {
td {
}
}
input {
height: 100%;
width: 90%;
border: 3px solid @base;
}
thead {
tr {
th {
span.remove {
}
}
}
}
tbody {
tr {
td {
}
}
}
tfoot {
tr {
td {
z-index: 4000;
cursor: pointer;
}
}
}
}
#addtime,
#adddate {
color: @cp-green;
border: 1px solid @cp-green;
padding: 15px;
}
#adddate { .top-left; }
#addtime { .bottom-left; }
}
}
Loading…
Cancel
Save