Merge branch 'soon'

pull/1/head
ansuz 7 years ago
commit 4cf3e0edf8

@ -1,5 +1,6 @@
[ignore]
.*/bower_components/.*
.*/node_modules/lesshint/*
[include]
[libs]

@ -0,0 +1,60 @@
{
"fileExtensions": [".less"],
// These rules are almost certainly crap and will not catch bugs (Caleb)
"newlineAfterBlock": { "enabled": false }, // not just a newline but an entire empty line after each block
"spaceAroundOperator": { "enabled": false }, // disallow calc(10px+10px);
"hexLength": { "enabled": false }, // require long hex color codes or require short where possible
"hexNotation": { "enabled": false }, // require hex lowercase
"propertyOrdering": { "enabled": false }, // require attributes to be in alphabetical order D:
"stringQuotes": { "enabled": false }, // force quoting of strings with ' or " (silly)
"importPath": { "enabled": false }, // require imports to not have .less, ridiculous
"qualifyingElement": { "enabled": false }, // disallow div.xxx and require .xxx
"decimalZero": { "enabled": false }, // disallow .5em
"borderZero": { "enabled": false }, // disallow border: none;
"selectorNaming": { "enabled": false }, // this would be crap because classes are what they are.
"zeroUnit": { "enabled": false },
"singleLinePerProperty": { "enabled": false },
"_singleLinePerProperty": {
"enabled": true,
"allowSingleLineRules": true
},
"spaceAroundComma": { "enabled": false },
"importantRule": { "enabled": false },
"universalSelector": { "enabled": false },
"idSelector": { "enabled": false },
"singleLinePerSelector": { "enabled": false },
"spaceBetweenParens": { "enabled": false },
"maxCharPerLine": { "enabled": false }, // using lesshint flags can cause long lines
"comment": { "enabled": false }, // ban multi-line comments ?
// These rules should be discussed, if they're crap then they should be moved up.
"colorVariables": { "enabled": false }, // require all colors to be stored as variables first...
"variableValue": { "enabled": false }, // any attribute types which should always be variables ? color?
"spaceBeforeBrace": { "enabled": true },//{ "enabled": true, "style": "one_space" },
// Turn everything else on
"spaceAfterPropertyColon": { "enabled": true },
"finalNewline": { "enabled": true }, // require an empty line at the end of the file (enabled for now)
"attributeQuotes": { "enabled": true },
"depthLevel": {
"depth": 1 // TODO(cjd) This is obviously not triggering, even with 1
},
"duplicateProperty": { "enabled": true },
"emptyRule": { "enabled": true },
"hexValidation": { "enabled": true }, // disallow actual garbage color hex codes (e.g. #ab)
"propertyUnits": {
"valid": ["rem", "vw", "em", "px"], // These units are allowed for all properties
"invalid": ["pt"], // The 'pt' unit is not allowed under any circumstances
"properties": {
//"line-height": [] // No units are allowed for line-height
}
},
"spaceAfterPropertyName": { "enabled": true, "style": "no_space" },
"spaceAfterPropertyValue": { "enabled": true, "style": "no_space" },
"spaceAroundBang": { "enabled": true, "style": "before" },
"trailingSemicolon": { "enabled": true },
"trailingWhitespace": { "enabled": true },
"urlFormat": { "enabled": true, "style": "relative" },
"urlQuotes": { "enabled": true }
}

@ -1,12 +1,4 @@
language: node_js
env:
matrix:
- "BROWSER='firefox::Windows 10'"
- "BROWSER='chrome::Windows 10'"
#- "BROWSER='MicrosoftEdge:14.14393:Windows 10'"
#- "BROWSER='internet explorer:11.103:Windows 10'"
#- "BROWSER='safari:10.0:macOS 10.12'"
#- "BROWSER='safari:9.0:OS X 10.11'"
branches:
only:
- master
@ -14,16 +6,6 @@ branches:
- staging
node_js:
- "6.6.0"
before_script:
script:
- npm run-script lint
- npm run-script flow
- cp config.example.js config.js
- npm install bower
- ./node_modules/bower/bin/bower install
- node ./server.js &
- sleep 2
addons:
sauce_connect:
username: "cjdelisle"
access_key:
secure: "pgGh8YGXLPq6fpdwwK2jnjRtwXPbVWQ/HIFvwX7E6HBpzxxcF2edE8sCdonWW9TP2LQisZFmVLqoSnZWMnjBr2CBAMKMFvaHQDJDQCo4v3BXkID7KgqyKmNcwW+FPfSJ5MxNBro8/GE/awkhZzJLYGUTS5zi/gVuIUwdi6cHI8s="

@ -169,10 +169,15 @@ module.exports = {
*/
customLimits: {
/*
"https://my.awesome.website/user/#/1/cryptpad-user/YZgXQxKR0Rcb6r6CmxHPdAGLVludrAF2lEnkbx1vVOo=": {
"https://my.awesome.website/user/#/1/cryptpad-user1/YZgXQxKR0Rcb6r6CmxHPdAGLVludrAF2lEnkbx1vVOo=": {
limit: 20 * 1024 * 1024 * 1024,
plan: 'insider',
note: 'storage space donated by my.awesome.website'
},
"https://my.awesome.website/user/#/1/cryptpad-user2/GdflkgdlkjeworijfkldfsdflkjeEAsdlEnkbx1vVOo=": {
limit: 10 * 1024 * 1024 * 1024,
plan: 'insider',
note: 'storage space donated by my.awesome.website'
}
*/
},

@ -65,5 +65,7 @@ define(function() {
contacts: 'fa-users',
};
config.displayCreationScreen = false;
return config;
});

@ -93,7 +93,11 @@ define(req, function(Util, Default, Language) {
var text = messages[key];
if (typeof(text) === 'string') {
return text.replace(/\{(\d+)\}/g, function (str, p1) {
return argArray[p1] || null;
if (typeof(argArray[p1]) === 'string' || typeof(argArray[p1]) === "number") {
return argArray[p1];
}
console.error("Only strings and numbers can be used in _getKey params!");
return '';
});
} else {
return text;

@ -71,7 +71,7 @@ define([
])
])
]),
h('div.cp-version-footer', "CryptPad v1.21.0 (Vampire)")
h('div.cp-version-footer', "CryptPad v1.22.0 (Wendigo)")
]);
};

@ -1,92 +0,0 @@
/* Bottom Bar */
@import (once) "../less2/include/colortheme.less";
.top-bar, .bottom-bar {
position:fixed;
height:4%;
height: 2.5em;
display: inline-block;
width: 100%;
background: @base;
border-top: 1px solid @cp-outline;
a {
color: @cp-green;
text-decoration: none;
}
p {
margin: -1px;
font-family: @colortheme_font;
font-size: 20px;
display:block;
margin-left: 10px;
padding-top:3px;
color: @fore;
}
img {
margin-right: 4px;
position: relative;
}
.big {
@media screen and (max-width: @media-not-big) {
display: none;
}
@media screen and (min-width: @media-not-small) {
display: inline-block;
}
}
.small {
@media screen and (max-width: @media-not-big) {
display: inline-block;
}
@media screen and (min-width: @media-not-small) {
display: none;
}
img {
height: 1.25em;
}
}
}
.bottom-bar {
bottom: 0px;
right: 0px;
}
.top-bar {
top: 0px;
right: 0px;
}
.bottom-bar-left {
display:block;
float:left;
padding-left:10px;
}
.bottom-bar-left p {
float: right;
}
.bottom-bar-right {
display:block;
float:right;
padding-right:20px
}
.bottom-bar-center {
width: 20%;
position: absolute;
left: 40%;
text-align: center;
}
.bottom-bar-heart {
top: 2px;
}
.bottom-bar-xwiki {
top: 3px;
}
.bottom-bar-openpaas {
top: 3px;
max-width: 100px;
}

@ -1,689 +0,0 @@
@import "./variables.less";
@import "./mixins.less";
@import "../less2/include/alertify.less";
@import "../less2/include/colortheme.less";
@import "../less2/include/modal.less";
@import "../less2/include/font.less";
@import "../less2/loading.less";
@import "./bar.less";
@import "./dropdown.less";
@import "./topbar.less";
@import "./footer.less";
@toolbar-green: #5cb85c;
.font_open-sans();
.alertify_main();
html.cp, .cp body {
font-size: .875em;
background-color: @page-white; //@base;
color: @fore;
height: 100%;
}
.fa {
cursor: default; // Fix for Edge displaying the text cursor on every icon
}
.cp {
// add font for tooltips
.tippy-popper {
font: 16px @colortheme_font;
}
// override bootstrap colors
.btn-primary {
background-color: @cp-blue;
&:hover {
color: #fff;
background-color: #025aa5;
border-color: #01549b;
}
}
body {
font-size: 1rem;
font-weight: 400;
line-height: 2rem;
margin: 0;
}
a.github-corner > svg {
fill: @cp-blue;
color: @old-base;
}
.lato {
font-family: lato, Helvetica, sans-serif;
font-size: 1.02em;
}
.unselectable {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
h1,h2,h3,h4,h5,h6 {
color: @fore;
font-family: @colortheme_font;
-webkit-font-feature-settings: 'dlig' 1,'liga' 1,'lnum' 1,'kern' 1;
-moz-font-feature-settings: 'dlig' 1,'liga' 1,'lnum' 1,'kern' 1;
font-feature-settings: 'dlig' 1,'liga' 1,'lnum' 1,'kern' 1;
font-style: normal;
font-weight: 600;
margin-top: 0;
}
h1 {
line-height: 3rem;
font-size: 2.05714rem;
margin-bottom: .21999rem;
padding-top: .78001rem;
}
h2 {
font-size: 1.95312rem;
margin-bottom: .18358rem;
padding-top: .81642rem;
}
h2,h3 {
line-height: 3rem;
}
h3 {
font-size: 1.64571rem;
margin-bottom: .07599rem;
padding-top: .92401rem;
}
h4 {
font-size: 1.5625rem;
margin-bottom: .54686rem;
padding-top: .45314rem;
}
h5 {
font-size: 1.25rem;
margin-bottom: -.56251rem;
padding-top: .56251rem;
}
h6 {
font-size: 1rem;
margin-bottom: -.65001rem;
padding-top: .65001rem;
}
p {
a:not(.btn) {
cursor: pointer;
color: @cp-link;
text-decoration: none;
&:hover {
color: @cp-link-hover;
}
&:visited {
color: @cp-link-visited;
}
}
}
a.btn {
font-family: sans-serif;
}
img {
height: auto;
max-width: 100%;
}
p {
padding-top: .66001rem;
margin-top: 0;
}
p,pre {
margin-bottom: 1.33999rem;
}
p, pre, td, a, table, tr {
.lato;
}
body.html {
display:flex;
flex-flow: column;
}
// Main page
.page {
width: 100%;
margin-left: auto;
margin-right: auto;
background: @page-white;
padding: 10px 0;//@main-border-width;
position: relative;
.info-container {
color: #121212;
width: 800px;
max-width: 100%;
margin: 0 auto;
&>div{
padding: 10px;
width: 400px;
max-width: 100%;
position: relative;
display: inline-block;
vertical-align: middle;
&:not(.image) {
@media screen and (max-width: @media-not-big) {
width: 100%;
left: 0;
}
}
&.image {
width:300px;
text-align: center;
@media screen and (max-width: @media-not-big) {
display: none;
}
}
}
}
&.first {
//margin-top: ~"min(calc(100vh - 150px), 650px)";
@media screen and (max-width: @media-not-big) {
//margin-top: 0;
}
}
&.even {
//background: darken(@base, 1%);
}
&.category {
background: @category-bg;
}
.app {
display: inline-block;
width: 300px;
vertical-align: middle;
margin: 0px 25px;
white-space: normal;
max-width: ~"calc(50% - 50px)";
@media screen and (max-width: 500px) {
display: block;
max-width: 100%;
margin: 0 auto;
}
}
.app-container {
width: 1400px;
max-width: 100%;
margin: 0 auto;
}
.app-row {
display: flex;
justify-content: center;
flex-flow: row wrap;
max-width: 100%;
margin: 0 auto;
@media screen and (max-width: 1399px) {
display: flex;
}
img {
@media screen and (max-width: @media-not-big) {
display: none;
}
}
}
.left {
//left: 10%; //@main-border-width;
}
.right {
left: 100px; //@main-border-width;
}
h1, h2, h3, h4, h5, h6 {
padding: 0;
}
@media screen and (max-width: @media-not-big) {
padding: 10px 5vh;
}
p {
font-size: 18px;
//text-align: justify;
}
}
.btn-default {
&:hover {
background-color: #d8d8d8;
}
}
#main {
.mainOverlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #000;
opacity: 0.35;
}
}
noscript {
#noscriptContainer {
color: black;
position: absolute;
top: @topbar-height;
left: 0;
bottom: 0;
right: 0;
z-index: 2; // noscriptContainer
#noscript {
width: 1000px;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
margin-left: auto;
margin-right: auto;
position: relative;
font-size: 25px;
text-align: center;
color: @main-color;
}
}
}
#main {
background: @main-bg;
background-size: cover;
background-attachment: fixed;
background-position: center;
height: ~"calc(100vh - 115px)";
min-height: 450px;
.hidden {
display: none !important;
}
}
#main_other {
padding: 0 @main-border-width;
background-color: @page-white;
}
.category {
margin-top: 5px;
}
#mainBlock {
flex: 1;
}
#main, #main_other {
position: relative;
left: 0;
right: 0;
margin: auto;
z-index: 1; // #main, #main_other
font-size: medium;
#align-container {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
margin-left: auto;
margin-right: auto;
width: 1000px;
max-width: 90%;
position: relative;
}
#main-container {
display: inline-block;
}
#userForm .extra {
p {
font-size: 28px;
padding: 15px;
text-align: center;
}
}
#data {
p {
margin: 0;
padding: 0;
font-size: 28px;
line-height: 1.5em;
&.register-explanation {
font-size: 18px;
}
}
h1, h2 {
font-weight: normal;
font-size: 48px;
line-height: 1.2em;
color: @main-color;
padding: 0;
}
h5 {
font-size: 1em;
color: @main-color;
}
width: 600px;
max-width: 60%;
color: @main-color;
padding: 0 15px;
box-sizing: border-box;
display: inline-block;
#tryit {
margin-top: 20px;
margin-bottom: 5px;
}
}
#loggedIn {
float: right;
color: @main-color;
display: inline-block;
width: 350px;
max-width: 35%;
text-align: center;
font-weight: bold;
button {
font-weight: bold;
cursor: pointer;
}
p {
margin: 20px;
padding: 0;
font-size: 20px;
line-height: 1.5em;
}
}
#userForm {
float: right;
display: inline-block;
width: 400px;
max-width: 40%;
padding: 10px;
box-sizing: border-box;
font-family: @colortheme_font;
color: @main-color;
label {
margin-bottom: 0;
margin-left: 5px;
vertical-align: middle;
}
button {
font-weight: bold;
width: 100%;
cursor: pointer;
&.half {
width: ~"calc(50% - 10px)";
&:not(.first) {
float: right;
}
}
}
p {
margin: 0;
padding: 0;
&.buttons {
margin-bottom: 10px;
.cp-dropdown-container {
button {
white-space: normal;
text-align: left;
.fa {
float: right;
}
}
a {
color: black;
&:hover, :visited {
color: black !important;
}
}
display: block;
}
}
&.separator {
margin: 5px 0 15px 0;
text-align: center;
font-weight: bold;
font-size: 1.1em;
}
a {
color: @main-color;
font-weight:bold;
font-size: 14px;
&:hover, :visited {
color: @main-color !important;
}
}
}
.driveLink {
padding-left: 1rem; //Bootstrap padding in buttons
font-size: 1em;
}
&> * {
margin-bottom: 10px;
}
}
@media screen and (max-width: @media-not-big) {
#align-container {
transform: initial;
position: relative;
display: block;
width: 90%;
left: 0;
}
#main-container {
position: relative;
transform: unset;
top:0;
}
#data {
text-align: center;
}
#userForm, #loggedIn, #data {
transform: initial;
position: relative;
display: block;
width: 100%;
max-width: 100%;
margin: 10px 0;
box-sizing: border-box;
float: none;
}
#userForm, #loggedIn {
//border: 1px solid #888;
}
position: relative;
height: auto;
}
.buttons {
margin-top: 15px;
}
}
/* buttons */
.create, .action {
display: inline-block;
@thick: 2px;
border: 0;
background-color: @cp-darkblue;
color: @topbar-button-color;
font-weight: bold;
font-size: large;
margin-right: 5px;
margin-left: 5px;
&:hover {
color: darken(@topbar-button-color, 20%);
}
}
// currently only used in /user/
.panel {
background-color: @dark-base;
}
/* Tables
* Currently only used by /poll/
*/
// form things
.bottom-left {
.bottom-left;
}
.top-left {
.top-left;
}
.remove {
color: @cp-red;
cursor: pointer !important;
}
}
/* Pin limit */
.limit-container {
display: inline-flex;
flex-flow: column-reverse;
width: 100%;
margin-top: 20px;
.cryptpad-limit-bar {
display: inline-block;
max-width: 100%;
margin: 3px;
box-sizing: border-box;
border: 1px solid #999;
background: white;
position: relative;
text-align: center;
vertical-align: middle;
width: ~"calc(100% - 6px)";
height: 25px;
line-height: 25px;
overflow: hidden;
.usage {
height: 100%;
display: inline-block;
background: blue;
position: absolute;
left: 0;
z-index:1; // .usage
&.normal {
background: @toolbar-green;
}
&.warning {
background: orange;
}
&.above {
background: red;
}
}
.usageText {
position: relative;
color: black;
text-shadow: 1px 0 2px white, 0 1px 2px white, -1px 0 2px white, 0 -1px 2px white;
z-index: 2; // .usageText
font-size: @main-font-size;
font-weight: bold;
}
}
.upgrade {
padding: 0;
line-height: 25px;
height: 25px;
margin: 0 3px;
border-radius: 0;
}
}
/* Upload status table */
#uploadStatusContainer {
.modal_base();
position: absolute;
left: 10vw; right: 10vw;
bottom: 10vh;
opacity: 0.9;
box-sizing: border-box;
z-index: 10000; // #uploadStatusContainer
display: none;
#uploadStatus {
width: 80vw;
tr:nth-child(1) {
background-color: darken(@colortheme_modal-bg, 20%);
td {
text-align: center;
font-weight: bold;
padding: 0.25em;
}
}
@upload_pad_h: 0.25em;
@upload_pad_v: 0.5em;
td {
padding: @upload_pad_h @upload_pad_v;
}
.upProgress {
width: 200px;
position: relative;
text-align: center;
box-sizing: border-box;
}
.progressContainer {
position: absolute;
width: 0px;
left: @upload_pad_v;
top: @upload_pad_h; bottom: @upload_pad_h;
background-color: rgba(0,0,255,0.3);
z-index: -1; // .progressContainer
}
.upCancel { text-align: center; }
.fa.cancel {
color: rgb(255, 0, 115);
}
}
}
// hack for our cross-origin iframe
#cors-store {
display: none;
}

@ -1,4 +0,0 @@
@import (once) "../less2/include/dropdown.less";
.dropdown_main();

@ -1,30 +0,0 @@
@import "./variables.less";
@import (once) "../less2/include/colortheme.less";
footer {
background: @category-bg;
font-family: @colortheme_font;
padding-top: 1em;
font-size: 1.2em;
a {
color: @cp-link;
&:visited {
color: @cp-link-visited;
}
&:hover {
color: @cp-link-hover;
}
}
li.title {
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;
}
}

@ -1,193 +0,0 @@
@import "/customize/src/less/variables.less";
@import (once) "/customize/src/less2/include/tools.less";
.fontface(@family, @src, @style: normal, @weight: 400, @fmt: 'truetype'){
@font-face {
font-family: @family;
src: url(@src) format(@fmt);
font-weight: @weight;
font-style: @style;
}
}
.transform(...) {
-webkit-transform: @arguments;
-moz-transform: @arguments;
-o-transform: @arguments;
-ms-transform: @arguments;
transform: @arguments;
}
.translate(@x:0, @y:0) {
.transform(translate(@x, @y));
}
.bottom-left(@s: 5px) { border-bottom-left-radius: @s; }
.top-left(@s: 5px) { border-top-left-radius: @s; }
.size (@n) {
// font-size: @n * 1vmin;
// line-height: @n * 1.1vmin;
font-size: @n * 10%;
// line-height: @n * 11%;
line-height: 110%;
}
.two-part-gradient (@start, @end) {
background: -webkit-linear-gradient(@start, @end); /* For Safari 5.1 to 6.0 */
background: -o-linear-gradient(@start, @end); /* For Opera 11.1 to 12.0 */
background: -moz-linear-gradient(@start, @end); /* For Firefox 3.6 to 15 */
background: linear-gradient(@start, @end); /* Standard syntax */
}
.avatar (@width) {
&.avatar {
overflow: hidden;
text-overflow: ellipsis;
font-size: 16px;
display: flex;
align-items: center;
&.clickable {
cursor: pointer;
&:hover {
background-color: rgba(0,0,0,0.3);
}
}
.default, media-tag {
display: inline-flex;
width: @width;
height: @width;
justify-content: center;
align-items: center;
border-radius: 4px;
overflow: hidden;
box-sizing: content-box;
}
.default {
.tools_unselectable();
background: white;
color: black;
font-size: @width/1.2;
}
.right-col {
order: 10;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: flex;
flex-flow: column;
.name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.friend {
padding: 0;
}
}
media-tag {
min-height: @width;
min-width: @width;
max-height: @width;
max-width: @width;
img {
min-width: 100%;
min-height: 100%;
max-width: none;
max-height: none; // To override 'media-tag img' in slide.less
}
}
}
}
.leftsideCategory {
.tools_unselectable();
padding: 5px 20px;
margin: 15px 0;
cursor: pointer;
height: @toolbar-line-height;
line-height: @toolbar-line-height - 10px;
.fa {
width: 25px;
}
&:hover {
background: rgba(0,0,0,0.05);
}
&.active {
background: white;
}
}
.fileIcon {
li {
display: inline-block;
margin: 10px 10px;
width: 140px;
height: 140px;
text-align: center;
vertical-align: top;
overflow: hidden;
text-overflow: ellipsis;
padding-top: 5px;
padding-bottom: 5px;
&:not(.selected):not(.selectedTmp) {
border: 1px solid #CCC;
}
.name {
width: 100%;
height: 48px;
margin: 8px 0;
display: inline-block;
//align-items: center;
//justify-content: center;
overflow: hidden;
//text-overflow: ellipsis;
word-wrap: break-word;
}
.truncated {
display: block;
position: absolute;
bottom: 0px;
left: 0; right: 0;
text-align: center;
}
img.icon {
height: 48px;
max-height: none;
max-width: none;
margin: 8px 0;
}
.fa {
display: block;
margin: auto;
font-size: 48px;
margin: 8px 0;
text-align: center;
&.listonly {
display: none;
}
}
}
}
.sidebarButton {
button.btn {
background-color: @button-bg;
border-color: darken(@button-bg, 10%);
color: white;
&:hover {
background-color: darken(@button-bg, 10%);
}
&.btn-danger {
background-color: @button-red-bg;
border-color: darken(@button-red-bg, 10%);
color: white;
&:hover {
background-color: darken(@button-red-bg, 10%);
}
}
}
}

@ -1,80 +0,0 @@
@import '/customize/src/less/variables.less';
@import '/customize/src/less/mixins.less';
@import (once) "/customize/src/less2/include/colortheme.less";
@leftside-bg: @colortheme_sidebar-left-bg;
@leftside-color: @colortheme_sidebar-left-fg;
@rightside-color: @colortheme_sidebar-right-fg;
@description-color: @colortheme_sidebar-description;
@button-width: 400px;
.cp {
input[type="text"] {
padding-left: 10px;
}
#container {
font-size: 16px;
display: flex;
flex: 1;
min-height: 0;
#leftSide {
color: @leftside-color;
width: 250px;
background: @leftside-bg;
display: flex;
flex-flow: column;
.categories {
flex: 1;
.category {
.leftsideCategory();
}
}
}
#rightSide {
flex: 1;
padding: 5px 20px;
color: @rightside-color;
overflow: auto;
.element {
label:not(.noTitle), .label {
display: block;
font-weight: bold;
margin-bottom: 0;
}
.description {
display: block;
color: @description-color;
margin-bottom: 5px;
}
margin-bottom: 20px;
}
[type="text"], button {
vertical-align: middle;
height: 40px;
box-sizing: border-box;
}
.inputBlock {
display: inline-flex;
width: @button-width;
input {
flex: 1;
border-radius: 0.25em 0 0 0.25em;
border: 1px solid #adadad;
border-right: 0px;
}
button {
border-radius: 0 0.25em 0.25em 0;
//border: 1px solid #adadad;
border-left: 0px;
}
}
&>div {
margin: 10px 0;
}
.sidebarButton;
}
}
}

File diff suppressed because it is too large Load Diff

@ -1,96 +0,0 @@
@import "./variables.less";
@import (once) "../less2/include/colortheme.less";
#cryptpadTopBar {
background: @topbar-back;
position: relative;
top: 0;
left: 0;
right: 0;
height: @topbar-height;
color: @topbar-color;
font-family: @colortheme_font;
padding: 5px;
box-sizing: border-box;
font-size: 30px;
border-bottom: 1px solid darken(@topbar-back, 15%);
&> span {
vertical-align: middle;
display: inline-block;
height: 100%;
}
.cryptpad-logo {
height: 40px;
vertical-align: middle;
}
.slogan {
font-size: 20px;
color: @topbar-color;
line-height: 40px;
}
.gotoMain {
color: @topbar-color;
height: 40px;
line-height: 40px;
&:hover {
text-decoration: none;
color: @cp-purple;
}
}
.right {
float: right;
font-size: 20px;
margin: 0px 10px;
line-height: 40px;
.buttonSuccess {
// Bootstrap 4 colors
color: #fff;
background: @toolbar-green;
border-color: @toolbar-green;
&:hover {
color: #fff;
background: #449d44;
border: 1px solid #419641;
}
span {
color: #fff;
}
.large {
margin-left: 5px;
}
}
button {
.cp-dropdown-button-title {
.fa-user {
margin-right: 5px;
}
}
}
&.link a {
font-weight: 500;
font-size: 0.75em;
color: @cp-link;
&:hover {
color: @cp-link-hover;
text-decoration: underline;
}
&:visited {
color: @cp-link-visited;
}
}
&.link {
@media screen and (max-width: @media-not-big) {
display: none;
}
}
}
}

