Merge branch 'soon'

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

1
.gitignore vendored

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

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

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

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

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

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

@ -1,6 +1,11 @@
/* Logs are shown to inform the user that something has happened
They are only displayed briefly
*/
@media print {
.alertify-logs {
visibility: hidden;
}
}
.alertify-logs > * {
padding: 12px 48px;
color: #fafafa;
@ -56,11 +61,25 @@
top: 50%;
transform: translateY(-50%);
}
.alertify .dialog .bright,
.alertify .alert .bright {
color: #ffffff;
}
.alertify .dialog > div,
.alertify .alert > div {
background-color: #444;
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 .alert > * {
width: 30%;
@ -118,6 +137,34 @@
border: 1px solid #302B28;
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 .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,
@ -319,6 +366,7 @@
.cp #loading .cryptofist {
margin-left: auto;
margin-right: auto;
height: 300px;
}
@media screen and (max-height: 450px) {
.cp #loading .cryptofist {
@ -332,6 +380,25 @@
.cp #loading .spinnerContainer > div {
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 */
.dropdown-bar {
position: relative;
@ -376,6 +443,10 @@
background-color: #f1f1f1;
color: black !important;
}
.dropdown-bar .dropdown-bar-content a.active {
background-color: #e8e8e8;
color: black !important;
}
.dropdown-bar .dropdown-bar-content hr {
margin: 5px 0px;
height: 1px;
@ -487,6 +558,14 @@
font-size: 1.2em;
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,
.cp body {
font-size: .875em;
@ -977,11 +1056,6 @@ html.cp,
.cp #main_other .buttons {
margin-top: 15px;
}
.cp #fileManagerIframe {
width: 100%;
height: 500px;
margin-top: 15px;
}
.cp .create,
.cp .action {
display: inline-block;
@ -1271,196 +1345,6 @@ html.cp,
.cp div.realtime #addoption {
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 {
display: none;
}

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

@ -1,11 +1,6 @@
(function () {
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
var map = {
'fr': 'Français',
@ -15,6 +10,19 @@ var map = {
'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'];
if (language && map[language]) { req.push('/customize/translations/messages.' + language + '.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');
// Select the current language in the list
var option = $(selector).find('[data-value="' + language + '"]');
if ($(option).length) {
$button.text($(option).text());
}
else {
$button.text('English');
}
selector.setValue(language || 'English');
// Listen for language change
$(selector).find('a.languageValue').on('click', function () {

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

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

@ -4,6 +4,9 @@
They are only displayed briefly
*/
.alertify-logs {
@media print {
visibility: hidden;
}
> * {
padding: @padding-base @padding-base * 4;
color: @alertify-fore;
@ -58,10 +61,19 @@
}
.dialog, .alert {
.bright {
color: @light-base;
}
& > div {
background-color: @alertify-dialog-bg;
border-radius: 5px;
&.half {
width: 50%;
@media (max-width: @media-medium-screen) {
width: 100%;
}
}
}
width: 100%;
@ -133,6 +145,25 @@
border: 1px solid @alertify-base;
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 {
background-color: @alertify-btn-bg-hover;
}

@ -494,12 +494,6 @@ noscript {
}
}
#fileManagerIframe {
width: 100%;
height: 500px;
margin-top: 15px;
}
/* buttons */
.create, .action {
@ -816,192 +810,6 @@ form.realtime, div.realtime {
#adduser { .top-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

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

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

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

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

@ -35,10 +35,8 @@ define(function () {
out.orangeLight = "La conexión es lenta y podria impactar la experiencia";
out.redLight = "Has sido desconectado de la sesión";
out.importButton = 'Importar';
out.importButtonTitle = 'Importar un documento de tus archivos locales';
out.exportButton = 'Exportar';
out.exportButtonTitle = 'Exportar este documento a un archivo local';
out.exportPrompt = '¿Cómo te gustaría llamar a este archivo?';
@ -46,22 +44,16 @@ define(function () {
out.clickToEdit = "Haz clic para cambiar";
out.forgetButton = 'Olvidar';
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.shareButton = 'Compartir';
out.shareSuccess = 'URL copiada al portapapeles';
out.presentButton = 'Presentar';
out.presentButtonTitle = "Entrar en el 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.colorButton = 'Color de texto';
out.colorButtonTitle = 'Cambiar el color de texto en el modo presentación';
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_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.wizardTitle = "Utiliza el asistente para crear 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_l3 = "Con CryptPoll puedes tomar votos rápidos, especialmente utíl para programar un horario que conviene a todo el mundo";
// 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.error = "Erreur";
out.saved = "Enregistré";
out.disconnected = 'Déconnecté';
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.redLight = "Vous êtes déconnectés de la session";
out.importButton = 'Import';
out.importButtonTitle = 'Importer un pad depuis un fichier local';
out.exportButton = 'Exporter';
out.exportButtonTitle = 'Exporter ce pad vers un fichier local';
out.exportPrompt = 'Comment souhaitez-vous nommer ce fichier ?';
@ -62,7 +61,6 @@ define(function () {
out.clickToEdit = 'Cliquer pour modifier';
out.forgetButton = 'Supprimer';
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.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.newButtonTitle = 'Créer un nouveau pad';
out.presentButton = 'Present';
out.presentButtonTitle = "Entrer en 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.colorButton = 'Couleur du texte';
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.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.viewOpen = "Voir 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_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.wizardTitle = "Utiliser l'assistant pour créer votre 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_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_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_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_explanation = [
@ -250,10 +255,16 @@ define(function () {
"Êtes-vous sûr de vouloir continuer ?<br>" +
"Tapez “<em>I love CryptPad</em>” pour confirmer.";
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_resetTipsButton = "Réinitialiser les astuces visibles dans CryptDrive";
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_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";
@ -306,7 +317,6 @@ define(function () {
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_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_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.";
@ -357,13 +367,23 @@ define(function () {
'</small>',
'</p>',
].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 = [
'/*\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',
' Même le serveur est incapable de voir ce que vous tapez.\n',
' Ce que vous voyez ici, ce que vous entendez, quand vous partez, ça reste ici.\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',
'*/'
].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_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;
});

@ -27,6 +27,7 @@ define(function () {
out.loading = "Loading...";
out.error = "Error";
out.saved = "Saved";
out.disconnected = 'Disconnected';
out.synchronizing = 'Synchronizing';
@ -50,10 +51,8 @@ define(function () {
out.orangeLight = "Your slow connection may impact your experience";
out.redLight = "You are disconnected from the session";
out.importButton = 'IMPORT';
out.importButtonTitle = 'Import a pad from a local file';
out.exportButton = 'EXPORT';
out.exportButtonTitle = 'Export this pad to a local file';
out.exportPrompt = 'What would you like to name your file?';
@ -64,7 +63,6 @@ define(function () {
out.clickToEdit = "Click to edit";
out.forgetButton = 'FORGET';
out.forgetButtonTitle = 'Move this pad to the trash';
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>';
@ -75,22 +73,27 @@ define(function () {
out.newButton = 'New';
out.newButtonTitle = 'Create a new pad';
out.presentButton = 'PRESENT';
out.presentButtonTitle = "Enter 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.colorButton = 'TEXT COLOR';
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.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.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.notifyJoined = "{0} has joined the collaborative session";
@ -99,7 +102,7 @@ define(function () {
out.okButton = 'OK (enter)';
out.cancel = "Cancel"; // Not used?
out.cancel = "Cancel";
out.cancelButton = 'Cancel (esc)';
// 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_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.wizardTitle = "Use the wizard to create 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_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_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.";
@ -240,6 +241,13 @@ define(function () {
"</ul>"
].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
out.settings_title = "Settings";
out.settings_save = "Save";
@ -252,10 +260,16 @@ define(function () {
"Are you sure you want to continue?<br>" +
"Type “<em>I love CryptPad</em>” to confirm.";
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_resetTipsButton = "Reset the available tips in CryptDrive";
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_userFeedbackHint2 = "Your pad's content will never be shared with the server.";
out.settings_userFeedback = "Enable user feedback";
@ -351,26 +365,22 @@ define(function () {
// Initial states
out.initialState = [
'<p>',
'This is <strong>CryptPad</strong>, the zero knowledge realtime collaborative editor.',
'<span style="font-size:18px;"><p>',
'This is&nbsp;<strong>CryptPad</strong>, the Zero Knowledge realtime collaborative editor. Everything is saved as you type.',
'<br>',
'What you type here is encrypted so only people who have the link can access it.',
'<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>',
'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.',
'</p>',
'<p><span style="color:#808080;"><em>',
'Go ahead, just start typing...',
'</em></span></p></span>'
].join('');
out.codeInitialState = [
'/*\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',
' Even the server cannot see what you type.\n',
' What you see here, what you hear here, when you leave here, let it stay here.\n',
' You can choose the programming language to highlight and the UI color scheme in the upper right.\n',
'*/'
].join('');
@ -391,6 +401,8 @@ define(function () {
' - Your slides are updated in realtime'
].join('');
// Readme
out.driveReadmeTitle = "What is CryptDrive?";
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.";
@ -412,5 +424,18 @@ define(function () {
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";
// 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;
});

@ -1,7 +1,7 @@
{
"name": "cryptpad",
"description": "realtime collaborative visual editor with zero knowlege server",
"version": "1.1.1",
"version": "1.2.0",
"dependencies": {
"express": "~4.10.1",
"ws": "^1.0.1",
@ -16,7 +16,7 @@
"scripts": {
"lint": "jshint --config .jshintrc --exclude-path .jshintignore .",
"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"
}
}

@ -35,13 +35,18 @@ bower install
## copy config.js.dist to 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
```
## 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
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:
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;
# 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 [leveldb adaptor](https://github.com/xwiki-labs/cryptpad-level-store), delete the datastore directory you have configured.
## Testing
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.
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
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
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
able to translate Cryptpad's interface, and would like to help, please contact us!
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!
You can also see [our translation guide](/customize.dist/translations/README.md).
## 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
[freenode](http://webchat.freenode.net/?channels=%23cryptpad&uio=MT1mYWxzZSY5PXRydWUmMTE9Mjg3JjE1PXRydWUe7)
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
sales@xwiki.com
* Icons thanks to http://www.famfamfam.com/ licensed [Creative Commons Attribution 2.5 License]
[ChainPad]: https://github.com/xwiki-contrib/chainpad
[CKEditor]: http://ckeditor.com/
[fragment identifier]: https://en.wikipedia.org/wiki/Fragment_identifier
[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/

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

@ -11,6 +11,7 @@
data-main-favicon="/customize/main-favicon.png"
data-alt-favicon="/customize/alt-favicon.png"
id="favicon" />
<link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css">
<link rel="stylesheet" href="/customize/main.css" />
<style>
html, body {
@ -45,5 +46,14 @@
<div id="iframe-container">
<iframe id="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>
</body>
</html>

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

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

@ -1,16 +1,14 @@
define([
'/api/config',
'/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/alertifyjs/dist/js/alertify.js',
'/bower_components/spin.js/spin.min.js',
'/common/clipboard.js',
'/customize/fsStore.js',
'/customize/application_config.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
any particular pad type. This includes functions for committing metadata
about pads to your local storage for future use and improved usability.
@ -22,6 +20,7 @@ define([
var common = window.Cryptpad = {
Messages: Messages,
Alertify: Alertify,
Clipboard: Clipboard
};
var store;
@ -223,6 +222,7 @@ define([
if (typeof keys === 'string') {
return chanKey + keys;
}
if (!keys.editKeyStr) { return; }
return '/1/edit/' + hexToBase64(chanKey) + '/' + Crypto.b64RemoveSlashes(keys.editKeyStr);
};
var getViewHashFromKeys = common.getViewHashFromKeys = function (chanKey, keys) {
@ -304,6 +304,18 @@ define([
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) {
// call slice so Uint8Arrays work as expected
return Array.prototype.slice.call(a).map(function (e, i) {
@ -427,6 +439,8 @@ define([
var ret = {};
if (!href) { return ret; }
if (!/^https*:\/\//.test(href)) {
var idx = href.indexOf('/#');
ret.type = href.slice(1, idx);
@ -605,22 +619,52 @@ define([
};
// STORAGE
var isNotStrongestStored = common.isNotStrongestStored = function (href, recents) {
var parsed = parsePadUrl(href);
var findWeaker = common.findWeaker = function (href, recents) {
var rHref = href || getRelativeHref(window.location.href);
var parsed = parsePadUrl(rHref);
if (!parsed.hash) { return false; }
return recents.some(function (pad) {
var weaker;
recents.some(function (pad) {
var p = parsePadUrl(pad.href);
if (p.type !== parsed.type) { return false; } // Not the same type
if (p.hash === parsed.hash) { return false; } // Same hash, not stronger
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 false; }
if (pHash.channel !== parsedHash.channel) { return false; }
if (pHash.mode === 'edit' && parsedHash.mode === 'view') { return true; }
if (pHash.mode === parsedHash.mode && parsedHash.present) { return true; }
return false;
if (pHash.version !== parsedHash.version) { return; }
if (pHash.channel !== parsedHash.channel) { return; }
if (pHash.mode === 'view' && parsedHash.mode === 'edit') {
weaker = pad.href;
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 href = window.location.href;
@ -632,6 +676,7 @@ define([
return;
}
var updateWeaker = [];
var contains;
var renamed = recent.map(function (pad) {
var p = parsePadUrl(pad.href);
@ -665,6 +710,14 @@ define([
// set the 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;
}
return pad;
@ -679,6 +732,11 @@ define([
}
setRecentPads(renamed, function (err, data) {
if (updateWeaker.length > 0) {
updateWeaker.forEach(function (obj) {
getStore().replaceHref(obj.o, obj.n);
});
}
cb(err, data);
});
});
@ -817,23 +875,46 @@ define([
};
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) {
var $loading, $container;
if ($('#' + LOADING).length) {
$('#' + LOADING).show();
return;
$loading = $('#' + LOADING).show();
if (loadingText) {
$('#' + LOADING).find('p').text(loadingText);
}
var $loading = $('<div>', {id: LOADING});
var $container = $('<div>', {'class': 'loadingContainer'});
$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'});
var loadingSpinner = common.spinner($spinner).show();
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);
}
};
common.removeLoadingScreen = function (cb) {
$('#' + LOADING).fadeOut(750, cb);
$('#loadingTip').css('top', '');
window.setTimeout(function () {
$('#loadingTip').fadeOut(750);
}, 3000);
};
common.errorLoadingScreen = function (error, transparent) {
if (!$('#' + LOADING).is(':visible')) { common.addLoadingScreen(); }
@ -912,7 +993,7 @@ define([
switch (type) {
case 'export':
button = $('<button>', {
title: Messages.exportButton + '\n' + Messages.exportButtonTitle,
title: Messages.exportButtonTitle,
}).append($('<span>', {'class':'fa fa-download', style: 'font:'+size+' FontAwesome'}));
if (callback) {
button.click(callback);
@ -920,7 +1001,7 @@ define([
break;
case 'import':
button = $('<button>', {
title: Messages.importButton + '\n' + Messages.importButtonTitle,
title: Messages.importButtonTitle,
}).append($('<span>', {'class':'fa fa-upload', style: 'font:'+size+' FontAwesome'}));
if (callback) {
button.click(common.importContent('text/plain', function (content, file) {
@ -931,7 +1012,7 @@ define([
case 'forget':
button = $('<button>', {
id: 'cryptpad-forget',
title: Messages.forgetButton + '\n' + Messages.forgetButtonTitle,
title: Messages.forgetButtonTitle,
'class': "fa fa-trash cryptpad-forget",
style: 'font:'+size+' FontAwesome'
});
@ -970,6 +1051,7 @@ define([
});
}
break;
// TODO remove editshare, viewshare, and viewopen
case 'editshare':
button = $('<a>', {
title: Messages.editShareTitle,
@ -1020,14 +1102,14 @@ define([
break;
case 'present':
button = $('<button>', {
title: Messages.presentButton + '\n' + Messages.presentButtonTitle,
title: Messages.presentButtonTitle,
'class': "fa fa-play-circle cryptpad-present-button", // class used in slide.js
style: 'font:'+size+' FontAwesome'
});
break;
case 'source':
button = $('<button>', {
title: Messages.sourceButton + '\n' + Messages.sourceButtonTitle,
title: Messages.sourceButtonTitle,
'class': "fa fa-stop-circle cryptpad-source-button", // class used in slide.js
style: 'font:'+size+' FontAwesome'
});
@ -1063,10 +1145,15 @@ define([
// Container
var $container = $(config.container);
if (!config.container) {
$container = $('<span>', {
var containerConfig = {
'class': 'dropdown-bar'
});
};
if (config.buttonTitle) {
containerConfig.title = config.buttonTitle;
}
if (!config.container) {
$container = $('<span>', containerConfig);
}
// Button
@ -1088,6 +1175,34 @@ define([
$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) {
e.stopPropagation();
var state = $innerblock.is(':visible');
@ -1100,12 +1215,64 @@ define([
// empty try catch in case this iframe is problematic (cross-origin)
}
if (state) {
$innerblock.hide();
hide();
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;
};
@ -1130,7 +1297,8 @@ define([
text: Messages.language, // Button initial text
options: options, // Entries displayed in the menu
left: true, // Open to the left of the button
container: $initBlock // optional
container: $initBlock, // optional
isSelect: true
};
var $block = createDropdown(dropdownConfig);
$block.attr('id', 'language-selector');
@ -1187,7 +1355,7 @@ define([
content: Messages.user_rename
});
}
if (parsed && parsed.type && parsed.type !== 'settings') {
if (parsed && (!parsed.type || parsed.type !== 'settings')) {
options.push({
tag: 'a',
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 || {};
cb = cb || function () {};
if (force !== true) { msg = fixHTML(msg); }
@ -1353,6 +1521,19 @@ define([
cb(false);
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) {
@ -1367,37 +1548,12 @@ define([
* spinner
*/
common.spinner = function (parent) {
var $target = $('<div>', {
//
var $target = $('<span>', {
'class': 'fa fa-spinner fa-pulse fa-4x fa-fw'
}).hide();
$(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 {
show: function () {
$target.show();
@ -1408,7 +1564,7 @@ define([
return this;
},
get: function () {
return spinner;
return $target;
},
};
};

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

@ -1,5 +1,7 @@
<!DOCTYPE html>
<head>
<title>About our feedback api</title>
<script data-main="feedback-main.js" src="/bower_components/requirejs/require.js"></script>
<style>
body {
max-width: 60vw;
@ -9,9 +11,9 @@ body {
</style>
</head>
<body>
<p>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_about">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_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>
<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 ROOT = "root";
var UNSORTED = "unsorted";
var TRASH = "trash";
var TEMPLATE = "template";
var ROOT = module.ROOT = "root";
var UNSORTED = module.UNSORTED = "unsorted";
var TRASH = module.TRASH = "trash";
var TEMPLATE = module.TEMPLATE = "template";
var init = module.init = function (files, config) {
var Cryptpad = config.Cryptpad;
@ -232,15 +232,95 @@ define([
return ret;
};
var getFilesDataFiles = function () {
var getFilesDataFiles = exp.getFilesDataFiles = function () {
var ret = [];
for (var el in files[FILES_DATA]) {
files[FILES_DATA].forEach(function (el) {
if (el.href && ret.indexOf(el.href) === -1) {
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;
};
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
var removeFileFromRoot = function (path, href) {
@ -374,7 +454,6 @@ define([
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
// of another element in the loop
console.log(path);
if (path.length === 4) {
trashRoot.push({
name: path[1],
@ -573,7 +652,6 @@ define([
// Import elements in the file manager
var importElements = exp.importElements = function (elements, path, cb) {
if (!elements || elements.length === 0) { return; }
console.log(elements);
var newParent = findElement(files, path);
if (!newParent) { debug("Trying to import elements into a non-existing folder"); return; }
elements.forEach(function (e) {
@ -670,7 +748,7 @@ define([
};
// 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; }
// Remove the last element from the path to get the parent path and the element name
var parentPath = path.slice();
@ -691,7 +769,9 @@ define([
parentEl[name] = undefined;
delete parentEl[name];
}
if (!nocheck) {
checkDeletedFiles();
}
if(cb) { cb(); }
};
@ -773,7 +853,7 @@ define([
pushToTrash(key, href, path);
};
var addUnsortedPad = exp.addPad = function (href, path, name) {
var addPad = exp.addPad = function (href, path, name) {
if (workgroup) { return; }
if (!href) { return; }
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) {
console.log('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
// first, we must add it to FILES_DATA, so the input has to be an fileDAta object
var addTemplate = exp.addTemplate = function (fileData) {

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

@ -94,17 +94,21 @@ define([
res.realtime = rt.realtime;
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
if (!isRegister && isProxyEmpty(rt.proxy)) {
rt.network.disconnect(); // clean up after yourself
return void cb('NO_SUCH_USER', res);
}
// they're registering...
res.userHash = opt.userHash;
res.userName = uname;
//res.displayName // TODO
// they tried to register, but those exact credentials exist
if (isRegister && !isProxyEmpty(rt.proxy)) {
rt.network.disconnect();
return void cb('ALREADY_REGISTERED', 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
if (config.displayed.indexOf('share') !== -1) {
var secret = Cryptpad.find(config, ['share', 'secret']);
var channel = Cryptpad.find(config, ['share', 'channel']);
if (!secret || !channel) {
throw new Error("Unable to display the share button: share.secret and share.channel required");
}
Cryptpad.getRecentPads(function (err, recent) {
var $shareIcon = $('<span>', {'class': 'fa fa-share-alt'});
var $span = $('<span>', {'class': 'large'}).append(' ' +Messages.shareButton);
var hashes = Cryptpad.getHashes(channel, secret);
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: 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) {
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) {
var viewText = '<span class="viewer">';

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

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

@ -9,6 +9,7 @@
data-main-favicon="/customize/main-favicon.png"
data-alt-favicon="/customize/alt-favicon.png"
id="favicon" />
<link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.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>
<style>
@ -33,5 +34,14 @@
</head>
<body>
<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>
</html>

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

@ -76,6 +76,8 @@ define([
if (result.proxy && !result.proxy.login_name) {
result.proxy.login_name = result.userName;
}
Cryptpad.whenRealtimeSyncs(result.realtime, function() {
Cryptpad.login(result.userHash, result.userName, function () {
if (sessionStorage.redirectTo) {
var h = sessionStorage.redirectTo;
@ -89,6 +91,7 @@ define([
}
window.location.href = '/drive/';
});
});
return;
}
switch (err) {

@ -4,6 +4,7 @@
<title>CryptPad</title>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<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"
href="/customize/main-favicon.png"
data-main-favicon="/customize/main-favicon.png"
@ -56,5 +57,14 @@
</head>
<body>
<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>
</html>

@ -110,7 +110,7 @@ define([
var defaultName = Cryptpad.getDefaultName(parsedHash);
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 */
@ -164,6 +164,16 @@ define([
send scripts over the wire.
*/
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)) {
console.log("Rejecting forbidden element attribute with name (%s)", info.diff.name);
return true;
@ -561,6 +571,10 @@ define([
userData: userData,
readOnly: readOnly,
ifrw: ifrw,
share: {
secret: secret,
channel: info.channel
},
title: {
onRename: renameCb,
defaultName: defaultName,
@ -573,8 +587,6 @@ define([
var $rightside = $bar.find('.' + Toolbar.constants.rightside);
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 editHash;
@ -584,10 +596,10 @@ define([
editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys);
}
var $existingButton = $bar.find('#cke_1_toolbar_collapser').hide();
if (!readOnly) {
// Expand / collapse the toolbar
var $existingButton = $bar.find('#cke_1_toolbar_collapser');
var $collapse = Cryptpad.createButton(null, true);
$existingButton.hide();
$collapse.removeClass('fa-question');
var updateIcon = function () {
$collapse.removeClass('fa-caret-down').removeClass('fa-caret-up');
@ -601,6 +613,7 @@ define([
updateIcon();
});
$rightside.append($collapse);
}
/* add an export button */
var $export = Cryptpad.createButton('export', true, {}, exportFile);
@ -624,20 +637,6 @@ define([
var $forgetPad = Cryptpad.createButton('forget', true, {}, forgetCb);
$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
if (!readOnly) { Cryptpad.replaceHash(editHash); }

@ -88,6 +88,7 @@
margin: auto;
min-width: 80%;
width: 80%;
min-height: 5em;
font-size: 20px;
font-weight: bold;
@ -142,3 +143,13 @@
<button data-localization-title="poll_commit" id="commit"><span class="fa fa-check"></span></button>
</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:
// Add the current user in the metadata if he has edit rights
if (readOnly) { return; }
if (typeof(lastName) === 'string' && lastName.length) {
if (typeof(lastName) === 'string') {
setName(lastName);
} else {
var myData = {};
@ -687,6 +687,10 @@ define([
displayed: ['useradmin', 'language', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad'],
userData: userData,
readOnly: readOnly,
share: {
secret: secret,
channel: info.channel
},
title: {
onRename: renameCb,
defaultName: defaultName,
@ -712,17 +716,6 @@ define([
var $forgetPad = Cryptpad.createButton('forget', true, {}, forgetCb);
$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
if (!readOnly) { Cryptpad.replaceHash(editHash); }

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

@ -71,9 +71,6 @@
<input id="accept-terms" type="checkbox" />
<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>
</div>
</div>

@ -58,10 +58,28 @@ define([
// checkboxes
var $checkImport = $('#import-recent');
var $checkAcceptTerms = $('#accept-terms');
var $checkPromise = $('#promise');
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 () {
var uname = $uname.val();
var passwd = $passwd.val();
@ -69,7 +87,6 @@ define([
var shouldImport = $checkImport[0].checked;
var doesAccept = $checkAcceptTerms[0].checked;
var doesPromise = $checkPromise[0].checked;
/* basic validation */
if (passwd !== confirmPassword) { // do their passwords match?
@ -80,9 +97,9 @@ define([
return void Cryptpad.alert(Messages.register_mustAcceptTerms);
}
if (!doesPromise) { // do they promise to remember their password?
return void Cryptpad.alert(Messages.register_mustRememberPass);
}
Cryptpad.confirm("<h2 class='bright'>" + Messages.register_warning + "</h2>",
function (yes) {
if (!yes) { return; }
Cryptpad.addLoadingScreen(Messages.login_hashing);
Login.loginOrRegister(uname, passwd, true, function (err, result) {
@ -103,14 +120,27 @@ define([
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;
localStorage.User_hash = result.userHash;
Cryptpad.eraseTempSessionValues();
if (shouldImport) {
sessionStorage.migrateAnonDrive = 1;
@ -120,21 +150,16 @@ define([
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/';
});
logMeIn(result);
});
}, {
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 class="version-footer">CryptPad v1.2.0 (Chupacabra)</div>
</footer>
</body>

@ -1,9 +1,10 @@
define([
'/common/cryptpad-common.js',
'/common/cryptget.js',
'/common/mergeDrive.js',
'/bower_components/file-saver/FileSaver.min.js',
'/bower_components/jquery/dist/jquery.min.js',
], function (Cryptpad, Crypt) {
], function (Cryptpad, Crypt, Merge) {
var $ = window.jQuery;
var saveAs = window.saveAs;
@ -41,7 +42,7 @@ define([
var obj = store.proxy;
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 $name = $('<span>').text(accountName || '');
if (!accountName) {
@ -65,10 +66,9 @@ define([
'id': 'displayName',
'placeholder': Messages.anonymous}).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 $spinner = $('<span>', {'class': 'fa fa-spinner fa-pulse'}).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);
$spinner.hide();
var displayName = obj[USERNAME_KEY] || '';
$input.val(displayName);
@ -124,7 +124,8 @@ define([
var exportFile = function () {
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.fixFileName(suggestion) + '.json', function (filename) {
if (!(typeof(filename) === 'string' && filename)) { return; }
@ -134,7 +135,7 @@ define([
};
var importFile = function (content, file) {
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); }
$spinner.remove();
});
@ -164,7 +165,10 @@ define([
$button.click(function () {
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();
Cryptpad.alert(Messages.settings_resetDone);
}, undefined, true);
@ -183,29 +187,66 @@ define([
$('<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>', {
'type': 'checkbox',
}).on('change', function () {
$spinner.show();
$ok.hide();
obj.proxy.allowUserFeedback = $checkbox.is(':checked') || false;
// TODO provide feedback to show if this is synced
// Cryptpad.whenRealtimeSyncs...
Cryptpad.whenRealtimeSyncs(obj.info.realtime, function () {
$spinner.hide();
$ok.show();
});
});
$checkbox.appendTo($div);
$label.appendTo($div);
$ok.hide().appendTo($div);
$spinner.hide().appendTo($div);
if (obj.proxy.allowUserFeedback) {
$checkbox[0].checked = true;
}
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) {
APP.$container.append(createTitle());
APP.$container.append(createInfoBlock(obj));
APP.$container.append(createDisplayNameInput(obj));
APP.$container.append(createResetTips());
APP.$container.append(createBackupDrive(obj));
APP.$container.append(createImportLocalPads(obj));
APP.$container.append(createResetDrive(obj));
APP.$container.append(createUserFeedbackToggle(obj));
obj.proxy.on('change', [], refresh);

@ -10,6 +10,7 @@
data-main-favicon="/customize/main-favicon.png"
data-alt-favicon="/customize/alt-favicon.png"
id="favicon" />
<link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css">
<link rel="stylesheet" href="/customize/main.css" />
<style>
html, body {
@ -34,19 +35,30 @@
}
/* 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 */
#pad-iframe.fullscreen {
body #pad-iframe.fullscreen {
top: 0px;
height: 100% !important;
height: 100%;
}
#iframe-container.fullscreen {
body #iframe-container.fullscreen {
top: 0px;
height: 100% !important;
height: 100%;
}
</style>
</head>
<body>
<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>
</body>
</html>

@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<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">
<script src="/bower_components/jquery/dist/jquery.min.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/comment-fold.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>
<body>
<div id="bar"></div>
@ -98,6 +50,7 @@
<div id="button_right" class="button"><span class="fa fa-chevron-right"></span></div>
<div id="content"></div>
</div>
<div id="print"></div>
</span>
<div id="nope"></div>
<div id="colorPicker_check"></div>

@ -128,12 +128,15 @@ define([
}
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 $content = $pad.contents().find('#content');
var $print = $pad.contents().find('#print');
Slide.setModal($modal, $content, $pad, ifrw, initialState);
@ -409,6 +412,79 @@ define([
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) {
userList = info.userList;
@ -417,6 +493,10 @@ define([
userData: userData,
readOnly: readOnly,
ifrw: ifrw,
share: {
secret: secret,
channel: info.channel
},
title: {
onRename: renameCb,
defaultName: defaultName,
@ -429,8 +509,6 @@ define([
var $rightside = $bar.find('.' + Toolbar.constants.rightside);
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 editHash;
@ -462,16 +540,15 @@ define([
var $forgetPad = Cryptpad.createButton('forget', true, {}, forgetCb);
$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 + '/present'}));
if (!readOnly) {
$viewShare.append(Cryptpad.createButton('viewopen', false, {viewHash: viewHash + '/present'}));
}
}
var $printButton = $('<button>', {
title: Messages.printButtonTitle,
'class': 'rightside-button fa fa-print',
style: 'font-size: 17px'
}).click(function () {
$print.html($content.html());
$('body').append(createPrintDialog());
});
$rightside.append($printButton);
var $present = Cryptpad.createButton('present', true)
.click(function () {
@ -509,6 +586,8 @@ define([
text: 'Theme', // Button initial text
options: options, // Entries displayed in the menu
left: true, // Open to the left of the button
isSelect: true,
initialValue: lastTheme
};
var $block = module.$theme = Cryptpad.createDropdown(dropdownConfig);
var $button = $block.find('.buttonTitle');
@ -518,7 +597,6 @@ define([
$block.find('a').click(function (e) {
var theme = $(this).attr('data-value');
setTheme(theme, $block);
$button.text($(this).text());
localStorage.setItem(themeKey, theme);
});
@ -530,13 +608,13 @@ define([
id: SLIDE_BACKCOLOR_ID,
'class': 'fa fa-square rightside-button',
'style': 'font-family: FontAwesome; color: #000;',
title: Messages.backgroundButton + '\n' + Messages.backgroundButtonTitle
title: Messages.backgroundButtonTitle
});
var $text = $('<button>', {
id: SLIDE_COLOR_ID,
'class': 'fa fa-i-cursor rightside-button',
'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 $check = $pad.contents().find("#colorPicker_check");
@ -634,7 +712,8 @@ define([
updateMetadata(userDoc);
editor.setValue(newDoc || initialState);
Slide.update(newDoc);
Slide.update(newDoc, true);
Slide.draw();
if (Cryptpad.initialName && APP.title === defaultName) {
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 $pad;
var placeholder;
var separator = '<hr data-pewpew="pezpez">';
var separatorReg = /<hr data\-pewpew="pezpez">/g;
var slideClass = 'slide-frame';
Slide.onChange = function (f) {
if (typeof(f) === 'function') {
@ -26,11 +29,15 @@ define([
}
};
var getNumberOfSlides = Slide.getNumberOfSlides = function () {
return $content.find('.' + slideClass).length;
};
var change = function (oldIndex, newIndex) {
if (Slide.changeHandlers.length) {
Slide.changeHandlers.some(function (f, i) {
// HERE
f(oldIndex, newIndex, Slide.content.length);
f(oldIndex, newIndex, getNumberOfSlides());
});
}
};
@ -76,7 +83,7 @@ define([
var Err;
var Els = [A, B].map(function (frag) {
if (typeof(frag) === 'object') {
if (!frag && frag.body) {
if (!frag || (frag && !frag.body)) {
Err = "No body";
return;
}
@ -108,19 +115,23 @@ define([
};
var draw = Slide.draw = function (i) {
console.log("Trying to draw slide #%s", i);
if (typeof(Slide.content[i]) !== 'string') { return; }
i = i || 0;
if (typeof(Slide.content) !== 'string') { return; }
var c = Slide.content[i];
var Dom = domFromHTML('<div id="content">' + Marked(c) + '</div>');
var c = Slide.content;
var m = '<span class="'+slideClass+'">'+Marked(c).replace(separatorReg, '</span><span class="'+slideClass+'">')+'</span>';
var Dom = domFromHTML('<div id="content">' + m + '</div>');
removeListeners(Dom.body);
var patch = makeDiff(domFromHTML($content[0].outerHTML), Dom);
if (typeof(patch) === 'string') {
$content.html(Marked(c));
$content.html(m);
} else {
DD.apply($content[0], patch);
}
$content.find('.' + slideClass).hide();
$content.find('.' + slideClass + ':eq( ' + i + ' )').show();
change(Slide.lastIndex, Slide.index);
};
@ -159,13 +170,13 @@ define([
$modal.removeClass('shown');
};
var update = Slide.update = function (content) {
if (!Slide.shown) { return; }
if (!content) { content = placeholder; }
var old = Slide.content[Slide.index];
Slide.content = content.split(/\n\s*\-\-\-\s*\n/).filter(truthy);
if (old !== Slide.content[Slide.index]) {
draw(Slide.index);
var update = Slide.update = function (content, init) {
if (!Slide.shown && !init) { return; }
if (!content) { content = ''; }
var old = Slide.content;
Slide.content = content.replace(/\n\s*\-\-\-\s*\n/g, '\n\n'+separator+'\n\n');
if (old !== Slide.content) {
draw();
return;
}
change(Slide.lastIndex, Slide.index);
@ -183,7 +194,7 @@ define([
console.log('right');
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);
};
@ -199,7 +210,7 @@ define([
console.log('end');
Slide.lastIndex = Slide.index;
var i = Slide.index = Slide.content.length - 1;
var i = Slide.index = getNumberOfSlides() - 1;
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