@ -1,118 +0,0 @@
@import (once) '../less2/include/colortheme.less';
@import (once) '../less2/include/browser.less';
@base: @colortheme_base;
@dark-base: darken(@base, 20%);
@less-dark-base: darken(@base, 10%);
@light-base: @colortheme_light-base;
@less-light-base: lighten(@base, 10%);
@fore: #555;
@old-base: @colortheme_old-base;
@old-fore: @colortheme_old-fore;
@main-font-size: 16px;
@cp-green: @colortheme_cp-green;
@cp-accent: lighten(@cp-green, 20%);
@cp-red: @colortheme_cp-red;
@cp-outline: #444;
@cp-orange: #FE9A2E;
@cp-blue: #00CFC1;
@cp-blue: #00ADEE;
@cp-light-blue: #41b7d8; // lighten(@cp-blue, 20%);
@cp-purple: #558;
@page-white: #fafafa;
// links
@cp-link: @cp-light-blue;
@cp-link-visited: @cp-light-blue;
@cp-link-hover: darken(@cp-light-blue, 10%);
@slide-default-bg: #000;
@bg-loading: #222;
@color-loading: @old-fore;
@media-not-big: @browser_media-not-big;
@media-not-small: @browser_media-not-small;
@media-short-screen: @browser_media-short-screen;
@media-narrow-screen: @browser_media-narrow-screen;
@media-medium-screen: @browser_media-medium-screen;
// Dropdown
@dropdown-font: @main-font-size @colortheme_font;
@dropdown-bg: #f9f9f9;
@dropdown-color: black;
@dropdown-bg-hover: #f1f1f1;
@dropdown-bg-active: #e8e8e8;
// Toolbar
@toolbar-button-font: @dropdown-font;
@toolbar-pad-bg: @colortheme_pad-bg;
@toolbar-pad-color: @colortheme_pad-color;
@toolbar-slide-bg: @colortheme_slide-bg;
@toolbar-slide-color: @colortheme_slide-color;
@toolbar-code-bg: @colortheme_code-bg;
@toolbar-code-color: @colortheme_code-color;
@toolbar-poll-bg: @colortheme_poll-bg;
@toolbar-poll-color: @colortheme_poll-color;
@toolbar-whiteboard-bg: @colortheme_whiteboard-bg;
@toolbar-whiteboard-color: @colortheme_whiteboard-color;
@toolbar-drive-bg: @colortheme_drive-bg;
@toolbar-drive-color: @colortheme_drive-color;
@toolbar-file-bg: @colortheme_file-bg;
@toolbar-file-color: @colortheme_file-color;
@toolbar-friends-bg: @colortheme_friends-bg;
@toolbar-friends-color: @colortheme_friends-color;
@toolbar-default-bg: @colortheme_default-bg;
@toolbar-default-color: @colortheme_default-color;
@toolbar-settings-bg: @colortheme_settings-bg;
@toolbar-settings-color: @colortheme_settings-color;
@toolbar-profile-bg: @colortheme_profile-bg;
@toolbar-profile-color: @colortheme_profile-color;
@toolbar-todo-bg: @colortheme_todo-bg;
@toolbar-todo-color: @colortheme_todo-color;
@topbar-back: #fff;
@topbar-color: #000;
@topbar-button-bg: #2E9AFE;
@topbar-button-color: #fff;
@topbar-height: 50px;
@toolbar-top-height: 64px;
@main-border-width: 15vw;
@cp-darkblue: #3333ff;
@cp-accent2: darken(@cp-darkblue, 20%);
@main-block-bg: rgba(200, 200, 200, 0.3);
@main-color: #fff;
@main-bg: url('/customize/bg4.jpg') no-repeat center center;
@category-bg: #f4f4f4;
@button-bg: @colortheme_sidebar-button-bg;
@button-alt-bg: @colortheme_sidebar-button-alt-bg;
@button-red-bg: @colortheme_sidebar-button-red-bg;
.unselectable () {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
@toolbar-line-height: 32px;

@ -22,7 +22,7 @@ html.cp-app-print {
.app-print_main();
}
body.cp-readonly .cp-hidden-if-readonly { display:none !important; }
body.cp-readonly .cp-hidden-if-readonly { display: none !important; }
body.cp-app-drive { @import "../../../drive/app-drive.less"; }
body.cp-app-pad { @import "../../../pad/app-pad.less"; }

@ -1,6 +1,6 @@
@import (once) "./colortheme.less";
@import (once) "./colortheme-all.less";
@import (once) "./browser.less";
@import (once) "./modal-theme.less";
@import (once) "./variables.less";
.alertify_main () {
@max-z-index: 2147483647;
@ -21,14 +21,14 @@
@alertify-input-bg: @colortheme_modal-input;
@alertify-input-fg: @colortheme_modal-fg;
@alertify_padding-base: @modal_padding;
@alertify_box-shadow: @modal_shadow;
@alertify_padding-base: @variables_padding;
@alertify_box-shadow: @variables_shadow;
// Logs to show that something has happened
// These show only once
.alertify-logs {
z-index:10000; // alertify logs
z-index: 10000; // alertify logs
@media print {
visibility: hidden;
}
@ -152,16 +152,13 @@
input:not(.form-control), textarea {
background-color: @alertify-input-bg;
color: @alertify-input-fg;
color: @alertify-input-fg;
border: 0px;
margin-bottom: 15px;
width: 100%;
font-size: 100%;
padding: @alertify_padding-base;
&:focus {
//outline-offset: -2px;
}
}
input[type="checkbox"] {
@ -180,7 +177,6 @@
box-sizing: border-box;
position: relative;
outline: 0;
border: 0;
display: inline-block;
align-items: center;
padding: 0 6px;
@ -226,7 +222,7 @@
border: 1px dotted @alertify-base;
}
&::-moz-focus-inner {
border:0;
border: 0;
}
}
@ -244,7 +240,7 @@
&.bottom, &:not(.top) {
bottom: 16px;
// Bottom left placement. Default. Use for transitions.
/* // Bottom left placement. Default. Use for transitions.
&.left, &:not(.right) {
> * {
@ -255,7 +251,7 @@
> * {
}
}
}*/
}
// All left positions.
@ -293,7 +289,7 @@
&.top {
top: 0;
// Top left placement, use for transitions.
/* // Top left placement, use for transitions.
&.left, &:not(.right) {
> * {
@ -304,7 +300,7 @@
> * {
}
}
}*/
}
> * {

@ -4,7 +4,7 @@
height: auto;
max-height: none;
overflow: visible;
display:block;
display: block;
@page {
margin: 0;
size: landscape;

@ -0,0 +1,6 @@
// Don't override/edit this file directly, you can create
// create a file: customize/src/less2/include/colortheme.less
// override whatever colors you want. When you update, the new colors will be
// added ok because the original file is pulled in first.
@import (once) "/customize.dist/src/less2/include/colortheme.less";
@import (once) "/customize/src/less2/include/colortheme.less";

@ -37,47 +37,59 @@
@colortheme_dropdown-bg-hover: #f1f1f1;
@colortheme_dropdown-bg-active: #e8e8e8;
// Apps
// Apps, these colors are used for customizing the toolbar for the apps.
@colortheme_pad-bg: #1c4fa0;
@colortheme_pad-color: #fff;
@colortheme_pad-toolbar-bg: #c1e7ff;
@colortheme_pad-warn: #F83A3A;
@colortheme_slide-bg: #e57614;
@colortheme_slide-color: #fff;
@colortheme_slide-warn: #58D697;
@colortheme_code-bg: #ffae00;
@colortheme_code-color: #000;
@colortheme_code-warn: #9A37F7;
@colortheme_poll-bg: #006304;
@colortheme_poll-color: #fff;
@colortheme_poll-help-bg: #bbffbb;
@colortheme_poll-th-bg: #005bef;
@colortheme_poll-th-fg: #fff;
@colortheme_poll-warn: #ffae00;
@colortheme_whiteboard-bg: #800080;
@colortheme_whiteboard-color: #fff;
@colortheme_whiteboard-warn: #ffae00;
@colortheme_drive-bg: #0087ff;
@colortheme_drive-color: #fff;
@colortheme_drive-warn: #cd2532;
@colortheme_file-bg: #cd2532;
@colortheme_file-color: #fff;
@colortheme_file-warn: #ffae00;
@colortheme_friends-bg: #607b8d;
@colortheme_friends-color: #fff;
@colortheme_friends-warn: #cd2532;
@colortheme_default-bg: #ddd;
@colortheme_default-color: #000;
@colortheme_default-warn: #cd2532;
@colortheme_settings-bg: #0087ff;
@colortheme_settings-color: #fff;
@colortheme_settings-warn: #cd2532;
@colortheme_profile-bg: #0087ff;
@colortheme_profile-color: #fff;
@colortheme_profile-warn: #cd2532;
@colortheme_todo-bg: #7bccd1;
@colortheme_todo-color: #000;
@colortheme_todo-warn: #cd2532;
// Sidebar layout (profile / settings)
@colortheme_sidebar-active: #fff;
@ -91,7 +103,7 @@
@colortheme_sidebar-button-red-bg: #e54e4e;
@colortheme_sidebar-button-alt-bg: #fff;
@cryptpad_color_blue: #4591C4;
@cryptpad_color_blue: #4591C4;
@cryptpad_color_grey: #999999;
@cryptpad_header_col: #1E1F1F;
@cryptpad_text_col: #3F4141;

@ -0,0 +1,143 @@
@import (once) "./colortheme-all.less";
@import (once) "./tools.less";
.creation_main() {
.tippy-popper {
z-index: 100000001 !important;
}
#cp-creation-container {
position: absolute;
z-index: 100000000; // #loading * 10
top: 0px;
background: @colortheme_loading-bg;
color: @colortheme_loading-color;
display: flex;
align-items: center;
width: 100%;
height: 100%;
overflow: auto;
@media screen and (max-height: 600px), screen and (max-width: 500px) {
align-items: baseline;
}
}
#cp-creation {
text-align: center;
font: @colortheme_app-font;
width: 100%;
& > div {
width: 60vw;
max-width: 100%;
margin: 40px auto;
text-align: left;
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
h2, p {
width: 100%;
}
h2 {
display: flex;
justify-content: space-between;
.cp-creation-help {
display: none;
}
}
@media screen and (max-width: 500px) {
width: ~"calc(100% - 30px)";
}
@media screen and (max-height: 600px), screen and (max-width: 500px) {
h2 .cp-creation-help {
display: inline;
}
p {
display: none;
}
}
@media screen and (min-height: 601px) {
@media screen and (min-width: 501px) {
p {
display: block !important;
}
}
}
}
.cp-creation-create {
button {
.tools_unselectable();
padding: 15px;
background: darken(@colortheme_loading-bg, 10%);
color: @colortheme_loading-color;
margin: 3px 10px;
border: none;
cursor: pointer;
&:hover {
background: darken(@colortheme_loading-bg, 5%);
}
}
}
input[type="radio"] {
display: none;
&:checked {
& + label {
font-weight: bold;
background-color: lighten(@colortheme_loading-bg, 20%);
cursor: default;
border: 1px solid #c1158e;
&:hover {
background-color: lighten(@colortheme_loading-bg, 20%);
}
}
}
}
input[type="radio"] + label {
.tools_unselectable();
display: inline-flex;
align-items: center;
justify-content: center;
width: 200px;
height: 50px;
padding: 5px;
margin: 0 20px;
border: 1px solid @colortheme_loading-color;
cursor: pointer;
&:hover {
background-color: lighten(@colortheme_loading-bg, 10%);
}
}
.cp-creation-expire {
#cp-creation-expire-true {
display: none;
&:checked {
& + label {
height: 100px;
.cp-creation-expire-picker {
display: inline;
}
}
}
}
label[for="cp-creation-expire-true"] {
flex-wrap: wrap;
.cp-creation-expire-picker {
display: none;
}
input {
width: 70px;
}
select {
width: 100px;
}
input, select {
border: none;
height: 30px;
background: @colortheme_loading-bg;
color: @colortheme_loading-color;
border-radius: 3px;
}
}
}
}
}

@ -1,4 +1,4 @@
@import (once) "./colortheme.less";
@import (once) "./colortheme-all.less";
@import (once) "./tools.less";
/* The container <div> - needed to position the dropdown content */
@ -13,7 +13,7 @@
}
button {
.fa-caret-down{
.fa-caret-down {
margin-right: 0px;
margin-left: 5px;
}

@ -1,4 +1,4 @@
@import (once) './colortheme.less';
@import (once) './colortheme-all.less';
@import (once) './modal.less';
.fileupload_main () {

@ -1,7 +1,7 @@
.font_neuropolitical () {
@font-face {
font-family: Neuropolitical;
src: url(/customize/fonts/neuropolitical.ttf)
src: url("/customize/fonts/neuropolitical.ttf");
}
}
.font_open-sans () {

@ -0,0 +1,18 @@
@import (once) "./toolbar.less";
@import (once) './fileupload.less';
@import (once) './alertify.less';
@import (once) './tokenfield.less';
@import (once) './creation.less';
.framework_main(@bg-color, @warn-color, @color) {
.toolbar_main(
@bg-color: @bg-color,
@warn-color: @warn-color,
@color: @color
);
.fileupload_main();
.alertify_main();
.tokenfield_main();
.creation_main();
}

@ -1,4 +1,4 @@
@import (once) "./colortheme.less";
@import (once) "./colortheme-all.less";
.iconColors_main () {
// Classes used in common-interface.js
.cp-icon-color-pad { color: @colortheme_pad-bg; }
@ -12,7 +12,7 @@
.cp-icon-color-settings { color: @colortheme_settings-bg; }
.cp-icon-color-profile { color: @colortheme_settings-bg; }
.cp-icon-color-default { color: @colortheme_default-bg; }
.cp-icon-color-todo { color:@colortheme_todo-bg; }
.cp-icon-color-todo { color: @colortheme_todo-bg; }
.cp-border-color-pad { border-color: @colortheme_pad-bg !important; }
.cp-border-color-code { border-color: @colortheme_code-bg !important; }
@ -25,6 +25,6 @@
.cp-border-color-settings { border-color: @colortheme_settings-bg !important; }
.cp-border-color-profile { border-color: @colortheme_settings-bg !important; }
.cp-border-color-default { border-color: @colortheme_default-bg !important; }
.cp-border-color-todo { border-color:@colortheme_todo-bg !important; }
.cp-border-color-todo { border-color: @colortheme_todo-bg !important; }
}

@ -1,4 +1,4 @@
@import (once) "./colortheme.less";
@import (once) "./colortheme-all.less";
@infopages_infobar-height: 64px;
@infopages_padding: 32px;
@ -89,7 +89,6 @@
color: #fff;
padding-top: 30%;
margin-bottom: 0;
}
}
@ -152,8 +151,8 @@
}
}
// navigation top bar
.navbar {
// navigation top bar
.navbar {
background: #fff;
.navbar-brand {
display: block;

@ -1,6 +1,6 @@
@import (once) "./unselectable.less";
@import (once) "./variables.less";
@import (once) "./colortheme.less";
@import (once) "./colortheme-all.less";
.leftside-menu_main() {
}

@ -1,4 +1,4 @@
@import (once) "./colortheme.less";
@import (once) "./colortheme-all.less";
.limit-bar_main () {
.cp-limit-container {
@ -27,7 +27,7 @@
background: blue;
position: absolute;
left: 0;
z-index:1; // .usage
z-index: 1; // .usage
&.cp-limit-usage-normal {
background: @colortheme_green;
}

@ -1,4 +0,0 @@
// Used in modal.less and alertify.less
@modal_padding: 12px;
@modal_shadow: 0 8px 32px 0 rgba(0,0,0,.4);

@ -1,12 +1,12 @@
@import (once) "./colortheme.less";
@import (once) "./modal-theme.less";
@import (once) "./colortheme-all.less";
@import (once) "./variables.less";
.modal_base() {
font-family: @colortheme_font;
background-color: @colortheme_modal-bg;
color: @colortheme_modal-fg;
box-shadow: @modal_shadow;
box-shadow: @variables_shadow;
a {
color: @colortheme_modal-link;
@ -31,9 +31,9 @@
.cp-modal {
background-color: @colortheme_modal-bg;
color: @colortheme_modal-fg;
box-shadow: @modal_shadow;
box-shadow: @variables_shadow;
padding: @modal_padding;
padding: @variables_padding;
position: absolute;
top: 15vh; bottom: 15vh;
@ -71,7 +71,7 @@
position: absolute;
top: 0;
right: 0;
margin: @modal_padding;
margin: @variables_padding;
cursor: pointer;
}
}

@ -1,4 +1,4 @@
@import (once) "/customize/src/less2/include/colortheme.less";
@import (once) "/customize/src/less2/include/colortheme-all.less";
@import (once) "/customize/src/less2/include/leftside-menu.less";
@leftside-bg: @colortheme_sidebar-left-bg;

@ -59,7 +59,6 @@
padding-right: 4px;
}
&.active {
border-color: #52a8ec;
border-color: rgba(82, 168, 236, 0.8);
}
&.duplicate {

@ -1,4 +1,4 @@
@import (once) "./colortheme.less";
@import (once) "./colortheme-all.less";
.history_main () {
.cp-toolbar-history {

@ -1,5 +1,5 @@
@import (once) "./dropdown.less";
@import (once) "./colortheme.less";
@import (once) "./colortheme-all.less";
@import (once) "./browser.less";
@import (once) "./ckeditor-fix.less";
@import (once) "./avatar.less";
@ -7,8 +7,12 @@
@import (once) "./icon-colors.less";
@import (once) "./tools.less";
.toolbar_main () {
.toolbar_main (
@color: @colortheme_default-color, // Color of the text for the toolbar
@bg-color: @colortheme_default-bg, // color of the toolbar background
@warn-color: @colortheme_default-warn, // color of the warning text in the toolbar
@barWidth: 600px // width of the toolbar
) {
@toolbar_line-height: 32px;
@toolbar_top-height: 64px;
@ -51,7 +55,7 @@
}
.cp-toolbar-userlist-drawer {
background-color: @colortheme_default-bg;
background-color: @bg-color;
font: @colortheme_app-font-size @colortheme_font;
min-width: 175px;
width: 175px;
@ -82,7 +86,7 @@
white-space: normal;
line-height: auto;
}
text-align:baseline;
text-align: baseline;
.cp-toolbar-userlist-viewer {
font-style: italic;
padding: 5px;
@ -169,162 +173,53 @@
}
}
.addToolbarColors (@color, @bg-color, @barWidth: 600px) {
.cp-markdown-toolbar {
height: @toolbar_line-height;
background-color: lighten(@bg-color, 20%);
display: none;
button {
height: @toolbar_line-height !important;
outline: 0;
color: @color;
.toolbar_button;
font: normal normal normal 14px/1 FontAwesome;
&:hover {
background-color: lighten(@bgcolor, 8%);
}
&.cp-markdown-help { float: right; }
// TODO(cjd) This ought to be in a less file for markdown-based editors
.cp-markdown-toolbar {
height: @toolbar_line-height;
background-color: lighten(@bg-color, 20%);
display: none;
button {
height: @toolbar_line-height !important;
outline: 0;
color: @color;
.toolbar_button;
font: normal normal normal 14px/1 FontAwesome;
&:hover {
background-color: lighten(@bg-color, 8%);
}
&.cp-markdown-help { float: right; }
}
.cp-toolbar-userlist-drawer {
background-color: @bgcolor;
}
.cp-toolbar-userlist-drawer {
background-color: @bg-color;
color: @color;
.cp-toolbar-userlist-drawer-close {
color: @color;
.cp-toolbar-userlist-drawer-close {
color: @color;
}
h2 {
background-color: darken(@bgcolor, 10%);
color: @color;
}
.cp-toolbar-userlist-name-input {
background-color: darken(@bg-color, 10%);
color: @color;
}
.cp-toolbar-userlist-name-edit {
color: contrast(@color,
lighten(@color, 20%),
darken(@color, 20%));
background: transparent;
&:hover {
color: @color;
}
}
.cp-toolbar-userlist-friend {
&:hover {
color: darken(@color, 15%);
}
}
}
.cp-toolbar {
background-color: @bgcolor;
h2 {
background-color: darken(@bg-color, 10%);
color: @color;
.cp-toolbar-spinner {
font-size: @colortheme_app-font-size;
color: @color;
}
.cp-toolbar-limit {
text-shadow: -1px 0 @color, 0 1px @color, 1px 0 @color, 0 -1px @color;
}
.cp-toolbar-leftside, .cp-toolbar-rightside {
background-color: lighten(@bgcolor, 8%);
button:hover, button.cp-toolbar-button-active {
background-color: @bgcolor;
}
}
.cp-toolbar-rightside {
@media screen and (max-width: @barWidth) { // 450px
flex-wrap: wrap;
height: auto;
width: 100%;
}
}
.cp-toolbar-title-hoverable:hover {
.cp-toolbar-title-editable, .cp-toolbar-title-edit {
cursor: text;
border: 1px solid darken(@bgcolor, 15%);
background: darken(@bgcolor, 10%);
transition: all 0.15s;
color: @color;
}
.cp-toolbar-title-editable {
cursor: text;
}
}
.cp-toolbar-title-save {
border: 1px solid darken(@bgcolor, 15%);
background: darken(@bgcolor, 10%);
color: @color;
&:hover {
background: darken(@bgcolor, 5%);
}
}
input {
border: 1px solid darken(@bgcolor, 15%);
background: darken(@bgcolor, 10%);
}
.cp-toolbar-userlist-name-input {
background-color: darken(@bg-color, 10%);
color: @color;
}
.cp-toolbar-userlist-name-edit {
color: contrast(@color,
lighten(@color, 20%),
darken(@color, 20%));
background: transparent;
&:hover {
color: @color;
}
.cp-dropdown-content.cp-dropdown-left a {
color: black;
}
.cp-toolbar-userlist-friend {
&:hover {
color: darken(@color, 15%);
}
}
}
&.cp-app-pad {
@bgcolor: @colortheme_pad-bg;
@color: @colortheme_pad-color;
.addToolbarColors(@color, @bgcolor);
}
&.cp-app-code {
@bgcolor: @colortheme_code-bg;
@color: @colortheme_code-color;
.addToolbarColors(@color, @bgcolor);
}
&.cp-app-slide {
@bgcolor: @colortheme_slide-bg;
@color: @colortheme_slide-color;
.addToolbarColors(@color, @bgcolor, 700px);
}
&.cp-app-poll {
@bgcolor: @colortheme_poll-bg;
@color: @colortheme_poll-color;
.addToolbarColors(@color, @bgcolor);
}
&.cp-app-whiteboard {
@bgcolor: @colortheme_whiteboard-bg;
@color: @colortheme_whiteboard-color;
.addToolbarColors(@color, @bgcolor);
}
&.cp-app-drive {
@bgcolor: @colortheme_drive-bg;
@color: @colortheme_drive-color;
.addToolbarColors(@color, @bgcolor);
}
&.cp-app-file {
@bgcolor: @colortheme_file-bg;
@color: @colortheme_file-color;
.addToolbarColors(@color, @bgcolor);
}
&.cp-app-contacts {
@bgcolor: @colortheme_friends-bg;
@color: @colortheme_friends-color;
.addToolbarColors(@color, @bgcolor);
}
&.cp-app-settings {
@bgcolor: @colortheme_settings-bg;
@color: @colortheme_settings-color;
.addToolbarColors(@color, @bgcolor);
}
&.cp-app-profile {
@bgcolor: @colortheme_profile-bg;
@color: @colortheme_profile-color;
.addToolbarColors(@color, @bgcolor);
}
&.cp-app-todo {
@bgcolor: @colortheme_todo-bg;
@color: @colortheme_todo-color;
.addToolbarColors(@color, @bgcolor);
}
.cp-toolbar {
* {
outline-width: 0;
@ -340,12 +235,8 @@
display: flex;
flex-wrap: wrap;
justify-content: space-between;
//background-color: #BBBBFF;
background-color: @colortheme_default-bg;
color: @colortheme_default-color;
background-color: @bg-color;
color: @color;
.fa {
font: normal normal normal 14px/1 FontAwesome;
@ -383,7 +274,6 @@
vertical-align: middle;
line-height: @toolbar_top-height;
span {
color: red;
cursor: pointer;
margin: auto;
font-size: 20px;
@ -407,7 +297,6 @@
}
select {
border: 0px;
margin-left: 5px;
margin-right: 5px;
padding-left: 5px;
@ -421,7 +310,7 @@
}
&.cp-toolbar-notitle {
.cp-toolbar-top-filler {
.cp-toolbar-top-filler {
flex: 1;
}
}
@ -430,7 +319,7 @@
@media screen and (max-width: @browser_media-medium-screen) {
flex-wrap: wrap;
height: auto;
.cp-toolbar-top-filler {
.cp-toolbar-top-filler {
flex: 1;
}
.cp-toolbar-title {
@ -473,6 +362,56 @@
}
}
}
.cp-toolbar-spinner {
font-size: @colortheme_app-font-size;
color: @color;
}
.cp-toolbar-limit {
text-shadow: -1px 0 @color, 0 1px @color, 1px 0 @color, 0 -1px @color;
color: @warn-color;
}
.cp-toolbar-leftside, .cp-toolbar-rightside {
background-color: lighten(@bg-color, 8%);
button:hover, button.cp-toolbar-button-active {
background-color: @bg-color;
}
}
.cp-toolbar-rightside {
@media screen and (max-width: @barWidth) { // 450px
flex-wrap: wrap;
height: auto;
width: 100%;
}
}
.cp-toolbar-title-hoverable:hover {
.cp-toolbar-title-editable, .cp-toolbar-title-edit {
cursor: text;
border: 1px solid darken(@bg-color, 15%);
background: darken(@bg-color, 10%);
transition: all 0.15s;
color: @color;
}
.cp-toolbar-title-editable {
cursor: text;
}
}
.cp-toolbar-title-save {
border: 1px solid darken(@bg-color, 15%);
background: darken(@bg-color, 10%);
color: @color;
&:hover {
background: darken(@bg-color, 5%);
}
}
input {
border: 1px solid darken(@bg-color, 15%);
background: darken(@bg-color, 10%);
color: @color;
}
.cp-dropdown-content.cp-dropdown-left a {
color: black;
}
}
.cp-toolbar-top {
@ -481,10 +420,45 @@
height: @toolbar_top-height;
position: relative;
width: 100%;
.cp-pad-not-pinned {
order: 4;
flex: 1;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
align-self: center;
padding-left: 20px;
padding-right: 5px;
font-size: @colortheme_app-font-size;
color: @warn-color;
.cp-pnp-msg {
padding-left: 5px;
font-family: @colortheme_font;
font-size: @colortheme_app-font-size;
a {
font-size: @colortheme_app-font-size;
font-weight: bold;
color: @warn-color;
&:hover {
text-decoration: underline;
}
}
@media screen and (max-width: (@browser_media-not-big)) {
display: none;
}
}
@media screen and (max-width: (@browser_media-not-big)) {
overflow: visible;
max-width: 20px;
}
}
.cp-toolbar-top-filler {
height: @toolbar_top-height;
display: inline-block;
order: 4;
order: 5;
//flex: 1;
}
.cp-toolbar-title {
@ -555,7 +529,6 @@
input {
max-width: ~"calc(100% - 40px)";
flex: 1;
font-size: 1.5em;
vertical-align: middle;
box-sizing: border-box;
cursor: auto;
@ -593,10 +566,9 @@
align-items: center;
justify-content: center;
width: 64px;
height: 64px !important; // Allows us to have a nice square outline when focused
font-size: 1em;
color: inherit;
height: auto;
height: 64px;
padding: 0px;
margin: 0;
&::before {
@ -646,7 +618,7 @@
.cp-toolbar-user {
height: @toolbar_top-height;
display: inline-flex;
order: 5;
order: 6;
line-height: @toolbar_top-height;
color: white;
.cp-toolbar-new { order: 2; }
@ -772,13 +744,13 @@
.cp-toolbar-drawer-content {
box-shadow: 0px 1px 5px 0px rgba(0, 0, 0, 0.2);
position: absolute;
right:0px;
right: 0px;
margin-top: @toolbar_line-height;
min-width: 50px;
background: @colortheme_dropdown-bg;
display: flex;
flex-flow: column;
z-index:10000; //Z cp-toolbar-drawer-content
z-index: 10000; //Z cp-toolbar-drawer-content
color: black;
.fa {
font-size: 17px;

@ -1,3 +1,9 @@
// This is a file for generic constants which we didn't want to hardcode everywhere.
// However, unlike colortheme, customizing these variables will cause breakage.
// Elements size
@variables_bar-height: 32px;
// Used in modal.less and alertify.less
@variables_padding: 12px;
@variables_shadow: 0 8px 32px 0 rgba(0,0,0,.4);

@ -3,7 +3,7 @@ WARNING: THIS FILE DOES NOTHING
It exists only as a proposal of what CSS you should use in loading.js
The CSS inside of loading.js is precompiled in order to save 200ish milliseconds to the loading screen.
*/
@import (once) "./include/colortheme.less";
@import (once) "./include/colortheme-all.less";
@import (once) "./include/browser.less";
#cp-loading {

@ -22,7 +22,7 @@ html.cp-app-print {
.app-print_main();
}
body.cp-readonly .cp-hidden-if-readonly { display:none !important; }
body.cp-readonly .cp-hidden-if-readonly { display: none !important; }
body.cp-app-drive { @import "../../../drive/app-drive.less"; }
body.cp-app-pad { @import "../../../pad/app-pad.less"; }
@ -37,4 +37,5 @@ body.cp-app-todo { @import "../../../todo/app-todo.less"; }
body.cp-app-profile { @import "../../../profile/app-profile.less"; }
body.cp-app-settings { @import "../../../settings/app-settings.less"; }
body.cp-app-debug { @import "../../../debug/app-debug.less"; }
body.cp-app-worker { @import "../../../worker/app-worker.less"; }

@ -1,4 +1,4 @@
@import (once) "../include/colortheme.less";
@import (once) "../include/colortheme-all.less";
@import (once) "../include/font.less";
.font_neuropolitical();
.font_open-sans();

@ -1,5 +1,5 @@
@import (once) "../include/infopages.less";
@import (once) "../include/colortheme.less";
@import (once) "../include/colortheme-all.less";
.infopages_main();
.infopages_topbar();
@ -44,7 +44,7 @@
}
}
}
.cp-bio-avatar-right {
.cp-bio-avatar-right {
padding-right: 15px;
padding-left: 0;
@media (max-width: 991px) {
@ -112,4 +112,4 @@
}
.cp-margin-bot {
margin-bottom: 1.5em;
}
}

@ -1,5 +1,5 @@
@import (once) "../include/infopages.less";
@import (once) "../include/colortheme.less";
@import (once) "../include/colortheme-all.less";
.infopages_main();
.infopages_topbar();
@ -12,7 +12,7 @@
}
.cp-container {
background: #fff;
.cp-iconCont{
.cp-iconCont {
h4 {
margin-top: 1.5em;
margin-bottom: 1.5em;
@ -31,7 +31,7 @@
@media (max-width: 1200px) and (min-width: 769px) {
min-height: 139px;
}
@media (max-width: 768px) and (min-width: 576px){
@media (max-width: 768px) and (min-width: 576px) {
min-height: 164px;
}
@media (max-width: 496px) {

@ -1,8 +1,5 @@
//@import (once) "./variables.less";
@import (once) "../include/infopages.less";
@import (once) "../include/colortheme.less";
@import (once) "../include/colortheme-all.less";
.infopages_main();
.infopages_topbar();
@ -96,13 +93,13 @@ body {
}
@callout-padding: 15px;
a:hover {
text-decoration: none
text-decoration: none;
}
.bs-callout {
display: flex;
align-items: stretch;
margin: 25px 0;
background:rgba(255,255,255,0.6);
background: rgba(255,255,255,0.6);
color: black;
transition: all .1s ease-in-out;
box-sizing: border-box;
@ -139,10 +136,7 @@ h4 {
.bs-callout:hover {
//color: white;
transform: scale(1.05);
cursor: pointer;
}
.bs-callout:hover .fa {
//width: 100%;
cursor: pointer;
}
.bs-callout:hover.cp-callout-more {
transform: none !important;
@ -164,7 +158,6 @@ h4 {
.cp-callout-recent .fa { background-color: @colortheme_drive-bg; }
.cp-hidden { display: none !important; }
.cp-callout-more {
width: auto;
display: inline-block;
align-content: center;
height: 2em;

@ -1,5 +1,5 @@
@import (once) "../include/infopages.less";
@import (once) "../include/colortheme.less";
@import (once) "../include/colortheme-all.less";
@import (once) "../include/alertify.less";
@import (once) "../loading.less";
@ -12,15 +12,12 @@
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
.login {
}
}
}
.cp-container {
#data {
background: #4591C4;
background: #4591C4;
padding-top: 3em;
padding-bottom: 7em;
padding-left: 30px;
@ -61,7 +58,7 @@
border-radius: 0;
&:hover {
transform: scale(1.05);
}
}
}
}
}

@ -1,5 +1,5 @@
@import (once) "../include/infopages.less";
@import (once) "../include/colortheme.less";
@import (once) "../include/colortheme-all.less";
.infopages_main();
.infopages_topbar();

@ -1,5 +1,5 @@
@import (once) "../include/infopages.less";
@import (once) "../include/colortheme.less";
@import (once) "../include/colortheme-all.less";
@import (once) "../include/alertify.less";
@import (once) "../loading.less";
@ -52,14 +52,22 @@
text-shadow: 0 1px 5px rgba(0,0,0,.2);
}
}
.cp-register-det {
margin-top: -7em;
background: #fff;
box-shadow: 0 5px 15px rgba(69,145,196, 0.3);
#data {
background: #4591C4; /* fallback for old browsers */
background: -webkit-linear-gradient(to right, #FF7C4F, #4592C4); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(to right, #FF7C4F, #4592C4); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
// Old browsers
background: #4591C4;
// Chrome 10-25, Safari 5.1-6
background: -webkit-linear-gradient(to right, #FF7C4F, #4592C4); // lesshint duplicateProperty: false
// W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+
background: linear-gradient(to right, #FF7C4F, #4592C4); // lesshint duplicateProperty: false
padding-top: 3em;
padding-bottom: 7em;
padding-left: 30px;

@ -1,5 +1,5 @@
@import (once) "../include/infopages.less";
@import (once) "../include/colortheme.less";
@import (once) "../include/colortheme-all.less";
.infopages_main();
.infopages_topbar();

@ -1,5 +1,5 @@
@import (once) "../include/infopages.less";
@import (once) "../include/colortheme.less";
@import (once) "../include/colortheme-all.less";
.infopages_main();
.infopages_topbar();
@ -28,7 +28,7 @@
color: @cryptpad_header_col;
}
p {
color: @cryptpad_text_col
color: @cryptpad_text_col;
}
#zeroknowledge {
width: 65%;
@ -40,4 +40,4 @@
display: block;
margin: 0 auto;
}
}
}

@ -156,7 +156,7 @@ define(function () {
out.filePickerButton = "Intégrer un fichier stocké dans CryptDrive";
out.filePicker_close = "Fermer";
out.filePicker_description = "Choisissez un fichier de votre CryptDrive pour l'intégrer ou uploadez-en un nouveau";
out.filePicker_description = "Choisissez un fichier de votre CryptDrive pour l'intégrer ou importez-en un nouveau";
out.filePicker_filter = "Filtrez les fichiers par leur nom";
out.or = 'ou';

@ -31,6 +31,7 @@ define(function () {
out.typeError = "This pad is not compatible with the selected application";
out.onLogout = 'You are logged out, <a href="/" target="_blank">click here</a> to log in<br>or press <em>Escape</em> to access your pad in read-only mode.';
out.wrongApp = "Unable to display the content of that realtime session in your browser. Please try to reload that page.";
out.padNotPinned = 'This pad will expire in 3 months, {0}login{1} or {2}register{3} to preserve it.';
out.loading = "Loading...";
out.error = "Error";
@ -786,5 +787,24 @@ define(function () {
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";
// Creation page
out.creation_404 = "This pad not longer exists. Use the following form to create a new pad";
out.creation_ownedTitle = "Type of pad";
out.creation_ownedTrue = "Owned pad";
out.creation_ownedFalse = "Open pad";
out.creation_owned1 = "An <b>owned</b> pad is a pad that you can delete from the server whenever you want. Once it is deleted, no one else can access it, even if it is stored in their CryptDrive.";
out.creation_owned2 = "An <b>open</b> pad doesn't have any owner and thus, it can't be deleted from the server unless it has reached its expiration time.";
out.creation_expireTitle = "Life time";
out.creation_expireTrue = "Add a life time";
out.creation_expireFalse = "Unlimited";
out.creation_expireHours = "Hours";
out.creation_expireDays = "Days";
out.creation_expireMonths = "Months";
out.creation_expire1 = "By default, a pad stored by a registered users will never be removed from the server, unless it is requested by its owner.";
out.creation_expire2 = "If you prefer, you can set a life time to make sure the pad will be permanently deleted from the server and unavailable after the specified date.";
out.creation_createTitle = "Create a pad";
out.creation_createFromTemplate = "From template";
out.creation_createFromScratch = "From scratch";
return out;
});

@ -1,7 +1,7 @@
{
"name": "cryptpad",
"description": "realtime collaborative visual editor with zero knowlege server",
"version": "1.21.0",
"version": "1.22.0",
"dependencies": {
"chainpad-server": "^1.0.1",
"express": "~4.10.1",
@ -11,15 +11,19 @@
"ws": "^1.0.1"
},
"devDependencies": {
"flow-bin": "^0.59.0",
"jshint": "~2.9.1",
"selenium-webdriver": "^2.53.1",
"less": "2.7.1",
"flow-bin": "^0.59.0"
"lesshint": "^4.5.0",
"selenium-webdriver": "^2.53.1"
},
"scripts": {
"start": "node server.js",
"dev": "DEV=1 node server.js",
"lint": "jshint --config .jshintrc --exclude-path .jshintignore .",
"fresh": "FRESH=1 node server.js",
"lint": "jshint --config .jshintrc --exclude-path .jshintignore . && ./node_modules/lesshint/bin/lesshint -c ./.lesshintrc ./customize.dist/src/less2/",
"lint:js": "jshint --config .jshintrc --exclude-path .jshintignore .",
"lint:less": "./node_modules/lesshint/bin/lesshint -c ./.lesshintrc ./customize.dist/src/less2/",
"flow": "./node_modules/.bin/flow",
"test": "node TestSelenium.js",
"template": "cd customize.dist/src && for page in ../index.html ../privacy.html ../terms.html ../about.html ../contact.html ../what-is-cryptpad.html ../../www/login/index.html ../../www/register/index.html ../../www/settings/index.html ../../www/user/index.html;do echo $page; cp template.html $page; done;"

@ -0,0 +1,74 @@
/* jshint esversion: 6, node: true */
const Fs = require('fs');
const Semaphore = require('saferphore');
const nThen = require('nthen');
const sema = Semaphore.create(20);
let dirList;
const fileList = [];
const pinned = {};
const hashesFromPinFile = (pinFile, fileName) => {
var pins = {};
pinFile.split('\n').filter((x)=>(x)).map((l) => JSON.parse(l)).forEach((l) => {
switch (l[0]) {
case 'RESET': {
pins = {};
//jshint -W086
// fallthrough
}
case 'PIN': {
l[1].forEach((x) => { pins[x] = 1; });
break;
}
case 'UNPIN': {
l[1].forEach((x) => { delete pins[x]; });
break;
}
default: throw new Error(JSON.stringify(l) + ' ' + fileName);
}
});
return Object.keys(pins);
};
module.exports.load = function (cb) {
nThen((waitFor) => {
Fs.readdir('./pins', waitFor((err, list) => {
if (err) { throw err; }
dirList = list;
}));
}).nThen((waitFor) => {
fileList.splice(0, fileList.length);
dirList.forEach((f) => {
sema.take((returnAfter) => {
Fs.readdir('./pins/' + f, waitFor(returnAfter((err, list2) => {
if (err) { throw err; }
list2.forEach((ff) => { fileList.push('./pins/' + f + '/' + ff); });
})));
});
});
}).nThen((waitFor) => {
fileList.forEach((f) => {
sema.take((returnAfter) => {
Fs.readFile(f, waitFor(returnAfter((err, content) => {
if (err) { throw err; }
const hashes = hashesFromPinFile(content.toString('utf8'), f);
hashes.forEach((x) => {
(pinned[x] = pinned[x] || {})[f.replace(/.*\/([^/]*).ndjson$/, (x, y)=>y)] = 1;
});
})));
});
});
}).nThen(() => {
cb(pinned);
});
};
if (!module.parent) {
module.exports.load(function (data) {
Object.keys(data).forEach(function (x) {
console.log(x + ' ' + JSON.stringify(data[x]));
});
});
}

140
rpc.js

@ -10,6 +10,7 @@ var Fs = require("fs");
var Path = require("path");
var Https = require("https");
const Package = require('./package.json');
const Pinned = require('./pinned');
var RPC = module.exports;
@ -212,7 +213,6 @@ var checkSignature = function (signedMsg, signature, publicKey) {
};
var loadUserPins = function (Env, publicKey, cb) {
var pinStore = Env.pinStore;
var session = beginSession(Env.Sessions, publicKey);
if (session.channels) {
@ -230,7 +230,7 @@ var loadUserPins = function (Env, publicKey, cb) {
pins[channel] = false;
};
pinStore.getMessages(publicKey, function (msg) {
Env.pinStore.getMessages(publicKey, function (msg) {
// handle messages...
var parsed;
try {
@ -325,9 +325,8 @@ var getFileSize = function (Env, channel, cb) {
};
var getMultipleFileSize = function (Env, channels, cb) {
var msgStore = Env.msgStore;
if (!Array.isArray(channels)) { return cb('INVALID_PIN_LIST'); }
if (typeof(msgStore.getChannelSize) !== 'function') {
if (typeof(Env.msgStore.getChannelSize) !== 'function') {
return cb('GET_CHANNEL_SIZE_UNSUPPORTED');
}
@ -526,6 +525,53 @@ var sumChannelSizes = function (sizes) {
.reduce(function (a, b) { return a + b; }, 0);
};
// inform that the
var loadChannelPins = function (Env) {
Pinned.load(function (data) {
Env.pinnedPads = data;
Env.evPinnedPadsReady.fire();
});
};
var addPinned = function (
Env,
publicKey /*:string*/,
channelList /*Array<string>*/,
cb /*:()=>void*/)
{
Env.evPinnedPadsReady.reg(() => {
channelList.forEach((c) => {
const x = Env.pinnedPads[c] = Env.pinnedPads[c] || {};
x[publicKey] = 1;
});
cb();
});
};
var removePinned = function (
Env,
publicKey /*:string*/,
channelList /*Array<string>*/,
cb /*:()=>void*/)
{
Env.evPinnedPadsReady.reg(() => {
channelList.forEach((c) => {
const x = Env.pinnedPads[c];
if (!x) { return; }
delete x[publicKey];
});
cb();
});
};
var isChannelPinned = function (Env, channel, cb) {
Env.evPinnedPadsReady.reg(() => {
if (Env.pinnedPads[channel] && Object.keys(Env.pinnedPads[channel]).length) {
cb(true);
} else {
delete Env.pinnedPads[channel];
cb(false);
}
});
};
var pinChannel = function (Env, publicKey, channels, cb) {
if (!channels && channels.filter) {
return void cb('INVALID_PIN_LIST');
@ -561,6 +607,7 @@ var pinChannel = function (Env, publicKey, channels, cb) {
toStore.forEach(function (channel) {
session.channels[channel] = true;
});
addPinned(Env, publicKey, toStore, () => {});
getHash(Env, publicKey, cb);
});
});
@ -569,7 +616,6 @@ var pinChannel = function (Env, publicKey, channels, cb) {
};
var unpinChannel = function (Env, publicKey, channels, cb) {
var pinStore = Env.pinStore;
if (!channels && channels.filter) {
// expected array
return void cb('INVALID_PIN_LIST');
@ -587,13 +633,13 @@ var unpinChannel = function (Env, publicKey, channels, cb) {
return void getHash(Env, publicKey, cb);
}
pinStore.message(publicKey, JSON.stringify(['UNPIN', toStore]),
Env.pinStore.message(publicKey, JSON.stringify(['UNPIN', toStore]),
function (e) {
if (e) { return void cb(e); }
toStore.forEach(function (channel) {
delete session.channels[channel];
});
removePinned(Env, publicKey, toStore, () => {});
getHash(Env, publicKey, cb);
});
});
@ -601,7 +647,6 @@ var unpinChannel = function (Env, publicKey, channels, cb) {
var resetUserPins = function (Env, publicKey, channelList, cb) {
if (!Array.isArray(channelList)) { return void cb('INVALID_PIN_LIST'); }
var pinStore = Env.pinStore;
var session = beginSession(Env.Sessions, publicKey);
if (!channelList.length) {
@ -632,13 +677,18 @@ var resetUserPins = function (Env, publicKey, channelList, cb) {
They will not be able to pin additional pads until they upgrade
or delete enough files to go back under their limit. */
if (pinSize > limit[0] && session.hasPinned) { return void(cb('E_OVER_LIMIT')); }
pinStore.message(publicKey, JSON.stringify(['RESET', channelList]),
Env.pinStore.message(publicKey, JSON.stringify(['RESET', channelList]),
function (e) {
if (e) { return void cb(e); }
channelList.forEach(function (channel) {
pins[channel] = true;
});
var oldChannels = Object.keys(session.channels);
removePinned(Env, publicKey, oldChannels, () => {
addPinned(Env, publicKey, channelList, ()=>{});
});
// update in-memory cache IFF the reset was allowed.
session.channels = pins;
getHash(Env, publicKey, function (e, hash) {
@ -906,6 +956,7 @@ var isUnauthenticatedCall = function (call) {
return [
'GET_FILE_SIZE',
'GET_MULTIPLE_FILE_SIZE',
'IS_CHANNEL_PINNED',
].indexOf(call) !== -1;
};
@ -921,10 +972,31 @@ var isAuthenticatedCall = function (call) {
'GET_LIMIT',
'UPLOAD_COMPLETE',
'UPLOAD_CANCEL',
'EXPIRE_SESSION',
'EXPIRE_SESSION'
].indexOf(call) !== -1;
};
const mkEvent = function (once) {
var handlers = [];
var fired = false;
return {
reg: function (cb) {
if (once && fired) { return void setTimeout(cb); }
handlers.push(cb);
},
unreg: function (cb) {
if (handlers.indexOf(cb) === -1) { throw new Error("Not registered"); }
handlers.splice(handlers.indexOf(cb), 1);
},
fire: function () {
if (once && fired) { return; }
fired = true;
var args = Array.prototype.slice.call(arguments);
handlers.forEach(function (h) { h.apply(null, args); });
}
};
};
/*::const ConfigType = require('./config.example.js');*/
RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)=>void*/) {
// load pin-store...
@ -936,14 +1008,19 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
return typeof(config[key]) === 'string'? config[key]: def;
};
var Env = {};
Env.defaultStorageLimit = config.defaultStorageLimit;
Env.maxUploadSize = config.maxUploadSize || (20 * 1024 * 1024);
var Sessions = Env.Sessions = {};
var Env = {
defaultStorageLimit: config.defaultStorageLimit,
maxUploadSize: config.maxUploadSize || (20 * 1024 * 1024),
Sessions: {},
paths: {},
msgStore: (undefined /*:any*/),
pinStore: (undefined /*:any*/),
pinnedPads: {},
evPinnedPadsReady: mkEvent(true)
};
var paths = Env.paths = {};
var Sessions = Env.Sessions;
var paths = Env.paths;
var pinPath = paths.pin = keyOrDefaultString('pinPath', './pins');
var blobPath = paths.blob = keyOrDefaultString('blobPath', './blob');
var blobStagingPath = paths.staging = keyOrDefaultString('blobStagingPath', './blobstage');
@ -970,17 +1047,19 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
}
respond(e, [null, dict, null]);
});
case 'IS_CHANNEL_PINNED':
return void isChannelPinned(Env, msg[1], function (isPinned) {
respond(null, [null, isPinned, null]);
});
default:
console.error("unsupported!");
return respond('UNSUPPORTED_RPC_CALL', msg);
}
};
var rpc = function (
ctx /*:{ store: Object }*/,
data /*:Array<Array<any>>*/,
respond /*:(?string, ?Array<any>)=>void*/)
{
var rpc0 = function (ctx, data, respond) {
if (!Env.msgStore) { Env.msgStore = ctx.store; }
if (!Array.isArray(data)) {
return void respond('INVALID_ARG_FORMAT');
}
@ -1059,8 +1138,6 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
Respond('E_ACCESS_DENIED');
};
if (!Env.msgStore) { Env.msgStore = ctx.store; }
var handleMessage = function (privileged) {
if (config.logRPC) { console.log(msg[0]); }
switch (msg[0]) {
@ -1191,6 +1268,19 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
handleMessage(session.privilege);
};
var rpc = function (
ctx /*:{ store: Object }*/,
data /*:Array<Array<any>>*/,
respond /*:(?string, ?Array<any>)=>void*/)
{
try {
return rpc0(ctx, data, respond);
} catch (e) {
console.log("Error from RPC with data " + JSON.stringify(data));
console.log(e.stack);
}
};
var updateLimitDaily = function () {
updateLimits(config, undefined, function (e) {
if (e) {
@ -1201,6 +1291,8 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
updateLimitDaily();
setInterval(updateLimitDaily, 24*3600*1000);
loadChannelPins(Env);
Store.create({
filePath: pinPath,
}, function (s) {

@ -32,6 +32,13 @@ if (DEV_MODE) {
console.log("DEV MODE ENABLED");
}
var FRESH_MODE = !!process.env.FRESH;
var FRESH_KEY = '';
if (FRESH_MODE) {
console.log("FRESH MODE ENABLED");
FRESH_KEY = +new Date();
}
const clone = (x) => (JSON.parse(JSON.stringify(x)));
var setHeaders = (function () {
@ -102,6 +109,7 @@ app.use("/blob", Express.static(Path.join(__dirname, (config.blobPath || './blob
app.use("/customize", Express.static(__dirname + '/customize'));
app.use("/customize", Express.static(__dirname + '/customize.dist'));
app.use("/customize.dist", Express.static(__dirname + '/customize.dist'));
app.use(/^\/[^\/]*$/, Express.static('customize'));
app.use(/^\/[^\/]*$/, Express.static('customize.dist'));
@ -135,7 +143,7 @@ app.get('/api/config', function(req, res){
'var obj = ' + JSON.stringify({
requireConf: {
waitSeconds: 60,
urlArgs: 'ver=' + Package.version + (DEV_MODE? '-' + (+new Date()): ''),
urlArgs: 'ver=' + Package.version + (FRESH_KEY? '-' + FRESH_KEY: '') + (DEV_MODE? '-' + (+new Date()): ''),
},
removeDonateButton: (config.removeDonateButton === true),
allowSubscriptions: (config.allowSubscriptions === true),

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<script src="respond.js"></script>
</head>
<body>

@ -0,0 +1,147 @@
(function () {
var Frame = {};
var uid = function () {
return Number(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER))
.toString(32).replace(/\./g, '');
};
// create an invisible iframe with a given source
// append it to a parent element
// execute a callback when it has loaded
Frame.create = function (parent, src, onload, timeout) {
var iframe = document.createElement('iframe');
timeout = timeout || 10000;
var to = window.setTimeout(function () {
onload('[timeoutError] could not load iframe at ' + src);
}, timeout);
iframe.setAttribute('id', 'cors-store');
iframe.onload = function (e) {
onload(void 0, iframe, e);
window.clearTimeout(to);
};
// We must pass a unique parameter here to avoid cache problems in Firefox with
// the NoScript plugin: if the iframe's content is taken from the cache, the JS
// is not executed with NoScript....
iframe.setAttribute('src', src + '?t=' + new Date().getTime());
iframe.style.display = 'none';
parent.appendChild(iframe);
};
/* given an iframe with an rpc script loaded, create a frame object
with an asynchronous 'send' method */
Frame.open = function (e, A, timeout) {
var win = e.contentWindow;
var frame = {};
frame.id = uid();
var listeners = {};
var timeouts = {};
timeout = timeout || 5000;
frame.accepts = function (o) {
return A.some(function (e) {
switch (typeof(e)) {
case 'string': return e === o;
case 'object': return e.test(o);
}
});
};
var changeHandlers = frame.changeHandlers = [];
frame.change = function (f) {
if (typeof(f) !== 'function') {
throw new Error('[Frame.change] expected callback');
}
changeHandlers.push(f);
};
var _listener = function (e) {
if (!frame.accepts(e.origin)) {
console.log("message from %s rejected!", e.origin);
return;
}
var message = JSON.parse(e.data);
var uid = message._uid;
var error = message.error;
var data = message.data;
if (!uid) {
console.log("No uid!");
return;
}
if (uid === 'change' && changeHandlers.length) {
changeHandlers.forEach(function (f) {
f(data);
});
return;
}
if (timeouts[uid]) {
window.clearTimeout(timeouts[uid]);
}
if (listeners[uid]) {
listeners[uid](error, data, e);
delete listeners[uid];
}
};
window.addEventListener('message', _listener);
frame.close = function () {
window.removeEventListener('message', _listener);
};
/* method (string): (set|get|remove)
key (string)
data (string)
cb (function) */
frame.send = function (method, content, cb) {
var req = {
method: method,
//key: key,
data: content, //data,
};
var id = req._uid = uid();
// uid must not equal 'change'
while(id === 'change') {
id = req._uid = uid();
}
if (typeof(cb) === 'function') {
//console.log("setting callback!");
listeners[id] = cb;
//console.log("setting timeout of %sms", timeout);
timeouts[id] = window.setTimeout(function () {
// when the callback is executed it will clear this timeout
cb('[TimeoutError] request timed out after ' + timeout + 'ms');
}, timeout);
} else {
console.log(typeof(cb));
}
win.postMessage(JSON.stringify(req), '*');
};
return frame;
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = Frame;
} else if (typeof(define) === 'function' && define.amd) {
define(['jquery'], function () {
return Frame;
});
} else {
window.Frame = Frame;
}
}());

@ -0,0 +1,32 @@
var validDomains = [ /.*/i, ];
var isValidDomain = function (o) {
return validDomains.some(function (e) {
switch (typeof(e)) {
case 'string': return e === o;
case 'object': return e.test(o);
}
});
};
window.addEventListener('message', function(e) {
if (!isValidDomain(e.origin)) { return; }
var payload = JSON.parse(e.data);
var parent = window.parent;
var respond = function (error, data) {
var res = {
_uid: payload._uid,
error: error,
data: data,
};
parent.postMessage(JSON.stringify(res), '*');
};
//console.error(payload);
switch(payload.method) {
case undefined:
return respond('No method supplied');
default:
return respond(void 0, "EHLO");
}
});

@ -137,7 +137,8 @@ define([
var secret = Hash.parsePadUrl('/pad/#67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy');
return cb(secret.hashData.channel === "67b8385b07352be53e40746d2be6ccd7" &&
secret.hashData.key === "XAYSuJYYqa9NfmInyHci7LNy" &&
secret.hashData.version === 0);
secret.hashData.version === 0 &&
typeof(secret.hashData.getURL) === 'function');
}, "Old hash failed to parse");
// make sure version 1 hashes parse correctly
@ -249,7 +250,6 @@ define([
}
};
var evt = Util.mkEvent();
var respond = function (e, out) {
evt.fire(e, out);
@ -259,9 +259,8 @@ define([
try {
var parsed = JSON.parse(raw);
var txid = parsed.txid;
var message = parsed.message;
setTimeout(function () {
service(message.command, message.content, function (e, result) {
service(parsed.q, parsed.content, function (e, result) {
respond(JSON.stringify({
txid: txid,
error: e,
@ -285,33 +284,56 @@ define([
});
}, "Test rpc factory");
/*
assert(function (cb) {
var getBlob = function (url, cb) {
var xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.responseType = "blob";
xhr.onload = function () {
cb(void 0, this.response);
};
xhr.send();
};
var $img = $('img#thumb-orig');
getBlob($img.attr('src'), function (e, blob) {
console.log(e, blob);
Thumb.fromImageBlob(blob, function (e, thumb) {
console.log(thumb);
var th = new Image();
th.src = URL.createObjectURL(thumb);
th.onload = function () {
$(document.body).append($(th).addClass('thumb'));
cb(th.width === Thumb.dimension && th.height === Thumb.dimension);
};
require([
'/assert/frame/frame.js',
], function (Frame) {
Frame.create(document.body, '/assert/frame/frame.html', function (e, frame) {
if (e) { return cb(false); }
var channel = Frame.open(frame, [
/.*/i,
], 5000);
channel.send('HELO', null, function (e, res) {
if (res === 'EHLO') { return cb(true); }
cb(false);
});
});
});
});
*/
}, "PEWPEW");
(function () {
var guid = Wire.uid();
var t = Wire.tracker({
timeout: 1000,
hook: function (txid, q, content) {
console.info(JSON.stringify({
guid: guid,
txid: txid,
q: q,
content: content,
}));
},
});
assert(function (cb) {
t.call('SHOULD_TIMEOUT', null, function (e) {
if (e === 'TIMEOUT') { return cb(true); }
cb(false);
});
}, 'tracker should timeout');
assert(function (cb) {
var id = t.call('SHOULD_NOT_TIMEOUT', null, function (e, out) {
if (e) { return cb(false); }
if (out === 'YES') { return cb(true); }
cb(false);
});
t.respond(id, void 0, 'YES');
}, "tracker should not timeout");
}());
Drive.test(assert);

@ -1,5 +1,5 @@
define([], function () {
if (window.localStorage && window.localStorage.FS_hash) {
define(['/api/config'], function (ApiConfig) {
if (ApiConfig.httpSafeOrigin !== window.location.origin) {
window.alert('The bounce application must only be used from the sandbox domain, ' +
'please report this issue on https://github.com/xwiki-labs/cryptpad');
return;

@ -1,14 +1,13 @@
@import (once) "../../customize/src/less2/include/browser.less";
@import (once) "../../customize/src/less2/include/toolbar.less";
@import (once) "../../customize/src/less2/include/markdown.less";
@import (once) '../../customize/src/less2/include/fileupload.less';
@import (once) '../../customize/src/less2/include/alertify.less';
@import (once) '../../customize/src/less2/include/tokenfield.less';
@import (once) "../../customize/src/less2/include/framework.less";
.toolbar_main();
.fileupload_main();
.alertify_main();
.tokenfield_main();
.framework_main(
@bg-color: @colortheme_code-bg,
@warn-color: @colortheme_code-warn,
@color: @colortheme_code-color
);
// body
&.cp-app-code {
@ -51,7 +50,7 @@
height: 100%;
overflow: hidden;
&.cp-app-code-present {
.CodeMirror { display: none; }
#cp-app-code-container { display: none; }
#cp-app-code-preview { border: 0; }
}
}
@ -96,7 +95,7 @@
}
@media (max-width: @browser_media-medium-screen) {
.CodeMirror {
#cp-app-code-container {
flex: 1;
max-width: 100%;
resize: none;
@ -104,6 +103,6 @@
#cp-app-code-preview {
display: none !important;
}
}
}
}

@ -125,7 +125,7 @@ Version 1
url += ret.type + '/';
if (!ret.hashData) { return url; }
if (ret.hashData.type !== 'pad') { return url + '#' + ret.hash; }
if (ret.hashData.version !== 1) { throw new Error("Only v1 hashes are managed here."); }
if (ret.hashData.version !== 1) { return url + '#' + ret.hash; }
url += '#/' + ret.hashData.version +
'/' + ret.hashData.mode +
'/' + ret.hashData.channel.replace(/\//g, '-') +

@ -55,6 +55,7 @@ define([
};
var stopListening = UI.stopListening = function (handler) {
if (!handler) { return; } // we don't want to stop all the 'keyup' listeners
$(window).off('keyup', handler);
};
@ -130,10 +131,19 @@ define([
element: target || h('input'),
};
var $t = t.tokenfield = $(t.element).tokenfield();
t.getTokens = function () {
return $t.tokenfield('getTokens').map(function (token) {
t.getTokens = function (ignorePending) {
var tokens = $t.tokenfield('getTokens').map(function (token) {
return token.value.toLowerCase();
});
if (ignorePending) { return tokens; }
var $pendingEl = $($t.parent().find('.token-input')[0]);
var val = ($pendingEl.val() || "").trim();
if (val && tokens.indexOf(val) === -1) {
return tokens.concat(val);
}
return tokens;
};
var $root = $t.parent();
@ -145,7 +155,7 @@ define([
$t.on('tokenfield:createtoken', function (ev) {
var val;
ev.attrs.value = ev.attrs.value.toLowerCase();
if (t.getTokens().some(function (t) {
if (t.getTokens(true).some(function (t) {
if (t === ev.attrs.value) { return ((val = t)); }
})) {
ev.preventDefault();
@ -210,7 +220,7 @@ define([
var $cancel = findCancelButton(tagger).click(function (e) {
close(null, e);
});
listenForKeys(function () {
listener = listenForKeys(function () {
$ok.click();
}, function () {
$cancel.click();

@ -6,11 +6,12 @@ define([
'/common/common-language.js',
'/common/common-interface.js',
'/common/common-feedback.js',
'/common/hyperscript.js',
'/common/media-tag.js',
'/customize/messages.js',
'css!/common/tippy.css',
], function ($, Config, Util, Hash, Language, UI, Feedback, MediaTag, Messages) {
], function ($, Config, Util, Hash, Language, UI, Feedback, h, MediaTag, Messages) {
var UIElements = {};
// Configure MediaTags to use our local viewer
@ -387,7 +388,6 @@ define([
common.getAttribute(['general', 'markdown-help'], function (e, data) {
if (e) { return void console.error(e); }
if (data === true && $toolbarButton.length && tbState) {
console.log($toolbar.is(':visible'));
$toolbarButton.click();
}
});
@ -1120,5 +1120,169 @@ define([
});
};
UIElements.getPadCreationScreen = function (common, cb) {
if (!common.isLoggedIn()) { return void cb(); }
var sframeChan = common.getSframeChannel();
var metadataMgr = common.getMetadataMgr();
var type = metadataMgr.getMetadataLazy().type;
var $body = $('body');
var $creationContainer = $('<div>', { id: 'cp-creation-container' }).appendTo($body);
var $creation = $('<div>', { id: 'cp-creation' }).appendTo($creationContainer);
var setHTML = function (e, html) {
e.innerHTML = html;
return e;
};
// Title
$creation.append(h('h1.cp-creation-title', Messages['button_new'+type]));
// Deleted pad warning
if (metadataMgr.getPrivateData().isDeleted) {
$creation.append(h('div.cp-creation-deleted', Messages.creation_404));
}
var createHelper = function (text) {
var q = h('span.cp-creation-help.fa.fa-question', {
title: text
});
return q;
};
// Owned pads
var owned = h('div.cp-creation-owned', [
h('h2', [
Messages.creation_ownedTitle,
createHelper(Messages.creation_owned1 + '\n' + Messages.creation_owned2)
]),
setHTML(h('p'), Messages.creation_owned1 + '<br>' + Messages.creation_owned2),
h('input#cp-creation-owned-true.cp-creation-owned-value', {
type: 'radio',
name: 'cp-creation-owned',
value: 1,
checked: 'checked'
}),
h('label', { 'for': 'cp-creation-owned-true' }, Messages.creation_ownedTrue),
h('input#cp-creation-owned-false.cp-creation-owned-value', {
type: 'radio',
name: 'cp-creation-owned',
value: 0
}),
h('label', { 'for': 'cp-creation-owned-false' }, Messages.creation_ownedFalse)
]);
$creation.append(owned);
// Life time
var expire = h('div.cp-creation-expire', [
h('h2', [
Messages.creation_expireTitle,
createHelper(Messages.creation_expire1, Messages.creation_expire2)
]),
setHTML(h('p'), Messages.creation_expire1 + '<br>' + Messages.creation_expire2),
h('input#cp-creation-expire-false.cp-creation-expire-value', {
type: 'radio',
name: 'cp-creation-expire',
value: 0,
checked: 'checked'
}),
h('label', { 'for': 'cp-creation-expire-false' }, Messages.creation_expireFalse),
h('input#cp-creation-expire-true.cp-creation-expire-value', {
type: 'radio',
name: 'cp-creation-expire',
value: 1
}),
h('label', { 'for': 'cp-creation-expire-true' }, [
Messages.creation_expireTrue,
h('span.cp-creation-expire-picker', [
h('input#cp-creation-expire-val', {
type: "number",
min: 1,
max: 100,
value: 3
}),
h('select#cp-creation-expire-unit', [
h('option', { value: 'hour' }, Messages.creation_expireHours),
h('option', { value: 'day' }, Messages.creation_expireDays),
h('option', {
value: 'month',
selected: 'selected'
}, Messages.creation_expireMonths)
])
])
])
]);
$creation.append(expire);
// Create the pad
var create = function (template) {
// Type of pad
var ownedVal = parseInt($('input[name="cp-creation-owned"]:checked').val());
// Life time
var expireVal = 0;
if(parseInt($('input[name="cp-creation-expire"]:checked').val())) {
var unit = 0;
switch ($('#cp-creation-expire-unit').val()) {
case "hour" : unit = 3600; break;
case "day" : unit = 3600 * 24; break;
case "month": unit = 3600 * 24 * 30; break;
default: unit = 0;
}
expireVal = ($('#cp-creation-expire-val').val() || 0) * unit;
}
// XXX TODO remove these lines
ownedVal = undefined;
expire = undefined;
sframeChan.query("Q_CREATE_PAD", {
owned: ownedVal,
expire: expireVal,
template: template
}, function () {
$creationContainer.remove();
cb();
});
};
var $create = $(h('div.cp-creation-create', [
h('h2', Messages.creation_createTitle)
])).appendTo($creation);
// Pick a template?
sframeChan.query("Q_TEMPLATE_EXIST", type, function (err, data) {
if (!data) { return; }
var $templateButton = $('<button>').text(Messages.creation_createFromTemplate)
.appendTo($create);
var pickerCfg = {
types: [type],
where: ['template'],
hidden: true
};
common.openFilePicker(pickerCfg);
$templateButton.click(function () {
// Show the template picker
delete pickerCfg.hidden;
common.openFilePicker(pickerCfg);
var first = true; // We can only pick a template once (for a new document)
var fileDialogCfg = {
onSelect: function (data) {
if (data.type === type && first) {
create(data.href);
first = false;
}
}
};
common.initFilePicker(fileDialogCfg);
});
});
var $button = $('<button>').text(Messages.creation_createFromScratch).appendTo($create);
$button.click(function () {
create();
});
};
return UIElements;
});

@ -4,18 +4,17 @@ define([
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-messaging.js',
'/common/common-realtime.js',
'/common/common-constants.js',
'/common/common-feedback.js',
'/common/outer/local-store.js',
'/common/outer/store-rpc.js',
'/common/pinpad.js',
'/customize/application_config.js',
'/bower_components/nthen/index.js',
], function (Config, Messages, Util, Hash,
Messaging, Realtime, Constants, Feedback, LocalStore, AStore,
Pinpad, AppConfig, Nthen) {
Messaging, Constants, Feedback, LocalStore, AStore,
AppConfig, Nthen) {
/* This file exposes functionality which is specific to Cryptpad, but not to
any particular pad type. This includes functions for committing metadata
@ -59,11 +58,6 @@ define([
// RESTRICTED
// Settings only
common.getUserObject = function (cb) {
postMessage("GET", [], function (obj) {
cb(obj);
});
};
common.resetDrive = function (cb) {
postMessage("RESET_DRIVE", null, function (obj) {
if (obj.error) { return void cb(obj.error); }
@ -81,6 +75,12 @@ define([
cb();
});
};
// Settings and drive
common.getUserObject = function (cb) {
postMessage("GET", [], function (obj) {
cb(obj);
});
};
// Settings and auth
common.getUserObject = function (cb) {
postMessage("GET", [], function (obj) {
@ -94,6 +94,14 @@ define([
};
postMessage("MIGRATE_ANON_DRIVE", data, cb);
};
// Drive
common.userObjectCommand = function (data, cb) {
postMessage("DRIVE_USEROBJECT", data, cb);
};
common.drive = {};
common.drive.onLog = Util.mkEvent();
common.drive.onChange = Util.mkEvent();
common.drive.onRemove = Util.mkEvent();
// Profile
common.getProfileEditUrl = function (cb) {
postMessage("GET", ['profile', 'edit'], function (obj) {
@ -270,8 +278,8 @@ define([
cb();
});
};
common.getPadAttribute = function (attr, cb) {
var href = Hash.getRelativeHref(window.location.href);
common.getPadAttribute = function (attr, cb, href) {
href = Hash.getRelativeHref(href || window.location.href);
postMessage("GET_PAD_ATTRIBUTE", {
href: href,
attr: attr,
@ -393,13 +401,15 @@ define([
});
};
common.useTemplate = function (href, Crypt, cb) {
common.useTemplate = function (href, Crypt, cb, opts) {
// opts is used to overrides options for chainpad-netflux in cryptput
// it allows us to add owners and expiration time if it is a new file
var parsed = Hash.parsePadUrl(href);
if(!parsed) { throw new Error("Cannot get template hash"); }
Crypt.get(parsed.hash, function (err, val) {
if (err) { throw new Error(err); }
var p = Hash.parsePadUrl(window.location.href);
Crypt.put(p.hash, val, cb);
Crypt.put(p.hash, val, cb, opts);
});
};
@ -450,6 +460,15 @@ define([
});
};
// Network
common.onNetworkDisconnect = Util.mkEvent();
common.onNetworkReconnect = Util.mkEvent();
// Messaging
var messaging = common.messaging = {};
messaging.onFriendRequest = Util.mkEvent();
messaging.onFriendComplete = Util.mkEvent();
// Messenger
var messenger = common.messenger = {};
messenger.getFriendList = function (cb) {
@ -486,7 +505,24 @@ define([
messenger.onFriendEvent = Util.mkEvent();
messenger.onUnfriendEvent = Util.mkEvent();
// HERE
// Pad RPC
var pad = common.padRpc = {};
pad.joinPad = function (data, cb) {
postMessage("JOIN_PAD", data, cb);
};
pad.sendPadMsg = function (data, cb) {
postMessage("SEND_PAD_MSG", data, cb);
};
pad.onReadyEvent = Util.mkEvent();
pad.onMessageEvent = Util.mkEvent();
pad.onJoinEvent = Util.mkEvent();
pad.onLeaveEvent = Util.mkEvent();
pad.onDisconnectEvent = Util.mkEvent();
common.getFullHistory = function (data, cb) {
postMessage("GET_FULL_HISTORY", data, cb);
};
common.getShareHashes = function (secret, cb) {
var hashes;
if (!window.location.hash) {
@ -569,15 +605,20 @@ define([
break;
}
case 'Q_FRIEND_REQUEST': {
if (!common.onFriendRequest) { break; }
common.onFriendRequest(data, cb);
common.messaging.onFriendRequest.fire(data, cb);
break;
}
case 'EV_FRIEND_COMPLETE': {
if (!common.onFriendComplete) { break; }
common.onFriendComplete(data);
common.messaging.onFriendComplete.fire(data);
break;
}
// Network
case 'NETWORK_DISCONNECT': {
common.onNetworkDisconnect.fire(); break;
}
case 'NETWORK_RECONNECT': {
common.onNetworkReconnect.fire(data); break;
}
// Messenger
case 'CONTACTS_MESSAGE': {
common.messenger.onMessageEvent.fire(data); break;
@ -597,6 +638,32 @@ define([
case 'CONTACTS_UNFRIEND': {
common.messenger.onUnfriendEvent.fire(data); break;
}
// Pad
case 'PAD_READY': {
common.padRpc.onReadyEvent.fire(); break;
}
case 'PAD_MESSAGE': {
common.padRpc.onMessageEvent.fire(data); break;
}
case 'PAD_JOIN': {
common.padRpc.onJoinEvent.fire(data); break;
}
case 'PAD_LEAVE': {
common.padRpc.onLeaveEvent.fire(data); break;
}
case 'PAD_DISCONNECT': {
common.padRpc.onDisconnectEvent.fire(data); break;
}
// Drive
case 'DRIVE_LOG': {
common.drive.onLog.fire(data); break;
}
case 'DRIVE_CHANGE': {
common.drive.onChange.fire(data); break;
}
case 'DRIVE_REMOVE': {
common.drive.onRemove.fire(data); break;
}
}
};
@ -627,6 +694,16 @@ define([
if (typeof(Symbol) === 'undefined') {
Feedback.send('NO_SYMBOL');
}
if (typeof(SharedWorker) === "undefined") {
Feedback.send('NO_SHAREDWORKER');
} else {
Feedback.send('SHAREDWORKER');
}
if (typeof(Worker) === "undefined") {
Feedback.send('NO_WEBWORKER');
}
Feedback.reportScreenDimensions();
Feedback.reportLanguage();
};
@ -643,7 +720,8 @@ define([
anonHash: LocalStore.getFSHash(),
localToken: tryParsing(localStorage.getItem(Constants.tokenKey)),
language: common.getLanguage(),
messenger: rdyCfg.messenger
messenger: rdyCfg.messenger,
driveEvents: rdyCfg.driveEvents
};
if (sessionStorage[Constants.newPadPathKey]) {
cfg.initialPath = sessionStorage[Constants.newPadPathKey];
@ -651,6 +729,9 @@ define([
}
AStore.query("CONNECT", cfg, waitFor(function (data) {
if (data.error) { throw new Error(data.error); }
if (data.state === 'ALREADY_INIT') {
data = data.returned;
}
if (data.anonHash && !cfg.userHash) { LocalStore.setFSHash(data.anonHash); }
@ -668,14 +749,13 @@ define([
}
}
// TODO ww
//Messaging.addDirectMessageHandler(common);
initFeedback(data.feedback);
}));
}).nThen(function (waitFor) {
// Load the new pad when the hash has changed
var oldHref = document.location.href;
window.onhashchange = function () {
window.onhashchange = function (ev) {
if (ev && ev.reset) { oldHref = document.location.href; return; }
var newHref = document.location.href;
var parsedOld = Hash.parsePadUrl(oldHref).hashData;
var parsedNew = Hash.parsePadUrl(newHref).hashData;

@ -86,6 +86,7 @@ define([
};
var fixSelection = cursor.fixSelection = function (sel, range) {
try {
if (Tree.contains(Range.start.el, inner) && Tree.contains(Range.end.el, inner)) {
var order = Tree.orderOfNodes(Range.start.el, Range.end.el, inner);
var backward;
@ -118,6 +119,7 @@ define([
console.error(errText);
return errText;
}
} catch (e) { console.error(e); }
};
cursor.pushDelta = function (oldVal, newVal) {

@ -1,4 +1,4 @@
@import (once) '../customize/src/less2/include/colortheme.less';
@import (once) '../customize/src/less2/include/colortheme-all.less';
@import '../customize/src/less2/include/modal.less';
.fileDialog_main () {

@ -105,7 +105,8 @@ define([
if (parsed) {
var proxy = proxyData.proxy;
var oldFo = FO.init(parsed.drive, {
loggedIn: proxyData.loggedIn
loggedIn: proxyData.loggedIn,
pinPads: function () {} // without pinPads /outer/userObject.js won't be loaded
});
var onMigrated = function () {
oldFo.fixFiles();

@ -8,13 +8,14 @@ define([
'/common/common-realtime.js',
'/common/common-messaging.js',
'/common/common-messenger.js',
'/common/outer/chainpad-netflux-worker.js',
'/common/outer/network-config.js',
'/bower_components/chainpad-crypto/crypto.js?v=0.1.5',
'/bower_components/chainpad/chainpad.dist.js',
'/bower_components/chainpad-listmap/chainpad-listmap.js',
], function (UserObject, Migrate, Hash, Util, Constants, Feedback, Realtime, Messaging, Messenger,
NetConfig,
CpNfWorker, NetConfig,
Crypto, ChainPad, Listmap) {
var Store = {};
@ -95,7 +96,6 @@ define([
var getCanonicalChannelList = function () {
return Util.deduplicateString(getUserChannelList()).sort();
};
//////////////////////////////////////////////////////////////////
/////////////////////// RPC //////////////////////////////////////
//////////////////////////////////////////////////////////////////
@ -264,7 +264,6 @@ define([
};
Store.getFileSize = function (data, cb) {
console.log(data, cb);
if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); }
var channelId = Hash.hrefToHexChannelId(data.href);
@ -617,11 +616,11 @@ define([
postMessage("UPDATE_METADATA");
},
pinPads: Store.pinPads,
friendComplete: function (data, cb) {
postMessage("Q_FRIEND_COMPLETE", data, cb);
friendComplete: function (data) {
postMessage("EV_FRIEND_COMPLETE", data);
},
friendRequest: function (data) {
postMessage("EV_FRIEND_REQUEST", data);
friendRequest: function (data, cb) {
postMessage("Q_FRIEND_REQUEST", data, cb);
},
};
};
@ -715,11 +714,134 @@ define([
}
};
//////////////////////////////////////////////////////////////////
/////////////////////// PAD //////////////////////////////////////
//////////////////////////////////////////////////////////////////
// TODO with sharedworker
// channel will be an object storing the webchannel associated to each browser tab
var channel = {
queue: []
};
Store.joinPad = function (data, cb) {
var conf = {
onReady: function () {
postMessage("PAD_READY");
}, // post EV_PAD_READY
onMessage: function (m) {
postMessage("PAD_MESSAGE", m);
}, // post EV_PAD_MESSAGE
onJoin: function (m) {
postMessage("PAD_JOIN", m);
}, // post EV_PAD_JOIN
onLeave: function (m) {
postMessage("PAD_LEAVE", m);
}, // post EV_PAD_LEAVE
onDisconnect: function () {
postMessage("PAD_DISCONNECT");
}, // post EV_PAD_DISCONNECT
channel: data.channel,
validateKey: data.validateKey,
owners: data.owners,
password: data.password,
expire: data.expire,
network: store.network,
readOnly: data.readOnly,
onConnect: function (wc, sendMessage) {
channel.sendMessage = sendMessage;
channel.wc = wc;
channel.queue.forEach(function (data) {
sendMessage(data.message);
});
cb({
myID: wc.myID,
id: wc.id,
members: wc.members
});
}
};
CpNfWorker.start(conf);
};
Store.sendPadMsg = function (data, cb) {
if (!channel.wc) { channel.queue.push(data); }
channel.sendMessage(data, cb);
};
// TODO
// GET_FULL_HISTORY from sframe-common-outer
Store.getFullHistory = function (data, cb) {
var network = store.network;
var hkn = network.historyKeeper;
//var crypto = Crypto.createEncryptor(data.keys);
// Get the history messages and send them to the iframe
var parse = function (msg) {
try {
return JSON.parse(msg);
} catch (e) {
return null;
}
};
var msgs = [];
var onMsg = function (msg) {
var parsed = parse(msg);
if (parsed[0] === 'FULL_HISTORY_END') {
cb(msgs);
return;
}
if (parsed[0] !== 'FULL_HISTORY') { return; }
if (parsed[1] && parsed[1].validateKey) { // First message
return;
}
if (parsed[1][3] !== data.channel) { return; }
msg = parsed[1][4];
if (msg) {
msg = msg.replace(/^cp\|/, '');
//var decryptedMsg = crypto.decrypt(msg, true);
msgs.push(msg);
}
};
network.on('message', onMsg);
network.sendto(hkn, JSON.stringify(['GET_FULL_HISTORY', data.channel, data.validateKey]));
};
// TODO with sharedworker
// when the tab is closed, leave the pad
// Drive
Store.userObjectCommand = function (cmdData, cb) {
if (!cmdData || !cmdData.cmd) { return; }
var data = cmdData.data;
switch (cmdData.cmd) {
case 'move':
store.userObject.move(data.paths, data.newPath, cb); break;
case 'restore':
store.userObject.restore(data.path, cb); break;
case 'addFolder':
store.userObject.addFolder(data.path, data.name, cb); break;
case 'delete':
store.userObject.delete(data.paths, cb, data.nocheck); break;
case 'emptyTrash':
store.userObject.emptyTrash(cb); break;
case 'rename':
store.userObject.rename(data.path, data.newName, cb); break;
default:
cb();
}
};
//////////////////////////////////////////////////////////////////
/////////////////////// Init /////////////////////////////////////
//////////////////////////////////////////////////////////////////
var onReady = function (returned, cb) {
var proxy = store.proxy;
var userObject = store.userObject = UserObject.init(proxy.drive, {
pinPads: Store.pinPads,
loggedIn: store.loggedIn
unpinPads: Store.unpinPads,
loggedIn: store.loggedIn,
log: function (msg) {
postMessage("DRIVE_LOG", msg);
}
});
var todo = function () {
userObject.fixFiles();
@ -811,7 +933,7 @@ define([
ChainPad: ChainPad,
classic: true,
};
var rt = Listmap.create(listmapConfig);
var rt = window.rt = Listmap.create(listmapConfig);
store.proxy = rt.proxy;
store.loggedIn = typeof(data.userHash) !== "undefined";
@ -840,8 +962,16 @@ define([
if (path[0] === 'drive' && path[1] === "migrate" && value === 1) {
rt.network.disconnect();
rt.realtime.abort();
postMessage('NETWORK_DISCONNECT');
}
});
rt.proxy.on('disconnect', function () {
postMessage('NETWORK_DISCONNECT');
});
rt.proxy.on('reconnect', function (info) {
postMessage('NETWORK_RECONNECT', {myId: info.myId});
});
};
/**
@ -858,7 +988,8 @@ define([
Store.init = function (data, callback) {
if (initialized) {
return void callback({
error: 'ALREADY_INIT'
state: 'ALREADY_INIT',
returned: store.returned
});
}
initialized = true;
@ -873,14 +1004,32 @@ define([
if (Object.keys(store.proxy).length === 1) {
Feedback.send("FIRST_APP_USE", true);
}
store.returned = ret;
callback(ret);
var messagingCfg = getMessagingCfg();
Messaging.addDirectMessageHandler(messagingCfg);
// Send events whenever there is a change or a removal in the drive
if (data.driveEvents) {
store.proxy.on('change', [], function (o, n, p) {
postMessage('DRIVE_CHANGE', {
old: o,
new: n,
path: p
});
});
store.proxy.on('remove', [], function (o, p) {
postMessage('DRIVE_REMOVE', {
old: o,
path: p
});
});
}
if (data.messenger) {
var messenger = store.messenger = Messenger.messenger(store); // TODO
var messenger = store.messenger = Messenger.messenger(store);
messenger.on('message', function (message) {
postMessage('CONTACTS_MESSAGE', message);
});

@ -0,0 +1,256 @@
/*
* Copyright 2014 XWiki SAS
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define([], function () {
var USE_HISTORY = true;
var verbose = function (x) { console.log(x); };
verbose = function () {}; // comment out to enable verbose logging
var unBencode = function (str) { return str.replace(/^\d+:/, ''); };
var start = function (conf) {
var channel = conf.channel;
var validateKey = conf.validateKey;
var readOnly = conf.readOnly || false;
var network = conf.network;
var onConnect = conf.onConnect || function () { };
var onMessage = conf.onMessage;
var onJoin = conf.onJoin;
var onLeave = conf.onLeave;
var onReady = conf.onReady;
var onDisconnect = conf.onDisconnect;
var owners = conf.owners;
var password = conf.password;
var expire = conf.expire;
conf = undefined;
var initializing = true;
var lastKnownHash;
var messageFromOuter = function () {};
var onRdy = function () {
// Trigger onReady only if not ready yet. This is important because the history keeper sends a direct
// message through "network" when it is synced, and it triggers onReady for each channel joined.
if (!initializing) { return; }
onReady();
//sframeChan.event('EV_RT_READY', null);
// we're fully synced
initializing = false;
};
// shim between chainpad and netflux
var msgIn = function (peerId, msg) {
return msg.replace(/^cp\|/, '');
/*try {
var decryptedMsg = Crypto.decrypt(msg, validateKey);
return decryptedMsg;
} catch (err) {
console.error(err);
return msg;
}*/
};
var msgOut = function (msg) {
if (readOnly) { return; }
return msg;
/*try {
var cmsg = Crypto.encrypt(msg);
if (msg.indexOf('[4') === 0) { cmsg = 'cp|' + cmsg; }
return cmsg;
} catch (err) {
console.log(msg);
throw err;
}*/
};
var onMsg = function(peer, msg, wc, network, direct) {
// unpack the history keeper from the webchannel
var hk = network.historyKeeper;
if (direct && peer !== hk) {
return;
}
if (direct) {
var parsed = JSON.parse(msg);
if (parsed.validateKey && parsed.channel) {
if (parsed.channel === wc.id && !validateKey) {
validateKey = parsed.validateKey;
}
// We have to return even if it is not the current channel:
// we don't want to continue with other channels messages here
return;
}
if (parsed.state && parsed.state === 1 && parsed.channel) {
if (parsed.channel === wc.id) {
onRdy();
}
// We have to return even if it is not the current channel:
// we don't want to continue with other channels messages here
return;
}
}
if (peer === hk) {
// if the peer is the 'history keeper', extract their message
var parsed1 = JSON.parse(msg);
msg = parsed1[4];
// Check that this is a message for our channel
if (parsed1[3] !== wc.id) { return; }
}
lastKnownHash = msg.slice(0,64);
var message = msgIn(peer, msg);
verbose(message);
// slice off the bencoded header
// Why are we getting bencoded stuff to begin with?
// FIXME this shouldn't be necessary
message = unBencode(message);//.slice(message.indexOf(':[') + 1);
// pass the message into Chainpad
onMessage(message);
//sframeChan.query('Q_RT_MESSAGE', message, function () { });
};
// We use an object to store the webchannel so that we don't have to push new handlers to chainpad
// and remove the old ones when reconnecting and keeping the same 'realtime' object
// See realtime.onMessage below: we call wc.bcast(...) but wc may change
var wcObject = {};
var onOpen = function(wc, network, firstConnection) {
wcObject.wc = wc;
channel = wc.id;
// Add the existing peers in the userList
//TODO sframeChan.event('EV_RT_CONNECT', { myID: wc.myID, members: wc.members, readOnly: readOnly });
// Add the handlers to the WebChannel
wc.on('message', function (msg, sender) { //Channel msg
onMsg(sender, msg, wc, network);
});
wc.on('join', function (m) { onJoin(m); /*sframeChan.event('EV_RT_JOIN', m);*/ });
wc.on('leave', function (m) { onLeave(m); /*sframeChan.event('EV_RT_LEAVE', m);*/ });
if (firstConnection) {
// Sending a message...
messageFromOuter = function(message, cb) {
// Filter messages sent by Chainpad to make it compatible with Netflux
message = msgOut(message);
if (message) {
// Do not remove wcObject, it allows us to use a new 'wc' without changing the handler if we
// want to keep the same chainpad (realtime) object
try {
wcObject.wc.bcast(message).then(function() {
cb();
}, function(err) {
// The message has not been sent, display the error.
console.error(err);
});
} catch (e) {
console.log(e);
// Just skip calling back and it will fail on the inside.
}
}
};
}
onConnect(wc, messageFromOuter);
// Get the channel history
if (USE_HISTORY) {
var hk;
wc.members.forEach(function (p) {
if (p.length === 16) { hk = p; }
});
network.historyKeeper = hk;
var cfg = {
validateKey: validateKey,
lastKnownHash: lastKnownHash,
owners: owners,
expire: expire,
password: password
};
var msg = ['GET_HISTORY', wc.id, cfg];
// Add the validateKey if we are the channel creator and we have a validateKey
if (hk) { network.sendto(hk, JSON.stringify(msg)); }
} else {
onRdy();
}
};
/*var isIntentionallyLeaving = false;
window.addEventListener("beforeunload", function () {
isIntentionallyLeaving = true;
});*/
var findChannelById = function (webChannels, channelId) {
var webChannel;
// Array.some terminates once a truthy value is returned
// best case is faster than forEach, though webchannel arrays seem
// to consistently have a length of 1
webChannels.some(function(chan) {
if(chan.id === channelId) { webChannel = chan; return true;}
});
return webChannel;
};
var connectTo = function (network, firstConnection) {
// join the netflux network, promise to handle opening of the channel
network.join(channel || null).then(function(wc) {
onOpen(wc, network, firstConnection);
}, function(error) {
console.error(error);
});
};
network.on('disconnect', function (reason) {
console.log('disconnect');
//if (isIntentionallyLeaving) { return; }
if (reason === "network.disconnect() called") { return; }
onDisconnect();
//sframeChan.event('EV_RT_DISCONNECT');
});
network.on('reconnect', function () {
initializing = true;
connectTo(network, false);
});
network.on('message', function (msg, sender) { // Direct message
var wchan = findChannelById(network.webChannels, channel);
if (wchan) {
onMsg(sender, msg, wchan, network, true);
}
});
connectTo(network, true);
};
return {
start: start
/*function (config) {
config.sframeChan.whenReg('EV_RT_READY', function () {
start(config);
});
}*/
};
});

@ -146,6 +146,20 @@ define([
case 'CONTACTS_SET_CHANNEL_HEAD': {
Store.messenger.setChannelHead(data, cb); break;
}
// Pad
case 'SEND_PAD_MSG': {
Store.sendPadMsg(data, cb); break;
}
case 'JOIN_PAD': {
Store.joinPad(data, cb); break;
}
case 'GET_FULL_HISTORY': {
Store.getFullHistory(data, cb); break;
}
// Drive
case 'DRIVE_USEROBJECT': {
Store.userObjectCommand(data, cb); break;
}
default: {
break;

@ -0,0 +1,595 @@
define([
'/customize/application_config.js',
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-realtime.js',
'/customize/messages.js'
], function (AppConfig, Util, Hash, Realtime, Messages) {
var module = {};
var clone = function (o) {
try { return JSON.parse(JSON.stringify(o)); }
catch (e) { return undefined; }
};
module.init = function (config, exp, files) {
var unpinPads = config.unpinPads || function () {
console.error("unpinPads was not provided");
};
var pinPads = config.pinPads;
var loggedIn = config.loggedIn;
var workgroup = config.workgroup;
var ROOT = exp.ROOT;
var FILES_DATA = exp.FILES_DATA;
var OLD_FILES_DATA = exp.OLD_FILES_DATA;
var UNSORTED = exp.UNSORTED;
var TRASH = exp.TRASH;
var TEMPLATE = exp.TEMPLATE;
var debug = exp.debug;
exp.setPadAttribute = function (href, attr, value, cb) {
cb = cb || function () {};
var id = exp.getIdFromHref(href);
if (!id) { return void cb("E_INVAL_HREF"); }
if (!attr || !attr.trim()) { return void cb("E_INVAL_ATTR"); }
var data = exp.getFileData(id);
data[attr] = clone(value);
cb(null);
};
exp.getPadAttribute = function (href, attr, cb) {
cb = cb || function () {};
var id = exp.getIdFromHref(href);
if (!id) { return void cb(null, undefined); }
var data = exp.getFileData(id);
cb(null, clone(data[attr]));
};
var removePadAttribute = exp.removePadAttribute = function (f) {
if (typeof(f) !== 'string') {
console.error("Can't find pad attribute for an undefined pad");
return;
}
Object.keys(files).forEach(function (key) {
var hash = f.indexOf('#') !== -1 ? f.slice(f.indexOf('#') + 1) : null;
if (hash && key.indexOf(hash) === 0) {
exp.debug("Deleting pad attribute in the realtime object");
delete files[key];
}
});
};
exp.pushData = function (data, cb) {
if (typeof cb !== "function") { cb = function () {}; }
var todo = function () {
var id = Util.createRandomInteger();
files[FILES_DATA][id] = data;
cb(null, id);
};
if (!loggedIn || !AppConfig.enablePinning || config.testMode) {
return void todo();
}
if (!pinPads) { return; }
pinPads([Hash.hrefToHexChannelId(data.href)], function (obj) {
if (obj && obj.error) { return void cb(obj.error); }
todo();
});
};
// FILES DATA
var spliceFileData = function (id) {
delete files[FILES_DATA][id];
};
exp.checkDeletedFiles = function () {
// Nothing in OLD_FILES_DATA for workgroups
if (workgroup || (!loggedIn && !config.testMode)) { return; }
var filesList = exp.getFiles([ROOT, 'hrefArray', TRASH]);
var toClean = [];
exp.getFiles([FILES_DATA]).forEach(function (id) {
if (filesList.indexOf(id) === -1) {
var fd = exp.getFileData(id);
if (fd && fd.href) {
toClean.push(Hash.hrefToHexChannelId(fd.href));
}
spliceFileData(id);
}
});
if (!toClean.length) { return; }
unpinPads(toClean, function (response) {
if (response && response.error) { return console.error(response.error); }
// console.error(response);
});
};
var deleteHrefs = function (ids) {
ids.forEach(function (obj) {
var idx = files[obj.root].indexOf(obj.id);
files[obj.root].splice(idx, 1);
});
};
var deleteMultipleTrashRoot = function (roots) {
roots.forEach(function (obj) {
var idx = files[TRASH][obj.name].indexOf(obj.el);
files[TRASH][obj.name].splice(idx, 1);
});
};
exp.deleteMultiplePermanently = function (paths, nocheck) {
var hrefPaths = paths.filter(function(x) { return exp.isPathIn(x, ['hrefArray']); });
var rootPaths = paths.filter(function(x) { return exp.isPathIn(x, [ROOT]); });
var trashPaths = paths.filter(function(x) { return exp.isPathIn(x, [TRASH]); });
var allFilesPaths = paths.filter(function(x) { return exp.isPathIn(x, [FILES_DATA]); });
if (!loggedIn && !config.testMode) {
allFilesPaths.forEach(function (path) {
var el = exp.find(path);
if (!el) { return; }
var id = exp.getIdFromHref(el.href);
if (!id) { return; }
spliceFileData(id);
removePadAttribute(el.href);
});
return;
}
var ids = [];
hrefPaths.forEach(function (path) {
var id = exp.find(path);
ids.push({
root: path[0],
id: id
});
});
deleteHrefs(ids);
rootPaths.forEach(function (path) {
var parentPath = path.slice();
var key = parentPath.pop();
var parentEl = exp.find(parentPath);
delete parentEl[key];
});
var trashRoot = [];
trashPaths.forEach(function (path) {
var parentPath = path.slice();
var key = parentPath.pop();
var parentEl = exp.find(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
if (path.length === 4) {
trashRoot.push({
name: path[1],
el: parentEl
});
return;
}
// Trash but not root: it's just a tree so remove the key
delete parentEl[key];
});
deleteMultipleTrashRoot(trashRoot);
// In some cases, we want to remove pads from a location without removing them from
// OLD_FILES_DATA (replaceHref)
if (!nocheck) { exp.checkDeletedFiles(); }
};
// Move
var pushToTrash = function (name, element, path) {
var trash = files[TRASH];
if (typeof(trash[name]) === "undefined") { trash[name] = []; }
var trashArray = trash[name];
var trashElement = {
element: element,
path: path
};
trashArray.push(trashElement);
};
exp.copyElement = function (elementPath, newParentPath) {
if (exp.comparePath(elementPath, newParentPath)) { return; } // Nothing to do...
var element = exp.find(elementPath);
var newParent = exp.find(newParentPath);
// Move to Trash
if (exp.isPathIn(newParentPath, [TRASH])) {
if (!elementPath || elementPath.length < 2 || elementPath[0] === TRASH) {
debug("Can't move an element from the trash to the trash: ", elementPath);
return;
}
var key = elementPath[elementPath.length - 1];
var elName = exp.isPathIn(elementPath, ['hrefArray']) ? exp.getTitle(element) : key;
var parentPath = elementPath.slice();
parentPath.pop();
pushToTrash(elName, element, parentPath);
return true;
}
// Move to hrefArray
if (exp.isPathIn(newParentPath, ['hrefArray'])) {
if (exp.isFolder(element)) {
exp.log(Messages.fo_moveUnsortedError);
return;
} else {
if (elementPath[0] === newParentPath[0]) { return; }
var fileRoot = newParentPath[0];
if (files[fileRoot].indexOf(element) === -1) {
files[fileRoot].push(element);
}
return true;
}
}
// Move to root
var newName = exp.isFile(element) ?
exp.getAvailableName(newParent, Hash.createChannelId()) :
exp.isInTrashRoot(elementPath) ?
elementPath[1] : elementPath.pop();
if (typeof(newParent[newName]) !== "undefined") {
exp.log(Messages.fo_unavailableName);
return;
}
newParent[newName] = element;
return true;
};
// FORGET (move with href not path)
exp.forget = function (href) {
var id = exp.getIdFromHref(href);
if (!id) { return; }
if (!loggedIn && !config.testMode) {
// delete permanently
exp.removePadAttribute(href);
spliceFileData(id);
return;
}
var paths = exp.findFile(id);
exp.move(paths, [TRASH]);
};
// REPLACE
exp.replace = function (o, n) {
var idO = exp.getIdFromHref(o);
if (!idO || !exp.isFile(idO)) { return; }
var data = exp.getFileData(idO);
if (!data) { return; }
data.href = n;
};
// If all the occurences of an href are in the trash, remvoe them and add the file in root.
// This is use with setPadTitle when we open a stronger version of a deleted pad
exp.restoreHref = function (href) {
var idO = exp.getIdFromHref(href);
if (!idO || !exp.isFile(idO)) { return; }
var paths = exp.findFile(idO);
// Remove all the occurences in the trash
// If all the occurences are in the trash or no occurence, add the pad to root
var allInTrash = true;
paths.forEach(function (p) {
if (p[0] === TRASH) {
exp.delete(p, null, true); // 3rd parameter means skip "checkDeletedFiles"
return;
}
allInTrash = false;
});
if (allInTrash) {
exp.add(idO);
}
};
exp.add = function (id, path) {
// TODO WW
if (!loggedIn && !config.testMode) { return; }
var data = files[FILES_DATA][id];
if (!data || typeof(data) !== "object") { return; }
var newPath = path, parentEl;
if (path && !Array.isArray(path)) {
newPath = decodeURIComponent(path).split(',');
}
// Add to href array
if (path && exp.isPathIn(newPath, ['hrefArray'])) {
parentEl = exp.find(newPath);
parentEl.push(id);
return;
}
// Add to root if path is ROOT or if no path
var filesList = exp.getFiles([ROOT, TRASH, 'hrefArray']);
if (path && exp.isPathIn(newPath, [ROOT]) || filesList.indexOf(id) === -1) {
parentEl = exp.find(newPath || [ROOT]);
if (parentEl) {
var newName = exp.getAvailableName(parentEl, Hash.createChannelId());
parentEl[newName] = id;
return;
}
}
};
/**
* INTEGRITY CHECK
*/
exp.migrate = function (cb) {
// Make sure unsorted doesn't exist anymore
// Note: Unsorted only works with the old structure where pads are href
// It should be called before the migration code
var fixUnsorted = function () {
if (!files[UNSORTED] || !files[OLD_FILES_DATA]) { return; }
debug("UNSORTED still exists in the object, removing it...");
var us = files[UNSORTED];
if (us.length === 0) {
delete files[UNSORTED];
return;
}
us.forEach(function (el) {
if (typeof el !== "string") {
return;
}
var data = files[OLD_FILES_DATA].filter(function (x) {
return x.href === el;
});
if (data.length === 0) {
files[OLD_FILES_DATA].push({
href: el
});
}
return;
});
delete files[UNSORTED];
};
// mergeDrive...
var migrateToNewFormat = function (todo) {
if (!files[OLD_FILES_DATA]) {
return void todo();
}
try {
debug("Migrating file system...");
files.migrate = 1;
var next = function () {
var oldData = files[OLD_FILES_DATA].slice();
if (!files[FILES_DATA]) {
files[FILES_DATA] = {};
}
var newData = files[FILES_DATA];
//var oldFiles = oldData.map(function (o) { return o.href; });
oldData.forEach(function (obj) {
if (!obj || !obj.href) { return; }
var href = obj.href;
var id = Util.createRandomInteger();
var paths = exp.findFile(href);
var data = obj;
var key = Hash.createChannelId();
if (data) {
newData[id] = data;
} else {
newData[id] = {href: href};
}
paths.forEach(function (p) {
var parentPath = p.slice();
var okey = parentPath.pop(); // get the parent
var parent = exp.find(parentPath);
if (exp.isInTrashRoot(p)) {
parent.element = id;
newData[id].filename = p[1];
return;
}
if (exp.isPathIn(p, ['hrefArray'])) {
parent[okey] = id;
return;
}
// else root or trash (not trashroot)
parent[key] = id;
newData[id].filename = okey;
delete parent[okey];
});
});
delete files[OLD_FILES_DATA];
delete files.migrate;
console.log('done');
todo();
};
if (exp.rt) {
exp.rt.sync();
// TODO
Realtime.whenRealtimeSyncs(exp.rt, next);
} else {
window.setTimeout(next, 1000);
}
} catch(e) {
console.error(e);
todo();
}
};
fixUnsorted();
migrateToNewFormat(cb);
};
exp.fixFiles = function () {
// Explore the tree and check that everything is correct:
// * 'root', 'trash', 'unsorted' and 'filesData' exist and are objects
// * ROOT: Folders are objects, files are href
// * TRASH: Trash root contains only arrays, each element of the array is an object {element:.., path:..}
// * OLD_FILES_DATA: - Data (title, cdate, adte) are stored in filesData. filesData contains only href keys linking to object with title, cdate, adate.
// - Dates (adate, cdate) can be parsed/formatted
// - All files in filesData should be either in 'root', 'trash' or 'unsorted'. If that's not the case, copy the fily to 'unsorted'
// * TEMPLATE: Contains only files (href), and does not contains files that are in ROOT
debug("Cleaning file system...");
var before = JSON.stringify(files);
var fixRoot = function (elem) {
if (typeof(files[ROOT]) !== "object") { debug("ROOT was not an object"); files[ROOT] = {}; }
var element = elem || files[ROOT];
for (var el in element) {
if (!exp.isFile(element[el], true) && !exp.isFolder(element[el])) {
debug("An element in ROOT was not a folder nor a file. ", element[el]);
delete element[el];
continue;
}
if (exp.isFolder(element[el])) {
fixRoot(element[el]);
continue;
}
if (typeof element[el] === "string") {
// We have an old file (href) which is not in filesData: add it
var id = Util.createRandomInteger();
var key = Hash.createChannelId();
files[FILES_DATA][id] = {href: element[el], filename: el};
element[key] = id;
delete element[el];
}
if (typeof element[el] === "number") {
var data = files[FILES_DATA][element[el]];
if (!data) {
debug("An element in ROOT doesn't have associated data", element[el], el);
delete element[el];
}
}
}
};
var fixTrashRoot = function () {
if (typeof(files[TRASH]) !== "object") { debug("TRASH was not an object"); files[TRASH] = {}; }
var tr = files[TRASH];
var toClean;
var addToClean = function (obj, idx, el) {
if (typeof(obj) !== "object") { toClean.push(idx); return; }
if (!exp.isFile(obj.element, true) &&
!exp.isFolder(obj.element)) { toClean.push(idx); return; }
if (!Array.isArray(obj.path)) { toClean.push(idx); return; }
if (typeof obj.element === "string") {
// We have an old file (href) which is not in filesData: add it
var id = Util.createRandomInteger();
files[FILES_DATA][id] = {href: obj.element, filename: el};
obj.element = id;
}
if (exp.isFolder(obj.element)) { fixRoot(obj.element); }
if (typeof obj.element === "number") {
var data = files[FILES_DATA][obj.element];
if (!data) {
debug("An element in TRASH doesn't have associated data", obj.element, el);
toClean.push(idx);
}
}
};
for (var el in tr) {
if (!Array.isArray(tr[el])) {
debug("An element in TRASH root is not an array. ", tr[el]);
delete tr[el];
} else if (tr[el].length === 0) {
debug("Empty array in TRASH root. ", tr[el]);
delete tr[el];
} else {
toClean = [];
for (var j=0; j<tr[el].length; j++) {
addToClean(tr[el][j], j, el);
}
for (var i = toClean.length-1; i>=0; i--) {
tr[el].splice(toClean[i], 1);
}
}
}
};
var fixTemplate = function () {
if (!Array.isArray(files[TEMPLATE])) { debug("TEMPLATE was not an array"); files[TEMPLATE] = []; }
files[TEMPLATE] = Util.deduplicateString(files[TEMPLATE].slice());
var us = files[TEMPLATE];
var rootFiles = exp.getFiles([ROOT]).slice();
var toClean = [];
us.forEach(function (el, idx) {
if (!exp.isFile(el, true) || rootFiles.indexOf(el) !== -1) {
toClean.push(el);
}
if (typeof el === "string") {
// We have an old file (href) which is not in filesData: add it
var id = Util.createRandomInteger();
files[FILES_DATA][id] = {href: el};
us[idx] = id;
}
if (typeof el === "number") {
var data = files[FILES_DATA][el];
if (!data) {
debug("An element in TEMPLATE doesn't have associated data", el);
toClean.push(el);
}
}
});
toClean.forEach(function (el) {
var idx = us.indexOf(el);
if (idx !== -1) {
us.splice(idx, 1);
}
});
};
var fixFilesData = function () {
if (typeof files[FILES_DATA] !== "object") { debug("OLD_FILES_DATA was not an object"); files[FILES_DATA] = {}; }
var fd = files[FILES_DATA];
var rootFiles = exp.getFiles([ROOT, TRASH, 'hrefArray']);
var root = exp.find([ROOT]);
var toClean = [];
for (var id in fd) {
id = Number(id);
var el = fd[id];
if (!el || typeof(el) !== "object") {
debug("An element in filesData was not an object.", el);
toClean.push(id);
continue;
}
if (!el.href) {
debug("Removing an element in filesData with a missing href.", el);
toClean.push(id);
continue;
}
if (/^https*:\/\//.test(el.href)) { el.href = Hash.getRelativeHref(el.href); }
if (!el.ctime) { el.ctime = el.atime; }
var parsed = Hash.parsePadUrl(el.href);
if (!el.title) { el.title = Hash.getDefaultName(parsed); }
if (!parsed.hash) {
debug("Removing an element in filesData with a invalid href.", el);
toClean.push(id);
continue;
}
if (!parsed.type) {
debug("Removing an element in filesData with a invalid type.", el);
toClean.push(id);
continue;
}
if ((loggedIn || config.testMode) && rootFiles.indexOf(id) === -1) {
debug("An element in filesData was not in ROOT, TEMPLATE or TRASH.", id, el);
var newName = Hash.createChannelId();
root[newName] = id;
continue;
}
}
toClean.forEach(function (id) {
spliceFileData(id);
});
};
var fixDrive = function () {
Object.keys(files).forEach(function (key) {
if (key.slice(0,1) === '/') { delete files[key]; }
});
};
fixRoot();
fixTrashRoot();
if (!workgroup) {
fixTemplate();
fixFilesData();
}
fixDrive();
if (JSON.stringify(files) !== before) {
debug("Your file system was corrupted. It has been cleaned so that the pads you visit can be stored safely");
return;
}
debug("File system was clean");
};
return exp;
};
return module;
});

@ -253,6 +253,13 @@ define([
newContent = normalize(newContent);
contentUpdate(newContent);
} else {
if (!cpNfInner.metadataMgr.getPrivateData().isNewFile) {
// We're getting 'new pad' but there is an existing file
// We don't know exactly why this can happen but under no circumstances
// should we overwrite the content, so lets just try again.
common.gotoURL();
return;
}
console.log('updating title');
title.updateTitle(title.defaultTitle);
evOnDefaultContentNeeded.fire();
@ -278,7 +285,7 @@ define([
}
}
if (newPad) {
if (newPad && !AppConfig.displayCreationScreen) {
common.openTemplatePicker();
}
};
@ -335,6 +342,7 @@ define([
};
var createFilePicker = function () {
if (!common.isLoggedIn()) { return; }
common.initFilePicker({
onSelect: function (data) {
if (data.type !== 'file') {
@ -362,6 +370,7 @@ define([
}).appendTo(toolbar.$rightside).hide();
};
var setMediaTagEmbedder = function (mte) {
if (!common.isLoggedIn()) { return; }
if (!mte || readOnly) {
$embedButton.hide();
return;
@ -373,13 +382,20 @@ define([
nThen(function (waitFor) {
UI.addLoadingScreen();
SFCommon.create(waitFor(function (c) { common = c; }));
}).nThen(function (waitFor) {
common.getSframeChannel().onReady(waitFor());
}).nThen(function (waitFor) {
if (!AppConfig.displayCreationScreen) { return; }
if (common.getMetadataMgr().getPrivateData().isNewFile) {
common.getPadCreationScreen(waitFor());
}
}).nThen(function (waitFor) {
cpNfInner = common.startRealtime({
// really basic operational transform
patchTransformer: options.patchTransformer || ChainPad.SmartJSONTransformer,
// cryptpad debug logging (default is 1)
// logLevel: 0,
// logLevel: 2,
validateContent: options.validateContent || function (content) {
try {
JSON.parse(content);
@ -461,7 +477,16 @@ define([
getHeadingText: function () { return titleRecommender(); }
}, onLocal);
var configTb = {
displayed: ['userlist', 'title', 'useradmin', 'spinner', 'newpad', 'share', 'limit'],
displayed: [
'userlist',
'title',
'useradmin',
'spinner',
'newpad',
'share',
'limit',
'unpinnedWarning'
],
title: title.getTitleConfig(),
metadataMgr: cpNfInner.metadataMgr,
readOnly: readOnly,

@ -15,8 +15,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define([], function () {
var USE_HISTORY = true;
var verbose = function (x) { console.log(x); };
verbose = function () {}; // comment out to enable verbose logging
@ -26,35 +24,24 @@ define([], function () {
var channel = conf.channel;
var Crypto = conf.crypto;
var validateKey = conf.validateKey;
var isNewHash = conf.isNewHash;
var readOnly = conf.readOnly || false;
var network = conf.network;
var padRpc = conf.padRpc;
var sframeChan = conf.sframeChan;
var password = conf.password;
var owners = conf.owners;
var expire = conf.expire;
var onConnect = conf.onConnect || function () { };
conf = undefined;
var initializing = true;
var lastKnownHash;
var queue = [];
var messageFromInner = function (m, cb) { queue.push([ m, cb ]); };
sframeChan.on('Q_RT_MESSAGE', function (message, cb) {
messageFromInner(message, cb);
});
var onReady = function () {
// Trigger onReady only if not ready yet. This is important because the history keeper sends a direct
// message through "network" when it is synced, and it triggers onReady for each channel joined.
if (!initializing) { return; }
padRpc.onReadyEvent.reg(function () {
sframeChan.event('EV_RT_READY', null);
// we're fully synced
initializing = false;
};
});
// shim between chainpad and netflux
var msgIn = function (peerId, msg) {
msg = msg.replace(/^cp\|/, '');
var msgIn = function (msg) {
try {
var decryptedMsg = Crypto.decrypt(msg, validateKey);
var decryptedMsg = Crypto.decrypt(msg, isNewHash);
return decryptedMsg;
} catch (err) {
console.error(err);
@ -74,44 +61,14 @@ define([], function () {
}
};
var onMessage = function(peer, msg, wc, network, direct) {
// unpack the history keeper from the webchannel
var hk = network.historyKeeper;
if (direct && peer !== hk) {
return;
}
if (direct) {
var parsed = JSON.parse(msg);
if (parsed.validateKey && parsed.channel) {
if (parsed.channel === wc.id && !validateKey) {
validateKey = parsed.validateKey;
}
// We have to return even if it is not the current channel:
// we don't want to continue with other channels messages here
return;
}
if (parsed.state && parsed.state === 1 && parsed.channel) {
if (parsed.channel === wc.id) {
onReady(wc);
}
// We have to return even if it is not the current channel:
// we don't want to continue with other channels messages here
return;
}
}
// The history keeper is different for each channel :
// no need to check if the message is related to the current channel
if (peer === hk) {
// if the peer is the 'history keeper', extract their message
var parsed1 = JSON.parse(msg);
msg = parsed1[4];
// Check that this is a message for us
if (parsed1[3] !== wc.id) { return; }
}
sframeChan.on('Q_RT_MESSAGE', function (message, cb) {
var msg = msgOut(message);
if (!msg) { return; }
padRpc.sendPadMsg(msg, cb);
});
lastKnownHash = msg.slice(0,64);
var message = msgIn(peer, msg);
var onMessage = function(msg) {
var message = msgIn(msg);
verbose(message);
@ -124,118 +81,34 @@ define([], function () {
sframeChan.query('Q_RT_MESSAGE', message, function () { });
};
// We use an object to store the webchannel so that we don't have to push new handlers to chainpad
// and remove the old ones when reconnecting and keeping the same 'realtime' object
// See realtime.onMessage below: we call wc.bcast(...) but wc may change
var wcObject = {};
var onOpen = function(wc, network, firstConnection) {
wcObject.wc = wc;
channel = wc.id;
onConnect(wc);
onConnect = function () { };
var onOpen = function(data) {
// Add the existing peers in the userList
sframeChan.event('EV_RT_CONNECT', { myID: wc.myID, members: wc.members, readOnly: readOnly });
// Add the handlers to the WebChannel
wc.on('message', function (msg, sender) { //Channel msg
onMessage(sender, msg, wc, network);
});
wc.on('join', function (m) { sframeChan.event('EV_RT_JOIN', m); });
wc.on('leave', function (m) { sframeChan.event('EV_RT_LEAVE', m); });
if (firstConnection) {
// Sending a message...
messageFromInner = function(message, cb) {
// Filter messages sent by Chainpad to make it compatible with Netflux
message = msgOut(message);
if (message) {
// Do not remove wcObject, it allows us to use a new 'wc' without changing the handler if we
// want to keep the same chainpad (realtime) object
try {
if (window.Cryptpad_SUPPRESS_MSG) { return; }
wcObject.wc.bcast(message).then(function() {
if (window.Cryptpad_SUPPRESS_ACK) { return; }
cb();
}, function(err) {
// The message has not been sent, display the error.
console.error(err);
});
} catch (e) {
console.log(e);
// Just skip calling back and it will fail on the inside.
}
}
};
queue.forEach(function (arr) { messageFromInner(arr[0], arr[1]); });
}
// Get the channel history
if (USE_HISTORY) {
var hk;
wc.members.forEach(function (p) {
if (p.length === 16) { hk = p; }
});
network.historyKeeper = hk;
var msg = ['GET_HISTORY', wc.id];
// Add the validateKey if we are the channel creator and we have a validateKey
msg.push(validateKey);
msg.push(lastKnownHash);
if (hk) { network.sendto(hk, JSON.stringify(msg)); }
} else {
onReady(wc);
}
};
var isIntentionallyLeaving = false;
window.addEventListener("beforeunload", function () {
isIntentionallyLeaving = true;
});
var findChannelById = function (webChannels, channelId) {
var webChannel;
onConnect(data.id);
onConnect = function () {};
// Array.some terminates once a truthy value is returned
// best case is faster than forEach, though webchannel arrays seem
// to consistently have a length of 1
webChannels.some(function(chan) {
if(chan.id === channelId) { webChannel = chan; return true;}
});
return webChannel;
};
sframeChan.event('EV_RT_CONNECT', { myID: data.myID, members: data.members, readOnly: readOnly });
var connectTo = function (network, firstConnection) {
// join the netflux network, promise to handle opening of the channel
network.join(channel || null).then(function(wc) {
onOpen(wc, network, firstConnection);
}, function(error) {
console.error(error);
});
// Add the handlers to the WebChannel
padRpc.onMessageEvent.reg(function (msg) { onMessage(msg); });
padRpc.onJoinEvent.reg(function (m) { sframeChan.event('EV_RT_JOIN', m); });
padRpc.onLeaveEvent.reg(function (m) { sframeChan.event('EV_RT_LEAVE', m); });
};
network.on('disconnect', function (reason) {
console.log('disconnect');
if (isIntentionallyLeaving) { return; }
if (reason === "network.disconnect() called") { return; }
padRpc.onDisconnectEvent.reg(function () {
sframeChan.event('EV_RT_DISCONNECT');
});
network.on('reconnect', function () {
initializing = true;
connectTo(network, false);
// join the netflux network, promise to handle opening of the channel
padRpc.joinPad({
channel: channel || null,
validateKey: validateKey,
readOnly: readOnly,
owners: owners,
password: password,
expire: expire
}, function(data) {
onOpen(data);
});
network.on('message', function (msg, sender) { // Direct message
var wchan = findChannelById(network.webChannels, channel);
if (wchan) {
onMessage(sender, msg, wchan, network, true);
}
});
connectTo(network, true);
};
return {

@ -110,9 +110,13 @@ define([
chan.whenReg('EV_REGISTER_HANDLER', evReady.fire);
// Make sure both iframes are ready
var isReady =false;
chan.onReady = function (h) {
if (isReady) {
return void h();
}
if (typeof(h) !== "function") { return; }
chan.on('EV_RPC_READY', function () { h(); });
chan.on('EV_RPC_READY', function () { isReady = true; h(); });
};
chan.ready = function () {
chan.whenReg('EV_RPC_READY', function () {

@ -36,7 +36,7 @@ define([
//patchTransformer: ChainPad.NaiveJSONTransformer,
//logLevel: 0,
//transformFunction: JsonOT.validate,
logLevel: config.debug ? 1 : 0,
logLevel: config.debug ? 2 : 0,
noPrune: true
});
};

@ -12,6 +12,7 @@ define([
var network;
var secret;
var hashes;
var isNewFile;
var CpNfOuter;
var Cryptpad;
var Crypto;
@ -19,10 +20,10 @@ define([
var SFrameChannel;
var sframeChan;
var FilePicker;
//var Messenger;
var Messaging;
var Notifier;
var Utils = {};
var AppConfig;
nThen(function (waitFor) {
// Load #2, the loading screen is up so grab whatever you need...
@ -41,11 +42,12 @@ define([
'/common/common-constants.js',
'/common/common-feedback.js',
'/common/outer/local-store.js',
'/customize/application_config.js',
'/common/outer/network-config.js',
'/bower_components/netflux-websocket/netflux-client.js',
], waitFor(function (_CpNfOuter, _Cryptpad, _Crypto, _Cryptget, _SFrameChannel,
_FilePicker, _Messaging, _Notifier, _Hash, _Util, _Realtime,
_Constants, _Feedback, _LocalStore, NetConfig, Netflux) {
_Constants, _Feedback, _LocalStore, _AppConfig, NetConfig, Netflux) {
CpNfOuter = _CpNfOuter;
Cryptpad = _Cryptpad;
Crypto = _Crypto;
@ -60,6 +62,7 @@ define([
Utils.Constants = _Constants;
Utils.Feedback = _Feedback;
Utils.LocalStore = _LocalStore;
AppConfig = _AppConfig;
if (localStorage.CRYPTPAD_URLARGS !== ApiConfig.requireConf.urlArgs) {
console.log("New version, flushing cache");
@ -86,7 +89,8 @@ define([
sframeChan = sfc;
}), false, { cache: cache, localStore: localStore, language: Cryptpad.getLanguage() });
Cryptpad.ready(waitFor(), {
messenger: cfg.messaging
messenger: cfg.messaging,
driveEvents: cfg.driveEvents
});
if (!cfg.newNetwork) {
@ -130,13 +134,28 @@ define([
}
Cryptpad.getShareHashes(secret, waitFor(function (err, h) { hashes = h; }));
}
}).nThen(function (waitFor) {
// Check if the pad exists on server
if (!window.location.hash) { isNewFile = true; return; }
Cryptpad.getFileSize(window.location.href, waitFor(function (err, size) {
if (size) {
isNewFile = false;
return;
}
isNewFile = true;
}));
}).nThen(function () {
var readOnly = secret.keys && !secret.keys.editKeyStr;
if (!secret.keys) { secret.keys = secret.key; }
var isNewHash = true;
if (!secret.keys) {
isNewHash = false;
secret.keys = secret.key;
readOnly = false;
}
var parsed = Utils.Hash.parsePadUrl(window.location.href);
if (!parsed.type) { throw new Error(); }
var defaultTitle = Utils.Hash.getDefaultName(parsed);
var edPublic;
var updateMeta = function () {
//console.log('EV_METADATA_UPDATE');
var metaObj, isTemplate;
@ -144,6 +163,7 @@ define([
Cryptpad.getMetadata(waitFor(function (err, m) {
if (err) { console.log(err); }
metaObj = m;
edPublic = metaObj.priv.edPublic; // needed to create an owned pad
}));
Cryptpad.isTemplate(window.location.href, waitFor(function (err, t) {
if (err) { console.log(err); }
@ -168,7 +188,9 @@ define([
accounts: {
donateURL: Cryptpad.donateURL,
upgradeURL: Cryptpad.upgradeURL
}
},
isNewFile: isNewFile,
isDeleted: window.location.hash.length > 0
};
for (var k in additionalPriv) { metaObj.priv[k] = additionalPriv[k]; }
@ -285,49 +307,27 @@ define([
});
sframeChan.on('Q_SEND_FRIEND_REQUEST', function (netfluxId, cb) {
Messaging.inviteFromUserlist(Cryptpad, netfluxId);
cb();
Cryptpad.inviteFromUserlist(netfluxId, cb);
});
Cryptpad.onFriendRequest = function (confirmText, cb) {
Cryptpad.messaging.onFriendRequest.reg(function (confirmText, cb) {
sframeChan.query('Q_INCOMING_FRIEND_REQUEST', confirmText, function (err, data) {
cb(data);
});
};
Cryptpad.onFriendComplete = function (data) {
});
Cryptpad.messaging.onFriendComplete.reg(function (data) {
sframeChan.event('EV_FRIEND_REQUEST', data);
};
});
sframeChan.on('Q_GET_FULL_HISTORY', function (data, cb) {
var hkn = network.historyKeeper;
var crypto = Crypto.createEncryptor(secret.keys);
// Get the history messages and send them to the iframe
var parse = function (msg) {
try {
return JSON.parse(msg);
} catch (e) {
return null;
}
};
var msgs = [];
var onMsg = function (msg) {
var parsed = parse(msg);
if (parsed[0] === 'FULL_HISTORY_END') {
cb(msgs);
return;
}
if (parsed[0] !== 'FULL_HISTORY') { return; }
if (parsed[1] && parsed[1].validateKey) { // First message
return;
}
msg = parsed[1][4];
if (msg) {
msg = msg.replace(/^cp\|/, '');
var decryptedMsg = crypto.decrypt(msg, true);
msgs.push(decryptedMsg);
}
};
network.on('message', onMsg);
network.sendto(hkn, JSON.stringify(['GET_FULL_HISTORY', secret.channel, secret.keys.validateKey]));
Cryptpad.getFullHistory({
channel: secret.channel,
validateKey: secret.keys.validateKey
}, function (encryptedMsgs) {
cb(encryptedMsgs.map(function (msg) {
return crypto.decrypt(msg, true);
}));
});
});
sframeChan.on('Q_GET_PAD_ATTRIBUTE', function (data, cb) {
@ -560,43 +560,97 @@ define([
});
}
sframeChan.ready();
Utils.Feedback.reportAppUsage();
if (!realtime) { return; }
// Join the netflux channel
var rtStarted = false;
var startRealtime = function () {
rtStarted = true;
var replaceHash = function (hash) {
if (window.history && window.history.replaceState) {
if (!/^#/.test(hash)) { hash = '#' + hash; }
void window.history.replaceState({}, window.document.title, hash);
if (typeof(window.onhashchange) === 'function') {
window.onhashchange();
}
return;
}
window.location.hash = hash;
};
var replaceHash = function (hash) {
if (window.history && window.history.replaceState) {
if (!/^#/.test(hash)) { hash = '#' + hash; }
void window.history.replaceState({}, window.document.title, hash);
if (typeof(window.onhashchange) === 'function') {
window.onhashchange();
CpNfOuter.start({
sframeChan: sframeChan,
channel: secret.channel,
padRpc: Cryptpad.padRpc,
validateKey: secret.keys.validateKey || undefined,
isNewHash: isNewHash,
readOnly: readOnly,
crypto: Crypto.createEncryptor(secret.keys),
onConnect: function (wc) {
if (window.location.hash && window.location.hash !== '#') {
window.location = parsed.getUrl({
present: parsed.hashData.present,
embed: parsed.hashData.embed
});
return;
}
if (readOnly || cfg.noHash) { return; }
replaceHash(Utils.Hash.getEditHashFromKeys(wc, secret.keys));
}
return;
}
window.location.hash = hash;
});
};
CpNfOuter.start({
sframeChan: sframeChan,
channel: secret.channel,
network: cfg.newNetwork || network,
validateKey: secret.keys.validateKey || undefined,
readOnly: readOnly,
crypto: Crypto.createEncryptor(secret.keys),
onConnect: function (wc) {
if (window.location.hash && window.location.hash !== '#') {
window.location = parsed.getUrl({
present: parsed.hashData.present,
embed: parsed.hashData.embed
});
return;
}
if (readOnly || cfg.noHash) { return; }
replaceHash(Utils.Hash.getEditHashFromKeys(wc.id, secret.keys));
sframeChan.on('Q_CREATE_PAD', function (data, cb) {
if (!isNewFile || rtStarted) { return; }
// Create a new hash
var newHash = Utils.Hash.createRandomHash();
secret = Utils.Hash.getSecrets(parsed.type, newHash);
// Update the hash in the address bar
var ohc = window.onhashchange;
window.onhashchange = function () {};
window.location.hash = newHash;
window.onhashchange = ohc;
ohc({reset: true});
// Update metadata values and send new metadata inside
parsed = Utils.Hash.parsePadUrl(window.location.href);
defaultTitle = Utils.Hash.getDefaultName(parsed);
readOnly = false;
updateMeta();
var rtConfig = {};
if (data.owned) {
//rtConfig.owners = [edPublic];
}
if (data.expire) {
//rtConfig.expire = data.expire;
}
if (data.template) {
// Pass rtConfig to useTemplate because Cryptput will create the file and
// we need to have the owners and expiration time in the first line on the
// server
Cryptpad.useTemplate(data.template, Cryptget, function () {
startRealtime();
cb();
}, rtConfig);
return;
}
// Start realtime outside the iframe and callback
startRealtime(rtConfig);
cb();
});
sframeChan.ready();
Utils.Feedback.reportAppUsage();
if (!realtime) { return; }
if (isNewFile && Utils.LocalStore.isLoggedIn()
&& AppConfig.displayCreationScreen) { return; }
startRealtime();
});
};

@ -90,6 +90,7 @@ define([
funcs.updateTags = callWithCommon(UIElements.updateTags);
funcs.createLanguageSelector = callWithCommon(UIElements.createLanguageSelector);
funcs.createMarkdownToolbar = callWithCommon(UIElements.createMarkdownToolbar);
funcs.getPadCreationScreen = callWithCommon(UIElements.getPadCreationScreen);
// Thumb
funcs.displayThumbnail = callWithCommon(Thumb.displayThumbnail);

@ -197,4 +197,20 @@ define({
// Anonymous users can empty their drive and remove FS_hash from localStorage
'EV_BURN_ANON_DRIVE': true,
// Inner drive needs to send command and receive updates from the async store
'Q_DRIVE_USEROBJECT': true,
'Q_DRIVE_GETOBJECT': true,
// Store's userObject need to send log messages to inner to display them in the UI
'EV_DRIVE_LOG': true,
// Refresh the drive when the drive has changed ('change' or 'remove' events)
'EV_DRIVE_CHANGE': true,
'EV_DRIVE_REMOVE': true,
// Notifications about connection and disconnection from the network
'EV_NETWORK_DISCONNECT': true,
'EV_NETWORK_RECONNECT': true,
// Pad creation screen: create a pad with the selected attributes (owned, expire)
'Q_CREATE_PAD': true,
});

@ -719,6 +719,58 @@ define([
return $titleContainer;
};
var createUnpinnedWarning0 = function (toolbar, config) {
if (true) { return; } // stub this call since it won't make it into the next release
if (Common.isLoggedIn()) { return; }
var pd = config.metadataMgr.getPrivateData();
var o = pd.origin;
var hashes = pd.availableHashes;
var url = pd.origin + pd.pathname + '#' + (hashes.editHash || hashes.viewHash);
var cid = Hash.hrefToHexChannelId(url);
Common.sendAnonRpcMsg('IS_CHANNEL_PINNED', cid, function (x) {
if (x.error || !Array.isArray(x.response)) { return void console.log(x); }
if (x.response[0] === true) {
$('.cp-pad-not-pinned').remove();
return;
}
if ($('.cp-pad-not-pinned').length) { return; }
var pnpTitle = Messages._getKey('padNotPinned', ['','','','']);
var pnpMsg = Messages._getKey('padNotPinned', [
'<a href="' + o + '/login" class="cp-pnp-login" target="blank" title>',
'</a>',
'<a href="' + o + '/register" class="cp-pnp-register" target="blank" title>',
'</a>'
]);
var $msg = $('<span>', {
'class': 'cp-pad-not-pinned'
}).append([
$('<span>', {'class': 'fa fa-exclamation-triangle', 'title': pnpTitle}),
$('<span>', {'class': 'cp-pnp-msg'}).append(pnpMsg)
]);
$msg.find('a.cp-pnp-login').click(function (ev) {
ev.preventDefault();
Common.setLoginRedirect(function () {
window.parent.location = o + '/login/';
});
});
$msg.find('a.cp-pnp-register').click(function (ev) {
ev.preventDefault();
Common.setLoginRedirect(function () {
window.parent.location = o + '/register/';
});
});
$('.cp-toolbar-top').append($msg);
UI.addTooltips();
});
};
var createUnpinnedWarning = function (toolbar, config) {
config.metadataMgr.onChange(function () {
createUnpinnedWarning0(toolbar, config);
});
createUnpinnedWarning0(toolbar, config);
};
var createPageTitle = function (toolbar, config) {
if (config.title || !config.pageTitle) { return; }
var $titleContainer = $('<span>', {
@ -1087,6 +1139,7 @@ define([
tb['upgrade'] = $.noop;
tb['newpad'] = createNewPad;
tb['useradmin'] = createUserAdmin;
tb['unpinnedWarning'] = createUnpinnedWarning;
var addElement = toolbar.addElement = function (arr, additionnalCfg, init) {
if (typeof additionnalCfg === "object") { $.extend(true, config, additionnalCfg); }

@ -4,8 +4,9 @@ define([
'/common/common-hash.js',
'/common/common-realtime.js',
'/common/common-constants.js',
'/common/outer/userObject.js',
'/customize/messages.js'
], function (AppConfig, Util, Hash, Realtime, Constants, Messages) {
], function (AppConfig, Util, Hash, Realtime, Constants, OuterFO, Messages) {
var module = {};
var ROOT = module.ROOT = "root";
@ -13,15 +14,10 @@ define([
var TRASH = module.TRASH = "trash";
var TEMPLATE = module.TEMPLATE = "template";
var clone = function (o) {
try { return JSON.parse(JSON.stringify(o)); }
catch (e) { return undefined; }
};
module.init = function (files, config) {
var exp = {};
var pinPads = config.pinPads;
var loggedIn = config.loggedIn;
var sframeChan = config.sframeChan;
var FILES_DATA = module.FILES_DATA = exp.FILES_DATA = Constants.storageKey;
var OLD_FILES_DATA = module.OLD_FILES_DATA = exp.OLD_FILES_DATA = Constants.oldStorageKey;
@ -39,7 +35,7 @@ define([
};
var log = config.log || logging;
var logError = config.logError || logging;
var debug = config.debug || logging;
var debug = exp.debug = config.debug || logging;
var error = exp.error = function() {
exp.fixFiles();
console.error.apply(console, arguments);
@ -48,6 +44,11 @@ define([
// TODO: workgroup
var workgroup = config.workgroup;
if (pinPads) {
// Extend "exp" with methods used only outside of the iframe (requires access to store)
OuterFO.init(config, exp, files);
}
/*
* UTILS
@ -146,26 +147,10 @@ define([
if (type === 'name') { return data.filename; }
return data.filename || data.title || NEW_FILE_NAME;
};
exp.getPadAttribute = function (href, attr, cb) {
cb = cb || function () {};
var id = exp.getIdFromHref(href);
if (!id) { return void cb(null, undefined); }
var data = getFileData(id);
cb(null, clone(data[attr]));
};
exp.setPadAttribute = function (href, attr, value, cb) {
cb = cb || function () {};
var id = exp.getIdFromHref(href);
if (!id) { return void cb("E_INVAL_HREF"); }
if (!attr || !attr.trim()) { return void cb("E_INVAL_ATTR"); }
var data = getFileData(id);
data[attr] = clone(value);
cb(null);
};
// PATHS
var comparePath = exp.comparePath = function (a, b) {
var comparePath = exp.comparePath = function (a, b) {
if (!a || !b || !Array.isArray(a) || !Array.isArray(b)) { return false; }
if (a.length !== b.length) { return false; }
var result = true;
@ -473,7 +458,7 @@ define([
* OPERATIONS
*/
var getAvailableName = function (parentEl, name) {
var getAvailableName = exp.getAvailableName = function (parentEl, name) {
if (typeof(parentEl[name]) === "undefined") { return name; }
var newName = name;
var i = 1;
@ -484,84 +469,17 @@ define([
return newName;
};
// FILES DATA
exp.pushData = function (data, cb) {
if (typeof cb !== "function") { cb = function () {}; }
var todo = function () {
var id = Util.createRandomInteger();
files[FILES_DATA][id] = data;
cb(null, id);
};
if (!loggedIn || !AppConfig.enablePinning || config.testMode) {
return void todo();
}
if (!pinPads) { return; }
pinPads([Hash.hrefToHexChannelId(data.href)], function (obj) {
if (obj && obj.error) { return void cb(obj.error); }
todo();
});
};
var spliceFileData = exp.removeData = function (id) {
delete files[FILES_DATA][id];
};
// MOVE
var pushToTrash = function (name, element, path) {
var trash = files[TRASH];
if (typeof(trash[name]) === "undefined") { trash[name] = []; }
var trashArray = trash[name];
var trashElement = {
element: element,
path: path
};
trashArray.push(trashElement);
};
var copyElement = function (elementPath, newParentPath) {
if (comparePath(elementPath, newParentPath)) { return; } // Nothing to do...
var element = find(elementPath);
var newParent = find(newParentPath);
// Move to Trash
if (isPathIn(newParentPath, [TRASH])) {
if (!elementPath || elementPath.length < 2 || elementPath[0] === TRASH) {
debug("Can't move an element from the trash to the trash: ", elementPath);
return;
}
var key = elementPath[elementPath.length - 1];
var elName = isPathIn(elementPath, ['hrefArray']) ? getTitle(element) : key;
var parentPath = elementPath.slice();
parentPath.pop();
pushToTrash(elName, element, parentPath);
return true;
}
// Move to hrefArray
if (isPathIn(newParentPath, ['hrefArray'])) {
if (isFolder(element)) {
log(Messages.fo_moveUnsortedError);
return;
} else {
if (elementPath[0] === newParentPath[0]) { return; }
var fileRoot = newParentPath[0];
if (files[fileRoot].indexOf(element) === -1) {
files[fileRoot].push(element);
var move = exp.move = function (paths, newPath, cb) {
if (sframeChan) {
return void sframeChan.query("Q_DRIVE_USEROBJECT", {
cmd: "move",
data: {
paths: paths,
newPath: newPath
}
return true;
}
}
// Move to root
var newName = isFile(element) ?
getAvailableName(newParent, Hash.createChannelId()) :
isInTrashRoot(elementPath) ?
elementPath[1] : elementPath.pop();
if (typeof(newParent[newName]) !== "undefined") {
log(Messages.fo_unavailableName);
return;
}, cb);
}
newParent[newName] = element;
return true;
};
var move = exp.move = function (paths, newPath, cb) {
// Copy the elements to their new location
var toRemove = [];
paths.forEach(function (p) {
@ -573,13 +491,21 @@ define([
return;
}
// Try to copy, and if success, remove the element from the old location
if (copyElement(p.slice(), newPath)) {
if (exp.copyElement(p.slice(), newPath)) {
toRemove.push(p);
}
});
exp.delete(toRemove, cb);
};
exp.restore = function (path, cb) {
if (sframeChan) {
return void sframeChan.query("Q_DRIVE_USEROBJECT", {
cmd: "restore",
data: {
path: path
}
}, cb);
}
if (!isInTrashRoot(path)) { return; }
var parentPath = path.slice();
parentPath.pop();
@ -589,165 +515,66 @@ define([
// ADD
var add = exp.add = function (id, path) {
if (!loggedIn && !config.testMode) { return; }
var data = files[FILES_DATA][id];
if (!data || typeof(data) !== "object") { return; }
var newPath = path, parentEl;
if (path && !Array.isArray(path)) {
newPath = decodeURIComponent(path).split(',');
}
// Add to href array
if (path && isPathIn(newPath, ['hrefArray'])) {
parentEl = find(newPath);
parentEl.push(id);
return;
}
// Add to root if path is ROOT or if no path
var filesList = getFiles([ROOT, TRASH, 'hrefArray']);
if (path && isPathIn(newPath, [ROOT]) || filesList.indexOf(id) === -1) {
parentEl = find(newPath || [ROOT]);
if (parentEl) {
var newName = getAvailableName(parentEl, Hash.createChannelId());
parentEl[newName] = id;
return;
}
}
};
exp.addFolder = function (folderPath, name, cb) {
if (sframeChan) {
return void sframeChan.query("Q_DRIVE_USEROBJECT", {
cmd: "addFolder",
data: {
path: folderPath,
name: name
}
}, cb);
}
var parentEl = find(folderPath);
var folderName = getAvailableName(parentEl, name || NEW_FOLDER_NAME);
parentEl[folderName] = {};
var newPath = folderPath.slice();
newPath.push(folderName);
cb(void 0, {
cb({
newPath: newPath
});
};
// FORGET (move with href not path)
exp.forget = function (href) {
var id = getIdFromHref(href);
if (!id) { return; }
if (!loggedIn && !config.testMode) {
// delete permanently
exp.removePadAttribute(href);
spliceFileData(id);
return;
}
var paths = findFile(id);
move(paths, [TRASH]);
};
// DELETE
// Permanently delete multiple files at once using a list of paths
// NOTE: We have to be careful when removing elements from arrays (trash root, unsorted or template)
var removePadAttribute = exp.removePadAttribute = function (f) {
if (typeof(f) !== 'string') {
console.error("Can't find pad attribute for an undefined pad");
return;
}
Object.keys(files).forEach(function (key) {
var hash = f.indexOf('#') !== -1 ? f.slice(f.indexOf('#') + 1) : null;
if (hash && key.indexOf(hash) === 0) {
debug("Deleting pad attribute in the realtime object");
delete files[key];
}
});
};
var checkDeletedFiles = function () {
// Nothing in OLD_FILES_DATA for workgroups
if (workgroup || (!loggedIn && !config.testMode)) { return; }
var filesList = getFiles([ROOT, 'hrefArray', TRASH]);
getFiles([FILES_DATA]).forEach(function (id) {
if (filesList.indexOf(id) === -1) {
spliceFileData(id);
}
});
};
var deleteHrefs = function (ids) {
ids.forEach(function (obj) {
var idx = files[obj.root].indexOf(obj.id);
files[obj.root].splice(idx, 1);
});
};
var deleteMultipleTrashRoot = function (roots) {
roots.forEach(function (obj) {
var idx = files[TRASH][obj.name].indexOf(obj.el);
files[TRASH][obj.name].splice(idx, 1);
});
};
var deleteMultiplePermanently = function (paths, nocheck) {
var hrefPaths = paths.filter(function(x) { return isPathIn(x, ['hrefArray']); });
var rootPaths = paths.filter(function(x) { return isPathIn(x, [ROOT]); });
var trashPaths = paths.filter(function(x) { return isPathIn(x, [TRASH]); });
var allFilesPaths = paths.filter(function(x) { return isPathIn(x, [FILES_DATA]); });
if (!loggedIn && !config.testMode) {
allFilesPaths.forEach(function (path) {
var el = find(path);
if (!el) { return; }
var id = getIdFromHref(el.href);
if (!id) { return; }
spliceFileData(id);
removePadAttribute(el.href);
});
return;
}
var ids = [];
hrefPaths.forEach(function (path) {
var id = find(path);
ids.push({
root: path[0],
id: id
});
});
deleteHrefs(ids);
rootPaths.forEach(function (path) {
var parentPath = path.slice();
var key = parentPath.pop();
var parentEl = find(parentPath);
delete parentEl[key];
});
var trashRoot = [];
trashPaths.forEach(function (path) {
var parentPath = path.slice();
var key = parentPath.pop();
var parentEl = find(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
if (path.length === 4) {
trashRoot.push({
name: path[1],
el: parentEl
});
return;
}
// Trash but not root: it's just a tree so remove the key
delete parentEl[key];
});
deleteMultipleTrashRoot(trashRoot);
// In some cases, we want to remove pads from a location without removing them from
// OLD_FILES_DATA (replaceHref)
if (!nocheck) { checkDeletedFiles(); }
};
exp.delete = function (paths, cb, nocheck) {
deleteMultiplePermanently(paths, nocheck);
if (sframeChan) {
return void sframeChan.query("Q_DRIVE_USEROBJECT", {
cmd: "delete",
data: {
paths: paths,
nocheck: nocheck
}
}, cb);
}
exp.deleteMultiplePermanently(paths, nocheck);
if (typeof cb === "function") { cb(); }
};
exp.emptyTrash = function (cb) {
if (sframeChan) {
return void sframeChan.query("Q_DRIVE_USEROBJECT", {
cmd: "emptyTrash"
}, cb);
}
files[TRASH] = {};
checkDeletedFiles();
exp.checkDeletedFiles();
if(cb) { cb(); }
};
// RENAME
exp.rename = function (path, newName, cb) {
if (sframeChan) {
console.log(path, newName);
return void sframeChan.query("Q_DRIVE_USEROBJECT", {
cmd: "rename",
data: {
path: path,
newName: newName
}
}, cb);
}
console.log(path, newName);
if (path.length <= 1) {
logError('Renaming `root` is forbidden');
return;
@ -784,322 +611,6 @@ define([
if (typeof cb === "function") { cb(); }
};
// REPLACE
exp.replace = function (o, n) {
var idO = getIdFromHref(o);
if (!idO || !isFile(idO)) { return; }
var data = getFileData(idO);
if (!data) { return; }
data.href = n;
};
// If all the occurences of an href are in the trash, remvoe them and add the file in root.
// This is use with setPadTitle when we open a stronger version of a deleted pad
exp.restoreHref = function (href) {
var idO = getIdFromHref(href);
if (!idO || !isFile(idO)) { return; }
var paths = findFile(idO);
// Remove all the occurences in the trash
// If all the occurences are in the trash or no occurence, add the pad to root
var allInTrash = true;
paths.forEach(function (p) {
if (p[0] === TRASH) {
exp.delete(p, null, true); // 3rd parameter means skip "checkDeletedFiles"
return;
}
allInTrash = false;
});
if (allInTrash) {
add(idO);
}
};
/**
* INTEGRITY CHECK
*/
exp.migrate = function (cb) {
// Make sure unsorted doesn't exist anymore
// Note: Unsorted only works with the old structure where pads are href
// It should be called before the migration code
var fixUnsorted = function () {
if (!files[UNSORTED] || !files[OLD_FILES_DATA]) { return; }
debug("UNSORTED still exists in the object, removing it...");
var us = files[UNSORTED];
if (us.length === 0) {
delete files[UNSORTED];
return;
}
us.forEach(function (el) {
if (typeof el !== "string") {
return;
}
var data = files[OLD_FILES_DATA].filter(function (x) {
return x.href === el;
});
if (data.length === 0) {
files[OLD_FILES_DATA].push({
href: el
});
}
return;
});
delete files[UNSORTED];
};
// mergeDrive...
var migrateToNewFormat = function (todo) {
if (!files[OLD_FILES_DATA]) {
return void todo();
}
try {
debug("Migrating file system...");
files.migrate = 1;
var next = function () {
var oldData = files[OLD_FILES_DATA].slice();
if (!files[FILES_DATA]) {
files[FILES_DATA] = {};
}
var newData = files[FILES_DATA];
//var oldFiles = oldData.map(function (o) { return o.href; });
oldData.forEach(function (obj) {
if (!obj || !obj.href) { return; }
var href = obj.href;
var id = Util.createRandomInteger();
var paths = findFile(href);
var data = obj;
var key = Hash.createChannelId();
if (data) {
newData[id] = data;
} else {
newData[id] = {href: href};
}
paths.forEach(function (p) {
var parentPath = p.slice();
var okey = parentPath.pop(); // get the parent
var parent = find(parentPath);
if (isInTrashRoot(p)) {
parent.element = id;
newData[id].filename = p[1];
return;
}
if (isPathIn(p, ['hrefArray'])) {
parent[okey] = id;
return;
}
// else root or trash (not trashroot)
parent[key] = id;
newData[id].filename = okey;
delete parent[okey];
});
});
delete files[OLD_FILES_DATA];
delete files.migrate;
console.log('done');
todo();
};
if (exp.rt) {
exp.rt.sync();
// TODO
Realtime.whenRealtimeSyncs(exp.rt, next);
} else {
window.setTimeout(next, 1000);
}
} catch(e) {
console.error(e);
todo();
}
};
fixUnsorted();
migrateToNewFormat(cb);
};
exp.fixFiles = function () {
// Explore the tree and check that everything is correct:
// * 'root', 'trash', 'unsorted' and 'filesData' exist and are objects
// * ROOT: Folders are objects, files are href
// * TRASH: Trash root contains only arrays, each element of the array is an object {element:.., path:..}
// * OLD_FILES_DATA: - Data (title, cdate, adte) are stored in filesData. filesData contains only href keys linking to object with title, cdate, adate.
// - Dates (adate, cdate) can be parsed/formatted
// - All files in filesData should be either in 'root', 'trash' or 'unsorted'. If that's not the case, copy the fily to 'unsorted'
// * TEMPLATE: Contains only files (href), and does not contains files that are in ROOT
debug("Cleaning file system...");
var before = JSON.stringify(files);
var fixRoot = function (elem) {
if (typeof(files[ROOT]) !== "object") { debug("ROOT was not an object"); files[ROOT] = {}; }
var element = elem || files[ROOT];
for (var el in element) {
if (!isFile(element[el], true) && !isFolder(element[el])) {
debug("An element in ROOT was not a folder nor a file. ", element[el]);
delete element[el];
continue;
}
if (isFolder(element[el])) {
fixRoot(element[el]);
continue;
}
if (typeof element[el] === "string") {
// We have an old file (href) which is not in filesData: add it
var id = Util.createRandomInteger();
var key = Hash.createChannelId();
files[FILES_DATA][id] = {href: element[el], filename: el};
element[key] = id;
delete element[el];
}
if (typeof element[el] === "number") {
var data = files[FILES_DATA][element[el]];
if (!data) {
debug("An element in ROOT doesn't have associated data", element[el], el);
delete element[el];
}
}
}
};
var fixTrashRoot = function () {
if (typeof(files[TRASH]) !== "object") { debug("TRASH was not an object"); files[TRASH] = {}; }
var tr = files[TRASH];
var toClean;
var addToClean = function (obj, idx, el) {
if (typeof(obj) !== "object") { toClean.push(idx); return; }
if (!isFile(obj.element, true) && !isFolder(obj.element)) { toClean.push(idx); return; }
if (!Array.isArray(obj.path)) { toClean.push(idx); return; }
if (typeof obj.element === "string") {
// We have an old file (href) which is not in filesData: add it
var id = Util.createRandomInteger();
files[FILES_DATA][id] = {href: obj.element, filename: el};
obj.element = id;
}
if (isFolder(obj.element)) { fixRoot(obj.element); }
if (typeof obj.element === "number") {
var data = files[FILES_DATA][obj.element];
if (!data) {
debug("An element in TRASH doesn't have associated data", obj.element, el);
toClean.push(idx);
}
}
};
for (var el in tr) {
if (!Array.isArray(tr[el])) {
debug("An element in TRASH root is not an array. ", tr[el]);
delete tr[el];
} else if (tr[el].length === 0) {
debug("Empty array in TRASH root. ", tr[el]);
delete tr[el];
} else {
toClean = [];
for (var j=0; j<tr[el].length; j++) {
addToClean(tr[el][j], j, el);
}
for (var i = toClean.length-1; i>=0; i--) {
tr[el].splice(toClean[i], 1);
}
}
}
};
var fixTemplate = function () {
if (!Array.isArray(files[TEMPLATE])) { debug("TEMPLATE was not an array"); files[TEMPLATE] = []; }
files[TEMPLATE] = Util.deduplicateString(files[TEMPLATE].slice());
var us = files[TEMPLATE];
var rootFiles = getFiles([ROOT]).slice();
var toClean = [];
us.forEach(function (el, idx) {
if (!isFile(el, true) || rootFiles.indexOf(el) !== -1) {
toClean.push(el);
}
if (typeof el === "string") {
// We have an old file (href) which is not in filesData: add it
var id = Util.createRandomInteger();
files[FILES_DATA][id] = {href: el};
us[idx] = id;
}
if (typeof el === "number") {
var data = files[FILES_DATA][el];
if (!data) {
debug("An element in TEMPLATE doesn't have associated data", el);
toClean.push(el);
}
}
});
toClean.forEach(function (el) {
var idx = us.indexOf(el);
if (idx !== -1) {
us.splice(idx, 1);
}
});
};
var fixFilesData = function () {
if (typeof files[FILES_DATA] !== "object") { debug("OLD_FILES_DATA was not an object"); files[FILES_DATA] = {}; }
var fd = files[FILES_DATA];
var rootFiles = getFiles([ROOT, TRASH, 'hrefArray']);
var root = find([ROOT]);
var toClean = [];
for (var id in fd) {
id = Number(id);
var el = fd[id];
if (!el || typeof(el) !== "object") {
debug("An element in filesData was not an object.", el);
toClean.push(id);
continue;
}
if (!el.href) {
debug("Removing an element in filesData with a missing href.", el);
toClean.push(id);
continue;
}
if (/^https*:\/\//.test(el.href)) { el.href = Hash.getRelativeHref(el.href); }
if (!el.ctime) { el.ctime = el.atime; }
var parsed = Hash.parsePadUrl(el.href);
if (!el.title) { el.title = Hash.getDefaultName(parsed); }
if (!parsed.hash) {
debug("Removing an element in filesData with a invalid href.", el);
toClean.push(id);
continue;
}
if (!parsed.type) {
debug("Removing an element in filesData with a invalid type.", el);
toClean.push(id);
continue;
}
if ((loggedIn || config.testMode) && rootFiles.indexOf(id) === -1) {
debug("An element in filesData was not in ROOT, TEMPLATE or TRASH.", id, el);
var newName = Hash.createChannelId();
root[newName] = id;
continue;
}
}
toClean.forEach(function (id) {
spliceFileData(id);
});
};
var fixDrive = function () {
Object.keys(files).forEach(function (key) {
if (key.slice(0,1) === '/') { delete files[key]; }
});
};
fixRoot();
fixTrashRoot();
if (!workgroup) {
fixTemplate();
fixFilesData();
}
fixDrive();
if (JSON.stringify(files) !== before) {
debug("Your file system was corrupted. It has been cleaned so that the pads you visit can be stored safely");
return;
}
debug("File system was clean");
};
return exp;
};
return module;

@ -7,25 +7,118 @@ define([
Requirements
* some transmission methods can be interrupted
* handle disconnects and reconnects
* handle callbacks
* configurable timeout
* Service should expose 'addClient' method
* and handle broadcast
* [x] some transmission methods can be interrupted
* [x] handle disconnects and reconnects
* [x] handle callbacks
* [x] configurable timeout
* [x] be able to answer only queries with a particular id
* be able to implement arbitrary listeners on the service-side
* and not call 'ready' until those listeners are ready
* identical API for:
* iframe postMessage
* server calls over netflux
* postMessage to webworker
* postMessage to sharedWorker
* on-wire protocol should actually be the same for rewriting purposes
* q
* guid (globally unique id)
* txid (message id)
* content
* be able to compose different RPCs as streams
* intercept and rewrite capacity
* multiplex multiple streams over one stream
* blind redirect
* intelligent router
* broadcast (with ACK?)
* message
*
*/
var uid = function () {
var uid = Wire.uid = function () {
return Number(Math.floor(Math.random () *
Number.MAX_SAFE_INTEGER)).toString(32);
};
/* tracker(options)
maintains a registry of asynchronous function calls
allows you to:
hook each call to actually send to a remote service...
abort any call
trigger the pending callback with arguments
set the state of the tracker (active/inactive)
*/
Wire.tracker = function (opt) {
opt = opt || {};
var hook = opt.hook || function () {};
var timeout = opt.timeout || 5000;
var pending = {};
var timeouts = {};
var call = function (method, data, cb) {
var id = uid();
// if the callback is not invoked in time, time out
timeouts[id] = setTimeout(function () {
if (typeof(pending[id]) === 'function') {
cb("TIMEOUT");
delete pending[id];
return;
}
throw new Error('timed out without function to call');
}, timeout);
pending[id] = function () {
// invoke the function with arguments...
cb.apply(null, Array.prototype.slice.call(arguments));
// clear its timeout
clearTimeout(timeouts[id]);
// remove the function from pending
delete pending[id];
};
hook(id, method, data);
return id;
};
var respond = function (id, err, response) {
if (typeof(pending[id]) !== 'function') {
throw new Error('invoked non-existent callback');
}
pending[id](err, response);
};
var abort = function (id) {
if (pending[id]) {
clearTimeout(timeouts[id]);
delete pending[id];
return true;
}
return false;
};
var t = {
call: call,
respond: respond,
abort: abort,
state: true,
};
t.setState = function (active) {
t.state = Boolean(active);
};
return t;
};
/*
opt = {
timeout: 30000,
send: function () {
},
@ -45,50 +138,44 @@ opt = {
};
*/
Wire.create = function (opt, cb) {
var ctx = {};
var pending = ctx.pending = {};
ctx.connected = false;
var rpc = {};
var parseMessage = function (raw) {
try { return JSON.parse(raw); } catch (e) { return; }
};
Wire.create = function (opt, cb) {
opt.constructor(function (e, service) {
if (e) { return setTimeout(function () { cb(e); }); }
var rpc = {};
var guid = Wire.uid();
var t = Wire.tracker({
timeout: opt.timeout,
hook: function (txid, q, content) {
service.send(JSON.stringify({
guid: guid,
q: q,
txid: txid,
content: content,
}));
},
});
rpc.send = function (type, data, cb) {
var txid = uid();
if (typeof(cb) !== 'function') {
throw new Error('expected callback');
}
ctx.pending[txid] = function (err, response) {
cb(err, response);
};
service.send(JSON.stringify({
txid: txid,
message: {
command: type,
content: data,
},
}));
t.call(type, data, cb);
};
service.receive(function (raw) {
try {
var data = JSON.parse(raw);
var txid = data.txid;
if (!txid) { throw new Error('NO_TXID'); }
var cb = pending[txid];
if (data.error) { return void cb(data.error); }
cb(void 0, data.content);
} catch (e) { console.error("UNHANDLED_MESSAGE", raw); }
var data = parseMessage(raw);
if (typeof(data) === 'undefined') {
return console.error("UNHANDLED_MESSAGE", raw);
}
if (!data.txid) { throw new Error('NO_TXID'); }
t.respond(data.txid, data.error, data.content);
});
cb(void 0, rpc);
});
};
return Wire;
});

@ -3,13 +3,13 @@
@import (once) "../../customize/src/less2/include/markdown.less";
@import (once) '../../customize/src/less2/include/fileupload.less';
@import (once) '../../customize/src/less2/include/alertify.less';
//@import (once) '../../customize/src/less/mixins.less';
//@import (once) '../../customize/src/less/variables.less";
@import (once) '../../customize/src/less2/include/avatar.less';
.toolbar_main();
.toolbar_main(
@bg-color: @colortheme_friends-bg,
@warn-color: @colortheme_friends-warn,
@color: @colortheme_friends-color
);
.fileupload_main();
.alertify_main();

@ -8,7 +8,11 @@
@import (once) "../../customize/src/less2/include/limit-bar.less";
@import (once) "../../customize/src/less2/include/tokenfield.less";
.toolbar_main();
.toolbar_main(
@bg-color: @colortheme_drive-bg,
@warn-color: @colortheme_drive-warn,
@color: @colortheme_drive-color
);
.fileupload_main();
.alertify_main();
.limit-bar_main();

@ -185,11 +185,25 @@ define([
isHistoryMode: false,
};
var copyObjectValue = function (objRef, objToCopy) {
for (var k in objRef) { delete objRef[k]; }
$.extend(true, objRef, objToCopy);
};
var updateObject = function (sframeChan, obj, cb) {
sframeChan.query('Q_DRIVE_GETOBJECT', null, function (err, newObj) {
copyObjectValue(obj, newObj);
cb();
});
};
var andThen = function (common, proxy) {
var files = proxy.drive;
var metadataMgr = common.getMetadataMgr();
var sframeChan = common.getSframeChannel();
var priv = metadataMgr.getPrivateData();
var user = metadataMgr.getUserData();
APP.origin = priv.origin;
var isOwnDrive = function () {
return true; // TODO
};
@ -198,12 +212,10 @@ define([
};
config.workgroup = isWorkgroup();
config.loggedIn = APP.loggedIn;
config.sframeChan = sframeChan;
APP.origin = priv.origin;
var filesOp = FO.init(files, config);
filesOp.fixFiles();
var error = filesOp.error;
var $tree = APP.$tree = $("#cp-app-drive-tree");
@ -2056,7 +2068,7 @@ define([
// Display the selected directory into the content part (rightside)
// 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 = APP.displayDirectory = function (path, force) {
var _displayDirectory = function (path, force) {
APP.hideMenu();
if (!APP.editable) { debug("Read-only mode"); }
if (!appStatus.isReady && !force) { return; }
@ -2065,7 +2077,7 @@ define([
if (!path || displayedCategories.indexOf(path[0]) === -1) {
log(Messages.categoryError);
currentPath = [ROOT];
displayDirectory(currentPath);
_displayDirectory(currentPath);
return;
}
appStatus.ready(false);
@ -2092,7 +2104,7 @@ define([
debug("Unable to locate the selected directory: ", path);
var parentPath = path.slice();
parentPath.pop();
displayDirectory(parentPath, true);
_displayDirectory(parentPath, true);
return;
}
if (!isSearch) { delete APP.Search.oldLocation; }
@ -2220,6 +2232,15 @@ define([
$content.scrollTop(s);
appStatus.ready(true);
};
var displayDirectory = APP.displayDirectory = function (path, force) {
if (history.isHistoryMode) {
return void _displayDirectory(path, force);
}
updateObject(sframeChan, proxy, function () {
copyObjectValue(files, proxy.drive);
_displayDirectory(path, force);
});
};
var createTreeElement = function (name, $icon, path, draggable, droppable, collapsable, active) {
var $name = $('<span>', { 'class': 'cp-app-drive-element' }).text(name);
@ -2617,6 +2638,10 @@ define([
APP.hideMenu();
});
if (!APP.loggedIn) {
$defaultContextMenu.find('.cp-app-drive-context-delete').text(Messages.fc_remove)
.attr('data-icon', 'fa-eraser');
}
$defaultContextMenu.on("click", "a", function(e) {
e.stopPropagation();
var paths = $(this).data('paths');
@ -2851,9 +2876,10 @@ define([
onRefresh.to = window.setTimeout(refresh, 500);
}
};
proxy.on('change', [], function () {
sframeChan.on('EV_DRIVE_CHANGE', function (data) {
if (history.isHistoryMode) { return; }
var path = arguments[2];
var path = data.path;
if (path[0] !== 'drive') { return false; }
path = path.slice(1);
var cPath = currentPath.slice();
@ -2866,36 +2892,29 @@ define([
}
APP.resetTree();
return false;
}).on('remove', [], function () {
});
sframeChan.on('EV_DRIVE_REMOVE', function (data) {
if (history.isHistoryMode) { return; }
var path = arguments[1];
var path = data.path;
if (path[0] !== 'drive') { return false; }
path = path.slice(1);
var cPath = currentPath.slice();
if ((filesOp.isPathIn(cPath, ['hrefArray', TRASH]) && cPath[0] === path[0]) ||
(path.length >= cPath.length && filesOp.isSubpath(path, cPath))) {
// Reload after a few to make sure all the change events have been received
onRefresh.to = window.setTimeout(refresh, 500);
onRefresh.refresh();
}
APP.resetTree();
return false;
}).on('change', ['drive', 'migrate'], function () {
var path = arguments[2];
var value = arguments[1];
if (path[1] === "migrate" && value === 1) {
if (APP.onDisconnect) { APP.onDisconnect(true); }
}
});
history.onEnterHistory = function (obj) {
var files = obj.drive;
filesOp = FO.init(files, config);
copyObjectValue(files, obj.drive);
appStatus.isReady = true;
refresh();
};
history.onLeaveHistory = function () {
var files = proxy.drive;
filesOp = FO.init(files, config);
copyObjectValue(files, proxy.drive);
refresh();
};
@ -2922,7 +2941,7 @@ define([
var main = function () {
var common;
var proxy;
var proxy = {};
var readOnly;
nThen(function (waitFor) {
@ -2942,11 +2961,11 @@ define([
}
metadataMgr.onChange(function () {
if (typeof(metadataMgr.getPrivateData().readOnly) === 'boolean') {
readOnly = metadataMgr.getPrivateData().readOnly;
readOnly = APP.readOnly = metadataMgr.getPrivateData().readOnly;
privReady();
}
});
}).nThen(function (/* waitFor */) {
}).nThen(function (waitFor) {
APP.loggedIn = common.isLoggedIn();
APP.SFCommon = common;
if (!APP.loggedIn) { Feedback.send('ANONYMOUS_DRIVE'); }
@ -2955,85 +2974,79 @@ define([
common.setTabTitle(Messages.type.drive);
var listmapConfig = {
/*var listmapConfig = {
data: {},
common: common,
logging: false
};*/
var sframeChan = common.getSframeChannel();
updateObject(sframeChan, proxy, waitFor());
}).nThen(function () {
var sframeChan = common.getSframeChannel();
var metadataMgr = common.getMetadataMgr();
var configTb = {
displayed: ['useradmin', 'pageTitle', 'newpad', 'limit'],
pageTitle: Messages.type.drive,
metadataMgr: metadataMgr,
readOnly: readOnly,
sfCommon: common,
$container: APP.$bar
};
var toolbar = APP.toolbar = Toolbar.create(configTb);
var metadataMgr;
var rt = APP.rt = Listmap.create(listmapConfig);
proxy = rt.proxy;
var onCreate = function (info) {
APP.realtime = info.realtime;
metadataMgr = common.getMetadataMgr();
var configTb = {
displayed: ['useradmin', 'pageTitle', 'newpad', 'limit'],
pageTitle: Messages.type.drive,
metadataMgr: metadataMgr,
readOnly: readOnly,
realtime: info.realtime,
sfCommon: common,
$container: APP.$bar
};
var toolbar = APP.toolbar = Toolbar.create(configTb);
var $rightside = toolbar.$rightside;
$rightside.html(''); // Remove the drawer if we don't use it to hide the toolbar
APP.$displayName = APP.$bar.find('.' + Toolbar.constants.username);
/* add the usage */
if (APP.loggedIn) {
common.createUsageBar(function (err, $limitContainer) {
if (err) { return void logError(err); }
APP.$limit = $limitContainer;
}, true);
}
var $rightside = toolbar.$rightside;
$rightside.html(''); // Remove the drawer if we don't use it to hide the toolbar
APP.$displayName = APP.$bar.find('.' + Toolbar.constants.username);
/* add a history button */
APP.histConfig = {
onLocal: function () {
proxy.drive = history.currentObj.drive;
},
onRemote: function () {},
setHistory: setHistory,
applyVal: function (val) {
var obj = JSON.parse(val || '{}');
history.currentObj = obj;
history.onEnterHistory(obj);
},
$toolbar: APP.$bar,
};
/* add the usage */
if (APP.loggedIn) {
common.createUsageBar(function (err, $limitContainer) {
if (err) { return void logError(err); }
APP.$limit = $limitContainer;
}, true);
}
/* add a history button */
APP.histConfig = {
onLocal: function () {
proxy.drive = history.currentObj.drive;
},
onRemote: function () {},
setHistory: setHistory,
applyVal: function (val) {
var obj = JSON.parse(val || '{}');
history.currentObj = obj;
history.onEnterHistory(obj);
},
$toolbar: APP.$bar,
};
// Add a "Burn this drive" button
if (!APP.loggedIn) {
APP.$burnThisDrive = common.createButton(null, true).click(function () {
UI.confirm(Messages.fm_burnThisDrive, function (yes) {
if (!yes) { return; }
common.getSframeChannel().event('EV_BURN_ANON_DRIVE');
}, null, true);
}).attr('title', Messages.fm_burnThisDriveButton)
.removeClass('fa-question')
.addClass('fa-ban');
}
// Add a "Burn this drive" button
if (!APP.loggedIn) {
APP.$burnThisDrive = common.createButton(null, true).click(function () {
UI.confirm(Messages.fm_burnThisDrive, function (yes) {
if (!yes) { return; }
common.getSframeChannel().event('EV_BURN_ANON_DRIVE');
}, null, true);
}).attr('title', Messages.fm_burnThisDriveButton)
.removeClass('fa-question')
.addClass('fa-ban');
}
metadataMgr.onChange(function () {
var name = metadataMgr.getUserData().name || Messages.anonymous;
APP.$displayName.text(name);
});
$('body').css('display', '');
APP.files = proxy;
if (!proxy.drive || typeof(proxy.drive) !== 'object') {
throw new Error("Corrupted drive");
}
andThen(common, proxy);
UI.removeLoadingScreen();
metadataMgr.onChange(function () {
var name = metadataMgr.getUserData().name || Messages.anonymous;
APP.$displayName.text(name);
});
};
var firstConnection = true;
var onReady = function () {
if (!firstConnection) { return; } // TODO fix this issue in listmap
firstConnection = false;
$('body').css('display', '');
APP.files = proxy;
if (!proxy.drive || typeof(proxy.drive) !== 'object') { proxy.drive = {}; }
andThen(common, proxy);
UI.removeLoadingScreen();
};
var onDisconnect = APP.onDisconnect = function (noAlert) {
setEditable(false);
if (APP.refresh) { APP.refresh(); }
@ -3047,16 +3060,15 @@ define([
UI.findOKButton().click();
};
proxy.on('create', function (info) {
onCreate(info);
}).on('ready', function () {
onReady();
sframeChan.on('EV_DRIVE_LOG', function (msg) {
UI.log(msg);
});
proxy.on('disconnect', function () {
sframeChan.on('EV_NETWORK_DISCONNECT', function () {
onDisconnect();
});
proxy.on('reconnect', function (info) {
onReconnect(info);
sframeChan.on('EV_NETWORK_RECONNECT', function (data) {
// data.myId;
onReconnect(data);
});
common.onLogout(function () { setEditable(false); });
});

@ -48,14 +48,35 @@ define([
Utils.LocalStore.clearThumbnail();
window.location.reload();
});
sframeChan.on('Q_DRIVE_USEROBJECT', function (data, cb) {
Cryptpad.userObjectCommand(data, cb);
});
sframeChan.on('Q_DRIVE_GETOBJECT', function (data, cb) {
Cryptpad.getUserObject(function (obj) {
cb(obj);
});
});
Cryptpad.onNetworkDisconnect.reg(function () {
sframeChan.event('EV_NETWORK_DISCONNECT');
});
Cryptpad.onNetworkReconnect.reg(function (data) {
sframeChan.event('EV_NETWORK_RECONNECT', data);
});
Cryptpad.drive.onLog.reg(function (msg) {
sframeChan.event('EV_DRIVE_LOG', msg);
});
Cryptpad.drive.onChange.reg(function (data) {
sframeChan.event('EV_DRIVE_CHANGE', data);
});
Cryptpad.drive.onRemove.reg(function (data) {
sframeChan.event('EV_DRIVE_REMOVE', data);
});
};
//Netflux.connect(NetConfig.getWebsocketURL()).then(function (network) {
SFCommonO.start({
getSecrets: getSecrets,
//newNetwork: network,
noHash: true,
addRpc: addRpc
});
//}, function (err) { console.error(err); });
SFCommonO.start({
getSecrets: getSecrets,
noHash: true,
driveEvents: true,
addRpc: addRpc
});
});
});

@ -362,7 +362,7 @@ define([
return cb();
}
var path;
fo.addFolder(["root", "Folder2"], "subsub", function (e, o) { path = o.newPath; });
fo.addFolder(["root", "Folder2"], "subsub", function (o) { path = o.newPath; });
if (!files.root.Folder2.subsub || path.length !== 3) {
console.log("DRIVE operations: add folder");
return cb();

@ -5,7 +5,11 @@
@import (once) '../../customize/src/less2/include/alertify.less';
@import (once) '../../customize/src/less2/include/tokenfield.less';
.toolbar_main();
.toolbar_main(
@bg-color: @colortheme_file-bg,
@warn-color: @colortheme_file-warn,
@color: @colortheme_file-color
);
.fileupload_main();
.alertify_main();
.tokenfield_main();

@ -1,4 +1,4 @@
@import (once) '../../customize/src/less2/include/colortheme.less';
@import (once) '../../customize/src/less2/include/colortheme-all.less';
@import (once) '../../customize/src/less2/include/modal.less';
@import (once) '../../customize/src/less2/include/icon-colors.less';
@import (once) '../../customize/src/less2/include/fileupload.less';

@ -1,12 +1,10 @@
// Defaults to avoid breaking existing themes
@import (once) "../../customize/src/less2/include/framework.less";
@colortheme_pad-toolbar-bg: #c1e7ff;
@import (once) "../../customize/src/less2/include/toolbar.less";
@import (once) '../../customize/src/less2/include/alertify.less';
@import (once) '../../customize/src/less2/include/tokenfield.less';
.toolbar_main();
.framework_main(
@bg-color: @colortheme_pad-bg,
@warn-color: @colortheme_pad-warn,
@color: @colortheme_pad-color
);
.alertify_main();
// body

@ -1,9 +1,3 @@
// Defaults to avoid breaking existing themes
@colortheme_poll-th-bg: #005bef;
@colortheme_poll-th-fg: #fff;
@colortheme_poll-help-bg: #bbffbb;
@import (once) "../../customize/src/less2/include/browser.less";
@import (once) "../../customize/src/less2/include/toolbar.less";
@import (once) "../../customize/src/less2/include/markdown.less";
@ -13,7 +7,11 @@
@import (once) '../../customize/src/less2/include/tools.less';
@import (once) '../../customize/src/less2/include/avatar.less';
.toolbar_main();
.toolbar_main(
@bg-color: @colortheme_poll-bg,
@warn-color: @colortheme_poll-warn,
@color: @colortheme_poll-color
);
.fileupload_main();
.alertify_main();
.tokenfield_main();

@ -1079,7 +1079,16 @@ define([
Title = common.createTitle(titleCfg);
var configTb = {
displayed: ['title', 'useradmin', 'spinner', 'share', 'userlist', 'newpad', 'limit'],
displayed: [
'userlist',
'title',
'useradmin',
'spinner',
'newpad',
'share',
'limit',
'unpinnedWarning'
],
title: Title.getTitleConfig(),
metadataMgr: metadataMgr,
readOnly: APP.readOnly,
@ -1133,27 +1142,29 @@ define([
APP.$publishButton = $publish;
updatePublishButton();
var fileDialogCfg = {
onSelect: function (data) {
if (data.type === 'file' && APP.editor) {
var mt = '<media-tag src="' + data.src + '" data-crypto-key="cryptpad:' + data.key + '"></media-tag>';
APP.editor.replaceSelection(mt);
return;
if (common.isLoggedIn()) {
var fileDialogCfg = {
onSelect: function (data) {
if (data.type === 'file' && APP.editor) {
var mt = '<media-tag src="' + data.src + '" data-crypto-key="cryptpad:' + data.key + '"></media-tag>';
APP.editor.replaceSelection(mt);
return;
}
}
}
};
common.initFilePicker(fileDialogCfg);
APP.$mediaTagButton = $('<button>', {
title: Messages.filePickerButton,
'class': 'cp-toolbar-rightside-button fa fa-picture-o',
style: 'font-size: 17px'
}).click(function () {
var pickerCfg = {
types: ['file'],
where: ['root']
};
common.openFilePicker(pickerCfg);
}).appendTo($rightside);
common.initFilePicker(fileDialogCfg);
APP.$mediaTagButton = $('<button>', {
title: Messages.filePickerButton,
'class': 'cp-toolbar-rightside-button fa fa-picture-o',
style: 'font-size: 17px'
}).click(function () {
var pickerCfg = {
types: ['file'],
where: ['root']
};
common.openFilePicker(pickerCfg);
}).appendTo($rightside);
}
var $tags = common.createButton('hashtag', true);
$rightside.append($tags);

@ -7,8 +7,11 @@
@import (once) '../../customize/src/less2/include/avatar.less';
@import (once) '../../customize/src/less2/include/sidebar-layout.less';
.toolbar_main();
.toolbar_main(
@bg-color: @colortheme_profile-bg,
@warn-color: @colortheme_profile-warn,
@color: @colortheme_profile-color
);
.fileupload_main();
.alertify_main();
.sidebar-layout_main();

@ -93,7 +93,6 @@ define([
getSecrets: getSecrets,
noHash: true, // Don't add the hash in the URL if it doesn't already exist
addRpc: addRpc,
noRealtime: !localStorage.User_hash
});
});
});

@ -1,12 +1,16 @@
@import (once) "../../customize/src/less2/include/colortheme.less";
@import (once) "../../customize/src/less2/include/colortheme-all.less";
@import (once) "../../customize/src/less2/include/browser.less";
@import (once) "../../customize/src/less2/include/toolbar.less";
@import (once) "../../customize/src/less2/include/markdown.less";
@import (once) '../../customize/src/less2/include/alertify.less';
@import (once) '../../customize/src/less2/include/sidebar-layout.less';
@import (once) "../../customize/src/less2/include/limit-bar.less";
@import (once) "../../customize/src/less2/include/limit-bar.less";
.toolbar_main();
.toolbar_main(
@bg-color: @colortheme_settings-bg,
@warn-color: @colortheme_settings-warn,
@color: @colortheme_settings-color
);
.alertify_main();
.sidebar-layout_main();
.limit-bar_main();

@ -1,16 +1,14 @@
@import (once) "../../customize/src/less2/include/browser.less";
@import (once) "../../customize/src/less2/include/toolbar.less";
@import (once) "../../customize/src/less2/include/markdown.less";
@import (once) '../../customize/src/less2/include/fileupload.less';
@import (once) '../../customize/src/less2/include/alertify.less';
@import (once) "../../customize/src/less2/include/mediatag.less";
@import (once) '../../customize/src/less2/include/tokenfield.less';
@import (once) "../../customize/src/less2/include/framework.less";
.mediatag_base();
.toolbar_main();
.fileupload_main();
.alertify_main();
.tokenfield_main();
.framework_main(
@bg-color: @colortheme_slide-bg,
@warn-color: @colortheme_slide-warn,
@color: @colortheme_slide-color
);
// body
font-size: unset;

@ -3,13 +3,13 @@
@import (once) "../../customize/src/less2/include/markdown.less";
@import (once) '../../customize/src/less2/include/fileupload.less';
@import (once) '../../customize/src/less2/include/alertify.less';
//@import (once) '../../customize/src/less/mixins.less';
//@import (once) '../../customize/src/less/variables.less";
@import (once) '../../customize/src/less2/include/avatar.less';
.toolbar_main();
.toolbar_main(
@bg-color: @colortheme_todo-bg,
@warn-color: @colortheme_todo-warn,
@color: @colortheme_todo-color
);
.fileupload_main();
.alertify_main();

@ -40,7 +40,7 @@ define([
Cryptpad.getTodoHash(function (hash) {
var nHash = hash || Utils.Hash.createRandomHash();
if (!hash) { Cryptpad.setTodoHash(nHash); }
cb(null, Utils.Hash.getSecrets('todo', hash));
cb(null, Utils.Hash.getSecrets('todo', nHash));
});
};
SFCommonO.start({

@ -6,7 +6,11 @@
@import (once) '../../customize/src/less2/include/tools.less';
@import (once) '../../customize/src/less2/include/tokenfield.less';
.toolbar_main();
.toolbar_main(
@bg-color: @colortheme_whiteboard-bg,
@warn-color: @colortheme_whiteboard-warn,
@color: @colortheme_whiteboard-color
);
.fileupload_main();
.alertify_main();
.tokenfield_main();

@ -393,7 +393,16 @@ define([
Title = common.createTitle({});
var configTb = {
displayed: ['title', 'useradmin', 'spinner', 'share', 'userlist', 'newpad', 'limit'],
displayed: [
'userlist',
'title',
'useradmin',
'spinner',
'newpad',
'share',
'limit',
'unpinnedWarning'
],
title: Title.getTitleConfig(),
metadataMgr: metadataMgr,
readOnly: readOnly,
@ -461,36 +470,39 @@ define([
.click(function () {
$('<input>', {type:'file'}).on('change', onUpload).click();
}).appendTo($rightside);
var fileDialogCfg = {
onSelect: function (data) {
if (data.type === 'file') {
var mt = '<media-tag src="' + data.src + '" data-crypto-key="cryptpad:' + data.key + '"></media-tag>';
common.displayMediatagImage($(mt), function (err, $image) {
Util.blobURLToImage($image.attr('src'), function (imgSrc) {
var img = new Image();
img.onload = function () { addImageToCanvas(img); };
img.src = imgSrc;
if (common.isLoggedIn()) {
var fileDialogCfg = {
onSelect: function (data) {
if (data.type === 'file') {
var mt = '<media-tag src="' + data.src + '" data-crypto-key="cryptpad:' + data.key + '"></media-tag>';
common.displayMediatagImage($(mt), function (err, $image) {
Util.blobURLToImage($image.attr('src'), function (imgSrc) {
var img = new Image();
img.onload = function () { addImageToCanvas(img); };
img.src = imgSrc;
});
});
});
return;
}
}
};
common.initFilePicker(fileDialogCfg);
APP.$mediaTagButton = $('<button>', {
title: Messages.filePickerButton,
'class': 'cp-toolbar-rightside-button fa fa-picture-o',
style: 'font-size: 17px'
}).click(function () {
var pickerCfg = {
types: ['file'],
where: ['root'],
filter: {
fileType: ['image/']
return;
}
}
};
common.openFilePicker(pickerCfg);
}).appendTo($rightside);
common.initFilePicker(fileDialogCfg);
APP.$mediaTagButton = $('<button>', {
title: Messages.filePickerButton,
'class': 'cp-toolbar-rightside-button fa fa-picture-o',
style: 'font-size: 17px'
}).click(function () {
var pickerCfg = {
types: ['file'],
where: ['root'],
filter: {
fileType: ['image/']
}
};
common.openFilePicker(pickerCfg);
}).appendTo($rightside);
}
}
metadataMgr.onChange(function () {

@ -0,0 +1,37 @@
@import (once) "../../customize/src/less2/include/browser.less";
@import (once) "../../customize/src/less2/include/toolbar.less";
@import (once) "../../customize/src/less2/include/markdown.less";
@import (once) '../../customize/src/less2/include/fileupload.less';
@import (once) '../../customize/src/less2/include/alertify.less';
@import (once) '../../customize/src/less2/include/avatar.less';
.toolbar_main();
.fileupload_main();
.alertify_main();
// body
&.cp-app-worker {
display: flex;
flex-flow: column;
#cp-toolbar {
display: flex; // We need this to remove a 3px border at the bottom of the toolbar
}
.cp-cryptpad-toolbar {
padding: 0px;
display: inline-block;
}
#cp-app-worker-container {
display: flex;
flex: 1;
flex-flow: column;
padding: 20px;
align-items: center;
background-color: lighten(@colortheme_todo-bg, 15%);
min-height: 0;
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save