mirror of https://github.com/xwiki-labs/cryptpad
resolve merge conflicts
This commit is contained in:
commit
351b95d6a9
|
@ -11,6 +11,7 @@ www/common/hyperscript.js
|
|||
www/common/tippy.min.js
|
||||
|
||||
www/pad/wysiwygarea-plugin.js
|
||||
www/pad2/wysiwygarea-plugin.js
|
||||
www/pad/mediatag-plugin.js
|
||||
www/pad/mediatag-plugin-dialog.js
|
||||
|
||||
www/common/media-tag-nacl.min.js
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
"jquery": "~2.1.3",
|
||||
"tweetnacl": "0.12.2",
|
||||
"components-font-awesome": "^4.6.3",
|
||||
"ckeditor": "~4.7",
|
||||
"ckeditor": "4.7.3",
|
||||
"codemirror": "^5.19.0",
|
||||
"requirejs": "2.3.5",
|
||||
"marked": "0.3.5",
|
||||
|
|
|
@ -10,7 +10,7 @@ CKEDITOR.editorConfig = function( config ) {
|
|||
// document itself and causes problems when it's sent across the wire and reflected back
|
||||
config.removePlugins= 'resize,elementspath';
|
||||
config.resize_enabled= false; //bottom-bar
|
||||
config.extraPlugins= 'autolink,colorbutton,colordialog,font,indentblock,justify';
|
||||
config.extraPlugins= 'autolink,colorbutton,colordialog,font,indentblock,justify,mediatag';
|
||||
config.toolbarGroups= [
|
||||
// {"name":"clipboard","groups":["clipboard","undo"]},
|
||||
//{"name":"editing","groups":["find","selection"]},
|
||||
|
|
|
@ -15,6 +15,7 @@ var map = {
|
|||
var getStoredLanguage = function () { return localStorage.getItem(LS_LANG); };
|
||||
var getBrowserLanguage = function () { return navigator.language || navigator.userLanguage; };
|
||||
var getLanguage = function () {
|
||||
if (window.cryptpadLanguage) { return window.cryptpadLanguage; }
|
||||
if (getStoredLanguage()) { return getStoredLanguage(); }
|
||||
var l = getBrowserLanguage() || '';
|
||||
if (Object.keys(map).indexOf(l) !== -1) {
|
||||
|
|
|
@ -570,69 +570,69 @@ define([
|
|||
};
|
||||
|
||||
var appToolbar = function () {
|
||||
return h('div#toolbar.toolbar-container');
|
||||
return h('div#toolbar.cryptpad-toolbar');
|
||||
};
|
||||
var appToolbar3 = function () {
|
||||
return h('div#cp-toolbar.cp-toolbar-container');
|
||||
};
|
||||
|
||||
Pages['/whiteboard/'] = Pages['/whiteboard/index.html'] = function () {
|
||||
return [
|
||||
appToolbar(),
|
||||
h('div#canvas-area', h('canvas#canvas', {
|
||||
appToolbar3(),
|
||||
h('div#cp-app-whiteboard-canvas-area', h('canvas#cp-app-whiteboard-canvas', {
|
||||
width: 600,
|
||||
height: 600
|
||||
})),
|
||||
h('div#controls', {
|
||||
h('div#cp-app-whiteboard-controls', {
|
||||
style: {
|
||||
display: 'block',
|
||||
}
|
||||
}, [
|
||||
h('button#clear.btn.btn-danger', Msg.canvas_clear), ' ',
|
||||
h('button#toggleDraw.btn.btn-secondary', Msg.canvas_disable),
|
||||
h('button#delete.btn.btn-secondary', {
|
||||
h('button#cp-app-whiteboard-clear.btn.btn-danger', Msg.canvas_clear), ' ',
|
||||
h('button#cp-app-whiteboard-toggledraw.btn.btn-secondary', Msg.canvas_disable),
|
||||
h('button#cp-app-whiteboard-delete.btn.btn-secondary', {
|
||||
style: {
|
||||
display: 'none',
|
||||
}
|
||||
}, Msg.canvas_delete),
|
||||
h('div.range-group', [
|
||||
h('div.cp-app-whiteboard-range-group', [
|
||||
h('label', {
|
||||
'for': 'width'
|
||||
'for': 'cp-app-whiteboard-width'
|
||||
}, Msg.canvas_width),
|
||||
h('input#width', {
|
||||
h('input#cp-app-whiteboard-width', {
|
||||
type: 'range',
|
||||
value: "5",
|
||||
min: "1",
|
||||
max: "100"
|
||||
}),
|
||||
h('span#width-val', '5px')
|
||||
h('span#cp-app-whiteboard-width-val', '5px')
|
||||
]),
|
||||
h('div.range-group', [
|
||||
h('div.cp-app-whiteboard-range-group', [
|
||||
h('label', {
|
||||
'for': 'opacity',
|
||||
'for': 'cp-app-whiteboard-opacity',
|
||||
}, Msg.canvas_opacity),
|
||||
h('input#opacity', {
|
||||
h('input#cp-app-whiteboard-opacity', {
|
||||
type: 'range',
|
||||
value: "1",
|
||||
min: "0.1",
|
||||
max: "1",
|
||||
step: "0.1"
|
||||
}),
|
||||
h('span#opacity-val', '100%')
|
||||
h('span#cp-app-whiteboard-opacity-val', '100%')
|
||||
]),
|
||||
h('span.selected', [
|
||||
h('span.cp-app-whiteboard-selected.cp-app-whiteboard-unselectable', [
|
||||
h('img', {
|
||||
title: Msg.canvas_currentBrush
|
||||
})
|
||||
])
|
||||
]),
|
||||
setHTML(h('div#colors'), ' '),
|
||||
loadingScreen(),
|
||||
h('div#cursors', {
|
||||
setHTML(h('div#cp-app-whiteboard-colors'), ' '),
|
||||
h('div#cp-app-whiteboard-cursors', {
|
||||
style: {
|
||||
display: 'none',
|
||||
background: 'white',
|
||||
'text-align': 'center',
|
||||
}
|
||||
}),
|
||||
h('div#pickers'),
|
||||
h('div#cp-app-whiteboard-pickers'),
|
||||
];
|
||||
};
|
||||
|
||||
|
@ -683,8 +683,7 @@ define([
|
|||
])
|
||||
])
|
||||
])
|
||||
]),
|
||||
loadingScreen()
|
||||
])
|
||||
];
|
||||
};
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@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 {
|
||||
|
@ -39,26 +40,6 @@
|
|||
background: linear-gradient(@start, @end); /* Standard syntax */
|
||||
}
|
||||
|
||||
.placeholderColor (@color) {
|
||||
&::-webkit-input-placeholder { /* WebKit, Blink, Edge */
|
||||
color: @color;;
|
||||
}
|
||||
&:-moz-placeholder { /* Mozilla Firefox 4 to 18 */
|
||||
color: @color;
|
||||
opacity: 1;
|
||||
}
|
||||
&::-moz-placeholder { /* Mozilla Firefox 19+ */
|
||||
color: @color;
|
||||
opacity: 1;
|
||||
}
|
||||
&:-ms-input-placeholder { /* Internet Explorer 10-11 */
|
||||
color: @color;
|
||||
}
|
||||
&::-ms-input-placeholder { /* Microsoft Edge */
|
||||
color: @color;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar (@width) {
|
||||
&.avatar {
|
||||
overflow: hidden;
|
||||
|
@ -83,7 +64,7 @@
|
|||
box-sizing: content-box;
|
||||
}
|
||||
.default {
|
||||
.unselectable();
|
||||
.tools_unselectable();
|
||||
background: white;
|
||||
color: black;
|
||||
font-size: @width/1.2;
|
||||
|
@ -122,7 +103,7 @@
|
|||
}
|
||||
|
||||
.leftsideCategory {
|
||||
.unselectable();
|
||||
.tools_unselectable();
|
||||
padding: 5px 20px;
|
||||
margin: 15px 0;
|
||||
cursor: pointer;
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
@import '/customize/src/less/variables.less';
|
||||
@import '/customize/src/less/mixins.less';
|
||||
@import (once) "/customize/src/less2/include/colortheme.less";
|
||||
|
||||
@leftside-bg: #eee;
|
||||
@leftside-color: #000;
|
||||
@rightside-color: #000;
|
||||
@description-color: #777;
|
||||
@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;
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
@import (once) "./tools.less";
|
||||
|
||||
.avatar_main (@width) {
|
||||
&.cp-avatar {
|
||||
overflow: hidden;
|
||||
|
@ -16,7 +18,7 @@
|
|||
box-sizing: content-box;
|
||||
}
|
||||
.cp-avatar-default {
|
||||
.unselectable();
|
||||
.tools_unselectable();
|
||||
background: white;
|
||||
color: black;
|
||||
font-size: @width/1.2;
|
||||
|
|
|
@ -75,6 +75,16 @@
|
|||
@colortheme_todo-bg: #7bccd1;
|
||||
@colortheme_todo-color: #000;
|
||||
|
||||
// Sidebar layout
|
||||
@colortheme_sidebar-active: #fff;
|
||||
@colortheme_sidebar-left-bg: #eee;
|
||||
@colortheme_sidebar-left-fg: #000;
|
||||
@colortheme_sidebar-left-branch: #888;
|
||||
@colortheme_sidebar-right-bg: #fff;
|
||||
@colortheme_sidebar-right-fg: #000;
|
||||
@colortheme_sidebar-description: #777;
|
||||
|
||||
|
||||
@cryptpad_color_blue: #4591C4;
|
||||
@cryptpad_color_grey: #999999;
|
||||
@cryptpad_header_col: #1E1F1F;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import (once) "./colortheme.less";
|
||||
@import (once) "./tools.less";
|
||||
|
||||
/* The container <div> - needed to position the dropdown content */
|
||||
.dropdown_main () {
|
||||
|
@ -17,7 +18,7 @@
|
|||
margin-left: 5px;
|
||||
}
|
||||
* {
|
||||
.unselectable();
|
||||
.tools_unselectable();
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,11 @@
|
|||
td {
|
||||
padding: @upload_pad_h @upload_pad_v;
|
||||
}
|
||||
.cp-fileupload-table-link {
|
||||
.fa {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
.cp-fileupload-table-progress {
|
||||
width: 200px;
|
||||
position: relative;
|
||||
|
|
|
@ -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,23 @@
|
|||
@import (once) "./unselectable.less";
|
||||
@import (once) "./variables.less";
|
||||
@import (once) "./colortheme.less";
|
||||
|
||||
.leftside-menu_main() {
|
||||
}
|
||||
.leftside-menu-category_main() {
|
||||
.unselectable_make();
|
||||
padding: 5px 20px;
|
||||
margin: 15px 0;
|
||||
cursor: pointer;
|
||||
height: @variables_bar-height;
|
||||
line-height: @variables_bar-height - 10px;
|
||||
.fa {
|
||||
width: 25px;
|
||||
}
|
||||
&:hover {
|
||||
background: rgba(0,0,0,0.05);
|
||||
}
|
||||
&.cp-leftside-active {
|
||||
background: @colortheme_sidebar-active;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
@import (once) "./colortheme.less";
|
||||
|
||||
.limit-bar_main () {
|
||||
.cp-limit-container {
|
||||
@colortheme_green: #5cb85c;
|
||||
display: inline-flex;
|
||||
flex-flow: column-reverse;
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
.cp-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;
|
||||
.cp-limit-usage {
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
background: blue;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
z-index:1; // .usage
|
||||
&.cp-limit-usage-normal {
|
||||
background: @colortheme_green;
|
||||
}
|
||||
&.cp-limit-usage-warning {
|
||||
background: orange;
|
||||
}
|
||||
&.cp-limit-usage-above {
|
||||
background: red;
|
||||
}
|
||||
}
|
||||
.cp-limit-usage-text {
|
||||
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: @colortheme_app-font-size;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
.cp-limit-upgrade {
|
||||
padding: 0;
|
||||
line-height: 25px;
|
||||
height: 25px;
|
||||
margin: 0 3px;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,23 +1,17 @@
|
|||
@import (once) "./tools.less";
|
||||
|
||||
.tokenfield_main () {
|
||||
.tokenfield {
|
||||
.unselectable () {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.unselectable();
|
||||
.tools_unselectable();
|
||||
height: auto;
|
||||
min-height: 34px;
|
||||
padding-bottom: 0px;
|
||||
&.focus {
|
||||
border-color: #66afe9;
|
||||
outline: 0;
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6);
|
||||
}
|
||||
background-color: unset;
|
||||
border: none;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
.token {
|
||||
box-sizing: border-box;
|
||||
border-radius: 3px;
|
||||
|
@ -25,8 +19,9 @@
|
|||
border: 1px solid #d9d9d9;
|
||||
background-color: #ededed;
|
||||
white-space: nowrap;
|
||||
margin: -1px 5px 5px 0;
|
||||
vertical-align: center;
|
||||
margin: 10px 5px;
|
||||
height: 24px;
|
||||
vertical-align: middle;
|
||||
cursor: default;
|
||||
|
||||
color: #222;
|
||||
|
@ -50,17 +45,17 @@
|
|||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding-left: 4px;
|
||||
vertical-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.close {
|
||||
font-family: Arial;
|
||||
display: inline-block;
|
||||
line-height: 100%;
|
||||
line-height: 24px;
|
||||
font-size: 1.1em;
|
||||
margin-left: 5px;
|
||||
float: none;
|
||||
height: 100%;
|
||||
vertical-align: center;
|
||||
vertical-align: middle;
|
||||
padding-right: 4px;
|
||||
}
|
||||
&.active {
|
||||
|
@ -73,11 +68,10 @@
|
|||
}
|
||||
.token-input {
|
||||
background: none;
|
||||
width: 0%; //60px;
|
||||
min-width: 60px;
|
||||
flex: 1;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin-bottom: 6px;
|
||||
margin: 0 !important; // Override alertify
|
||||
box-shadow: none;
|
||||
max-width: 100%;
|
||||
&:focus {
|
||||
|
@ -86,9 +80,5 @@
|
|||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
@import (once) "./colortheme.less";
|
||||
|
||||
.history_main () {
|
||||
body .cp-toolbar-history {
|
||||
.cp-toolbar-history {
|
||||
display: none;
|
||||
text-align: center;
|
||||
* {
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
@import (once) "./avatar.less";
|
||||
@import (once) "./toolbar-history.less";
|
||||
@import (once) "./icon-colors.less";
|
||||
@import (once) "./tools.less";
|
||||
|
||||
|
||||
.toolbar_main () {
|
||||
|
@ -13,15 +14,6 @@
|
|||
@toolbar_top-height: 64px;
|
||||
@toolbar_button-font: @colortheme_app-font;
|
||||
|
||||
.unselectable () {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.dropdown_main();
|
||||
.ckeditor_fix();
|
||||
.history_main();
|
||||
|
@ -270,7 +262,7 @@
|
|||
font-family: FontAwesome;
|
||||
}
|
||||
|
||||
.unselectable();
|
||||
.tools_unselectable();
|
||||
|
||||
font: @toolbar_button-font;
|
||||
width: 100%;
|
||||
|
@ -289,7 +281,7 @@
|
|||
|
||||
button {
|
||||
transition: all 0.15s;
|
||||
.unselectable();
|
||||
.tools_unselectable();
|
||||
&.cp-toolbar-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
@ -658,6 +650,14 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
p.cp-toolbar-account {
|
||||
&> span {
|
||||
font-weight: bold;
|
||||
span {
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-toolbar-backup {
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
.tools_placeholder-color (@color) {
|
||||
&::-webkit-input-placeholder { /* WebKit, Blink, Edge */
|
||||
color: @color;;
|
||||
}
|
||||
&:-moz-placeholder { /* Mozilla Firefox 4 to 18 */
|
||||
color: @color;
|
||||
opacity: 1;
|
||||
}
|
||||
&::-moz-placeholder { /* Mozilla Firefox 19+ */
|
||||
color: @color;
|
||||
opacity: 1;
|
||||
}
|
||||
&:-ms-input-placeholder { /* Internet Explorer 10-11 */
|
||||
color: @color;
|
||||
}
|
||||
&::-ms-input-placeholder { /* Microsoft Edge */
|
||||
color: @color;
|
||||
}
|
||||
}
|
||||
|
||||
.tools_unselectable () {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
.unselectable_make() {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
.unselectable_main() {
|
||||
.cp-unselectable {
|
||||
.unselectable_make();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
// Elements size
|
||||
@variables_bar-height: 32px;
|
||||
|
|
@ -40,7 +40,7 @@ The CSS inside of loading.js is precompiled in order to save 200ish milliseconds
|
|||
}
|
||||
#cp-loading-tip {
|
||||
position: fixed;
|
||||
z-index: 100000; // loading tip
|
||||
z-index: 10000000; // loading tip
|
||||
top: 80%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
|
|
@ -22,10 +22,13 @@ html.cp-app-print {
|
|||
.app-print_main();
|
||||
}
|
||||
|
||||
body.cp-app-drive { @import "../../../drive/app-drive.less"; }
|
||||
body.cp-app-pad { @import "../../../pad/app-pad.less"; }
|
||||
body.cp-app-code { @import "../../../code/app-code.less"; }
|
||||
body.cp-app-slide { @import "../../../slide/app-slide.less"; }
|
||||
body.cp-app-file { @import "../../../file/app-file.less"; }
|
||||
body.cp-app-filepicker { @import "../../../filepicker/app-filepicker.less"; }
|
||||
body.cp-app-contacts { @import "../../../contacts/app-contacts.less"; }
|
||||
//body.cp-app-poll { @import "../../../poll/app-poll.less"; }
|
||||
body.cp-app-whiteboard { @import "../../../whiteboard/app-whiteboard.less"; }
|
||||
|
||||
|
|
|
@ -157,6 +157,10 @@ define(function () {
|
|||
out.filePicker_filter = "Filtrez les fichiers par leur nom";
|
||||
out.or = 'ou';
|
||||
|
||||
out.tags_title = "Mots-clés du pad";
|
||||
out.tags_add = "Modifier les mots-clés du pad";
|
||||
out.tags_duplicate = "Mot-clé déjà présent : {0}";
|
||||
|
||||
out.slideOptionsText = "Options";
|
||||
out.slideOptionsTitle = "Personnaliser la présentation";
|
||||
out.slideOptionsButton = "Enregistrer (Entrée)";
|
||||
|
@ -204,8 +208,11 @@ define(function () {
|
|||
out.history_restoreDone = "Document restauré";
|
||||
out.history_version = "Version :";
|
||||
|
||||
// Ckeditor links
|
||||
// Ckeditor
|
||||
out.openLinkInNewTab = "Ouvrir le lien dans un nouvel onglet";
|
||||
out.pad_mediatagTitle = "Options du Media-Tag";
|
||||
out.pad_mediatagWidth = "Largeur (px)";
|
||||
out.pad_mediatagHeight = "Hauteur (px)";
|
||||
|
||||
// Polls
|
||||
|
||||
|
@ -363,6 +370,8 @@ define(function () {
|
|||
out.fm_error_cantPin = "Erreur interne du serveur. Veuillez recharger la page et essayer de nouveau.";
|
||||
out.fm_viewListButton = "Liste";
|
||||
out.fm_viewGridButton = "Grille";
|
||||
out.fm_renamedPad = "Vous avez renommé ce pad dans votre Drive. Son titre est:<br><b>{0}</b>";
|
||||
out.fm_prop_tagsList = "Mots-clés";
|
||||
// File - Context menu
|
||||
out.fc_newfolder = "Nouveau dossier";
|
||||
out.fc_rename = "Renommer";
|
||||
|
|
|
@ -159,6 +159,10 @@ define(function () {
|
|||
out.filePicker_filter = "Filter files by name";
|
||||
out.or = 'or';
|
||||
|
||||
out.tags_title = "Tags";
|
||||
out.tags_add = "Update this pad's tags";
|
||||
out.tags_duplicate = "Duplicate tag: {0}";
|
||||
|
||||
out.slideOptionsText = "Options";
|
||||
out.slideOptionsTitle = "Customize your slides";
|
||||
out.slideOptionsButton = "Save (enter)";
|
||||
|
@ -206,8 +210,11 @@ define(function () {
|
|||
out.history_restoreDone = "Document restored";
|
||||
out.history_version = "Version:";
|
||||
|
||||
// Ckeditor links
|
||||
// Ckeditor
|
||||
out.openLinkInNewTab = "Open Link in New Tab";
|
||||
out.pad_mediatagTitle = "Media-Tag settings";
|
||||
out.pad_mediatagWidth = "Width (px)";
|
||||
out.pad_mediatagHeight = "Height (px)";
|
||||
|
||||
// Polls
|
||||
|
||||
|
@ -364,6 +371,8 @@ define(function () {
|
|||
out.fm_error_cantPin = "Internal server error. Please reload the page and try again.";
|
||||
out.fm_viewListButton = "List view";
|
||||
out.fm_viewGridButton = "Grid view";
|
||||
out.fm_renamedPad = "You've set a custom name for this pad. Its shared title is:<br><b>{0}</b>";
|
||||
out.fm_prop_tagsList = "Tags";
|
||||
// File - Context menu
|
||||
out.fc_newfolder = "New folder";
|
||||
out.fc_rename = "Rename";
|
||||
|
|
|
@ -3,10 +3,12 @@
|
|||
@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';
|
||||
|
||||
.toolbar_main();
|
||||
.fileupload_main();
|
||||
.alertify_main();
|
||||
.tokenfield_main();
|
||||
|
||||
// body
|
||||
&.cp-app-code {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<html class="cp-app-noscroll">
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<script async data-bootload="/code/inner.js" data-main="/common/sframe-boot.js?ver=1.4" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<script async data-bootload="/code/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<style>
|
||||
.loading-hidden { display: none; }
|
||||
#editor1 { display: none; }
|
||||
|
|
|
@ -230,7 +230,7 @@ define([
|
|||
if (data !== false) {
|
||||
$previewContainer.show();
|
||||
APP.$previewButton.addClass('active');
|
||||
$codeMirror.removeClass('fullPage');
|
||||
$codeMirror.removeClass('cp-app-code-fullpage');
|
||||
}
|
||||
});
|
||||
return;
|
||||
|
@ -238,7 +238,7 @@ define([
|
|||
APP.$previewButton.hide();
|
||||
$previewContainer.hide();
|
||||
APP.$previewButton.removeClass('active');
|
||||
$codeMirror.addClass('fullPage');
|
||||
$codeMirror.addClass('cp-app-code-fullpage');
|
||||
};
|
||||
|
||||
config.onInit = function (info) {
|
||||
|
@ -374,11 +374,13 @@ define([
|
|||
};
|
||||
common.openFilePicker(pickerCfg);
|
||||
}).appendTo($rightside);
|
||||
|
||||
var $tags = common.createButton('hashtag', true);
|
||||
$rightside.append($tags);
|
||||
}
|
||||
};
|
||||
|
||||
config.onReady = function (info) {
|
||||
console.log('onready');
|
||||
if (APP.realtime !== info.realtime) {
|
||||
var realtime = APP.realtime = info.realtime;
|
||||
APP.patchText = TextPatcher.create({
|
||||
|
@ -400,7 +402,8 @@ define([
|
|||
metadataMgr.updateMetadata(hjson.metadata);
|
||||
}
|
||||
if (typeof (hjson) !== 'object' || Array.isArray(hjson) ||
|
||||
(typeof(hjson.type) !== 'undefined' && hjson.type !== 'code')) {
|
||||
(hjson.metadata && typeof(hjson.metadata.type) !== 'undefined' &&
|
||||
hjson.metadata.type !== 'code')) {
|
||||
var errorText = Messages.typeError;
|
||||
Cryptpad.errorLoadingScreen(errorText);
|
||||
throw new Error(errorText);
|
||||
|
@ -437,35 +440,35 @@ define([
|
|||
});
|
||||
|
||||
|
||||
/*
|
||||
|
||||
// add the splitter
|
||||
if (!$iframe.has('.cp-splitter').length) {
|
||||
var $preview = $iframe.find('#previewContainer');
|
||||
if (!$('.cp-splitter').length) {
|
||||
var splitter = $('<div>', {
|
||||
'class': 'cp-splitter'
|
||||
}).appendTo($preview);
|
||||
}).appendTo($previewContainer);
|
||||
|
||||
$preview.on('scroll', function() {
|
||||
splitter.css('top', $preview.scrollTop() + 'px');
|
||||
});
|
||||
|
||||
var $target = $iframe.find('.CodeMirror');
|
||||
var $target = $('.CodeMirror');
|
||||
|
||||
splitter.on('mousedown', function (e) {
|
||||
e.preventDefault();
|
||||
var x = e.pageX;
|
||||
var w = $target.width();
|
||||
|
||||
$iframe.on('mouseup mousemove', function handler(evt) {
|
||||
$(window).on('mouseup mousemove', function handler(evt) {
|
||||
if (evt.type === 'mouseup') {
|
||||
$iframe.off('mouseup mousemove', handler);
|
||||
$(window).off('mouseup mousemove', handler);
|
||||
return;
|
||||
}
|
||||
$target.css('width', (w - x + evt.pageX) + 'px');
|
||||
editor.refresh();
|
||||
});
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
Cryptpad.removeLoadingScreen();
|
||||
setEditable(!readOnly);
|
||||
|
@ -533,7 +536,7 @@ define([
|
|||
APP.patchText(shjson2);
|
||||
}
|
||||
}
|
||||
if (oldDoc !== remoteDoc) { Cryptpad.notify(); }
|
||||
if (oldDoc !== remoteDoc) { common.notify(); }
|
||||
};
|
||||
|
||||
config.onAbort = function () {
|
||||
|
@ -604,7 +607,7 @@ define([
|
|||
SFCommon.create(waitFor(function (c) { APP.common = common = c; }));
|
||||
}).nThen(function (/*waitFor*/) {
|
||||
CodeMirror = common.initCodeMirrorApp(null, CM);
|
||||
$('.CodeMirror').addClass('fullPage');
|
||||
$('.CodeMirror').addClass('cp-app-code-fullpage');
|
||||
editor = CodeMirror.editor;
|
||||
Cryptpad.onError(function (info) {
|
||||
if (info && info.type === "store") {
|
||||
|
|
|
@ -9,18 +9,20 @@ define([
|
|||
var module = { exports: {} };
|
||||
var key = Config.requireConf.urlArgs;
|
||||
var localStorage = {};
|
||||
try {
|
||||
localStorage = window.localStorage || {};
|
||||
if (localStorage['LESS_CACHE'] !== key) {
|
||||
Object.keys(localStorage).forEach(function (k) {
|
||||
if (k.indexOf('LESS_CACHE|') !== 0) { return; }
|
||||
delete localStorage[k];
|
||||
});
|
||||
localStorage['LESS_CACHE'] = key;
|
||||
if (!window.cryptpadCache) {
|
||||
try {
|
||||
localStorage = window.localStorage || {};
|
||||
if (localStorage['LESS_CACHE'] !== key) {
|
||||
Object.keys(localStorage).forEach(function (k) {
|
||||
if (k.indexOf('LESS_CACHE|') !== 0) { return; }
|
||||
delete localStorage[k];
|
||||
});
|
||||
localStorage['LESS_CACHE'] = key;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
localStorage = {};
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
localStorage = {};
|
||||
}
|
||||
|
||||
var cacheGet = function (k, cb) {
|
||||
|
@ -118,7 +120,7 @@ define([
|
|||
}
|
||||
console.log('CACHE MISS ' + url);
|
||||
((/\.less([\?\#].*)?$/.test(url)) ? loadLess : loadCSS)(url, function (err, css) {
|
||||
if (err) { console.log(err); }
|
||||
if (err) { console.error(err); }
|
||||
var output = fixAllURLs(css, url);
|
||||
cachePut(url, output);
|
||||
inject(output, url);
|
||||
|
|
|
@ -131,13 +131,19 @@ define([
|
|||
var $t = t.tokenfield = $(t.element).tokenfield();
|
||||
t.getTokens = function () {
|
||||
return $t.tokenfield('getTokens').map(function (token) {
|
||||
return token.value;
|
||||
return token.value.toLowerCase();
|
||||
});
|
||||
};
|
||||
|
||||
var $root = $t.parent();
|
||||
$t.on('tokenfield:removetoken', function () {
|
||||
$root.find('.token-input').focus();
|
||||
});
|
||||
|
||||
t.preventDuplicates = function (cb) {
|
||||
$t.on('tokenfield:createtoken', function (ev) {
|
||||
var val;
|
||||
ev.attrs.value = ev.attrs.value.toLowerCase();
|
||||
if (t.getTokens().some(function (t) {
|
||||
if (t === ev.attrs.value) { return ((val = t)); }
|
||||
})) {
|
||||
|
@ -152,8 +158,8 @@ define([
|
|||
$t.tokenfield('setTokens',
|
||||
tokens.map(function (token) {
|
||||
return {
|
||||
value: token,
|
||||
label: token,
|
||||
value: token.toLowerCase(),
|
||||
label: token.toLowerCase(),
|
||||
};
|
||||
}));
|
||||
};
|
||||
|
@ -171,35 +177,38 @@ define([
|
|||
var input = dialog.textInput();
|
||||
|
||||
var tagger = dialog.frame([
|
||||
dialog.message('make some tags'), // TODO translate
|
||||
dialog.message(Messages.tags_add),
|
||||
input,
|
||||
dialog.nav(),
|
||||
]);
|
||||
|
||||
var field = UI.tokenField(input).preventDuplicates(function (val) {
|
||||
UI.warn('Duplicate tag: ' + val); // TODO translate
|
||||
UI.warn(Messages._getKey('tags_duplicate', [val]));
|
||||
});
|
||||
|
||||
var close = Util.once(function () {
|
||||
var $t = $(tagger).fadeOut(150, function () { $t.remove(); });
|
||||
var listener;
|
||||
var close = Util.once(function (result, ev) {
|
||||
var $frame = $(tagger).fadeOut(150, function () {
|
||||
stopListening(listener);
|
||||
$frame.remove();
|
||||
cb(result, ev);
|
||||
});
|
||||
});
|
||||
|
||||
var listener = listenForKeys(function () {}, function () {
|
||||
close();
|
||||
stopListening(listener);
|
||||
});
|
||||
|
||||
var CB = Util.once(cb);
|
||||
findOKButton(tagger).click(function () {
|
||||
var $ok = findOKButton(tagger).click(function () {
|
||||
var tokens = field.getTokens();
|
||||
close();
|
||||
CB(tokens);
|
||||
close(tokens);
|
||||
});
|
||||
findCancelButton(tagger).click(function () {
|
||||
close();
|
||||
CB(null);
|
||||
var $cancel = findCancelButton(tagger).click(function () {
|
||||
close(null);
|
||||
});
|
||||
listenForKeys(function () {
|
||||
$ok.click();
|
||||
}, function () {
|
||||
$cancel.click();
|
||||
});
|
||||
|
||||
document.body.appendChild(tagger);
|
||||
// :(
|
||||
setTimeout(function () {
|
||||
field.setTokens(tags);
|
||||
|
@ -244,7 +253,7 @@ define([
|
|||
stopListening(listener);
|
||||
cb();
|
||||
});
|
||||
listener = listenForKeys(close, close);
|
||||
listener = listenForKeys(close, close, ok);
|
||||
var $ok = $(ok).click(close);
|
||||
|
||||
document.body.appendChild(frame);
|
||||
|
@ -293,7 +302,7 @@ define([
|
|||
$ok.click();
|
||||
}, function () { // no
|
||||
$cancel.click();
|
||||
});
|
||||
}, input);
|
||||
|
||||
document.body.appendChild(frame);
|
||||
setTimeout(function () {
|
||||
|
@ -341,7 +350,7 @@ define([
|
|||
$ok.click();
|
||||
}, function () {
|
||||
$cancel.click();
|
||||
});
|
||||
}, ok);
|
||||
|
||||
document.body.appendChild(frame);
|
||||
setTimeout(function () {
|
||||
|
|
|
@ -16,9 +16,10 @@ define([], function () {
|
|||
handlers.splice(handlers.indexOf(cb), 1);
|
||||
},
|
||||
fire: function () {
|
||||
if (fired) { return; }
|
||||
if (once && fired) { return; }
|
||||
fired = true;
|
||||
handlers.forEach(function (h) { h(); });
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
handlers.forEach(function (h) { h.apply(null, args); });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -184,6 +184,9 @@ define([
|
|||
}
|
||||
return;
|
||||
};
|
||||
common.getLanguage = function () {
|
||||
return Messages._languageUsed;
|
||||
};
|
||||
common.getUserlist = function () {
|
||||
if (store) {
|
||||
if (store.getProxy() && store.getProxy().info) {
|
||||
|
@ -533,6 +536,17 @@ define([
|
|||
cb(void 0, entry);
|
||||
};
|
||||
|
||||
common.resetTags = function (href, tags, cb) {
|
||||
cb = cb || $.noop;
|
||||
if (!Array.isArray(tags)) { return void cb('INVALID_TAGS'); }
|
||||
getFileEntry(href, function (e, entry) {
|
||||
if (e) { return void cb(e); }
|
||||
if (!entry) { cb('NO_ENTRY'); }
|
||||
entry.tags = tags.slice();
|
||||
cb();
|
||||
});
|
||||
};
|
||||
|
||||
common.tagPad = function (href, tag, cb) {
|
||||
if (typeof(cb) !== 'function') {
|
||||
return void console.error('EXPECTED_CALLBACK');
|
||||
|
@ -2222,6 +2236,46 @@ define([
|
|||
}
|
||||
$iframe.load(w2); //cb);
|
||||
}
|
||||
}).nThen(function (waitFor) {
|
||||
if (sessionStorage.createReadme) {
|
||||
var w = waitFor();
|
||||
require(['/common/cryptget.js'], function (Crypt) {
|
||||
var hash = common.createRandomHash();
|
||||
Crypt.put(hash, Messages.driveReadme, function (e) {
|
||||
if (e) {
|
||||
console.error("Error while creating the default pad:", e);
|
||||
return void w();
|
||||
}
|
||||
var href = '/pad/#' + hash;
|
||||
var data = {
|
||||
href: href,
|
||||
title: Messages.driveReadmeTitle,
|
||||
atime: new Date().toISOString(),
|
||||
ctime: new Date().toISOString()
|
||||
};
|
||||
common.getFO().pushData(data, function (e, id) {
|
||||
if (e) {
|
||||
console.error("Error while creating the default pad:", e);
|
||||
return void w();
|
||||
}
|
||||
common.getFO().add(id);
|
||||
w();
|
||||
});
|
||||
});
|
||||
delete sessionStorage.createReadme;
|
||||
});
|
||||
}
|
||||
}).nThen(function (waitFor) {
|
||||
if (sessionStorage.migrateAnonDrive) {
|
||||
var w = waitFor();
|
||||
require(['/common/mergeDrive.js'], function (Merge) {
|
||||
var hash = localStorage.FS_hash;
|
||||
Merge.anonDriveIntoUser(getStore().getProxy(), hash, function () {
|
||||
delete sessionStorage.migrateAnonDrive;
|
||||
w();
|
||||
});
|
||||
});
|
||||
}
|
||||
}).nThen(function () {
|
||||
updateLocalVersion();
|
||||
common.addTooltips();
|
||||
|
|
|
@ -4,7 +4,7 @@ define([
|
|||
'/bower_components/chainpad-crypto/crypto.js?v=0.1.5',
|
||||
'/bower_components/textpatcher/TextPatcher.amd.js',
|
||||
'/common/userObject.js',
|
||||
'/common/migrate-user-object.js'
|
||||
'/common/migrate-user-object.js',
|
||||
], function ($, Listmap, Crypto, TextPatcher, FO, Migrate) {
|
||||
/*
|
||||
This module uses localStorage, which is synchronous, but exposes an
|
||||
|
@ -196,11 +196,11 @@ define([
|
|||
|
||||
Migrate(proxy, Cryptpad);
|
||||
|
||||
//storeObj = proxy;
|
||||
store = initStore(fo, proxy, exp);
|
||||
if (typeof(f) === 'function') {
|
||||
f(void 0, store);
|
||||
}
|
||||
//storeObj = proxy;
|
||||
|
||||
var requestLogin = function () {
|
||||
// log out so that you don't go into an endless loop...
|
||||
|
|
|
@ -99,7 +99,7 @@ function context () {
|
|||
} else if (k.substr(0, 5) === "data-") {
|
||||
e.setAttribute(k, l[k])
|
||||
} else {
|
||||
e[k] = l[k]
|
||||
e.setAttribute(k, l[k]);
|
||||
}
|
||||
}
|
||||
} else if ('function' === typeof l) {
|
||||
|
|
|
@ -36,7 +36,7 @@ define([], function () {
|
|||
}
|
||||
#cp-loading-tip {
|
||||
position: fixed;
|
||||
z-index: 100000;
|
||||
z-index: 10000000;
|
||||
top: 80%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
|
|
@ -83,9 +83,9 @@ define([
|
|||
});
|
||||
};
|
||||
|
||||
exp.anonDriveIntoUser = function (proxy, cb) {
|
||||
exp.anonDriveIntoUser = function (proxyData, fsHash, cb) {
|
||||
// Make sure we have an FS_hash and we don't use it, otherwise just stop the migration and cb
|
||||
if (!localStorage.FS_hash || !Cryptpad.isLoggedIn()) {
|
||||
if (!fsHash || !Cryptpad.isLoggedIn()) {
|
||||
if (typeof(cb) === "function") { return void cb(); }
|
||||
}
|
||||
// Get the content of FS_hash and then merge the objects, remove the migration key and cb
|
||||
|
@ -102,21 +102,23 @@ define([
|
|||
return;
|
||||
}
|
||||
if (parsed) {
|
||||
var proxy = proxyData.proxy;
|
||||
var oldFo = FO.init(parsed.drive, {
|
||||
Cryptpad: Cryptpad
|
||||
});
|
||||
var onMigrated = function () {
|
||||
oldFo.fixFiles();
|
||||
var newData = Cryptpad.getStore().getProxy();
|
||||
var newFo = newData.fo;
|
||||
var newFo = proxyData.fo;
|
||||
var oldRecentPads = parsed.drive[newFo.FILES_DATA];
|
||||
var newRecentPads = proxy.drive[newFo.FILES_DATA];
|
||||
var newFiles = newFo.getFiles([newFo.FILES_DATA]);
|
||||
var oldFiles = oldFo.getFiles([newFo.FILES_DATA]);
|
||||
var newHrefs = Object.keys(newRecentPads).map(function (id) {
|
||||
return newRecentPads[id].href;
|
||||
});
|
||||
oldFiles.forEach(function (id) {
|
||||
var href = oldRecentPads[id].href;
|
||||
// Do not migrate a pad if we already have it, it would create a duplicate in the drive
|
||||
if (newFiles.indexOf(id) !== -1) { return; }
|
||||
if (newHrefs.indexOf(href) !== -1) { return; }
|
||||
// If we have a stronger version, do not add the current href
|
||||
if (Cryptpad.findStronger(href, newRecentPads)) { return; }
|
||||
// If we have a weaker version, replace the href by the new one
|
||||
|
@ -150,7 +152,7 @@ define([
|
|||
if (!proxy.FS_hashes || !Array.isArray(proxy.FS_hashes)) {
|
||||
proxy.FS_hashes = [];
|
||||
}
|
||||
proxy.FS_hashes.push(localStorage.FS_hash);
|
||||
proxy.FS_hashes.push(fsHash);
|
||||
if (typeof(cb) === "function") { cb(); }
|
||||
};
|
||||
oldFo.migrate(onMigrated);
|
||||
|
@ -158,7 +160,7 @@ define([
|
|||
}
|
||||
if (typeof(cb) === "function") { cb(); }
|
||||
};
|
||||
Crypt.get(localStorage.FS_hash, todo);
|
||||
Crypt.get(fsHash, todo);
|
||||
};
|
||||
|
||||
return exp;
|
||||
|
|
|
@ -73,7 +73,6 @@ define(['json.sortify'], function (Sortify) {
|
|||
});
|
||||
};
|
||||
|
||||
console.log('here register');
|
||||
sframeChan.on('EV_METADATA_UPDATE', function (ev) {
|
||||
meta = ev;
|
||||
if (ev.priv) {
|
||||
|
|
|
@ -7,41 +7,13 @@ define([], function () {
|
|||
return function (userObject, Cryptpad) {
|
||||
var version = userObject.version || 0;
|
||||
|
||||
// DEPRECATED
|
||||
// Migration 1: pad attributes moved to filesData
|
||||
var migratePadAttributesToData = function () {
|
||||
var files = userObject && userObject.drive;
|
||||
if (!files) { return; }
|
||||
|
||||
var migratePadAttributes = function (el, id, parsed) {
|
||||
// Migrate old pad attributes
|
||||
['userid', 'previewMode'].forEach(function (attr) {
|
||||
var key = parsed.hash + '.' + attr;
|
||||
var key2 = parsed.hash.slice(0,-1) + '.' + attr;// old pads not ending with /
|
||||
if (typeof(files[key]) !== "undefined" || typeof(files[key2]) !== "undefined") {
|
||||
console.log("Migrating pad attribute", attr, "for pad", id);
|
||||
el[attr] = files[key] || files[key2];
|
||||
delete files[key];
|
||||
delete files[key2];
|
||||
}
|
||||
});
|
||||
};
|
||||
var filesData = files.filesData;
|
||||
if (!filesData) { return; }
|
||||
|
||||
var el, parsed;
|
||||
for (var id in filesData) {
|
||||
id = Number(id);
|
||||
el = filesData[id];
|
||||
parsed = el.href && Cryptpad.parsePadUrl(el.href);
|
||||
if (!parsed) { continue; }
|
||||
migratePadAttributes(el, id, parsed);
|
||||
}
|
||||
// Migration done
|
||||
return true;
|
||||
};
|
||||
if (version < 1) {
|
||||
migratePadAttributesToData();
|
||||
Cryptpad.feedback('Migrate-1', true);
|
||||
userObject.version = version = 1;
|
||||
}
|
||||
|
||||
// Migration 2: global attributes from root to 'settings' subobjects
|
||||
|
@ -77,5 +49,19 @@ define([], function () {
|
|||
Cryptpad.feedback('Migrate-2', true);
|
||||
userObject.version = version = 2;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Migration 3: language from localStorage to settings
|
||||
var migrateLanguage = function () {
|
||||
if (!localStorage.CRYPTPAD_LANG) { return; }
|
||||
var l = localStorage.CRYPTPAD_LANG;
|
||||
userObject.settings.language = l;
|
||||
};
|
||||
if (version < 3) {
|
||||
migrateLanguage();
|
||||
Cryptpad.feedback('Migrate-3', true);
|
||||
userObject.version = version = 3;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -42,6 +42,26 @@ var afterLoaded = function (req) {
|
|||
updated: updated,
|
||||
cache: data.cache
|
||||
};
|
||||
data.localStore = data.localStore || {};
|
||||
var lsUpdated = {};
|
||||
window.cryptpadStore = {
|
||||
get: function (k, cb) {
|
||||
setTimeout(function () { cb(data.localStore[k]); });
|
||||
},
|
||||
getAll: function (cb) {
|
||||
setTimeout(function () {
|
||||
cb(JSON.parse(JSON.stringify(data.localStore)));
|
||||
});
|
||||
},
|
||||
put: function (k, v, cb) {
|
||||
cb = cb || function () { };
|
||||
lsUpdated[k] = v;
|
||||
setTimeout(function () { data.localStore[k] = v; cb(); });
|
||||
},
|
||||
updated: lsUpdated,
|
||||
store: data.localStore
|
||||
};
|
||||
window.cryptpadLanguage = data.language;
|
||||
require(['/common/sframe-boot2.js'], function () { });
|
||||
};
|
||||
window.addEventListener('message', onReply);
|
||||
|
|
|
@ -0,0 +1,748 @@
|
|||
require.config({ paths: { 'json.sortify': '/bower_components/json.sortify/dist/JSON.sortify' } });
|
||||
define([
|
||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||
'/bower_components/chainpad-json-validator/json-ot.js',
|
||||
'json.sortify',
|
||||
'/bower_components/textpatcher/TextPatcher.js',
|
||||
], function (Realtime, JsonOT, Sortify, TextPatcher) {
|
||||
var api = {};
|
||||
// "Proxy" is undefined in Safari : we need to use an normal object and check if there are local
|
||||
// changes regurlarly.
|
||||
var isFakeProxy = typeof window.Proxy === "undefined";
|
||||
|
||||
var DeepProxy = api.DeepProxy = (function () {
|
||||
var deepProxy = {};
|
||||
|
||||
var isArray = deepProxy.isArray = Array.isArray || function (obj) {
|
||||
return Object.toString(obj) === '[object Array]';
|
||||
};
|
||||
|
||||
/* Arrays and nulls both register as 'object' when using native typeof
|
||||
we need to distinguish them as their own types, so use this instead. */
|
||||
var type = deepProxy.type = function (dat) {
|
||||
return dat === null? 'null': isArray(dat)?'array': typeof(dat);
|
||||
};
|
||||
|
||||
/* Check if an (sub-)element in an object or an array and should be a proxy.
|
||||
If the browser doesn't support Proxy, return false */
|
||||
var isProxyable = deepProxy.isProxyable = function (obj, forceCheck) {
|
||||
if (typeof forceCheck === "undefined" && isFakeProxy) { return false; }
|
||||
return ['object', 'array'].indexOf(type(obj)) !== -1;
|
||||
};
|
||||
|
||||
/* Any time you set a value, check its type.
|
||||
If that type is proxyable, make a new proxy. */
|
||||
var setter = deepProxy.set = function (cb) {
|
||||
return function (obj, prop, value) {
|
||||
if (prop === 'on') {
|
||||
throw new Error("'on' is a reserved attribute name for realtime lists and maps");
|
||||
}
|
||||
if (isProxyable(value)) {
|
||||
obj[prop] = deepProxy.create(value, cb);
|
||||
} else {
|
||||
obj[prop] = value;
|
||||
}
|
||||
|
||||
cb();
|
||||
return obj[prop] || true; // always return truthey or you have problems
|
||||
};
|
||||
};
|
||||
|
||||
var pathMatches = deepProxy.pathMatches = function (path, pattern) {
|
||||
return !pattern.some(function (x, i) {
|
||||
return x !== path[i];
|
||||
});
|
||||
};
|
||||
|
||||
var lengthDescending = function (a, b) { return b.pattern.length - a.pattern.length; };
|
||||
|
||||
/* TODO implement 'off' as well.
|
||||
change 'setter' to warn users when they attempt to set 'off'
|
||||
*/
|
||||
var on = function(events) {
|
||||
return function (evt, pattern, f) {
|
||||
switch (evt) {
|
||||
case 'change':
|
||||
// pattern needs to be an array
|
||||
pattern = type(pattern) === 'array'? pattern: [pattern];
|
||||
|
||||
events.change.push({
|
||||
cb: function (oldval, newval, path, root) {
|
||||
if (pathMatches(path, pattern)) {
|
||||
return f(oldval, newval, path, root);
|
||||
}
|
||||
},
|
||||
pattern: pattern,
|
||||
});
|
||||
// sort into descending order so we evaluate in order of specificity
|
||||
events.change.sort(lengthDescending);
|
||||
|
||||
break;
|
||||
case 'remove':
|
||||
pattern = type(pattern) === 'array'? pattern: [pattern];
|
||||
|
||||
events.remove.push({
|
||||
cb: function (oldval, path, root) {
|
||||
if (pathMatches(path, pattern)) { return f(oldval, path, root); }
|
||||
},
|
||||
pattern: pattern,
|
||||
});
|
||||
|
||||
events.remove.sort(lengthDescending);
|
||||
|
||||
break;
|
||||
case 'ready':
|
||||
events.ready.push({
|
||||
// on('ready' has a different signature than
|
||||
// change and delete, so use 'pattern', not 'f'
|
||||
|
||||
cb: function (info) {
|
||||
pattern(info);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'disconnect':
|
||||
events.disconnect.push({
|
||||
cb: function (info) {
|
||||
// as above
|
||||
pattern(info);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'reconnect':
|
||||
events.reconnect.push({
|
||||
cb: function (info) {
|
||||
// as above
|
||||
pattern(info);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'create':
|
||||
events.create.push({
|
||||
cb: function (info) {
|
||||
pattern(info);
|
||||
}
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
};
|
||||
|
||||
var getter = deepProxy.get = function (/* cb */) {
|
||||
var events = {
|
||||
disconnect: [],
|
||||
reconnect: [],
|
||||
change: [],
|
||||
ready: [],
|
||||
remove: [],
|
||||
create: [],
|
||||
};
|
||||
|
||||
return function (obj, prop) {
|
||||
if (prop === 'on') {
|
||||
return on(events);
|
||||
} else if (prop === '_isProxy') {
|
||||
return true;
|
||||
} else if (prop === '_events') {
|
||||
return events;
|
||||
}
|
||||
return obj[prop];
|
||||
};
|
||||
};
|
||||
|
||||
var deleter = deepProxy.delete = function (cb) {
|
||||
return function (obj, prop) {
|
||||
if (typeof(obj[prop]) === 'undefined') { return true; }
|
||||
delete obj[prop];
|
||||
cb();
|
||||
return true;
|
||||
};
|
||||
};
|
||||
|
||||
var handlers = deepProxy.handlers = function (cb, isRoot) {
|
||||
if (!isRoot) {
|
||||
return {
|
||||
set: setter(cb),
|
||||
get: function (obj, prop) {
|
||||
if (prop === '_isProxy') {
|
||||
return true;
|
||||
}
|
||||
return obj[prop];
|
||||
},
|
||||
deleteProperty: deleter(cb),
|
||||
};
|
||||
}
|
||||
return {
|
||||
set: setter(cb),
|
||||
get: getter(cb),
|
||||
deleteProperty: deleter(cb),
|
||||
};
|
||||
};
|
||||
|
||||
var remoteChangeFlag = deepProxy.remoteChangeFlag = false;
|
||||
|
||||
var stringifyFakeProxy = deepProxy.stringifyFakeProxy = function (proxy) {
|
||||
var copy = JSON.parse(Sortify(proxy));
|
||||
delete copy._events;
|
||||
delete copy._isProxy;
|
||||
return Sortify(copy);
|
||||
};
|
||||
|
||||
deepProxy.checkLocalChange = function (obj, cb) {
|
||||
if (!isFakeProxy) { return; }
|
||||
var oldObj = stringifyFakeProxy(obj);
|
||||
window.setInterval(function() {
|
||||
var newObj = stringifyFakeProxy(obj);
|
||||
if (newObj !== oldObj) {
|
||||
oldObj = newObj;
|
||||
if (remoteChangeFlag) {
|
||||
remoteChangeFlag = false;
|
||||
} else {
|
||||
cb();
|
||||
}
|
||||
}
|
||||
},300);
|
||||
};
|
||||
|
||||
var create = deepProxy.create = function (obj, opt, isRoot) {
|
||||
/* recursively create proxies in case users do:
|
||||
`x.a = {b: {c: 5}};
|
||||
|
||||
otherwise the inner object is not a proxy, which leads to incorrect
|
||||
behaviour on the client that initiated the object (but not for
|
||||
clients that receive the objects) */
|
||||
|
||||
// if the user supplied a callback, use it to create handlers
|
||||
// this saves a bit of work in recursion
|
||||
var methods = type(opt) === 'function'? handlers(opt, isRoot) : opt;
|
||||
switch (type(obj)) {
|
||||
case 'object':
|
||||
var keys = Object.keys(obj);
|
||||
keys.forEach(function (k) {
|
||||
if (isProxyable(obj[k]) && !obj[k]._isProxy) {
|
||||
obj[k] = create(obj[k], opt);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'array':
|
||||
obj.forEach(function (o, i) {
|
||||
if (isProxyable(o) && !o._isProxy) {
|
||||
obj[i] = create(obj[i], opt);
|
||||
}
|
||||
});
|
||||
break;
|
||||
default:
|
||||
// if it's not an array or object, you don't need to proxy it
|
||||
throw new Error('attempted to make a proxy of an unproxyable object');
|
||||
}
|
||||
if (!isFakeProxy) {
|
||||
if (obj._isProxy) {
|
||||
return obj;
|
||||
}
|
||||
return new window.Proxy(obj, methods);
|
||||
}
|
||||
|
||||
var proxy = JSON.parse(JSON.stringify(obj));
|
||||
|
||||
if (isRoot) {
|
||||
var events = {
|
||||
disconnect: [],
|
||||
reconnect: [],
|
||||
change: [],
|
||||
ready: [],
|
||||
remove: [],
|
||||
create: [],
|
||||
};
|
||||
proxy.on = on(events);
|
||||
proxy._events = events;
|
||||
}
|
||||
return proxy;
|
||||
};
|
||||
|
||||
// onChange(path, key, root, oldval, newval)
|
||||
var onChange = function (path, key, root, oldval, newval) {
|
||||
var P = path.slice(0);
|
||||
P.push(key);
|
||||
|
||||
/* returning false in your callback terminates 'bubbling up'
|
||||
we can accomplish this with Array.some because we've presorted
|
||||
listeners by the specificity of their path
|
||||
*/
|
||||
root._events.change.some(function (handler) {
|
||||
return handler.cb(oldval, newval, P, root) === false;
|
||||
});
|
||||
};
|
||||
|
||||
var find = deepProxy.find = function (map, path) {
|
||||
/* safely search for nested values in an object via a path */
|
||||
return (map && path.reduce(function (p, n) {
|
||||
return typeof p[n] !== 'undefined' && p[n];
|
||||
}, map)) || undefined;
|
||||
};
|
||||
|
||||
var onRemove = function (path, key, root, old, top) {
|
||||
var newpath = path.concat(key);
|
||||
var X = find(root, newpath);
|
||||
|
||||
var t_X = type(X);
|
||||
|
||||
/* TODO 'find' is correct but unnecessarily expensive.
|
||||
optimize it. */
|
||||
|
||||
switch (t_X) {
|
||||
case 'array':
|
||||
|
||||
if (top) {
|
||||
// the top of an onremove should emit an onchange instead
|
||||
onChange(path, key, root, old, undefined);// no newval since it's a deletion
|
||||
} else {
|
||||
root._events.remove.forEach(function (handler) {
|
||||
return handler.cb(X, newpath, root);
|
||||
});
|
||||
}
|
||||
// remove all of the array's children
|
||||
X.forEach(function (x, i) {
|
||||
onRemove(newpath, i, root);
|
||||
});
|
||||
|
||||
break;
|
||||
case 'object':
|
||||
if (top) {
|
||||
onChange(path, key, root, old, undefined);// no newval since it's a deletion
|
||||
} else {
|
||||
root._events.remove.forEach(function (handler) {
|
||||
return handler.cb(X, newpath, root, old, false);
|
||||
});
|
||||
}
|
||||
// remove all of the object's children
|
||||
Object.keys(X).forEach(function (key) {
|
||||
onRemove(newpath, key, root, X[key], false);
|
||||
});
|
||||
|
||||
break;
|
||||
default:
|
||||
root._events.remove.forEach(function (handler) {
|
||||
return handler.cb(X, newpath, root);
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/* compare a new object 'B' against an existing proxy object 'A'
|
||||
provide a unary function 'f' for the purpose of constructing new
|
||||
deep proxies from regular objects and arrays.
|
||||
|
||||
Supply the path as you recurse, for the purpose of emitting events
|
||||
attached to particular paths within the complete structure.
|
||||
|
||||
Operates entirely via side effects on 'A'
|
||||
*/
|
||||
var objects = deepProxy.objects = function (A, B, f, path, root) {
|
||||
var Akeys = Object.keys(A);
|
||||
var Bkeys = Object.keys(B);
|
||||
|
||||
/* iterating over the keys in B will tell you if a new key exists
|
||||
it will not tell you if a key has been removed.
|
||||
to accomplish that you will need to iterate over A's keys
|
||||
*/
|
||||
|
||||
/* TODO return a truthy or falsey value (in 'objects' and 'arrays')
|
||||
so that we have some measure of whether an object or array changed
|
||||
(from the higher level in the tree, rather than doing everything
|
||||
at the leaf level).
|
||||
|
||||
bonus points if you can defer events until the complete diff has
|
||||
finished (collect them into an array or something, and simplify
|
||||
the event if possible)
|
||||
*/
|
||||
|
||||
Bkeys.forEach(function (b) {
|
||||
var t_b = type(B[b]);
|
||||
var old = A[b];
|
||||
|
||||
if (Akeys.indexOf(b) === -1) {
|
||||
// there was an insertion
|
||||
|
||||
// mind the fallthrough behaviour
|
||||
switch (t_b) {
|
||||
case 'undefined':
|
||||
// umm. this should never happen?
|
||||
throw new Error("undefined type has key. this shouldn't happen?");
|
||||
case 'array':
|
||||
case 'object':
|
||||
A[b] = f(B[b]);
|
||||
break;
|
||||
default:
|
||||
A[b] = B[b];
|
||||
}
|
||||
|
||||
// insertions are a change
|
||||
|
||||
// onChange(path, key, root, oldval, newval)
|
||||
onChange(path, b, root, old, B[b]);
|
||||
return;
|
||||
}
|
||||
|
||||
// else the key already existed
|
||||
var t_a = type(A[b]);
|
||||
if (t_a !== t_b) {
|
||||
// its type changed!
|
||||
console.log("type changed from [%s] to [%s]", t_a, t_b);
|
||||
switch (t_b) {
|
||||
case 'undefined':
|
||||
throw new Error("first pass should never reveal undefined keys");
|
||||
case 'array':
|
||||
A[b] = f(B[b]);
|
||||
// make a new proxy
|
||||
break;
|
||||
case 'object':
|
||||
A[b] = f(B[b]);
|
||||
// make a new proxy
|
||||
break;
|
||||
default:
|
||||
// all other datatypes just require assignment.
|
||||
A[b] = B[b];
|
||||
break;
|
||||
}
|
||||
|
||||
// type changes always mean a change happened
|
||||
onChange(path, b, root, old, B[b]);
|
||||
return;
|
||||
}
|
||||
|
||||
// values might have changed, if not types
|
||||
if (['array', 'object'].indexOf(t_a) === -1) {
|
||||
// it's not an array or object, so we can do deep equality
|
||||
if (A[b] !== B[b]) {
|
||||
// not equal, so assign
|
||||
A[b] = B[b];
|
||||
|
||||
onChange(path, b, root, old, B[b]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// else it's an array or object
|
||||
var nextPath = path.slice(0).concat(b);
|
||||
if (t_a === 'object') {
|
||||
// it's an object
|
||||
objects.call(root, A[b], B[b], f, nextPath, root);
|
||||
} else {
|
||||
// it's an array
|
||||
deepProxy.arrays.call(root, A[b], B[b], f, nextPath, root);
|
||||
}
|
||||
});
|
||||
Akeys.forEach(function (a) {
|
||||
var old = A[a];
|
||||
|
||||
if (a === "on" || a === "_events") { return; }
|
||||
|
||||
// the key was deleted
|
||||
if (Bkeys.indexOf(a) === -1 || type(B[a]) === 'undefined') {
|
||||
onRemove(path, a, root, old, true);
|
||||
delete A[a];
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
var arrays = deepProxy.arrays = function (A, B, f, path, root) {
|
||||
var l_A = A.length;
|
||||
var l_B = B.length;
|
||||
|
||||
if (l_A !== l_B) {
|
||||
// B is longer than Aj
|
||||
// there has been an insertion
|
||||
|
||||
// OR
|
||||
|
||||
// A is longer than B
|
||||
// there has been a deletion
|
||||
|
||||
B.forEach(function (b, i) {
|
||||
var t_a = type(A[i]);
|
||||
var t_b = type(b);
|
||||
|
||||
var old = A[i];
|
||||
|
||||
if (t_a !== t_b) {
|
||||
// type changes are always destructive
|
||||
// that's good news because destructive is easy
|
||||
switch (t_b) {
|
||||
case 'undefined':
|
||||
throw new Error('this should never happen');
|
||||
case 'object':
|
||||
A[i] = f(b);
|
||||
break;
|
||||
case 'array':
|
||||
A[i] = f(b);
|
||||
break;
|
||||
default:
|
||||
A[i] = b;
|
||||
break;
|
||||
}
|
||||
|
||||
// path, key, root object, oldvalue, newvalue
|
||||
onChange(path, i, root, old, b);
|
||||
} else {
|
||||
// same type
|
||||
var nextPath = path.slice(0).concat(i);
|
||||
|
||||
switch (t_b) {
|
||||
case 'object':
|
||||
objects.call(root, A[i], b, f, nextPath, root);
|
||||
break;
|
||||
case 'array':
|
||||
if (arrays.call(root, A[i], b, f, nextPath, root)) {
|
||||
onChange(path, i, root, old, b);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (b !== A[i]) {
|
||||
A[i] = b;
|
||||
onChange(path, i, root, old, b);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (l_A > l_B) {
|
||||
// A was longer than B, so there have been deletions
|
||||
var i = l_B;
|
||||
//var t_a;
|
||||
var old;
|
||||
|
||||
for (; i <= l_B; i++) {
|
||||
// recursively delete
|
||||
old = A[i];
|
||||
|
||||
onRemove(path, i, root, old, true);
|
||||
}
|
||||
// cool
|
||||
}
|
||||
|
||||
A.length = l_B;
|
||||
return;
|
||||
}
|
||||
|
||||
// else they are the same length, iterate over their values
|
||||
A.forEach(function (a, i) {
|
||||
var t_a = type(a);
|
||||
var t_b = type(B[i]);
|
||||
|
||||
var old = a;
|
||||
|
||||
// they have different types
|
||||
if (t_a !== t_b) {
|
||||
switch (t_b) {
|
||||
case 'undefined':
|
||||
onRemove(path, i, root, old, true);
|
||||
break;
|
||||
|
||||
// watch out for fallthrough behaviour
|
||||
// if it's an object or array, create a proxy
|
||||
case 'object':
|
||||
case 'array':
|
||||
A[i] = f(B[i]);
|
||||
break;
|
||||
default:
|
||||
A[i] = B[i];
|
||||
break;
|
||||
}
|
||||
|
||||
onChange(path, i, root, old, B[i]);
|
||||
return;
|
||||
}
|
||||
|
||||
// they are the same type, clone the paths array and push to it
|
||||
var nextPath = path.slice(0).concat(i);
|
||||
|
||||
// same type
|
||||
switch (t_b) {
|
||||
case 'undefined':
|
||||
throw new Error('existing key had type `undefined`. this should never happen');
|
||||
case 'object':
|
||||
if (objects.call(root, A[i], B[i], f, nextPath, root)) {
|
||||
onChange(path, i, root, old, B[i]);
|
||||
}
|
||||
break;
|
||||
case 'array':
|
||||
if (arrays.call(root, A[i], B[i], f, nextPath, root)) {
|
||||
onChange(path, i, root, old, B[i]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (A[i] !== B[i]) {
|
||||
A[i] = B[i];
|
||||
onChange(path, i, root, old, B[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
return;
|
||||
};
|
||||
|
||||
deepProxy.update = function (A, B, cb) {
|
||||
var t_A = type(A);
|
||||
var t_B = type(B);
|
||||
|
||||
if (t_A !== t_B) {
|
||||
throw new Error("Proxy updates can't result in type changes");
|
||||
}
|
||||
|
||||
switch (t_B) {
|
||||
/* use .call so you can supply a different `this` value */
|
||||
case 'array':
|
||||
arrays.call(A, A, B, function (obj) {
|
||||
return create(obj, cb);
|
||||
}, [], A);
|
||||
break;
|
||||
case 'object':
|
||||
// arrays.call(this, A , B , f, path , root)
|
||||
objects.call(A, A, B, function (obj) {
|
||||
return create(obj, cb);
|
||||
}, [], A);
|
||||
break;
|
||||
default:
|
||||
throw new Error("unsupported realtime datatype:" + t_B);
|
||||
}
|
||||
};
|
||||
|
||||
return deepProxy;
|
||||
}());
|
||||
|
||||
api.create = function (cfg) {
|
||||
/* validate your inputs before proceeding */
|
||||
|
||||
if (!DeepProxy.isProxyable(cfg.data, true)) {
|
||||
throw new Error('unsupported datatype: '+ DeepProxy.type(cfg.data));
|
||||
}
|
||||
|
||||
var realtimeOptions = {
|
||||
userName: cfg.userName,
|
||||
initialState: Sortify(cfg.data),
|
||||
readOnly: cfg.readOnly,
|
||||
transformFunction: JsonOT.transform || JsonOT.validate,
|
||||
logLevel: typeof(cfg.logLevel) === 'undefined'? 0: cfg.logLevel,
|
||||
validateContent: function (content) {
|
||||
try {
|
||||
JSON.parse(content);
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error("Failed to parse, rejecting patch");
|
||||
return false;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
var initializing = true;
|
||||
|
||||
var setterCb = function () {
|
||||
if (!DeepProxy.remoteChangeFlag) { realtimeOptions.onLocal(); }
|
||||
};
|
||||
|
||||
var rt = {};
|
||||
var realtime;
|
||||
|
||||
var proxy;
|
||||
var patchText;
|
||||
|
||||
realtimeOptions.onRemote = function () {
|
||||
if (initializing) { return; }
|
||||
// TODO maybe implement history support here
|
||||
|
||||
var userDoc = realtime.getUserDoc();
|
||||
var parsed = JSON.parse(userDoc);
|
||||
|
||||
DeepProxy.remoteChangeFlag = true;
|
||||
DeepProxy.update(proxy, parsed, setterCb);
|
||||
DeepProxy.remoteChangeFlag = false;
|
||||
};
|
||||
|
||||
var onLocal = realtimeOptions.onLocal = function () {
|
||||
if (initializing) { return; }
|
||||
var strung = isFakeProxy? DeepProxy.stringifyFakeProxy(proxy): Sortify(proxy);
|
||||
patchText(strung);
|
||||
|
||||
// try harder
|
||||
if (realtime.getUserDoc() !== strung) { patchText(strung); }
|
||||
|
||||
// onLocal
|
||||
if (cfg.onLocal) { cfg.onLocal(); }
|
||||
};
|
||||
|
||||
proxy = DeepProxy.create(cfg.data, setterCb, true);
|
||||
console.log(proxy);
|
||||
|
||||
realtimeOptions.onInit = function (info) {
|
||||
proxy._events.create.forEach(function (handler) {
|
||||
handler.cb(info);
|
||||
});
|
||||
};
|
||||
|
||||
realtimeOptions.onReady = function (info) {
|
||||
// create your patcher
|
||||
if (realtime !== info.realtime) {
|
||||
realtime = rt.realtime = info.realtime;
|
||||
patchText = TextPatcher.create({
|
||||
realtime: realtime,
|
||||
logging: cfg.logging || false,
|
||||
});
|
||||
} else {
|
||||
console.error(realtime);
|
||||
}
|
||||
|
||||
var userDoc = realtime.getUserDoc();
|
||||
var parsed = JSON.parse(userDoc);
|
||||
|
||||
DeepProxy.update(proxy, parsed, setterCb);
|
||||
|
||||
proxy._events.ready.forEach(function (handler) {
|
||||
handler.cb(info);
|
||||
});
|
||||
|
||||
DeepProxy.checkLocalChange(proxy, onLocal);
|
||||
|
||||
initializing = false;
|
||||
};
|
||||
|
||||
realtimeOptions.onAbort = function (info) {
|
||||
proxy._events.disconnect.forEach(function (handler) {
|
||||
handler.cb(info);
|
||||
});
|
||||
};
|
||||
|
||||
realtimeOptions.onConnectionChange = function (info) {
|
||||
if (info.state) { // reconnect
|
||||
initializing = true;
|
||||
proxy._events.reconnect.forEach(function (handler) {
|
||||
handler.cb(info);
|
||||
});
|
||||
return;
|
||||
}
|
||||
// disconnected
|
||||
proxy._events.disconnect.forEach(function (handler) {
|
||||
handler.cb(info);
|
||||
});
|
||||
};
|
||||
|
||||
realtimeOptions.onError = function (info) {
|
||||
proxy._events.disconnect.forEach(function (handler) {
|
||||
handler.cb(info);
|
||||
});
|
||||
};
|
||||
|
||||
realtime = rt.cpCnInner = cfg.common.startRealtime(realtimeOptions);
|
||||
rt.proxy = proxy;
|
||||
rt.realtime = realtime;
|
||||
|
||||
return rt;
|
||||
};
|
||||
|
||||
return api;
|
||||
});
|
|
@ -46,7 +46,21 @@ define([
|
|||
var metadataMgr = config.metadataMgr;
|
||||
config = undefined;
|
||||
|
||||
var chainpad;
|
||||
var chainpad = ChainPad.create({
|
||||
userName: userName,
|
||||
initialState: initialState,
|
||||
transformFunction: transformFunction,
|
||||
validateContent: validateContent,
|
||||
avgSyncMilliseconds: avgSyncMilliseconds,
|
||||
logLevel: logLevel
|
||||
});
|
||||
chainpad.onMessage(function(message, cb) {
|
||||
sframeChan.query('Q_RT_MESSAGE', message, cb);
|
||||
});
|
||||
chainpad.onPatch(function () {
|
||||
onRemote({ realtime: chainpad });
|
||||
});
|
||||
|
||||
var myID;
|
||||
var isReady = false;
|
||||
var evConnected = Util.mkEvent(true);
|
||||
|
@ -67,33 +81,20 @@ define([
|
|||
|
||||
sframeChan.on('EV_RT_DISCONNECT', function () {
|
||||
isReady = false;
|
||||
if (chainpad) { chainpad.abort(); }
|
||||
chainpad.abort();
|
||||
onConnectionChange({ state: false });
|
||||
});
|
||||
sframeChan.on('EV_RT_CONNECT', function (content) {
|
||||
//content.members.forEach(userList.onJoin);
|
||||
myID = content.myID;
|
||||
isReady = false;
|
||||
if (chainpad) {
|
||||
if (myID) {
|
||||
// it's a reconnect
|
||||
if (chainpad) { chainpad.start(); }
|
||||
myID = content.myID;
|
||||
chainpad.start();
|
||||
onConnectionChange({ state: true, myId: myID });
|
||||
return;
|
||||
}
|
||||
chainpad = ChainPad.create({
|
||||
userName: userName,
|
||||
initialState: initialState,
|
||||
transformFunction: transformFunction,
|
||||
validateContent: validateContent,
|
||||
avgSyncMilliseconds: avgSyncMilliseconds,
|
||||
logLevel: logLevel
|
||||
});
|
||||
chainpad.onMessage(function(message, cb) {
|
||||
sframeChan.query('Q_RT_MESSAGE', message, cb);
|
||||
});
|
||||
chainpad.onPatch(function () {
|
||||
onRemote({ realtime: chainpad });
|
||||
});
|
||||
myID = content.myID;
|
||||
onInit({
|
||||
myID: myID,
|
||||
realtime: chainpad,
|
||||
|
@ -130,7 +131,8 @@ define([
|
|||
getMyID: function () { return myID; },
|
||||
metadataMgr: metadataMgr,
|
||||
whenRealtimeSyncs: whenRealtimeSyncs,
|
||||
onInfiniteSpinner: evInfiniteSpinner.reg
|
||||
onInfiniteSpinner: evInfiniteSpinner.reg,
|
||||
chainpad: chainpad,
|
||||
});
|
||||
};
|
||||
return Object.freeze(module.exports);
|
||||
|
|
|
@ -154,7 +154,9 @@ define([], function () {
|
|||
// 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.
|
||||
|
|
|
@ -51,9 +51,7 @@ define([
|
|||
$('<td>').text(Messages.cancel).appendTo($thead);
|
||||
|
||||
var createTableContainer = function ($body) {
|
||||
console.log($body);
|
||||
File.$container = $('<div>', { id: 'cp-fileupload' }).append($table).appendTo($body);
|
||||
console.log('done');
|
||||
return File.$container;
|
||||
};
|
||||
|
||||
|
@ -114,10 +112,13 @@ define([
|
|||
};
|
||||
|
||||
onComplete = function (href) {
|
||||
var mdMgr = common.getMetadataMgr();
|
||||
var origin = mdMgr.getPrivateData().origin;
|
||||
$link.prepend($('<span>', {'class': 'fa fa-external-link'}));
|
||||
$link.attr('href', href)
|
||||
.click(function (e) {
|
||||
e.preventDefault();
|
||||
window.open($link.attr('href'), '_blank');
|
||||
window.open(origin + $link.attr('href'), '_blank');
|
||||
});
|
||||
var title = metadata.name;
|
||||
Cryptpad.log(Messages._getKey('upload_success', [title]));
|
||||
|
@ -290,10 +291,22 @@ define([
|
|||
onFileDrop(dropped, e);
|
||||
});
|
||||
};
|
||||
var createCkeditorDropHandler = function () {
|
||||
var editor = config.ckeditor;
|
||||
editor.document.on('drop', function (ev) {
|
||||
var dropped = ev.data.$.dataTransfer.files;
|
||||
onFileDrop(dropped, ev);
|
||||
ev.data.preventDefault(true);
|
||||
});
|
||||
};
|
||||
|
||||
var createUploader = function ($area, $hover, $body) {
|
||||
if (!config.noHandlers) {
|
||||
createAreaHandlers($area, null);
|
||||
if (config.ckeditor) {
|
||||
createCkeditorDropHandler();
|
||||
} else {
|
||||
createAreaHandlers($area, null);
|
||||
}
|
||||
}
|
||||
createTableContainer($body);
|
||||
};
|
||||
|
|
|
@ -21,6 +21,15 @@ define([
|
|||
var createRealtime = function () {
|
||||
return ChainPad.create({
|
||||
userName: 'history',
|
||||
validateContent: function (content) {
|
||||
try {
|
||||
JSON.parse(content);
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.log('Failed to parse, rejecting patch');
|
||||
return false;
|
||||
}
|
||||
},
|
||||
initialState: '',
|
||||
transformFunction: JsonOT.validate,
|
||||
logLevel: 0,
|
||||
|
@ -69,9 +78,9 @@ define([
|
|||
config.onLocal();
|
||||
config.onRemote();
|
||||
};
|
||||
var onReady = function () {
|
||||
config.setHistory(true);
|
||||
};
|
||||
|
||||
config.setHistory(true);
|
||||
var onReady = function () { };
|
||||
|
||||
var Messages = common.Messages;
|
||||
var Cryptpad = common.getCryptpadCommon();
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
define([
|
||||
'jquery',
|
||||
'/api/config',
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/common-util.js',
|
||||
'/common/media-tag.js',
|
||||
'/common/tippy.min.js',
|
||||
'/customize/application_config.js',
|
||||
|
||||
'css!/common/tippy.css',
|
||||
], function ($, Cryptpad, MediaTag, Tippy, AppConfig) {
|
||||
], function ($, Config, Cryptpad, Util, MediaTag, Tippy, AppConfig) {
|
||||
var UI = {};
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
||||
|
@ -182,11 +184,35 @@ define([
|
|||
break;
|
||||
case 'more':
|
||||
button = $('<button>', {
|
||||
title: Messages.moreActions || 'TODO',
|
||||
title: Messages.moreActions,
|
||||
'class': "cp-toolbar-drawer-button fa fa-ellipsis-h",
|
||||
style: 'font:'+size+' FontAwesome'
|
||||
});
|
||||
break;
|
||||
case 'savetodrive':
|
||||
button = $('<button>', {
|
||||
'class': 'fa fa-cloud-upload',
|
||||
title: Messages.canvas_saveToDrive,
|
||||
})
|
||||
.click(common.prepareFeedback(type));
|
||||
break;
|
||||
case 'hashtag':
|
||||
button = $('<button>', {
|
||||
'class': 'fa fa-hashtag',
|
||||
title: Messages.tags_title,
|
||||
})
|
||||
.click(common.prepareFeedback(type))
|
||||
.click(function () {
|
||||
sframeChan.query('Q_TAGS_GET', null, function (err, res) {
|
||||
if (err || res.error) { return void console.error(err || res.error); }
|
||||
Cryptpad.dialog.tagPrompt(res.data, function (tags) {
|
||||
if (!Array.isArray(tags)) { return; }
|
||||
console.error(tags);
|
||||
sframeChan.event('EV_TAGS_SET', tags);
|
||||
});
|
||||
});
|
||||
});
|
||||
break;
|
||||
default:
|
||||
button = $('<button>', {
|
||||
'class': "fa fa-question",
|
||||
|
@ -267,6 +293,103 @@ define([
|
|||
}
|
||||
};
|
||||
|
||||
/* Create a usage bar which keeps track of how much storage space is used
|
||||
by your CryptDrive. The getPinnedUsage RPC is one of the heavier calls,
|
||||
so we throttle its usage. Clients will not update more than once per
|
||||
LIMIT_REFRESH_RATE. It will be update at least once every three such intervals
|
||||
If changes are made to your drive in the interim, they will trigger an
|
||||
update.
|
||||
*/
|
||||
var LIMIT_REFRESH_RATE = 30000; // milliseconds
|
||||
UI.createUsageBar = function (common, cb) {
|
||||
if (!common.isLoggedIn()) { return cb("NOT_LOGGED_IN"); }
|
||||
// getPinnedUsage updates common.account.usage, and other values
|
||||
// so we can just use those and only check for errors
|
||||
var $container = $('<span>', {'class':'cp-limit-container'});
|
||||
var todo;
|
||||
var updateUsage = Cryptpad.notAgainForAnother(function () {
|
||||
common.getPinUsage(todo);
|
||||
}, LIMIT_REFRESH_RATE);
|
||||
|
||||
todo = function (err, data) {
|
||||
if (err) { return void console.error(err); }
|
||||
|
||||
var usage = data.usage;
|
||||
var limit = data.limit;
|
||||
var plan = data.plan;
|
||||
$container.html('');
|
||||
var unit = Util.magnitudeOfBytes(limit);
|
||||
|
||||
usage = unit === 'GB'? Util.bytesToGigabytes(usage):
|
||||
Util.bytesToMegabytes(usage);
|
||||
limit = unit === 'GB'? Util.bytesToGigabytes(limit):
|
||||
Util.bytesToMegabytes(limit);
|
||||
|
||||
var $limit = $('<span>', {'class': 'cp-limit-bar'}).appendTo($container);
|
||||
var quota = usage/limit;
|
||||
var $usage = $('<span>', {'class': 'cp-limit-usage'}).css('width', quota*100+'%');
|
||||
|
||||
var makeDonateButton = function () {
|
||||
$('<a>', {
|
||||
'class': 'cp-limit-upgrade btn btn-success',
|
||||
href: Cryptpad.donateURL,
|
||||
rel: "noreferrer noopener",
|
||||
target: "_blank",
|
||||
}).text(Messages.supportCryptpad).appendTo($container);
|
||||
};
|
||||
|
||||
var makeUpgradeButton = function () {
|
||||
$('<a>', {
|
||||
'class': 'cp-limit-upgrade btn btn-success',
|
||||
href: Cryptpad.upgradeURL,
|
||||
rel: "noreferrer noopener",
|
||||
target: "_blank",
|
||||
}).text(Messages.upgradeAccount).appendTo($container);
|
||||
};
|
||||
|
||||
if (!Config.removeDonateButton) {
|
||||
if (!common.isLoggedIn() || !Config.allowSubscriptions) {
|
||||
// user is not logged in, or subscriptions are disallowed
|
||||
makeDonateButton();
|
||||
} else if (!plan) {
|
||||
// user is logged in and subscriptions are allowed
|
||||
// and they don't have one. show upgrades
|
||||
makeUpgradeButton();
|
||||
} else {
|
||||
// they have a plan. show nothing
|
||||
}
|
||||
}
|
||||
|
||||
var prettyUsage;
|
||||
var prettyLimit;
|
||||
|
||||
if (unit === 'GB') {
|
||||
prettyUsage = Messages._getKey('formattedGB', [usage]);
|
||||
prettyLimit = Messages._getKey('formattedGB', [limit]);
|
||||
} else {
|
||||
prettyUsage = Messages._getKey('formattedMB', [usage]);
|
||||
prettyLimit = Messages._getKey('formattedMB', [limit]);
|
||||
}
|
||||
|
||||
if (quota < 0.8) { $usage.addClass('cp-limit-usage-normal'); }
|
||||
else if (quota < 1) { $usage.addClass('cp-limit-usage-warning'); }
|
||||
else { $usage.addClass('cp-limit-usage-above'); }
|
||||
var $text = $('<span>', {'class': 'cp-limit-usage-text'});
|
||||
$text.text(usage + ' / ' + prettyLimit);
|
||||
$limit.append($usage).append($text);
|
||||
};
|
||||
|
||||
setInterval(function () {
|
||||
updateUsage();
|
||||
}, LIMIT_REFRESH_RATE * 3);
|
||||
|
||||
updateUsage();
|
||||
/*getProxy().on('change', ['drive'], function () {
|
||||
updateUsage();
|
||||
}); TODO*/
|
||||
cb(null, $container);
|
||||
};
|
||||
|
||||
UI.createUserAdminMenu = function (Common, config) {
|
||||
var metadataMgr = Common.getMetadataMgr();
|
||||
|
||||
|
@ -294,7 +417,7 @@ define([
|
|||
$userAdminContent.append($userName);
|
||||
options.push({
|
||||
tag: 'p',
|
||||
attributes: {'class': 'accountData'},
|
||||
attributes: {'class': 'cp-toolbar-account'},
|
||||
content: $userAdminContent.html()
|
||||
});
|
||||
}
|
||||
|
|
|
@ -47,14 +47,21 @@ define([
|
|||
localStorage.CRYPTPAD_URLARGS = ApiConfig.requireConf.urlArgs;
|
||||
}
|
||||
var cache = {};
|
||||
var localStore = {};
|
||||
Object.keys(localStorage).forEach(function (k) {
|
||||
if (k.indexOf('CRYPTPAD_CACHE|') !== 0) { return; }
|
||||
cache[k.slice(('CRYPTPAD_CACHE|').length)] = localStorage[k];
|
||||
if (k.indexOf('CRYPTPAD_CACHE|') === 0) {
|
||||
cache[k.slice(('CRYPTPAD_CACHE|').length)] = localStorage[k];
|
||||
return;
|
||||
}
|
||||
if (k.indexOf('CRYPTPAD_STORE|') === 0) {
|
||||
localStore[k.slice(('CRYPTPAD_STORE|').length)] = localStorage[k];
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
SFrameChannel.create($('#sbox-iframe')[0].contentWindow, waitFor(function (sfc) {
|
||||
sframeChan = sfc;
|
||||
}), false, { cache: cache });
|
||||
}), false, { cache: cache, localStore: localStore, language: Cryptpad.getLanguage() });
|
||||
Cryptpad.ready(waitFor());
|
||||
}));
|
||||
}).nThen(function (waitFor) {
|
||||
|
@ -64,8 +71,17 @@ define([
|
|||
localStorage['CRYPTPAD_CACHE|' + k] = x[k];
|
||||
});
|
||||
});
|
||||
sframeChan.on('EV_LOCALSTORE_PUT', function (x) {
|
||||
Object.keys(x).forEach(function (k) {
|
||||
if (typeof(x[k]) === "undefined") {
|
||||
delete localStorage['CRYPTPAD_STORE|' + k];
|
||||
return;
|
||||
}
|
||||
localStorage['CRYPTPAD_STORE|' + k] = x[k];
|
||||
});
|
||||
});
|
||||
|
||||
secret = Cryptpad.getSecrets();
|
||||
secret = cfg.getSecrets ? cfg.getSecrets(Cryptpad) : Cryptpad.getSecrets();
|
||||
if (!secret.channel) {
|
||||
// New pad: create a new random channel id
|
||||
secret.channel = Cryptpad.createChannelId();
|
||||
|
@ -231,23 +247,22 @@ define([
|
|||
return null;
|
||||
}
|
||||
};
|
||||
var msgs = [];
|
||||
var onMsg = function (msg) {
|
||||
var parsed = parse(msg);
|
||||
if (parsed[0] === 'FULL_HISTORY_END') {
|
||||
console.log('END');
|
||||
cb();
|
||||
cb(msgs);
|
||||
return;
|
||||
}
|
||||
if (parsed[0] !== 'FULL_HISTORY') { return; }
|
||||
if (parsed[1] && parsed[1].validateKey) { // First message
|
||||
secret.keys.validateKey = parsed[1].validateKey;
|
||||
return;
|
||||
}
|
||||
msg = parsed[1][4];
|
||||
if (msg) {
|
||||
msg = msg.replace(/^cp\|/, '');
|
||||
var decryptedMsg = crypto.decrypt(msg, secret.keys.validateKey);
|
||||
sframeChan.event('EV_RT_HIST_MESSAGE', decryptedMsg);
|
||||
msgs.push(decryptedMsg);
|
||||
}
|
||||
};
|
||||
network.on('message', onMsg);
|
||||
|
@ -282,6 +297,12 @@ define([
|
|||
});
|
||||
});
|
||||
|
||||
sframeChan.on('Q_SESSIONSTORAGE_PUT', function (data, cb) {
|
||||
sessionStorage[data.key] = data.value;
|
||||
cb();
|
||||
});
|
||||
|
||||
|
||||
// Present mode URL
|
||||
sframeChan.on('Q_PRESENT_URL_GET_VALUE', function (data, cb) {
|
||||
var parsed = Cryptpad.parsePadUrl(window.location.href);
|
||||
|
@ -373,6 +394,35 @@ define([
|
|||
}
|
||||
});
|
||||
|
||||
sframeChan.on('EV_OPEN_URL', function (url) {
|
||||
if (url) {
|
||||
window.open(url);
|
||||
}
|
||||
});
|
||||
|
||||
sframeChan.on('Q_TAGS_GET', function (data, cb) {
|
||||
Cryptpad.getPadTags(null, function (err, data) {
|
||||
cb({
|
||||
error: err,
|
||||
data: data
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
sframeChan.on('EV_TAGS_SET', function (data) {
|
||||
console.log(data);
|
||||
Cryptpad.resetTags(null, data);
|
||||
});
|
||||
|
||||
sframeChan.on('Q_PIN_GET_USAGE', function (data, cb) {
|
||||
Cryptpad.isOverPinLimit(function (err, overLimit, data) {
|
||||
cb({
|
||||
error: err,
|
||||
data: data
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if (cfg.addRpc) {
|
||||
cfg.addRpc(sframeChan, Cryptpad);
|
||||
}
|
||||
|
@ -487,7 +537,7 @@ define([
|
|||
CpNfOuter.start({
|
||||
sframeChan: sframeChan,
|
||||
channel: secret.channel,
|
||||
network: Cryptpad.getNetwork(),
|
||||
network: cfg.newNetwork || Cryptpad.getNetwork(),
|
||||
validateKey: secret.keys.validateKey || undefined,
|
||||
readOnly: readOnly,
|
||||
crypto: Crypto.createEncryptor(secret.keys),
|
||||
|
@ -499,7 +549,7 @@ define([
|
|||
});
|
||||
return;
|
||||
}
|
||||
if (readOnly) { return; }
|
||||
if (readOnly || cfg.noHash) { return; }
|
||||
Cryptpad.replaceHash(Cryptpad.getEditHashFromKeys(wc.id, secret.keys));
|
||||
}
|
||||
});
|
||||
|
|
|
@ -77,6 +77,7 @@ define([
|
|||
funcs.openTemplatePicker = callWithCommon(UI.openTemplatePicker);
|
||||
funcs.displayAvatar = callWithCommon(UI.displayAvatar);
|
||||
funcs.createButton = callWithCommon(UI.createButton);
|
||||
funcs.createUsageBar = callWithCommon(UI.createUsageBar);
|
||||
|
||||
// History
|
||||
funcs.getHistory = callWithCommon(History.create);
|
||||
|
@ -156,6 +157,12 @@ define([
|
|||
if (cb) { cb(data); }
|
||||
});
|
||||
};
|
||||
funcs.getPinUsage = function (cb) {
|
||||
cb = cb || $.noop;
|
||||
ctx.sframeChan.query('Q_PIN_GET_USAGE', null, function (err, data) {
|
||||
cb(err || data.error, data.data);
|
||||
});
|
||||
};
|
||||
|
||||
funcs.isOverPinLimit = function (cb) {
|
||||
ctx.sframeChan.query('Q_GET_PIN_LIMIT_STATUS', null, function (err, data) {
|
||||
|
@ -164,10 +171,14 @@ define([
|
|||
};
|
||||
|
||||
funcs.getFullHistory = function (realtime, cb) {
|
||||
ctx.sframeChan.on('EV_RT_HIST_MESSAGE', function (content) {
|
||||
realtime.message(content);
|
||||
ctx.sframeChan.query('Q_GET_FULL_HISTORY', null, function (err, messages) {
|
||||
if (err) { return void console.error(err); }
|
||||
if (!Array.isArray(messages)) { return; }
|
||||
messages.forEach(function (m) {
|
||||
realtime.message(m);
|
||||
});
|
||||
cb();
|
||||
});
|
||||
ctx.sframeChan.query('Q_GET_FULL_HISTORY', null, cb);
|
||||
};
|
||||
|
||||
funcs.getPadAttribute = function (key, cb) {
|
||||
|
@ -199,6 +210,15 @@ define([
|
|||
}, cb);
|
||||
};
|
||||
|
||||
funcs.sessionStorage = {
|
||||
put: function (key, value, cb) {
|
||||
ctx.sframeChan.query('Q_SESSIONSTORAGE_PUT', {
|
||||
key: key,
|
||||
value: value
|
||||
}, cb);
|
||||
}
|
||||
};
|
||||
|
||||
funcs.isStrongestStored = function () {
|
||||
var data = ctx.metadataMgr.getPrivateData();
|
||||
if (data.availableHashes.fileHash) { return true; }
|
||||
|
@ -210,6 +230,9 @@ define([
|
|||
ctx.sframeChan.query('Q_SETTINGS_SET_DISPLAY_NAME', name, cb);
|
||||
};
|
||||
|
||||
funcs.mergeAnonDrive = function (cb) {
|
||||
ctx.sframeChan.query('Q_MERGE_ANON_DRIVE', null, cb);
|
||||
};
|
||||
// Friends
|
||||
var pendingFriends = [];
|
||||
funcs.getPendingFriends = function () {
|
||||
|
@ -260,6 +283,7 @@ define([
|
|||
}; */
|
||||
|
||||
funcs.gotoURL = function (url) { ctx.sframeChan.event('EV_GOTO_URL', url); };
|
||||
funcs.openURL = function (url) { ctx.sframeChan.event('EV_OPEN_URL', url); };
|
||||
|
||||
funcs.whenRealtimeSyncs = evRealtimeSynced.reg;
|
||||
|
||||
|
@ -283,6 +307,18 @@ define([
|
|||
ctx.sframeChan.event('EV_CACHE_PUT', x);
|
||||
};
|
||||
});
|
||||
ctx.sframeChan.whenReg('EV_LOCALSTORE_PUT', function () {
|
||||
if (Object.keys(window.cryptpadStore.updated).length) {
|
||||
ctx.sframeChan.event('EV_LOCALSTORE_PUT', window.cryptpadStore.updated);
|
||||
}
|
||||
window.cryptpadStore._put = window.cryptpadStore.put;
|
||||
window.cryptpadStore.put = function (k, v, cb) {
|
||||
window.cryptpadStore._put(k, v, cb);
|
||||
var x = {};
|
||||
x[k] = v;
|
||||
ctx.sframeChan.event('EV_LOCALSTORE_PUT', x);
|
||||
};
|
||||
});
|
||||
|
||||
UI.addTooltips();
|
||||
|
||||
|
|
|
@ -67,6 +67,9 @@ define({
|
|||
// Use anonymous rpc from inside the iframe (for avatars & pin usage).
|
||||
'Q_ANON_RPC_MESSAGE': true,
|
||||
|
||||
// Get the user's pin limit, usage and plan
|
||||
'Q_PIN_GET_USAGE': true,
|
||||
|
||||
// Check the pin limit to determine if we can store the pad in the drive or if we should.
|
||||
// display a warning
|
||||
'Q_GET_PIN_LIMIT_STATUS': true,
|
||||
|
@ -123,12 +126,16 @@ define({
|
|||
|
||||
// Make the browser window navigate to a given URL, if no URL is passed then it will reload.
|
||||
'EV_GOTO_URL': true,
|
||||
// Make the parent window open a given URL in a new tab. It allows us to keep sessionStorage
|
||||
// form the parent window.
|
||||
'EV_OPEN_URL': true,
|
||||
|
||||
// Present mode URL
|
||||
'Q_PRESENT_URL_GET_VALUE': true,
|
||||
'EV_PRESENT_URL_SET_VALUE': true,
|
||||
|
||||
// Put one or more entries to the cache which will go in localStorage.
|
||||
// Cache is wiped after each new release
|
||||
'EV_CACHE_PUT': true,
|
||||
|
||||
// Contacts
|
||||
|
@ -147,4 +154,17 @@ define({
|
|||
'Q_CONTACTS_SEND_MESSAGE': true,
|
||||
'Q_CONTACTS_SET_CHANNEL_HEAD': true,
|
||||
|
||||
// Put one or more entries to the localStore which will go in localStorage.
|
||||
'EV_LOCALSTORE_PUT': true,
|
||||
// Put one entry in the parent sessionStorage
|
||||
'Q_SESSIONSTORAGE_PUT': true,
|
||||
|
||||
// Set and get the tags using the tag prompt button
|
||||
'Q_TAGS_GET': true,
|
||||
'EV_TAGS_SET': true,
|
||||
|
||||
// Merge the anonymous drive (FS_hash) into the current logged in user's drive, to keep the pads
|
||||
// in the drive at registration.
|
||||
'Q_MERGE_ANON_DRIVE': true,
|
||||
|
||||
});
|
||||
|
|
|
@ -688,7 +688,7 @@ define([
|
|||
};
|
||||
|
||||
var typing = -1;
|
||||
var kickSpinner = function (toolbar, config, local) {
|
||||
var kickSpinner = function (toolbar, config/*, local*/) {
|
||||
if (!toolbar.spinner) { return; }
|
||||
var $spin = toolbar.spinner;
|
||||
|
||||
|
@ -708,7 +708,7 @@ define([
|
|||
window.clearInterval($spin.interval);
|
||||
typing = -1;
|
||||
$spin.text(Messages.saved);
|
||||
}, local ? 0 : SPINNER_DISAPPEAR_TIME);
|
||||
}, /*local ? 0 :*/ SPINNER_DISAPPEAR_TIME);
|
||||
};
|
||||
config.sfCommon.whenRealtimeSyncs(onSynced);
|
||||
};
|
||||
|
|
|
@ -18,6 +18,7 @@ define([
|
|||
var exp = {};
|
||||
var Cryptpad = config.Cryptpad;
|
||||
var Messages = Cryptpad.Messages;
|
||||
var loggedIn = config.loggedIn || Cryptpad.isLoggedIn();
|
||||
|
||||
var FILES_DATA = module.FILES_DATA = exp.FILES_DATA = Cryptpad.storageKey;
|
||||
var OLD_FILES_DATA = module.OLD_FILES_DATA = exp.OLD_FILES_DATA = Cryptpad.oldStorageKey;
|
||||
|
@ -415,6 +416,7 @@ define([
|
|||
var containsSearchedTag = function (T) {
|
||||
if (!tags) { return false; }
|
||||
if (!T.length) { return false; }
|
||||
T = T.map(function (t) { return t.toLowerCase(); });
|
||||
return tags.some(function (tag) {
|
||||
return T.some(function (t) {
|
||||
return t.indexOf(tag) !== -1;
|
||||
|
@ -424,6 +426,7 @@ define([
|
|||
|
||||
getFiles([FILES_DATA]).forEach(function (id) {
|
||||
var data = allFilesList[id];
|
||||
if (!data) { return; }
|
||||
if (Array.isArray(data.tags) && containsSearchedTag(data.tags)) {
|
||||
res.push(id);
|
||||
} else
|
||||
|
@ -446,6 +449,7 @@ define([
|
|||
res.forEach(function (l) {
|
||||
//var paths = findFile(l);
|
||||
ret.push({
|
||||
id: l,
|
||||
paths: findFile(l),
|
||||
data: exp.getFileData(l)
|
||||
});
|
||||
|
@ -454,7 +458,7 @@ define([
|
|||
};
|
||||
exp.getRecentPads = function () {
|
||||
var allFiles = files[FILES_DATA];
|
||||
var sorted = Object.keys(allFiles)
|
||||
var sorted = Object.keys(allFiles).filter(function (a) { return allFiles[a]; })
|
||||
.sort(function (a,b) {
|
||||
return allFiles[a].atime < allFiles[b].atime;
|
||||
})
|
||||
|
@ -479,13 +483,14 @@ define([
|
|||
|
||||
// FILES DATA
|
||||
exp.pushData = function (data, cb) {
|
||||
// TODO: can only be called from outside atm
|
||||
if (typeof cb !== "function") { cb = function () {}; }
|
||||
var todo = function () {
|
||||
var id = Cryptpad.createRandomInteger();
|
||||
files[FILES_DATA][id] = data;
|
||||
cb(null, id);
|
||||
};
|
||||
if (!Cryptpad.isLoggedIn() || !AppConfig.enablePinning || config.testMode) {
|
||||
if (!loggedIn || !AppConfig.enablePinning || config.testMode) {
|
||||
return void todo();
|
||||
}
|
||||
Cryptpad.pinPads([Cryptpad.hrefToHexChannelId(data.href)], function (e) {
|
||||
|
@ -583,7 +588,7 @@ define([
|
|||
|
||||
// ADD
|
||||
var add = exp.add = function (id, path) {
|
||||
if (!Cryptpad.isLoggedIn() && !config.testMode) { return; }
|
||||
if (!loggedIn && !config.testMode) { return; }
|
||||
var data = files[FILES_DATA][id];
|
||||
if (!data || typeof(data) !== "object") { return; }
|
||||
var newPath = path, parentEl;
|
||||
|
@ -622,7 +627,7 @@ define([
|
|||
exp.forget = function (href) {
|
||||
var id = getIdFromHref(href);
|
||||
if (!id) { return; }
|
||||
if (!Cryptpad.isLoggedIn() && !config.testMode) {
|
||||
if (!loggedIn && !config.testMode) {
|
||||
// delete permanently
|
||||
exp.removePadAttribute(href);
|
||||
spliceFileData(id);
|
||||
|
@ -651,7 +656,7 @@ define([
|
|||
};
|
||||
var checkDeletedFiles = function () {
|
||||
// Nothing in OLD_FILES_DATA for workgroups
|
||||
if (workgroup || (!Cryptpad.isLoggedIn() && !config.testMode)) { return; }
|
||||
if (workgroup || (!loggedIn && !config.testMode)) { return; }
|
||||
|
||||
var filesList = getFiles([ROOT, 'hrefArray', TRASH]);
|
||||
getFiles([FILES_DATA]).forEach(function (id) {
|
||||
|
@ -678,7 +683,7 @@ define([
|
|||
var trashPaths = paths.filter(function(x) { return isPathIn(x, [TRASH]); });
|
||||
var allFilesPaths = paths.filter(function(x) { return isPathIn(x, [FILES_DATA]); });
|
||||
|
||||
if (!Cryptpad.isLoggedIn() && !config.testMode) {
|
||||
if (!loggedIn && !config.testMode) {
|
||||
allFilesPaths.forEach(function (path) {
|
||||
var el = find(path);
|
||||
if (!el) { return; }
|
||||
|
@ -853,6 +858,7 @@ define([
|
|||
}
|
||||
try {
|
||||
debug("Migrating file system...");
|
||||
// TODO
|
||||
Cryptpad.feedback('Migrate-oldFilesData', true);
|
||||
files.migrate = 1;
|
||||
var next = function () {
|
||||
|
@ -902,6 +908,7 @@ define([
|
|||
};
|
||||
if (exp.rt) {
|
||||
exp.rt.sync();
|
||||
// TODO
|
||||
Cryptpad.whenRealtimeSyncs(exp.rt, next);
|
||||
} else {
|
||||
window.setTimeout(next, 1000);
|
||||
|
@ -1035,20 +1042,6 @@ define([
|
|||
}
|
||||
});
|
||||
};
|
||||
var migrateAttributes = function (el, id, parsed) {
|
||||
// Migrate old pad attributes
|
||||
['userid', 'previewMode'].forEach(function (attr) {
|
||||
var key = parsed.hash + '.' + attr;
|
||||
var key2 = parsed.hash.slice(0,-1) + '.' + attr;// old pads not ending with /
|
||||
if (typeof(files[key]) !== "undefined" || typeof(files[key2]) !== "undefined") {
|
||||
debug("Migrating pad attribute", attr, "for pad", id);
|
||||
el[attr] = files[key] || files[key2];
|
||||
delete files[key];
|
||||
delete files[key2];
|
||||
}
|
||||
});
|
||||
// Migration done
|
||||
};
|
||||
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];
|
||||
|
@ -1075,9 +1068,7 @@ define([
|
|||
continue;
|
||||
}
|
||||
|
||||
migrateAttributes(el, id, parsed);
|
||||
|
||||
if ((Cryptpad.isLoggedIn() || config.testMode) && rootFiles.indexOf(id) === -1) {
|
||||
if ((loggedIn || config.testMode) && rootFiles.indexOf(id) === -1) {
|
||||
debug("An element in filesData was not in ROOT, TEMPLATE or TRASH.", id, el);
|
||||
var newName = Cryptpad.createChannelId();
|
||||
root[newName] = id;
|
||||
|
|
|
@ -0,0 +1,820 @@
|
|||
@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/leftside-menu.less';
|
||||
@import (once) "../../customize/src/less2/include/tools.less";
|
||||
@import (once) "../../customize/src/less2/include/limit-bar.less";
|
||||
|
||||
.toolbar_main();
|
||||
.fileupload_main();
|
||||
.alertify_main();
|
||||
.limit-bar_main();
|
||||
|
||||
@drive_hover: #eee;
|
||||
@drive_hover-light: lighten(@drive_hover, 20%);
|
||||
@drive_info-box-bg: #d2e1f2;
|
||||
@drive_info-box-border: #bbb;
|
||||
@drive_table-header-fg: #555;
|
||||
@drive_table-header-bg: #e8e8e8;
|
||||
@drive_mobile-tree-border-col: #ccc;
|
||||
|
||||
@drive_content-fg: @colortheme_sidebar-right-fg;
|
||||
@drive_content-bg: @colortheme_sidebar-right-bg;
|
||||
@drive_content-bg-ro: darken(@drive_content-bg, 10%);
|
||||
|
||||
|
||||
/* PAGE */
|
||||
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
max-height: 100%;
|
||||
min-height: auto;
|
||||
|
||||
.cp-unselectable {
|
||||
.tools_unselectable();
|
||||
}
|
||||
|
||||
/* local mixins */
|
||||
.drive_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(.cp-app-drive-element-selected):not(.cp-app-drive-element-selected-tmp) {
|
||||
border: 1px solid #CCC;
|
||||
}
|
||||
.cp-app-drive-element-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;
|
||||
}
|
||||
.cp-app-drive-element-truncated {
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
left: 0; right: 0;
|
||||
text-align: center;
|
||||
}
|
||||
img.cp-app-drive-content-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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
img.cp-app-drive-icon {
|
||||
max-width: 20px;
|
||||
max-height: 16px;
|
||||
}
|
||||
|
||||
.cp-app-drive-container {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
@media screen and (max-width: @browser_media-medium-screen) {
|
||||
display: block;
|
||||
#cp-app-drive-toolbar {
|
||||
.path .element {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
#cp-app-drive-tree {
|
||||
resize: none;
|
||||
width: 100%;
|
||||
max-width: unset;
|
||||
max-height: unset;
|
||||
border-bottom: 1px solid @drive_mobile-tree-border-col;
|
||||
.cp-app-drive-tree-category {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.fa {
|
||||
font-family: FontAwesome;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding-left: 0px; // Remove the default padding
|
||||
}
|
||||
|
||||
li {
|
||||
padding: 0px 5px;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.cp-app-drive-context {
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 500;
|
||||
li {
|
||||
padding: 0;
|
||||
font-size: @colortheme_app-font-size;
|
||||
a {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cp-app-drive-element-droppable {
|
||||
background-color: #FE9A2E;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.cp-app-drive-element-selected {
|
||||
background: #666 !important;
|
||||
color: #eee;
|
||||
margin: -1px;
|
||||
.fa-minus-square-o, .fa-plus-square-o {
|
||||
color: @colortheme_sidebar-left-fg;
|
||||
}
|
||||
}
|
||||
|
||||
.cp-app-drive-element-selected-tmp {
|
||||
border: 1px dotted #bbb;
|
||||
background: #AAA;
|
||||
color: #ddd;
|
||||
margin: -1px;
|
||||
.fa-minus-square-o, .fa-plus-square-o {
|
||||
color: @colortheme_sidebar-left-fg;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
&.fa-folder, &.fa-folder-open {
|
||||
//color: #FEDE8B;
|
||||
//text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;
|
||||
}
|
||||
}
|
||||
|
||||
/* TREE */
|
||||
|
||||
|
||||
#cp-app-drive-tree {
|
||||
font-size: @colortheme_app-font-size;
|
||||
//border-right: 1px solid #ccc;
|
||||
box-sizing: border-box;
|
||||
background: @colortheme_sidebar-left-bg;
|
||||
overflow: auto;
|
||||
resize: horizontal;
|
||||
width: auto;
|
||||
white-space: nowrap;
|
||||
max-width: 500px;
|
||||
min-width: 200px;
|
||||
padding: 0px;
|
||||
color: @colortheme_sidebar-left-fg;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
max-height: 100%;
|
||||
.cp-app-drive-tree-categories-container {
|
||||
flex: 1;
|
||||
max-width: 500px;
|
||||
overflow: auto;
|
||||
}
|
||||
img.cp-app-drive-icon {
|
||||
margin-bottom: 3px;
|
||||
margin-left: -2px;
|
||||
}
|
||||
.cp-app-drive-tree-docs {
|
||||
margin-top: 20px;
|
||||
//padding: 0 0 0 20px;
|
||||
padding: 0;
|
||||
cursor: auto;
|
||||
&li, li {
|
||||
padding: 0;
|
||||
&.cp-app-drive-element-collapsed ul {
|
||||
display: none;
|
||||
}
|
||||
input {
|
||||
width: ~"calc(100% - 30px)";
|
||||
padding: 0 10px;
|
||||
border: 0;
|
||||
color: lighten(@colortheme_sidebar-left-fg, 40%);
|
||||
}
|
||||
& > span.cp-app-drive-element-row {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
//min-width: ~"calc(100% + 5px)";
|
||||
.leftside-menu-category_main();
|
||||
width: ~"calc(100% + 5px)";
|
||||
margin: 0;
|
||||
margin-bottom: -6px;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
margin-left: -5px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
& > span.cp-app-drive-element-row:not(.cp-app-drive-element-selected):not(.cp-app-drive-element-active):hover {
|
||||
}
|
||||
}
|
||||
}
|
||||
span.cp-app-drive-element {
|
||||
cursor: pointer;
|
||||
}
|
||||
.cp-app-drive-tree-category {
|
||||
margin: 0;
|
||||
margin-top: 15px;
|
||||
.cp-app-drive-tree-root {
|
||||
&> .fa {
|
||||
min-width: 30px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
li {
|
||||
padding: 0;
|
||||
.cp-app-drive-element-row {
|
||||
display: block;
|
||||
padding-left: 20px;
|
||||
.leftside-menu-category_main();
|
||||
margin: 0;
|
||||
.fa {
|
||||
width: 25px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-app-drive-tree-category:last-child {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.limit-container {
|
||||
margin-top: 0;
|
||||
}
|
||||
#cp-app-drive-tree-search {
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
input {
|
||||
background: lighten(@colortheme_drive-bg, 8%);
|
||||
color: @colortheme_drive-color;
|
||||
.tools_placeholder-color(@colortheme_drive-color);
|
||||
outline-width: 0px;
|
||||
border-radius: 0;
|
||||
width: 100%;
|
||||
//border: 1px solid #ccc;
|
||||
border: 0;
|
||||
border-right: 1px solid lighten(@colortheme_drive-bg, 16%);
|
||||
//border-right: 0;
|
||||
height: @variables_bar-height;
|
||||
padding: 0 5px;
|
||||
padding-left: 45px;
|
||||
&:focus {
|
||||
outline-width: 0px;
|
||||
}
|
||||
}
|
||||
.cp-app-drive-tree-search-con {
|
||||
color: @colortheme_drive-color;
|
||||
position: absolute;
|
||||
left: 20px; // TODO align with drive categories
|
||||
top: 8px;
|
||||
}
|
||||
}
|
||||
.fa.cp-app-drive-icon-expcol {
|
||||
margin-left: -10px;
|
||||
font-size: 14px;
|
||||
position: absolute;
|
||||
left: -20px;
|
||||
top: 10px;
|
||||
width: 11px !important;
|
||||
height: 11px !important;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background: white;
|
||||
z-index: 10;
|
||||
cursor: default;
|
||||
&:before {
|
||||
position:relative;
|
||||
top: -1px;
|
||||
}
|
||||
}
|
||||
.cp-app-drive-tree-docs {
|
||||
.cp-app-drive-tree-root > .cp-app-drive-element-row > .cp-app-drive-icon-expcol {
|
||||
position: relative;
|
||||
top:0;
|
||||
left: -10px;
|
||||
}
|
||||
.cp-app-drive-tree-root > .cp-app-drive-element-row > .cp-app-drive-icon-folder {
|
||||
margin-left: -5px;
|
||||
}
|
||||
.cp-app-drive-tree-root {
|
||||
&> .cp-app-drive-element-row {
|
||||
padding-left: 20px;
|
||||
}
|
||||
&> ul {
|
||||
padding-left: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Expand/collapse lines
|
||||
.cp-app-drive-tree-docs ul {
|
||||
margin: 0px 0px 0px 10px;
|
||||
list-style: none;
|
||||
padding-left: 10px;
|
||||
li {
|
||||
position: relative;
|
||||
&:before {
|
||||
position: absolute;
|
||||
left: -15px;
|
||||
top: -11px;
|
||||
content: '';
|
||||
display: block;
|
||||
border-left: 1px solid @colortheme_sidebar-left-branch;
|
||||
height: ~"calc(1em + 11px)";
|
||||
border-bottom: 1px solid @colortheme_sidebar-left-branch;
|
||||
width: 15px;
|
||||
}
|
||||
&:after {
|
||||
position: absolute;
|
||||
left: -15px;
|
||||
bottom: -7px;
|
||||
content: '';
|
||||
display: block;
|
||||
border-left: 1px solid @colortheme_sidebar-left-branch;
|
||||
height: 100%;
|
||||
}
|
||||
&.cp-app-drive-tree-root {
|
||||
margin: 0px 0px 0px -10px;
|
||||
&:before {
|
||||
display: none;
|
||||
}
|
||||
&:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&:last-child:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* CONTENT */
|
||||
#cp-app-drive-content-container {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
flex: 1;
|
||||
// Needed to avoid the folder's path to overflows
|
||||
// https://stackoverflow.com/questions/38223879/white-space-nowrap-breaks-flexbox-layout
|
||||
min-width: 0;
|
||||
}
|
||||
#cp-app-drive-content {
|
||||
box-sizing: border-box;
|
||||
background: @drive_content-bg;
|
||||
color: @drive_content-fg;
|
||||
overflow: auto;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
position: relative;
|
||||
.cp-app-drive-content-select-box {
|
||||
display: none;
|
||||
background-color: rgba(100, 100, 100, 0.7);
|
||||
position: absolute;
|
||||
z-index: 50;
|
||||
}
|
||||
&.cp-app-drive-readonly {
|
||||
background: @drive_content-bg-ro;
|
||||
}
|
||||
h1 {
|
||||
padding-left: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.cp-app-drive-content-info-box {
|
||||
line-height: 2em;
|
||||
padding: 0.25em 0.75em;
|
||||
margin: 1em;
|
||||
background: @drive_info-box-bg;
|
||||
span {
|
||||
cursor: pointer;
|
||||
float: right;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
}
|
||||
li {
|
||||
cursor: default;
|
||||
&:not(.cp-app-drive-element-header) {
|
||||
&:hover {
|
||||
&:not(.-cp-app-drive-element-selected, .cp-app-drive-element-selected-tmp) {
|
||||
background-color: @drive_hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#cp-app-drive-content-folder {
|
||||
li {
|
||||
&.cp-app-drive-search-result {
|
||||
border-bottom: 1px solid @drive_info-box-border;
|
||||
display: block;
|
||||
&:hover {
|
||||
background-color: initial;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
.cp-app-drive-search-label2 {
|
||||
width: 150px;
|
||||
font-size: 15px;
|
||||
text-align: right;
|
||||
padding-right: 15px;
|
||||
}
|
||||
.cp-app-drive-search-opendir {
|
||||
a {
|
||||
cursor: pointer;
|
||||
color: #41b7d8;
|
||||
&:hover {
|
||||
color: #014c8c;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-app-drive-search-path {
|
||||
font-style: italic;
|
||||
direction: rtl;
|
||||
.cp-app-drive-path-element {
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
.cp-app-drive-search-title {
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: @drive_hover;
|
||||
}
|
||||
}
|
||||
.cp-app-drive-search-col2 {
|
||||
width: 250px;
|
||||
}
|
||||
td.cp-app-drive-search-icon {
|
||||
width: 50px;
|
||||
font-size: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-app-drive-element {
|
||||
.cp-app-drive-element-truncated { display: none; }
|
||||
}
|
||||
div.cp-app-drive-content-grid {
|
||||
padding: 20px;
|
||||
.drive_fileIcon;
|
||||
li {
|
||||
&.cp-app-drive-element {
|
||||
position: relative;
|
||||
}
|
||||
input {
|
||||
width: 100%;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.cp-app-drive-element-state {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
right: 3px;
|
||||
.fa {
|
||||
margin:0;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-app-drive-element-list {
|
||||
display: none;
|
||||
}
|
||||
.cp-app-drive-new-ghost {
|
||||
cursor: pointer;
|
||||
opacity: 0.5;
|
||||
padding: 0;
|
||||
&:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
.fa {
|
||||
cursor: pointer;
|
||||
font-size: 90px;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cp-app-drive-content-list {
|
||||
.cp-app-drive-element-grid {
|
||||
display: none;
|
||||
}
|
||||
// Make it act as a table!
|
||||
padding-left: 20px;
|
||||
ul {
|
||||
display: table;
|
||||
width: 100%;
|
||||
padding: 0px 10px;
|
||||
}
|
||||
li {
|
||||
display: table-row;
|
||||
&> span {
|
||||
padding: 0 5px;
|
||||
display: table-cell;
|
||||
}
|
||||
&:not(.cp-app-drive-element-header) {
|
||||
height: @variables_bar-height;
|
||||
line-height: @variables_bar-height;
|
||||
}
|
||||
&.cp-app-drive-element-header {
|
||||
cursor: default;
|
||||
color: @drive_table-header-fg;
|
||||
span {
|
||||
&:not(.fa) {
|
||||
text-align: left;
|
||||
}
|
||||
&.sortasc, &.sortdesc {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
&> span {
|
||||
padding: 15px 5px;
|
||||
&.cp-app-drive-sort-active {
|
||||
font-weight: bold;
|
||||
}
|
||||
&.cp-app-drive-sort-clickable {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: @drive_table-header-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-app-drive-element {
|
||||
span {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
box-sizing: border-box;
|
||||
&.cp-app-drive-element-state {
|
||||
.fa:not(:last-child) {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
&.cp-app-drive-content-icon, &.cp-app-drive-element-state {
|
||||
width: 30px;
|
||||
}
|
||||
&.cp-app-drive-element-type, &.cp-app-drive-element-atime, &.cp-app-drive-element-ctime {
|
||||
width: 175px;
|
||||
}
|
||||
&.cp-app-drive-element-title {
|
||||
width: 250px;
|
||||
@media screen and (max-width: 1200px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&.cp-app-drive-element-folders, &.cp-app-drive-element-files {
|
||||
width: 150px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#cp-app-drive-content-folder {
|
||||
padding-right: 10px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#cp-app-drive-new-ghost-dialog.cp-modal-container {
|
||||
.drive_fileIcon;
|
||||
|
||||
li:not(.cp-app-drive-element-selected):hover {
|
||||
border: 1px solid white;
|
||||
}
|
||||
.cp-modal {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
li, li .fa {
|
||||
cursor: pointer;
|
||||
}
|
||||
&> p {
|
||||
margin: 50px;
|
||||
}
|
||||
&> div {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
overflow-y: auto;
|
||||
.cp-app-drive-new-upload {
|
||||
break-after: always;
|
||||
page-break-after: always;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-height: @browser_media-not-big) {
|
||||
.cp-modal {
|
||||
& > p {
|
||||
display: none;
|
||||
}
|
||||
& > div {
|
||||
align-content: unset;
|
||||
li {
|
||||
height: 40px;
|
||||
width: 90%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.fa {
|
||||
font-size: 32px;
|
||||
}
|
||||
.cp-app-drive-new-name {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Toolbar */
|
||||
|
||||
#cp-app-drive-toolbar {
|
||||
background: lighten(@colortheme_drive-bg, 8%);
|
||||
color: @colortheme_drive-color;
|
||||
//height: 30px;
|
||||
//display: flex;
|
||||
//flex-flow: row;
|
||||
z-index: 100;
|
||||
box-sizing: border-box;
|
||||
height: @variables_bar-height;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
|
||||
* {
|
||||
outline-width: 0;
|
||||
&:focus {
|
||||
outline-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.history {
|
||||
float: right;
|
||||
.cp-toolbar-drawer-element {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.cp-app-drive-toolbar-rightside, .cp-app-drive-toolbar-leftside {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
.fa {
|
||||
margin: 0;
|
||||
}
|
||||
button {
|
||||
height: @variables_bar-height;
|
||||
padding: 0 10px;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
box-sizing: border-box;
|
||||
background: transparent;
|
||||
font-size: @colortheme_app-font-size;
|
||||
color: @colortheme_drive-color;
|
||||
transition: all 0.15s;
|
||||
.drawer {
|
||||
display: none;
|
||||
}
|
||||
.fa, span {
|
||||
font-size: @colortheme_app-font-size;
|
||||
}
|
||||
&:hover {
|
||||
background: @colortheme_drive-bg;
|
||||
}
|
||||
&.cp-app-drive-toolbar-active {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.cp-app-drive-toolbar-rightside {
|
||||
float: right;
|
||||
& > * {
|
||||
float: right;
|
||||
}
|
||||
#cp-app-drive-toolbar-contextbuttons {
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
}
|
||||
padding-left: 10px;
|
||||
}
|
||||
.cp-app-drive-toolbar-leftside {
|
||||
& > span {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
button {
|
||||
padding: 0 10px;
|
||||
.fa {
|
||||
margin-right: 5px;
|
||||
}
|
||||
.cp-dropdown-button-title {
|
||||
display: inline-flex;
|
||||
height: @variables_bar-height;
|
||||
align-items: center;
|
||||
span:not(.fa) {
|
||||
line-height: 23px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
font: @colortheme_app-font;
|
||||
span {
|
||||
font: @colortheme_app-font;
|
||||
}
|
||||
.fa, &.fa {
|
||||
font-family: FontAwesome;
|
||||
}
|
||||
}
|
||||
/* The container <div> - needed to position the dropdown content */
|
||||
.cp-dropdown-container {
|
||||
margin: 2px 2px;
|
||||
line-height: 1em;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
.cp-dropdown-content {
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.cp-app-drive-path {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: @variables_bar-height;
|
||||
line-height: @variables_bar-height;
|
||||
cursor: default;
|
||||
width: auto;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
direction: rtl;
|
||||
max-width: 100%;
|
||||
text-align: left;
|
||||
.cp-app-drive-path-element {
|
||||
display: inline-block;
|
||||
height: @variables_bar-height;
|
||||
line-height: @variables_bar-height;
|
||||
font-size: @colortheme_app-font-size;
|
||||
padding: 0 5px;
|
||||
border: 0;
|
||||
background: darken(@colortheme_drive-bg, 10%);
|
||||
color: @colortheme_drive-color;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.15s;
|
||||
&.cp-app-drive-path-separator {
|
||||
color: #ccc;
|
||||
}
|
||||
&.cp-app-drive-path-lickable {
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: darken(@colortheme_drive-bg, 15%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,18 +1,19 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="cp">
|
||||
<html>
|
||||
<head>
|
||||
<title>CryptDrive</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>CryptPad</title>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<script async data-bootload="/customize/template.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="referrer" content="no-referrer" />
|
||||
<script async data-bootload="main.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
#pad-iframe {
|
||||
#sbox-iframe {
|
||||
position:fixed;
|
||||
top:0;
|
||||
top:0px;
|
||||
left:0px;
|
||||
bottom:0px;
|
||||
right:0px;
|
||||
|
@ -23,7 +24,15 @@
|
|||
padding:0;
|
||||
overflow:hidden;
|
||||
}
|
||||
#sbox-filePicker-iframe {
|
||||
position: fixed;
|
||||
top:0; left:0;
|
||||
bottom:0; right:0;
|
||||
width:100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<iframe id="pad-iframe"></iframe><script src="/common/noscriptfix.js"></script>
|
||||
<iframe id="sbox-iframe">
|
||||
|
|
|
@ -1,57 +1,60 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html class="cp-app-noscroll">
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<script src="/bower_components/jquery/dist/jquery.min.js"></script>
|
||||
<script async data-bootload="/drive/inner.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<script async data-bootload="/drive/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<style>
|
||||
.loading-hidden { display: none; }
|
||||
#editor1 { display: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body style="display: none;">
|
||||
<div id="toolbar" class="toolbar-container"></div>
|
||||
<div class="app-container" tabindex="0">
|
||||
<div id="tree">
|
||||
<body class="cp-app-drive">
|
||||
<div id="cp-toolbar" class="cp-toolbar-container"></div>
|
||||
<div class="cp-app-drive-container" tabindex="0">
|
||||
<div id="cp-app-drive-tree">
|
||||
</div>
|
||||
<div id="rightCol">
|
||||
<div id="driveToolbar"></div>
|
||||
<div id="content" tabindex="2"></div>
|
||||
<div id="cp-app-drive-content-container">
|
||||
<div id="cp-app-drive-toolbar"></div>
|
||||
<div id="cp-app-drive-content" tabindex="2"></div>
|
||||
</div>
|
||||
<div id="treeContextMenu" class="contextMenu dropdown clearfix unselectable">
|
||||
<div id="cp-app-drive-context-tree" class="cp-app-drive-context dropdown cp-unselectable">
|
||||
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu" style="display:block;position:static;margin-bottom:5px;">
|
||||
<li><a tabindex="-1" data-icon="fa-folder-open" class="open dropdown-item" data-localization="fc_open">Open</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-eye" class="open_ro dropdown-item" data-localization="fc_open_ro">Open (read-only)</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-pencil" class="rename editable dropdown-item" data-localization="fc_rename">Rename</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-trash" class="delete editable dropdown-item" data-localization="fc_delete">Delete</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-folder" class="newfolder editable dropdown-item" data-localization="fc_newfolder">New folder</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-database" class="properties dropdown-item" data-localization="fc_prop">Properties</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-folder-open" class="cp-app-drive-context-open dropdown-item" data-localization="fc_open">Open</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-eye" class="cp-app-drive-context-openro dropdown-item" data-localization="fc_open_ro">Open (read-only)</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-pencil" class="cp-app-drive-context-rename cp-app-drive-context-editable dropdown-item" data-localization="fc_rename">Rename</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-trash" class="cp-app-drive-context-delete cp-app-drive-context-editable dropdown-item" data-localization="fc_delete">Delete</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-folder" class="cp-app-drive-context-newfolder cp-app-drive-context-editable dropdown-item" data-localization="fc_newfolder">New folder</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-database" class="cp-app-drive-context-properties dropdown-item" data-localization="fc_prop">Properties</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="contentContextMenu" class="contextMenu dropdown clearfix unselectable">
|
||||
<div id="cp-app-drive-context-content" class="cp-app-drive-context dropdown cp-unselectable">
|
||||
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu" style="display:block;position:static;margin-bottom:5px;">
|
||||
<li><a tabindex="-1" data-icon="fa-folder" class="newfolder editable dropdown-item" data-localization="fc_newfolder">New folder</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-file-word-o" class="newdoc own editable dropdown-item" data-type="pad" data-localization="button_newpad">New pad</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-file-code-o" class="newdoc own editable dropdown-item" data-type="code" data-localization="button_newcode">New code</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-file-powerpoint-o" class="newdoc own editable dropdown-item" data-type="slide" data-localization="button_newslide">New slide</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-calendar" class="newdoc own editable dropdown-item" data-type="poll" data-localization="button_newpoll">New poll</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-paint-brush" class="newdoc own editable dropdown-item" data-type="whiteboard" data-localization="button_newwhiteboard">New whiteboard</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-folder" class="cp-app-drive-context-newfolder cp-app-drive-context-editable dropdown-item" data-localization="fc_newfolder">New folder</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-file-word-o" class="cp-app-drive-context-newdoc cp-app-drive-context-own cp-app-drive-context-editable dropdown-item" data-type="pad" data-localization="button_newpad">New pad</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-file-code-o" class="cp-app-drive-context-newdoc cp-app-drive-context-own cp-app-drive-context-editable dropdown-item" data-type="code" data-localization="button_newcode">New code</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-file-powerpoint-o" class="cp-app-drive-context-newdoc cp-app-drive-context-own cp-app-drive-context-editable dropdown-item" data-type="slide" data-localization="button_newslide">New slide</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-calendar" class="cp-app-drive-context-newdoc cp-app-drive-context-own cp-app-drive-context-editable dropdown-item" data-type="poll" data-localization="button_newpoll">New poll</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-paint-brush" class="cp-app-drive-context-newdoc cp-app-drive-context-own cp-app-drive-context-editable dropdown-item" data-type="whiteboard" data-localization="button_newwhiteboard">New whiteboard</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="defaultContextMenu" class="contextMenu dropdown clearfix unselectable">
|
||||
<div id="cp-app-drive-context-default" class="cp-app-drive-context dropdown cp-unselectable">
|
||||
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu" style="display:block;position:static;margin-bottom:5px;">
|
||||
<li><a tabindex="-1" data-icon="fa-folder-open" class="open dropdown-item" data-localization="fc_open">Open</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-eye" class="open_ro dropdown-item" data-localization="fc_open_ro">Open (read-only)</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-trash" class="delete dropdown-item" data-localization="fc_delete">Delete</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-database" class="properties dropdown-item" data-localization="fc_prop">Properties</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-folder-open" class="cp-app-drive-context-open dropdown-item" data-localization="fc_open">Open</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-eye" class="cp-app-drive-context-openro dropdown-item" data-localization="fc_open_ro">Open (read-only)</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-trash" class="cp-app-drive-context-delete dropdown-item" data-localization="fc_delete">Delete</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-database" class="cp-app-drive-context-properties dropdown-item" data-localization="fc_prop">Properties</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="trashTreeContextMenu" class="contextMenu dropdown clearfix unselectable">
|
||||
<div id="cp-app-drive-context-trashtree" class="cp-app-drive-context dropdown cp-unselectable">
|
||||
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu" style="display:block;position:static;margin-bottom:5px;">
|
||||
<li><a tabindex="-1" data-icon="fa-trash-o" class="empty editable dropdown-item" data-localization="fc_empty">Empty the trash</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-trash-o" class="cp-app-drive-context-empty cp-app-drive-context-editable dropdown-item" data-localization="fc_empty">Empty the trash</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="trashContextMenu" class="contextMenu dropdown clearfix unselectable">
|
||||
<div id="cp-app-drive-context-trash" class="cp-app-drive-context dropdown cp-unselectable">
|
||||
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu" style="display:block;position:static;margin-bottom:5px;">
|
||||
<li><a tabindex="-1" data-icon="fa-eraser" class="remove editable dropdown-item" data-localization="fc_remove">Delete permanently</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-repeat" class="restore editable dropdown-item" data-localization="fc_restore">Restore</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-database" class="properties dropdown-item" data-localization="fc_prop">Properties</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-eraser" class="cp-app-drive-context-remove cp-app-drive-context-editable dropdown-item" data-localization="fc_remove">Delete permanently</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-repeat" class="cp-app-drive-context-restore cp-app-drive-context-editable dropdown-item" data-localization="fc_restore">Restore</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-database" class="cp-app-drive-context-properties dropdown-item" data-localization="fc_prop">Properties</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
3029
www/drive/inner.js
3029
www/drive/inner.js
File diff suppressed because it is too large
Load Diff
3017
www/drive/main.js
3017
www/drive/main.js
File diff suppressed because it is too large
Load Diff
|
@ -243,6 +243,7 @@ define([
|
|||
}, "DRIVE4: migration and fixFiles with a pad in trash not root");
|
||||
|
||||
// Pad attributes migration
|
||||
/*
|
||||
assert(function (cb) {
|
||||
console.log('START PAD ATTRIBUTES');
|
||||
var files = JSON.parse(JSON.stringify(example));
|
||||
|
@ -253,6 +254,7 @@ define([
|
|||
return cb(files.filesData[id1].userid === 'value'
|
||||
&& files.filesData[id1].previewMode);
|
||||
}, "PAD ATTRIBUTES");
|
||||
*/
|
||||
|
||||
// userObject Tests
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<html class="cp-app-noscroll">
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<script async data-bootload="/file/inner.js" data-main="/common/sframe-boot.js?ver=1.4" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<script async data-bootload="/file/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<style>
|
||||
.loading-hidden { display: none; }
|
||||
#editor1 { display: none; }
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
<html style="height: 100%; background: transparent;">
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<script async data-bootload="/filepicker/inner.js" data-main="/common/sframe-boot.js?ver=1.4" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<script async data-bootload="/filepicker/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<style>
|
||||
.loading-hidden { display: none; }
|
||||
body #loading {
|
||||
body #cp-loading {
|
||||
position: absolute;
|
||||
top: 15vh;
|
||||
bottom: 15vh;
|
||||
|
@ -14,10 +14,10 @@
|
|||
z-index: 200000;
|
||||
overflow: hidden;
|
||||
}
|
||||
body #loading .loadingContainer {
|
||||
body #cp-loading .cp-loading-container {
|
||||
margin-top: 35vh;
|
||||
}
|
||||
body #loading .cryptofist {
|
||||
body #cp-loading .cp-loading-cryptofist {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
@import "/customize/src/less/variables.less";
|
||||
@import "/customize/src/less/mixins.less";
|
||||
@import (once) "/customize/src/less2/include/tools.less";
|
||||
|
||||
@tree-bg: #eee;
|
||||
@tree-fg: #000;
|
||||
|
@ -267,7 +268,7 @@ span {
|
|||
input {
|
||||
background: lighten(@toolbar-drive-bg, 8%);
|
||||
color: @toolbar-drive-color;
|
||||
.placeholderColor(@toolbar-drive-color);
|
||||
.tools_placeholder-color(@toolbar-drive-color);
|
||||
outline-width: 0px;
|
||||
border-radius: 0;
|
||||
width: 100%;
|
|
@ -0,0 +1,29 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="cp">
|
||||
<head>
|
||||
<title>CryptDrive</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<script async data-bootload="/customize/template.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
#pad-iframe {
|
||||
position:fixed;
|
||||
top:0;
|
||||
left:0px;
|
||||
bottom:0px;
|
||||
right:0px;
|
||||
width:100%;
|
||||
height:100%;
|
||||
border:none;
|
||||
margin:0;
|
||||
padding:0;
|
||||
overflow:hidden;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<iframe id="pad-iframe"></iframe><script src="/common/noscriptfix.js"></script>
|
|
@ -0,0 +1,60 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<script src="/bower_components/jquery/dist/jquery.min.js"></script>
|
||||
<script async data-bootload="/drive/inner.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
</head>
|
||||
<body style="display: none;">
|
||||
<div id="toolbar" class="toolbar-container"></div>
|
||||
<div class="app-container" tabindex="0">
|
||||
<div id="tree">
|
||||
</div>
|
||||
<div id="rightCol">
|
||||
<div id="driveToolbar"></div>
|
||||
<div id="content" tabindex="2"></div>
|
||||
</div>
|
||||
<div id="treeContextMenu" class="contextMenu dropdown clearfix unselectable">
|
||||
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu" style="display:block;position:static;margin-bottom:5px;">
|
||||
<li><a tabindex="-1" data-icon="fa-folder-open" class="open dropdown-item" data-localization="fc_open">Open</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-eye" class="open_ro dropdown-item" data-localization="fc_open_ro">Open (read-only)</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-pencil" class="rename editable dropdown-item" data-localization="fc_rename">Rename</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-trash" class="delete editable dropdown-item" data-localization="fc_delete">Delete</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-folder" class="newfolder editable dropdown-item" data-localization="fc_newfolder">New folder</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-database" class="properties dropdown-item" data-localization="fc_prop">Properties</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="contentContextMenu" class="contextMenu dropdown clearfix unselectable">
|
||||
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu" style="display:block;position:static;margin-bottom:5px;">
|
||||
<li><a tabindex="-1" data-icon="fa-folder" class="newfolder editable dropdown-item" data-localization="fc_newfolder">New folder</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-file-word-o" class="newdoc own editable dropdown-item" data-type="pad" data-localization="button_newpad">New pad</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-file-code-o" class="newdoc own editable dropdown-item" data-type="code" data-localization="button_newcode">New code</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-file-powerpoint-o" class="newdoc own editable dropdown-item" data-type="slide" data-localization="button_newslide">New slide</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-calendar" class="newdoc own editable dropdown-item" data-type="poll" data-localization="button_newpoll">New poll</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-paint-brush" class="newdoc own editable dropdown-item" data-type="whiteboard" data-localization="button_newwhiteboard">New whiteboard</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="defaultContextMenu" class="contextMenu dropdown clearfix unselectable">
|
||||
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu" style="display:block;position:static;margin-bottom:5px;">
|
||||
<li><a tabindex="-1" data-icon="fa-folder-open" class="open dropdown-item" data-localization="fc_open">Open</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-eye" class="open_ro dropdown-item" data-localization="fc_open_ro">Open (read-only)</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-trash" class="delete dropdown-item" data-localization="fc_delete">Delete</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-database" class="properties dropdown-item" data-localization="fc_prop">Properties</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="trashTreeContextMenu" class="contextMenu dropdown clearfix unselectable">
|
||||
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu" style="display:block;position:static;margin-bottom:5px;">
|
||||
<li><a tabindex="-1" data-icon="fa-trash-o" class="empty editable dropdown-item" data-localization="fc_empty">Empty the trash</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="trashContextMenu" class="contextMenu dropdown clearfix unselectable">
|
||||
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu" style="display:block;position:static;margin-bottom:5px;">
|
||||
<li><a tabindex="-1" data-icon="fa-eraser" class="remove editable dropdown-item" data-localization="fc_remove">Delete permanently</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-repeat" class="restore editable dropdown-item" data-localization="fc_restore">Restore</a></li>
|
||||
<li><a tabindex="-1" data-icon="fa-database" class="properties dropdown-item" data-localization="fc_prop">Properties</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
define([
|
||||
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
||||
'less!/drive/file.less',
|
||||
'less!/customize/src/less/cryptpad.less',
|
||||
'less!/customize/src/less/toolbar.less',
|
||||
], function () {});
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,52 @@
|
|||
define(function () {
|
||||
var padZero = function (str, len) {
|
||||
len = len || 2;
|
||||
var zeros = new Array(len).join('0');
|
||||
return (zeros + str).slice(-len);
|
||||
};
|
||||
var invertColor = function (hex) {
|
||||
if (hex.indexOf('#') === 0) {
|
||||
hex = hex.slice(1);
|
||||
}
|
||||
// convert 3-digit hex to 6-digits.
|
||||
if (hex.length === 3) {
|
||||
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
||||
}
|
||||
if (hex.length !== 6) {
|
||||
console.error(hex);
|
||||
throw new Error('Invalid HEX color.');
|
||||
}
|
||||
// invert color components
|
||||
var r = (255 - parseInt(hex.slice(0, 2), 16)).toString(16),
|
||||
g = (255 - parseInt(hex.slice(2, 4), 16)).toString(16),
|
||||
b = (255 - parseInt(hex.slice(4, 6), 16)).toString(16);
|
||||
// pad each with zeros and return
|
||||
return '#' + padZero(r) + padZero(g) + padZero(b);
|
||||
};
|
||||
var rgb2hex = function (rgb) {
|
||||
if (rgb.indexOf('#') === 0) { return rgb; }
|
||||
rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
|
||||
var hex = function (x) {
|
||||
return ("0" + parseInt(x).toString(16)).slice(-2);
|
||||
};
|
||||
return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
|
||||
};
|
||||
var hex2rgba = function (hex, opacity) {
|
||||
if (hex.indexOf('#') === 0) {
|
||||
hex = hex.slice(1);
|
||||
}
|
||||
if (hex.length === 3) {
|
||||
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
||||
}
|
||||
if (!opacity) { opacity = 1; }
|
||||
var r = parseInt(hex.slice(0,2), 16);
|
||||
var g = parseInt(hex.slice(2,4), 16);
|
||||
var b = parseInt(hex.slice(4,6), 16);
|
||||
return 'rgba('+r+', '+g+', '+b+', '+opacity+')';
|
||||
};
|
||||
return {
|
||||
invert: invertColor,
|
||||
rgb2hex: rgb2hex,
|
||||
hex2rgba: hex2rgba
|
||||
};
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="cp">
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<script async data-bootload="/customize/template.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
|
@ -0,0 +1,511 @@
|
|||
define([
|
||||
'jquery',
|
||||
'/api/config',
|
||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/common/toolbar2.js',
|
||||
'/bower_components/textpatcher/TextPatcher.amd.js',
|
||||
'json.sortify',
|
||||
'/bower_components/chainpad-json-validator/json-ot.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/cryptget.js',
|
||||
'/whiteboard/colors.js',
|
||||
'/customize/application_config.js',
|
||||
'/common/common-thumbnail.js',
|
||||
'/bower_components/secure-fabric.js/dist/fabric.min.js',
|
||||
'/bower_components/file-saver/FileSaver.min.js',
|
||||
|
||||
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
||||
'less!/customize/src/less/cryptpad.less',
|
||||
'less!/whiteboard/whiteboard.less',
|
||||
'less!/customize/src/less/toolbar.less',
|
||||
], function ($, Config, Realtime, Crypto, Toolbar, TextPatcher, JSONSortify, JsonOT, Cryptpad, Cryptget, Colors, AppConfig, Thumb) {
|
||||
var saveAs = window.saveAs;
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
||||
var module = window.APP = { $:$ };
|
||||
var Fabric = module.Fabric = window.fabric;
|
||||
|
||||
$(function () {
|
||||
Cryptpad.addLoadingScreen();
|
||||
var onConnectError = function () {
|
||||
Cryptpad.errorLoadingScreen(Messages.websocketError);
|
||||
};
|
||||
var toolbar;
|
||||
|
||||
var secret = Cryptpad.getSecrets();
|
||||
var readOnly = secret.keys && !secret.keys.editKeyStr;
|
||||
if (!secret.keys) {
|
||||
secret.keys = secret.key;
|
||||
}
|
||||
|
||||
var andThen = function () {
|
||||
/* Initialize Fabric */
|
||||
var canvas = module.canvas = new Fabric.Canvas('canvas');
|
||||
var $canvas = $('canvas');
|
||||
var $controls = $('#controls');
|
||||
var $canvasContainer = $('canvas').parents('.canvas-container');
|
||||
var $pickers = $('#pickers');
|
||||
var $colors = $('#colors');
|
||||
var $cursors = $('#cursors');
|
||||
var $deleteButton = $('#delete');
|
||||
|
||||
var brush = {
|
||||
color: '#000000',
|
||||
opacity: 1
|
||||
};
|
||||
|
||||
var $toggle = $('#toggleDraw');
|
||||
var $width = $('#width');
|
||||
var $widthLabel = $('label[for="width"]');
|
||||
var $opacity = $('#opacity');
|
||||
var $opacityLabel = $('label[for="opacity"]');
|
||||
window.canvas = canvas;
|
||||
var createCursor = function () {
|
||||
var w = canvas.freeDrawingBrush.width;
|
||||
var c = canvas.freeDrawingBrush.color;
|
||||
var size = w > 30 ? w+2 : w+32;
|
||||
$cursors.html('<canvas width="'+size+'" height="'+size+'"></canvas>');
|
||||
var $ccanvas = $cursors.find('canvas');
|
||||
var ccanvas = $ccanvas[0];
|
||||
|
||||
var ctx = ccanvas.getContext('2d');
|
||||
var centerX = size / 2;
|
||||
var centerY = size / 2;
|
||||
var radius = w/2;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
|
||||
ctx.fillStyle = c;
|
||||
ctx.fill();
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = brush.color;
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(size/2, 0); ctx.lineTo(size/2, 10);
|
||||
ctx.moveTo(size/2, size); ctx.lineTo(size/2, size-10);
|
||||
ctx.moveTo(0, size/2); ctx.lineTo(10, size/2);
|
||||
ctx.moveTo(size, size/2); ctx.lineTo(size-10, size/2);
|
||||
ctx.strokeStyle = '#000000';
|
||||
ctx.stroke();
|
||||
|
||||
var img = ccanvas.toDataURL("image/png");
|
||||
$controls.find('.selected > img').attr('src', img);
|
||||
canvas.freeDrawingCursor = 'url('+img+') '+size/2+' '+size/2+', crosshair';
|
||||
};
|
||||
|
||||
var updateBrushWidth = function () {
|
||||
var val = $width.val();
|
||||
canvas.freeDrawingBrush.width = Number(val);
|
||||
$widthLabel.text(Cryptpad.Messages._getKey("canvas_widthLabel", [val]));
|
||||
$('#width-val').text(val + 'px');
|
||||
createCursor();
|
||||
};
|
||||
updateBrushWidth();
|
||||
|
||||
$width.on('change', updateBrushWidth);
|
||||
|
||||
var updateBrushOpacity = function () {
|
||||
var val = $opacity.val();
|
||||
brush.opacity = Number(val);
|
||||
canvas.freeDrawingBrush.color = Colors.hex2rgba(brush.color, brush.opacity);
|
||||
$opacityLabel.text(Cryptpad.Messages._getKey("canvas_opacityLabel", [val]));
|
||||
$('#opacity-val').text((Number(val) * 100) + '%');
|
||||
createCursor();
|
||||
};
|
||||
updateBrushOpacity();
|
||||
|
||||
$opacity.on('change', updateBrushOpacity);
|
||||
|
||||
var pickColor = function (current, cb) {
|
||||
var $picker = $('<input>', {
|
||||
type: 'color',
|
||||
value: '#FFFFFF',
|
||||
})
|
||||
// TODO confirm that this is safe to remove
|
||||
//.css({ visibility: 'hidden' })
|
||||
.on('change', function () {
|
||||
var color = this.value;
|
||||
cb(color);
|
||||
}).appendTo($pickers);
|
||||
setTimeout(function () {
|
||||
$picker.val(current);
|
||||
$picker.click();
|
||||
});
|
||||
};
|
||||
|
||||
var setColor = function (c) {
|
||||
c = Colors.rgb2hex(c);
|
||||
brush.color = c;
|
||||
canvas.freeDrawingBrush.color = Colors.hex2rgba(brush.color, brush.opacity);
|
||||
module.$color.css({
|
||||
'color': c,
|
||||
});
|
||||
createCursor();
|
||||
};
|
||||
|
||||
|
||||
var palette = AppConfig.whiteboardPalette || [
|
||||
'red', 'blue', 'green', 'white', 'black', 'purple',
|
||||
'gray', 'beige', 'brown', 'cyan', 'darkcyan', 'gold', 'yellow', 'pink'
|
||||
];
|
||||
|
||||
$('.palette-color').on('click', function () {
|
||||
var color = $(this).css('background-color');
|
||||
setColor(color);
|
||||
});
|
||||
|
||||
module.draw = true;
|
||||
var toggleDrawMode = function () {
|
||||
module.draw = !module.draw;
|
||||
canvas.isDrawingMode = module.draw;
|
||||
$toggle.text(module.draw ? Messages.canvas_disable : Messages.canvas_enable);
|
||||
if (module.draw) { $deleteButton.hide(); }
|
||||
else { $deleteButton.show(); }
|
||||
};
|
||||
$toggle.click(toggleDrawMode);
|
||||
|
||||
var deleteSelection = function () {
|
||||
if (canvas.getActiveObject()) {
|
||||
canvas.getActiveObject().remove();
|
||||
}
|
||||
if (canvas.getActiveGroup()) {
|
||||
canvas.getActiveGroup()._objects.forEach(function (el) {
|
||||
el.remove();
|
||||
});
|
||||
canvas.discardActiveGroup();
|
||||
}
|
||||
canvas.renderAll();
|
||||
module.onLocal();
|
||||
};
|
||||
$deleteButton.click(deleteSelection);
|
||||
$(window).on('keyup', function (e) {
|
||||
if (e.which === 46) { deleteSelection (); }
|
||||
});
|
||||
|
||||
var setEditable = function (bool) {
|
||||
if (readOnly && bool) { return; }
|
||||
if (bool) { $controls.css('display', 'flex'); }
|
||||
else { $controls.hide(); }
|
||||
|
||||
canvas.isDrawingMode = bool ? module.draw : false;
|
||||
if (!bool) {
|
||||
canvas.deactivateAll();
|
||||
canvas.renderAll();
|
||||
}
|
||||
canvas.forEachObject(function (object) {
|
||||
object.selectable = bool;
|
||||
});
|
||||
$canvasContainer.find('canvas').css('border-color', bool? 'black': 'red');
|
||||
};
|
||||
|
||||
var saveImage = module.saveImage = function () {
|
||||
var defaultName = "pretty-picture.png";
|
||||
Cryptpad.prompt(Messages.exportPrompt, defaultName, function (filename) {
|
||||
if (!(typeof(filename) === 'string' && filename)) { return; }
|
||||
$canvas[0].toBlob(function (blob) {
|
||||
saveAs(blob, filename);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.FM = Cryptpad.createFileManager({});
|
||||
module.upload = function (title) {
|
||||
var canvas = $canvas[0];
|
||||
var finish = function (thumb) {
|
||||
canvas.toBlob(function (blob) {
|
||||
blob.name = title;
|
||||
module.FM.handleFile(blob, void 0, thumb);
|
||||
});
|
||||
};
|
||||
|
||||
Thumb.fromCanvas(canvas, function (e, blob) {
|
||||
// carry on even if you can't get a thumbnail
|
||||
if (e) { console.error(e); }
|
||||
finish(blob);
|
||||
});
|
||||
};
|
||||
|
||||
var initializing = true;
|
||||
|
||||
var $bar = $('#toolbar');
|
||||
|
||||
var Title;
|
||||
var UserList;
|
||||
var Metadata;
|
||||
|
||||
var config = module.config = {
|
||||
initialState: '{}',
|
||||
websocketURL: Cryptpad.getWebsocketURL(),
|
||||
validateKey: secret.keys.validateKey,
|
||||
readOnly: readOnly,
|
||||
channel: secret.channel,
|
||||
crypto: Crypto.createEncryptor(secret.keys),
|
||||
transformFunction: JsonOT.transform,
|
||||
};
|
||||
|
||||
var addColorToPalette = function (color, i) {
|
||||
if (readOnly) { return; }
|
||||
var $color = $('<span>', {
|
||||
'class': 'palette-color',
|
||||
})
|
||||
.css({
|
||||
'background-color': color,
|
||||
})
|
||||
.click(function () {
|
||||
var c = Colors.rgb2hex($color.css('background-color'));
|
||||
setColor(c);
|
||||
})
|
||||
.on('dblclick', function (e) {
|
||||
e.preventDefault();
|
||||
pickColor(Colors.rgb2hex($color.css('background-color')), function (c) {
|
||||
$color.css({
|
||||
'background-color': c,
|
||||
});
|
||||
palette.splice(i, 1, c);
|
||||
config.onLocal();
|
||||
setColor(c);
|
||||
});
|
||||
});
|
||||
|
||||
$colors.append($color);
|
||||
};
|
||||
|
||||
var metadataCfg = {};
|
||||
var updatePalette = metadataCfg.updatePalette = function (newPalette) {
|
||||
palette = newPalette;
|
||||
$colors.html('<div class="hidden"> </div>');
|
||||
palette.forEach(addColorToPalette);
|
||||
};
|
||||
updatePalette(palette);
|
||||
|
||||
var makeColorButton = function ($container) {
|
||||
var $testColor = $('<input>', { type: 'color', value: '!' });
|
||||
|
||||
// if colors aren't supported, bail out
|
||||
if ($testColor.attr('type') !== 'color' ||
|
||||
$testColor.val() === '!') {
|
||||
console.log("Colors aren't supported. Aborting");
|
||||
return;
|
||||
}
|
||||
|
||||
var $color = module.$color = $('<button>', {
|
||||
id: "color-picker",
|
||||
title: Messages.canvas_chooseColor,
|
||||
'class': "fa fa-square rightside-button",
|
||||
})
|
||||
.on('click', function () {
|
||||
pickColor($color.css('background-color'), function (color) {
|
||||
setColor(color);
|
||||
});
|
||||
});
|
||||
|
||||
setColor('#000');
|
||||
|
||||
$container.append($color);
|
||||
|
||||
return $color;
|
||||
};
|
||||
|
||||
config.onInit = function (info) {
|
||||
UserList = Cryptpad.createUserList(info, config.onLocal, Cryptget, Cryptpad);
|
||||
|
||||
Title = Cryptpad.createTitle({}, config.onLocal, Cryptpad);
|
||||
|
||||
Metadata = Cryptpad.createMetadata(UserList, Title, metadataCfg, Cryptpad);
|
||||
|
||||
var configTb = {
|
||||
displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit', 'upgrade'],
|
||||
userList: UserList.getToolbarConfig(),
|
||||
share: {
|
||||
secret: secret,
|
||||
channel: info.channel
|
||||
},
|
||||
title: Title.getTitleConfig(),
|
||||
common: Cryptpad,
|
||||
readOnly: readOnly,
|
||||
ifrw: window,
|
||||
realtime: info.realtime,
|
||||
network: info.network,
|
||||
$container: $bar,
|
||||
$contentContainer: $('#canvas-area')
|
||||
};
|
||||
|
||||
toolbar = module.toolbar = Toolbar.create(configTb);
|
||||
|
||||
Title.setToolbar(toolbar);
|
||||
|
||||
var $rightside = toolbar.$rightside;
|
||||
|
||||
/* save as template */
|
||||
if (!Cryptpad.isTemplate(window.location.href)) {
|
||||
var templateObj = {
|
||||
rt: info.realtime,
|
||||
Crypt: Cryptget,
|
||||
getTitle: function () { return document.title; }
|
||||
};
|
||||
var $templateButton = Cryptpad.createButton('template', true, templateObj);
|
||||
$rightside.append($templateButton);
|
||||
}
|
||||
|
||||
var $export = Cryptpad.createButton('export', true, {}, saveImage);
|
||||
$rightside.append($export);
|
||||
|
||||
Cryptpad.createButton('savetodrive', true, {}, function () {})
|
||||
.click(function () {
|
||||
Cryptpad.prompt(Messages.exportPrompt, document.title + '.png',
|
||||
function (name) {
|
||||
if (name === null || !name.trim()) { return; }
|
||||
module.upload(name);
|
||||
});
|
||||
}).appendTo($rightside);
|
||||
|
||||
var $forget = Cryptpad.createButton('forget', true, {}, function (err) {
|
||||
if (err) { return; }
|
||||
setEditable(false);
|
||||
toolbar.failed();
|
||||
});
|
||||
$rightside.append($forget);
|
||||
|
||||
var editHash;
|
||||
|
||||
if (!readOnly) {
|
||||
editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys);
|
||||
makeColorButton($rightside);
|
||||
}
|
||||
if (!readOnly) { Cryptpad.replaceHash(editHash); }
|
||||
};
|
||||
|
||||
// used for debugging, feel free to remove
|
||||
var Catch = function (f) {
|
||||
return function () {
|
||||
try {
|
||||
f();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var onRemote = config.onRemote = Catch(function () {
|
||||
if (initializing) { return; }
|
||||
var userDoc = module.realtime.getUserDoc();
|
||||
|
||||
Metadata.update(userDoc);
|
||||
var json = JSON.parse(userDoc);
|
||||
var remoteDoc = json.content;
|
||||
|
||||
// TODO update palette if it has changed
|
||||
|
||||
canvas.loadFromJSON(remoteDoc);
|
||||
canvas.renderAll();
|
||||
|
||||
var content = canvas.toDatalessJSON();
|
||||
if (content !== remoteDoc) { Cryptpad.notify(); }
|
||||
if (readOnly) { setEditable(false); }
|
||||
});
|
||||
setEditable(false);
|
||||
|
||||
var stringifyInner = function (textValue) {
|
||||
var obj = {
|
||||
content: textValue,
|
||||
metadata: {
|
||||
users: UserList.userData,
|
||||
palette: palette,
|
||||
defaultTitle: Title.defaultTitle,
|
||||
type: 'whiteboard',
|
||||
}
|
||||
};
|
||||
if (!initializing) {
|
||||
obj.metadata.title = Title.title;
|
||||
}
|
||||
// stringify the json and send it into chainpad
|
||||
return JSONSortify(obj);
|
||||
};
|
||||
|
||||
|
||||
var onLocal = module.onLocal = config.onLocal = Catch(function () {
|
||||
if (initializing) { return; }
|
||||
if (readOnly) { return; }
|
||||
|
||||
var content = stringifyInner(canvas.toDatalessJSON());
|
||||
|
||||
module.patchText(content);
|
||||
});
|
||||
|
||||
config.onReady = function (info) {
|
||||
var realtime = module.realtime = info.realtime;
|
||||
module.patchText = TextPatcher.create({
|
||||
realtime: realtime
|
||||
});
|
||||
|
||||
var isNew = false;
|
||||
var userDoc = module.realtime.getUserDoc();
|
||||
if (userDoc === "" || userDoc === "{}") { isNew = true; }
|
||||
else {
|
||||
var hjson = JSON.parse(userDoc);
|
||||
if (typeof(hjson) !== 'object' || Array.isArray(hjson) ||
|
||||
(typeof(hjson.type) !== 'undefined' && hjson.type !== 'whiteboard')) {
|
||||
Cryptpad.errorLoadingScreen(Messages.typeError);
|
||||
throw new Error(Messages.typeError);
|
||||
}
|
||||
}
|
||||
|
||||
Cryptpad.removeLoadingScreen();
|
||||
setEditable(true);
|
||||
initializing = false;
|
||||
onRemote();
|
||||
|
||||
/* TODO: restore palette from metadata.palette */
|
||||
|
||||
if (readOnly) { return; }
|
||||
UserList.getLastName(toolbar.$userNameButton, isNew);
|
||||
};
|
||||
|
||||
config.onAbort = function () {
|
||||
setEditable(false);
|
||||
toolbar.failed();
|
||||
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
|
||||
};
|
||||
|
||||
// TODO onConnectionStateChange
|
||||
config.onConnectionChange = function (info) {
|
||||
setEditable(info.state);
|
||||
toolbar.failed();
|
||||
if (info.state) {
|
||||
initializing = true;
|
||||
toolbar.reconnecting(info.myId);
|
||||
Cryptpad.findOKButton().click();
|
||||
} else {
|
||||
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
|
||||
}
|
||||
};
|
||||
|
||||
module.rt = Realtime.start(config);
|
||||
|
||||
canvas.on('mouse:up', onLocal);
|
||||
|
||||
$('#clear').on('click', function () {
|
||||
canvas.clear();
|
||||
onLocal();
|
||||
});
|
||||
|
||||
$('#save').on('click', function () {
|
||||
saveImage();
|
||||
});
|
||||
};
|
||||
|
||||
Cryptpad.ready(function () {
|
||||
andThen();
|
||||
Cryptpad.reportAppUsage();
|
||||
});
|
||||
Cryptpad.onError(function (info) {
|
||||
if (info) {
|
||||
onConnectError();
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
});
|
|
@ -1,11 +1,13 @@
|
|||
@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();
|
||||
.alertify_main();
|
||||
|
||||
// body
|
||||
&.cp-app-pad {
|
||||
.tokenfield_main();
|
||||
#cke_1_top {
|
||||
overflow: visible;
|
||||
padding: 0px;
|
||||
|
@ -38,6 +40,6 @@
|
|||
display:none !important;
|
||||
}
|
||||
&.cp-app-pad .cp-toolbar-userlist-drawer {
|
||||
display:none;
|
||||
display:none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<html class="cp-app-noscroll">
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<script async data-bootload="/pad/inner.js" data-main="/common/sframe-boot.js?ver=1.4" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<script async data-bootload="/pad/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
</head>
|
||||
<body class="cp-app-pad">
|
||||
<textarea style="display:none" id="editor1" name="editor1"></textarea>
|
||||
|
|
121
www/pad/inner.js
121
www/pad/inner.js
|
@ -32,6 +32,7 @@ define([
|
|||
'/pad/links.js',
|
||||
'/bower_components/nthen/index.js',
|
||||
'/common/sframe-common.js',
|
||||
'/common/media-tag.js',
|
||||
'/api/config',
|
||||
|
||||
'/bower_components/file-saver/FileSaver.min.js',
|
||||
|
@ -55,6 +56,7 @@ define([
|
|||
Links,
|
||||
nThen,
|
||||
SFCommon,
|
||||
MediaTag,
|
||||
ApiConfig)
|
||||
{
|
||||
var saveAs = window.saveAs;
|
||||
|
@ -113,6 +115,15 @@ define([
|
|||
if (hj[1].type === '_moz') { hj[1].type = undefined; }
|
||||
return hj;
|
||||
};
|
||||
var mediatagContentFilter = function (hj) {
|
||||
if (hj[0] === 'MEDIA-TAG') { hj[2] = []; }
|
||||
return hj;
|
||||
};
|
||||
var hjsonFilters = function (hj) {
|
||||
brFilter(hj);
|
||||
mediatagContentFilter(hj);
|
||||
return hj;
|
||||
};
|
||||
|
||||
var onConnectError = function () {
|
||||
Cryptpad.errorLoadingScreen(Messages.websocketError);
|
||||
|
@ -124,11 +135,11 @@ define([
|
|||
|
||||
var forbiddenTags = [
|
||||
'SCRIPT',
|
||||
'IFRAME',
|
||||
//'IFRAME',
|
||||
'OBJECT',
|
||||
'APPLET',
|
||||
'VIDEO',
|
||||
'AUDIO'
|
||||
//'VIDEO',
|
||||
//'AUDIO'
|
||||
];
|
||||
|
||||
var getHTML = function (inner) {
|
||||
|
@ -359,6 +370,46 @@ define([
|
|||
|
||||
var DD = new DiffDom(mkDiffOptions(cursor, readOnly));
|
||||
|
||||
var mediaMap = {};
|
||||
var restoreMediaTags = function (tempDom) {
|
||||
var tags = tempDom.querySelectorAll('media-tag:empty');
|
||||
Cryptpad.slice(tags).forEach(function (tag) {
|
||||
var src = tag.getAttribute('src');
|
||||
if (mediaMap[src]) {
|
||||
mediaMap[src].forEach(function (n) {
|
||||
tag.appendChild(n);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
var displayMediaTags = function (dom) {
|
||||
setTimeout(function () { // Just in case
|
||||
var tags = dom.querySelectorAll('media-tag:empty');
|
||||
Cryptpad.slice(tags).forEach(function (el) {
|
||||
MediaTag(el);
|
||||
$(el).on('keydown', function (e) {
|
||||
if ([8,46].indexOf(e.which) !== -1) {
|
||||
$(el).remove();
|
||||
onLocal();
|
||||
}
|
||||
});
|
||||
var observer = new MutationObserver(function(mutations) {
|
||||
mutations.forEach(function(mutation) {
|
||||
if (mutation.type === 'childList') {
|
||||
var list_values = [].slice.call(el.children);
|
||||
mediaMap[el.getAttribute('src')] = list_values;
|
||||
}
|
||||
});
|
||||
});
|
||||
observer.observe(el, {
|
||||
attributes: false,
|
||||
childList: true,
|
||||
characterData: false
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// apply patches, and try not to lose the cursor in the process!
|
||||
var applyHjson = function (shjson) {
|
||||
var userDocStateDom = hjsonToDom(JSON.parse(shjson));
|
||||
|
@ -368,8 +419,10 @@ define([
|
|||
} else if (readOnly) {
|
||||
userDocStateDom.removeAttribute("contenteditable");
|
||||
}
|
||||
restoreMediaTags(userDocStateDom);
|
||||
var patch = (DD).diff(inner, userDocStateDom);
|
||||
(DD).apply(inner, patch);
|
||||
displayMediaTags(inner);
|
||||
if (readOnly) {
|
||||
var $links = $(inner).find('a');
|
||||
// off so that we don't end up with multiple identical handlers
|
||||
|
@ -378,7 +431,7 @@ define([
|
|||
};
|
||||
|
||||
var stringifyDOM = module.stringifyDOM = function (dom) {
|
||||
var hjson = Hyperjson.fromDOM(dom, isNotMagicLine, brFilter);
|
||||
var hjson = Hyperjson.fromDOM(dom, isNotMagicLine, hjsonFilters);
|
||||
hjson[3] = {
|
||||
metadata: metadataMgr.getMetadataLazy()
|
||||
};
|
||||
|
@ -508,7 +561,6 @@ define([
|
|||
|
||||
realtimeOptions.onInit = function (info) {
|
||||
readOnly = metadataMgr.getPrivateData().readOnly;
|
||||
console.log('onInit');
|
||||
var titleCfg = { getHeadingText: getHeadingText };
|
||||
Title = common.createTitle(titleCfg, realtimeOptions.onLocal);
|
||||
var configTb = {
|
||||
|
@ -617,11 +669,38 @@ define([
|
|||
};
|
||||
var $forgetPad = common.createButton('forget', true, {}, forgetCb);
|
||||
$rightside.append($forgetPad);
|
||||
|
||||
if (!readOnly) {
|
||||
var fileDialogCfg = {
|
||||
onSelect: function (data) {
|
||||
if (data.type === 'file') {
|
||||
var mt = '<media-tag contenteditable="false" src="' + data.src + '" data-crypto-key="cryptpad:' + data.key + '" tabindex="1"></media-tag>';
|
||||
editor.insertElement(window.CKEDITOR.dom.element.createFromHtml(mt));
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
common.initFilePicker(fileDialogCfg);
|
||||
window.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);
|
||||
}
|
||||
};
|
||||
|
||||
// this should only ever get called once, when the chain syncs
|
||||
realtimeOptions.onReady = function (info) {
|
||||
console.log('onReady');
|
||||
if (!module.isMaximized) {
|
||||
module.isMaximized = true;
|
||||
$('iframe.cke_wysiwyg_frame').css('width', '');
|
||||
|
@ -644,10 +723,13 @@ define([
|
|||
if (shjson === '') { newPad = true; }
|
||||
|
||||
if (!newPad) {
|
||||
if (shjson[0] !== '[') {
|
||||
var errorText = Messages.typeError;
|
||||
Cryptpad.errorLoadingScreen(errorText);
|
||||
throw new Error(errorText);
|
||||
}
|
||||
applyHjson(shjson);
|
||||
|
||||
// Update the user list (metadata) from the hyperjson
|
||||
// XXX Metadata.update(shjson);
|
||||
var parsed = JSON.parse(shjson);
|
||||
if (parsed[3] && parsed[3].metadata) {
|
||||
metadataMgr.updateMetadata(parsed[3].metadata);
|
||||
|
@ -683,6 +765,20 @@ define([
|
|||
}
|
||||
|
||||
onLocal();
|
||||
|
||||
var fmConfig = {
|
||||
ckeditor: editor,
|
||||
body: $('body'),
|
||||
onUploaded: function (ev, data) {
|
||||
var parsed = Cryptpad.parsePadUrl(data.url);
|
||||
var hexFileName = Cryptpad.base64ToHex(parsed.hashData.channel);
|
||||
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
|
||||
var mt = '<media-tag contenteditable="false" src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '" tabindex="1"></media-tag>';
|
||||
editor.insertElement(window.CKEDITOR.dom.element.createFromHtml(mt));
|
||||
}
|
||||
};
|
||||
window.APP.FM = common.createFileManager(fmConfig);
|
||||
|
||||
editor.focus();
|
||||
if (newPad) {
|
||||
cursor.setToEnd();
|
||||
|
@ -693,10 +789,8 @@ define([
|
|||
|
||||
realtimeOptions.onConnectionChange = function (info) {
|
||||
setEditable(info.state);
|
||||
//toolbar.failed(); TODO
|
||||
if (info.state) {
|
||||
initializing = true;
|
||||
//toolbar.reconnecting(info.myId); // TODO
|
||||
Cryptpad.findOKButton().click();
|
||||
} else {
|
||||
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
|
||||
|
@ -713,6 +807,7 @@ define([
|
|||
|
||||
// stringify the json and send it into chainpad
|
||||
var shjson = stringifyDOM(inner);
|
||||
displayMediaTags(inner);
|
||||
|
||||
module.patchText(shjson);
|
||||
if (module.realtime.getUserDoc() !== shjson) {
|
||||
|
@ -794,11 +889,17 @@ define([
|
|||
}
|
||||
// Used in ckeditor-config.js
|
||||
Ckeditor.CRYPTPAD_URLARGS = ApiConfig.requireConf.urlArgs;
|
||||
Ckeditor.plugins.addExternal('mediatag','/pad/', 'mediatag-plugin.js');
|
||||
module.ckeditor = editor = Ckeditor.replace('editor1', {
|
||||
customConfig: '/customize/ckeditor-config.js',
|
||||
});
|
||||
editor.on('instanceReady', waitFor());
|
||||
}).nThen(function (/*waitFor*/) {
|
||||
editor.plugins.mediatag.translations = {
|
||||
title: Messages.pad_mediatagTitle,
|
||||
width: Messages.pad_mediatagWidth,
|
||||
height: Messages.pad_mediatagHeight
|
||||
};
|
||||
/*if (Ckeditor.env.safari) {
|
||||
var fixIframe = function () {
|
||||
$('iframe.cke_wysiwyg_frame').height($('#cke_1_contents').height());
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
define(['/common/cryptpad-common.js'], function (Cryptpad) {
|
||||
define(['/customize/messages.js'], function (Messages) {
|
||||
// Adds a context menu entry to open the selected link in a new tab.
|
||||
// See https://github.com/xwiki-contrib/application-ckeditor/commit/755d193497bf23ed874d874b4ae92fbee887fc10
|
||||
var Messages = Cryptpad.Messages;
|
||||
return {
|
||||
addSupportForOpeningLinksInNewTab : function (Ckeditor) {
|
||||
// Returns the DOM element of the active (currently focused) link. It has also support for linked image widgets.
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
CKEDITOR.dialog.add('mediatag', function (editor) {
|
||||
var Messages = editor.plugins.mediatag.translations;
|
||||
return {
|
||||
title: Messages.title,
|
||||
minWidth: 400,
|
||||
minHeight: 200,
|
||||
contents: [
|
||||
{
|
||||
id: 'tab-basic',
|
||||
label: Messages.title,
|
||||
elements: [
|
||||
{
|
||||
type: 'text',
|
||||
id: 'width',
|
||||
label: Messages.width,
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
id: 'height',
|
||||
label: Messages.height,
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
onShow: function () {
|
||||
var el = editor.plugins.mediatag.clicked;
|
||||
var rect = el.getClientRect();
|
||||
var dialog = this.parts.contents.$;
|
||||
var inputs = dialog.querySelectorAll('input');
|
||||
var wInput = inputs[0];
|
||||
var hInput = inputs[1];
|
||||
wInput.value = Math.round(rect.width);
|
||||
hInput.value = Math.round(rect.height);
|
||||
},
|
||||
onOk: function() {
|
||||
var dialog = this;
|
||||
var el = editor.plugins.mediatag.clicked;
|
||||
var dialog = this.parts.contents.$;
|
||||
var inputs = dialog.querySelectorAll('input');
|
||||
var wInput = inputs[0];
|
||||
var hInput = inputs[1];
|
||||
|
||||
window.setTimeout(function () {
|
||||
if (wInput.value === "") {
|
||||
el.removeAttribute('width');
|
||||
el.removeStyle('width');
|
||||
} else {
|
||||
el.setSize('width', parseInt(wInput.value));
|
||||
}
|
||||
if (hInput.value === "") {
|
||||
el.removeAttribute('height');
|
||||
el.removeStyle('height');
|
||||
} else {
|
||||
el.setSize('height', parseInt(hInput.value));
|
||||
}
|
||||
editor.fire( 'change' );
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
|
@ -0,0 +1,181 @@
|
|||
/**
|
||||
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
|
||||
* For licensing, see LICENSE.md or http://ckeditor.com/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileOverview The Image plugin.
|
||||
*/
|
||||
|
||||
( function() {
|
||||
|
||||
CKEDITOR.plugins.add( 'mediatag', {
|
||||
requires: 'dialog',
|
||||
//icons: 'image',
|
||||
//hidpi: true,
|
||||
onLoad: function () {
|
||||
|
||||
CKEDITOR.addCss(
|
||||
'media-tag{' +
|
||||
'display:inline-block;' +
|
||||
'}' +
|
||||
'media-tag.selected{' +
|
||||
'border: 1px solid black;' +
|
||||
'}' +
|
||||
'media-tag iframe{' +
|
||||
'border: 6px solid #eee;' +
|
||||
'}' +
|
||||
'media-tag img{' +
|
||||
'vertical-align: top;' +
|
||||
'}' +
|
||||
'media-tag *{' +
|
||||
'width:100%; height:100%;' +
|
||||
'}');
|
||||
},
|
||||
init: function( editor ) {
|
||||
var pluginName = 'mediatag';
|
||||
|
||||
// Register the dialog.
|
||||
CKEDITOR.dialog.add( pluginName, this.path + 'mediatag-plugin-dialog.js' );
|
||||
|
||||
var allowed = 'media-tag[!data-crypto-key,!src,contenteditable,width,height]{border-style,border-width,float,height,margin,margin-bottom,margin-left,margin-right,margin-top,width}',
|
||||
required = 'media-tag[data-crypto-key,src]';
|
||||
|
||||
// Register the command.
|
||||
editor.addCommand( pluginName, new CKEDITOR.dialogCommand( pluginName, {
|
||||
allowedContent: allowed,
|
||||
requiredContent: required,
|
||||
contentTransformations: [
|
||||
[ 'media-tag{width}: sizeToStyle', 'media-tag[width]: sizeToAttribute' ],
|
||||
[ 'media-tag{float}: alignmentToStyle', 'media-tag[align]: alignmentToAttribute' ]
|
||||
]
|
||||
} ) );
|
||||
|
||||
var isMediaTag = function (el) {
|
||||
if (el.is('media-tag')) { return el; }
|
||||
var mt = el.getParents().slice().filter(function (p) {
|
||||
return p.is('media-tag');
|
||||
});
|
||||
if (mt.length !== 1) { return; }
|
||||
return mt[0];
|
||||
};
|
||||
editor.on('doubleclick', function (evt) {
|
||||
var element = evt.data.element;
|
||||
var mt = isMediaTag(element);
|
||||
if (mt && !element.data('cke-realelement')) {
|
||||
editor.plugins.mediatag.clicked = mt;
|
||||
evt.data.dialog = 'mediatag';
|
||||
}
|
||||
});
|
||||
|
||||
// If the "contextmenu" plugin is loaded, register the listeners.
|
||||
if (editor.contextMenu) {
|
||||
editor.contextMenu.addListener(function (element) {
|
||||
if (getSelectedMediatag(editor, element)) {
|
||||
return { mediatag: CKEDITOR.TRISTATE_OFF };
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
afterInit: function( editor ) {
|
||||
// Customize the behavior of the alignment commands. (http://dev.ckeditor.com/ticket/7430)
|
||||
setupAlignCommand('left');
|
||||
setupAlignCommand('right');
|
||||
setupAlignCommand('center');
|
||||
setupAlignCommand('block');
|
||||
|
||||
function setupAlignCommand (value) {
|
||||
var command = editor.getCommand('justify' + value);
|
||||
if (command) {
|
||||
if (value === 'left' || value === 'right') {
|
||||
command.on('exec', function (evt) {
|
||||
var img = getSelectedMediatag(editor), align;
|
||||
if (img) {
|
||||
align = getMediatagAlignment(img);
|
||||
if (align === value) {
|
||||
img.removeStyle('float');
|
||||
|
||||
// Remove "align" attribute when necessary.
|
||||
if (value === getMediatagAlignment(img))
|
||||
img.removeAttribute( 'align' );
|
||||
} else {
|
||||
img.setStyle( 'float', value );
|
||||
}
|
||||
|
||||
evt.cancel();
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
command.on('refresh', function (evt) {
|
||||
var img = getSelectedMediatag(editor), align;
|
||||
if (img) {
|
||||
align = getMediatagAlignment(img);
|
||||
|
||||
this.setState(
|
||||
(align === value) ? CKEDITOR.TRISTATE_ON : ( value === 'right' || value === 'left' ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED );
|
||||
|
||||
evt.cancel();
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
function getSelectedMediatag (editor, element) {
|
||||
if (!element) {
|
||||
var sel = editor.getSelection();
|
||||
element = sel.getSelectedElement();
|
||||
}
|
||||
|
||||
if (element && element.is('media-tag') && !element.data('cke-realelement')
|
||||
&& !element.isReadOnly()) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
function getMediatagAlignment (element) {
|
||||
var align = element.getStyle('float');
|
||||
|
||||
if (align === 'inherit' || align === 'none') {
|
||||
align = 0;
|
||||
}
|
||||
|
||||
if (!align) {
|
||||
align = element.getAttribute('align');
|
||||
}
|
||||
|
||||
return align;
|
||||
}
|
||||
} )();
|
||||
|
||||
/**
|
||||
* Determines whether dimension inputs should be automatically filled when the image URL changes in the Image plugin dialog window.
|
||||
*
|
||||
* config.image_prefillDimensions = false;
|
||||
*
|
||||
* @since 4.5
|
||||
* @cfg {Boolean} [image_prefillDimensions=true]
|
||||
* @member CKEDITOR.config
|
||||
*/
|
||||
|
||||
/**
|
||||
* Whether to remove links when emptying the link URL field in the Image dialog window.
|
||||
*
|
||||
* config.image_removeLinkByEmptyURL = false;
|
||||
*
|
||||
* @cfg {Boolean} [image_removeLinkByEmptyURL=true]
|
||||
* @member CKEDITOR.config
|
||||
*/
|
||||
CKEDITOR.config.mediatag_removeLinkByEmptyURL = true;
|
||||
|
||||
/**
|
||||
* Padding text to set off the image in the preview area.
|
||||
*
|
||||
* config.image_previewText = CKEDITOR.tools.repeat( '___ ', 100 );
|
||||
*
|
||||
* @cfg {String} [image_previewText='Lorem ipsum dolor...' (placeholder text)]
|
||||
* @member CKEDITOR.config
|
||||
*/
|
||||
|
|
@ -39,7 +39,7 @@ body {
|
|||
line-height: auto;
|
||||
}
|
||||
.cryptpad-toolbar {
|
||||
display: inline-block;
|
||||
display: block;
|
||||
}
|
||||
.realtime {
|
||||
display: block;
|
||||
|
|
|
@ -85,6 +85,8 @@ define([
|
|||
Cryptpad.whenRealtimeSyncs(result.realtime, function () {
|
||||
Cryptpad.login(result.userHash, result.userName, function () {
|
||||
registering = false;
|
||||
/*
|
||||
FIXME: migration and readme not working if not redirected to drive
|
||||
if (sessionStorage.redirectTo) {
|
||||
var h = sessionStorage.redirectTo;
|
||||
var parser = document.createElement('a');
|
||||
|
@ -95,6 +97,7 @@ define([
|
|||
return;
|
||||
}
|
||||
}
|
||||
*/
|
||||
window.location.href = '/drive/';
|
||||
});
|
||||
});
|
||||
|
|
|
@ -381,7 +381,7 @@ define([
|
|||
if (!yes) { return; }
|
||||
$spinner.show();
|
||||
$ok.hide();
|
||||
Merge.anonDriveIntoUser(obj.proxy, function () {
|
||||
Merge.anonDriveIntoUser(obj, localStorage.FS_hash, function () {
|
||||
$spinner.hide();
|
||||
$ok.show();
|
||||
Cryptpad.alert(Messages.settings_importDone);
|
||||
|
|
|
@ -4,11 +4,13 @@
|
|||
@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';
|
||||
|
||||
.mediatag_base();
|
||||
.toolbar_main();
|
||||
.fileupload_main();
|
||||
.alertify_main();
|
||||
.tokenfield_main();
|
||||
|
||||
// body
|
||||
font-size: unset;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<html class="cp-app-noscroll cp-app-print">
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<script async data-bootload="/slide/inner.js" data-main="/common/sframe-boot.js?ver=1.4" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<script async data-bootload="/slide/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<style>
|
||||
.loading-hidden { display: none; }
|
||||
#editor1 { display: none; }
|
||||
|
|
|
@ -516,6 +516,9 @@ define([
|
|||
};
|
||||
common.openFilePicker(pickerCfg);
|
||||
}).appendTo($rightside);
|
||||
|
||||
var $tags = common.createButton('hashtag', true);
|
||||
$rightside.append($tags);
|
||||
}
|
||||
|
||||
metadataMgr.onChange(function () {
|
||||
|
@ -552,7 +555,8 @@ define([
|
|||
metadataMgr.updateMetadata(hjson.metadata);
|
||||
}
|
||||
if (typeof (hjson) !== 'object' || Array.isArray(hjson) ||
|
||||
(typeof(hjson.type) !== 'undefined' && hjson.type !== 'code')) {
|
||||
(hjson.metadata && typeof(hjson.metadata.type) !== 'undefined' &&
|
||||
hjson.metadata.type !== 'slide')) {
|
||||
var errorText = Messages.typeError;
|
||||
Cryptpad.errorLoadingScreen(errorText);
|
||||
throw new Error(errorText);
|
||||
|
@ -655,7 +659,7 @@ define([
|
|||
}
|
||||
}
|
||||
Slide.update(remoteDoc);
|
||||
if (oldDoc !== remoteDoc) { Cryptpad.notify(); }
|
||||
if (oldDoc !== remoteDoc) { common.notify(); }
|
||||
};
|
||||
|
||||
config.onAbort = function () {
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
@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/tools.less';
|
||||
|
||||
.toolbar_main();
|
||||
.fileupload_main();
|
||||
.alertify_main();
|
||||
|
||||
// body
|
||||
&.cp-app-whiteboard {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
height: 100%;
|
||||
|
||||
.middle () {
|
||||
position: relative;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// created in the html
|
||||
#cp-app-whiteboard-canvas-area {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
}
|
||||
// created by fabricjs. styled so defaults don't break anything
|
||||
.cp-app-whiteboard-canvas-container {
|
||||
margin: auto;
|
||||
background: white;
|
||||
& > canvas {
|
||||
border: 1px solid black;
|
||||
}
|
||||
}
|
||||
|
||||
.cp-app-whiteboard-unselectable {
|
||||
.tools_unselectable();
|
||||
}
|
||||
|
||||
// contains user tools
|
||||
#cp-app-whiteboard-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
position: relative;
|
||||
border-top: 1px solid black;
|
||||
background: white;
|
||||
|
||||
padding: 1em;
|
||||
|
||||
& > * + * {
|
||||
margin: 0;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
#cp-app-whiteboard-width, #cp-app-whiteboard-opacity {
|
||||
.middle;
|
||||
}
|
||||
#cp-app-whiteboard-clear, #cp-app-whiteboard-delete, #cp-app-whiteboard-toggledraw {
|
||||
display: inline;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.cp-app-whiteboard-selected {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9001;
|
||||
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.cp-app-whiteboard-range-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
|
||||
input[type="range"] {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
& > span {
|
||||
cursor: default;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
.cp-app-whiteboard-range-group:first-of-type {
|
||||
margin-left: 2em;
|
||||
}
|
||||
.cp-app-whiteboard-range-group:last-of-type {
|
||||
margin-right: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
/* Colors */
|
||||
#cp-app-whiteboard-colors {
|
||||
.middle;
|
||||
z-index: 100;
|
||||
background: white;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
padding: 1em;
|
||||
|
||||
span.cp-app-whiteboard-palette-color {
|
||||
height: 4vw;
|
||||
width: 4vw;
|
||||
display: block;
|
||||
margin: 5px;
|
||||
border: 1px solid black;
|
||||
vertical-align: top;
|
||||
border-radius: 50%;
|
||||
transition: transform 0.1s;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// used in the toolbar if supported
|
||||
#cp-app-whiteboard-color-picker {
|
||||
display: block;
|
||||
}
|
||||
|
||||
// input[type=color] must exist in the dom to work correctly
|
||||
// styled so that they don't break layouts
|
||||
|
||||
#cp-app-whiteboard-pickers {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
z-index: -5;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,38 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="cp">
|
||||
<html>
|
||||
<head>
|
||||
<title>CryptPad</title>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<script async data-bootload="/customize/template.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="referrer" content="no-referrer" />
|
||||
<script async data-bootload="main.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
#sbox-iframe {
|
||||
position:fixed;
|
||||
top:0px;
|
||||
left:0px;
|
||||
bottom:0px;
|
||||
right:0px;
|
||||
width:100%;
|
||||
height:100%;
|
||||
border:none;
|
||||
margin:0;
|
||||
padding:0;
|
||||
overflow:hidden;
|
||||
}
|
||||
#sbox-filePicker-iframe {
|
||||
position: fixed;
|
||||
top:0; left:0;
|
||||
bottom:0; right:0;
|
||||
width:100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<iframe id="sbox-iframe">
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<script async data-bootload="/whiteboard/inner.js" data-main="/common/sframe-boot.js?ver=1.6" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<style>
|
||||
.loading-hidden { display: none; }
|
||||
#editor1 { display: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="cp-app-whiteboard">
|
||||
|
|
@ -0,0 +1,556 @@
|
|||
define([
|
||||
'jquery',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/bower_components/textpatcher/TextPatcher.js',
|
||||
'/common/toolbar3.js',
|
||||
'json.sortify',
|
||||
'/bower_components/chainpad-json-validator/json-ot.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/cryptget.js',
|
||||
'/bower_components/nthen/index.js',
|
||||
'/common/sframe-common.js',
|
||||
'/api/config',
|
||||
'/common/common-realtime.js',
|
||||
'/customize/pages.js',
|
||||
|
||||
'/customize/application_config.js',
|
||||
'/common/common-thumbnail.js',
|
||||
'/whiteboard/colors.js',
|
||||
|
||||
'/bower_components/secure-fabric.js/dist/fabric.min.js',
|
||||
'/bower_components/file-saver/FileSaver.min.js',
|
||||
|
||||
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
||||
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
'less!/customize/src/less2/main.less',
|
||||
], function (
|
||||
$,
|
||||
Crypto,
|
||||
TextPatcher,
|
||||
Toolbar,
|
||||
JSONSortify,
|
||||
JsonOT,
|
||||
Cryptpad,
|
||||
Cryptget,
|
||||
nThen,
|
||||
SFCommon,
|
||||
ApiConfig,
|
||||
CommonRealtime,
|
||||
Pages,
|
||||
AppConfig,
|
||||
Thumb,
|
||||
Colors)
|
||||
{
|
||||
var saveAs = window.saveAs;
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
||||
var APP = window.APP = {
|
||||
Cryptpad: Cryptpad,
|
||||
$: $
|
||||
};
|
||||
var Fabric = APP.Fabric = window.fabric;
|
||||
|
||||
var stringify = function (obj) {
|
||||
return JSONSortify(obj);
|
||||
};
|
||||
|
||||
var toolbar;
|
||||
|
||||
var onConnectError = function () {
|
||||
Cryptpad.errorLoadingScreen(Messages.websocketError);
|
||||
};
|
||||
|
||||
var andThen = function (common) {
|
||||
var config = {};
|
||||
/* Initialize Fabric */
|
||||
var canvas = APP.canvas = new Fabric.Canvas('cp-app-whiteboard-canvas', {
|
||||
containerClass: 'cp-app-whiteboard-canvas-container'
|
||||
});
|
||||
var $canvas = $('canvas');
|
||||
var $controls = $('#cp-app-whiteboard-controls');
|
||||
var $canvasContainer = $('canvas').parents('.cp-app-whiteboard-canvas-container');
|
||||
var $pickers = $('#cp-app-whiteboard-pickers');
|
||||
var $colors = $('#cp-app-whiteboard-colors');
|
||||
var $cursors = $('#cp-app-whiteboard-cursors');
|
||||
var $deleteButton = $('#cp-app-whiteboard-delete');
|
||||
var $toggle = $('#cp-app-whiteboard-toggledraw');
|
||||
var $width = $('#cp-app-whiteboard-width');
|
||||
var $widthLabel = $('label[for="cp-app-whiteboard-width"]');
|
||||
var $opacity = $('#cp-app-whiteboard-opacity');
|
||||
var $opacityLabel = $('label[for="cp-app-whiteboard-opacity"]');
|
||||
|
||||
|
||||
// Brush
|
||||
|
||||
var readOnly = false;
|
||||
var brush = {
|
||||
color: '#000000',
|
||||
opacity: 1
|
||||
};
|
||||
|
||||
var createCursor = function () {
|
||||
var w = canvas.freeDrawingBrush.width;
|
||||
var c = canvas.freeDrawingBrush.color;
|
||||
var size = w > 30 ? w+2 : w+32;
|
||||
$cursors.html('<canvas width="'+size+'" height="'+size+'"></canvas>');
|
||||
var $ccanvas = $cursors.find('canvas');
|
||||
var ccanvas = $ccanvas[0];
|
||||
|
||||
var ctx = ccanvas.getContext('2d');
|
||||
var centerX = size / 2;
|
||||
var centerY = size / 2;
|
||||
var radius = w/2;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
|
||||
ctx.fillStyle = c;
|
||||
ctx.fill();
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = brush.color;
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(size/2, 0); ctx.lineTo(size/2, 10);
|
||||
ctx.moveTo(size/2, size); ctx.lineTo(size/2, size-10);
|
||||
ctx.moveTo(0, size/2); ctx.lineTo(10, size/2);
|
||||
ctx.moveTo(size, size/2); ctx.lineTo(size-10, size/2);
|
||||
ctx.strokeStyle = '#000000';
|
||||
ctx.stroke();
|
||||
|
||||
var img = ccanvas.toDataURL("image/png");
|
||||
$controls.find('.cp-app-whiteboard-selected > img').attr('src', img);
|
||||
canvas.freeDrawingCursor = 'url('+img+') '+size/2+' '+size/2+', crosshair';
|
||||
};
|
||||
|
||||
var updateBrushWidth = function () {
|
||||
var val = $width.val();
|
||||
canvas.freeDrawingBrush.width = Number(val);
|
||||
$widthLabel.text(Cryptpad.Messages._getKey("canvas_widthLabel", [val]));
|
||||
$('#cp-app-whiteboard-width-val').text(val + 'px');
|
||||
createCursor();
|
||||
};
|
||||
updateBrushWidth();
|
||||
$width.on('change', updateBrushWidth);
|
||||
|
||||
var updateBrushOpacity = function () {
|
||||
var val = $opacity.val();
|
||||
brush.opacity = Number(val);
|
||||
canvas.freeDrawingBrush.color = Colors.hex2rgba(brush.color, brush.opacity);
|
||||
$opacityLabel.text(Cryptpad.Messages._getKey("canvas_opacityLabel", [val]));
|
||||
$('#cp-app-whiteboard-opacity-val').text((Number(val) * 100) + '%');
|
||||
createCursor();
|
||||
};
|
||||
updateBrushOpacity();
|
||||
$opacity.on('change', updateBrushOpacity);
|
||||
|
||||
var pickColor = function (current, cb) {
|
||||
var $picker = $('<input>', {
|
||||
type: 'color',
|
||||
value: '#FFFFFF',
|
||||
})
|
||||
.on('change', function () {
|
||||
var color = this.value;
|
||||
cb(color);
|
||||
}).appendTo($pickers);
|
||||
setTimeout(function () {
|
||||
$picker.val(current);
|
||||
$picker.click();
|
||||
});
|
||||
};
|
||||
var setColor = function (c) {
|
||||
c = Colors.rgb2hex(c);
|
||||
brush.color = c;
|
||||
canvas.freeDrawingBrush.color = Colors.hex2rgba(brush.color, brush.opacity);
|
||||
APP.$color.css({
|
||||
'color': c,
|
||||
});
|
||||
createCursor();
|
||||
};
|
||||
|
||||
var palette = AppConfig.whiteboardPalette || [
|
||||
'red', 'blue', 'green', 'white', 'black', 'purple',
|
||||
'gray', 'beige', 'brown', 'cyan', 'darkcyan', 'gold', 'yellow', 'pink'
|
||||
];
|
||||
$('.cp-app-whiteboard-palette-color').on('click', function () {
|
||||
var color = $(this).css('background-color');
|
||||
setColor(color);
|
||||
});
|
||||
|
||||
APP.draw = true;
|
||||
var toggleDrawMode = function () {
|
||||
APP.draw = !APP.draw;
|
||||
canvas.isDrawingMode = APP.draw;
|
||||
$toggle.text(APP.draw ? Messages.canvas_disable : Messages.canvas_enable);
|
||||
if (APP.draw) { $deleteButton.hide(); }
|
||||
else { $deleteButton.show(); }
|
||||
};
|
||||
$toggle.click(toggleDrawMode);
|
||||
|
||||
var deleteSelection = function () {
|
||||
if (canvas.getActiveObject()) {
|
||||
canvas.getActiveObject().remove();
|
||||
}
|
||||
if (canvas.getActiveGroup()) {
|
||||
canvas.getActiveGroup()._objects.forEach(function (el) {
|
||||
el.remove();
|
||||
});
|
||||
canvas.discardActiveGroup();
|
||||
}
|
||||
canvas.renderAll();
|
||||
config.onLocal();
|
||||
};
|
||||
$deleteButton.click(deleteSelection);
|
||||
$(window).on('keyup', function (e) {
|
||||
if (e.which === 46) { deleteSelection (); }
|
||||
});
|
||||
|
||||
var setEditable = function (bool) {
|
||||
if (readOnly && bool) { return; }
|
||||
if (bool) { $controls.css('display', 'flex'); }
|
||||
else { $controls.hide(); }
|
||||
|
||||
canvas.isDrawingMode = bool ? APP.draw : false;
|
||||
if (!bool) {
|
||||
canvas.deactivateAll();
|
||||
canvas.renderAll();
|
||||
}
|
||||
canvas.forEachObject(function (object) {
|
||||
object.selectable = bool;
|
||||
});
|
||||
$canvasContainer.find('canvas').css('border-color', bool? 'black': 'red');
|
||||
};
|
||||
|
||||
var saveImage = APP.saveImage = function () {
|
||||
var defaultName = "pretty-picture.png";
|
||||
Cryptpad.prompt(Messages.exportPrompt, defaultName, function (filename) {
|
||||
if (!(typeof(filename) === 'string' && filename)) { return; }
|
||||
$canvas[0].toBlob(function (blob) {
|
||||
saveAs(blob, filename);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
APP.FM = common.createFileManager({});
|
||||
APP.upload = function (title) {
|
||||
var canvas = $canvas[0];
|
||||
var finish = function (thumb) {
|
||||
canvas.toBlob(function (blob) {
|
||||
blob.name = title;
|
||||
APP.FM.handleFile(blob, void 0, thumb);
|
||||
});
|
||||
};
|
||||
|
||||
Thumb.fromCanvas(canvas, function (e, blob) {
|
||||
// carry on even if you can't get a thumbnail
|
||||
if (e) { console.error(e); }
|
||||
finish(blob);
|
||||
});
|
||||
};
|
||||
|
||||
var initializing = true;
|
||||
var $bar = $('#cp-toolbar');
|
||||
var Title;
|
||||
var cpNfInner;
|
||||
var metadataMgr;
|
||||
|
||||
config = {
|
||||
readOnly: readOnly,
|
||||
transformFunction: JsonOT.validate,
|
||||
// cryptpad debug logging (default is 1)
|
||||
// logLevel: 0,
|
||||
validateContent: function (content) {
|
||||
try {
|
||||
JSON.parse(content);
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.log("Failed to parse, rejecting patch");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var addColorToPalette = function (color, i) {
|
||||
if (readOnly) { return; }
|
||||
var $color = $('<span>', {
|
||||
'class': 'cp-app-whiteboard-palette-color',
|
||||
})
|
||||
.css({
|
||||
'background-color': color,
|
||||
})
|
||||
.click(function () {
|
||||
var c = Colors.rgb2hex($color.css('background-color'));
|
||||
setColor(c);
|
||||
})
|
||||
.on('dblclick', function (e) {
|
||||
e.preventDefault();
|
||||
pickColor(Colors.rgb2hex($color.css('background-color')), function (c) {
|
||||
$color.css({
|
||||
'background-color': c,
|
||||
});
|
||||
palette.splice(i, 1, c);
|
||||
APP.updateLocalPalette(palette);
|
||||
setColor(c);
|
||||
});
|
||||
});
|
||||
|
||||
$colors.append($color);
|
||||
};
|
||||
|
||||
var first = true;
|
||||
var updatePalette = function (newPalette) {
|
||||
if (first || stringify(palette) !== stringify(newPalette)) {
|
||||
palette = newPalette;
|
||||
$colors.html('<div class="hidden"> </div>');
|
||||
palette.forEach(addColorToPalette);
|
||||
first = false;
|
||||
}
|
||||
};
|
||||
var updateLocalPalette = APP.updateLocalPalette = function (newPalette) {
|
||||
updatePalette(newPalette);
|
||||
var metadata = JSON.parse(JSON.stringify(metadataMgr.getMetadata()));
|
||||
metadata.palette = newPalette;
|
||||
metadataMgr.updateMetadata(metadata);
|
||||
config.onLocal();
|
||||
};
|
||||
|
||||
var makeColorButton = function ($container) {
|
||||
var $testColor = $('<input>', { type: 'color', value: '!' });
|
||||
|
||||
// if colors aren't supported, bail out
|
||||
if ($testColor.attr('type') !== 'color' ||
|
||||
$testColor.val() === '!') {
|
||||
console.log("Colors aren't supported. Aborting");
|
||||
return;
|
||||
}
|
||||
|
||||
var $color = APP.$color = $('<button>', {
|
||||
id: "cp-app-whiteboard-color-picker",
|
||||
title: Messages.canvas_chooseColor,
|
||||
'class': "fa fa-square cp-toolbar-rightside-button",
|
||||
})
|
||||
.on('click', function () {
|
||||
pickColor($color.css('background-color'), function (color) {
|
||||
setColor(color);
|
||||
});
|
||||
});
|
||||
|
||||
setColor('#000');
|
||||
|
||||
$container.append($color);
|
||||
|
||||
return $color;
|
||||
};
|
||||
|
||||
var stringifyInner = function (textValue) {
|
||||
var obj = {
|
||||
content: textValue,
|
||||
metadata: metadataMgr.getMetadataLazy()
|
||||
};
|
||||
// stringify the json and send it into chainpad
|
||||
return stringify(obj);
|
||||
};
|
||||
|
||||
var onLocal = config.onLocal = function () {
|
||||
if (initializing) { return; }
|
||||
if (readOnly) { return; }
|
||||
|
||||
var content = stringifyInner(canvas.toDatalessJSON());
|
||||
|
||||
APP.patchText(content);
|
||||
};
|
||||
|
||||
config.onInit = function (info) {
|
||||
updateLocalPalette(palette);
|
||||
readOnly = metadataMgr.getPrivateData().readOnly;
|
||||
|
||||
Title = common.createTitle({}, config.onLocal);
|
||||
|
||||
var configTb = {
|
||||
displayed: ['title', 'useradmin', 'spinner', 'share', 'userlist', 'newpad', 'limit'],
|
||||
title: Title.getTitleConfig(),
|
||||
metadataMgr: metadataMgr,
|
||||
readOnly: readOnly,
|
||||
realtime: info.realtime,
|
||||
common: Cryptpad,
|
||||
sfCommon: common,
|
||||
$container: $bar,
|
||||
$contentContainer: $('#cp-app-whiteboard-canvas-area')
|
||||
};
|
||||
toolbar = APP.toolbar = Toolbar.create(configTb);
|
||||
Title.setToolbar(toolbar);
|
||||
|
||||
var $rightside = toolbar.$rightside;
|
||||
|
||||
/* save as template */
|
||||
if (!metadataMgr.getPrivateData().isTemplate) {
|
||||
var templateObj = {
|
||||
rt: info.realtime,
|
||||
getTitle: function () { return metadataMgr.getMetadata().title; }
|
||||
};
|
||||
var $templateButton = common.createButton('template', true, templateObj);
|
||||
$rightside.append($templateButton);
|
||||
}
|
||||
|
||||
/* add an export button */
|
||||
var $export = common.createButton('export', true, {}, saveImage);
|
||||
$rightside.append($export);
|
||||
|
||||
common.createButton('savetodrive', true, {}, function () {})
|
||||
.click(function () {
|
||||
Cryptpad.prompt(Messages.exportPrompt, document.title + '.png',
|
||||
function (name) {
|
||||
if (name === null || !name.trim()) { return; }
|
||||
APP.upload(name);
|
||||
});
|
||||
}).appendTo($rightside);
|
||||
|
||||
var $forget = common.createButton('forget', true, {}, function (err) {
|
||||
if (err) { return; }
|
||||
setEditable(false);
|
||||
});
|
||||
$rightside.append($forget);
|
||||
|
||||
if (!readOnly) {
|
||||
makeColorButton($rightside);
|
||||
}
|
||||
|
||||
metadataMgr.onChange(function () {
|
||||
var md = metadataMgr.getMetadata();
|
||||
if (md.palette) {
|
||||
updateLocalPalette(md.palette);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
config.onReady = function (info) {
|
||||
if (APP.realtime !== info.realtime) {
|
||||
var realtime = APP.realtime = info.realtime;
|
||||
APP.patchText = TextPatcher.create({
|
||||
realtime: realtime,
|
||||
//logging: true
|
||||
});
|
||||
}
|
||||
|
||||
var userDoc = APP.realtime.getUserDoc();
|
||||
var isNew = false;
|
||||
var newDoc = '';
|
||||
if (userDoc === "" || userDoc === "{}") { isNew = true; }
|
||||
|
||||
if (userDoc !== "") {
|
||||
var hjson = JSON.parse(userDoc);
|
||||
|
||||
if (hjson && hjson.metadata) {
|
||||
metadataMgr.updateMetadata(hjson.metadata);
|
||||
}
|
||||
if (typeof (hjson) !== 'object' || Array.isArray(hjson) ||
|
||||
(hjson.metadata && typeof(hjson.metadata.type) !== 'undefined' &&
|
||||
hjson.metadata.type !== 'whiteboard')) {
|
||||
var errorText = Messages.typeError;
|
||||
Cryptpad.errorLoadingScreen(errorText);
|
||||
throw new Error(errorText);
|
||||
}
|
||||
newDoc = hjson.content;
|
||||
} else {
|
||||
Title.updateTitle(Cryptpad.initialName || Title.defaultTitle);
|
||||
}
|
||||
if (newDoc) {
|
||||
canvas.loadFromJSON(newDoc);
|
||||
canvas.renderAll();
|
||||
}
|
||||
|
||||
setEditable(!readOnly);
|
||||
initializing = false;
|
||||
config.onLocal();
|
||||
Cryptpad.removeLoadingScreen();
|
||||
if (readOnly) { return; }
|
||||
if (isNew) {
|
||||
common.openTemplatePicker();
|
||||
}
|
||||
};
|
||||
|
||||
config.onRemote = function () {
|
||||
if (initializing) { return; }
|
||||
var userDoc = APP.realtime.getUserDoc();
|
||||
|
||||
var json = JSON.parse(userDoc);
|
||||
var remoteDoc = json.content;
|
||||
|
||||
if (json.metadata) {
|
||||
metadataMgr.updateMetadata(json.metadata);
|
||||
}
|
||||
|
||||
// TODO update palette if it has changed
|
||||
|
||||
canvas.loadFromJSON(remoteDoc);
|
||||
canvas.renderAll();
|
||||
|
||||
var content = canvas.toDatalessJSON();
|
||||
if (content !== remoteDoc) { common.notify(); }
|
||||
if (readOnly) { setEditable(false); }
|
||||
};
|
||||
|
||||
config.onAbort = function () {
|
||||
// inform of network disconnect
|
||||
setEditable(false);
|
||||
toolbar.failed();
|
||||
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
|
||||
};
|
||||
|
||||
config.onConnectionChange = function (info) {
|
||||
setEditable(info.state);
|
||||
if (info.state) {
|
||||
initializing = true;
|
||||
Cryptpad.findOKButton().click();
|
||||
} else {
|
||||
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
|
||||
}
|
||||
};
|
||||
|
||||
config.onError = onConnectError;
|
||||
|
||||
cpNfInner = common.startRealtime(config);
|
||||
metadataMgr = cpNfInner.metadataMgr;
|
||||
|
||||
cpNfInner.onInfiniteSpinner(function () {
|
||||
setEditable(false);
|
||||
Cryptpad.confirm(Messages.realtime_unrecoverableError, function (yes) {
|
||||
if (!yes) { return; }
|
||||
common.gotoURL();
|
||||
});
|
||||
});
|
||||
|
||||
canvas.on('mouse:up', onLocal);
|
||||
|
||||
$('#cp-app-whiteboard-clear').on('click', function () {
|
||||
canvas.clear();
|
||||
onLocal();
|
||||
});
|
||||
|
||||
$('#save').on('click', function () {
|
||||
saveImage();
|
||||
});
|
||||
|
||||
Cryptpad.onLogout(function () { setEditable(false); });
|
||||
};
|
||||
|
||||
var main = function () {
|
||||
var common;
|
||||
|
||||
nThen(function (waitFor) {
|
||||
$(waitFor(function () {
|
||||
Cryptpad.addLoadingScreen();
|
||||
var $div = $('<div>').append(Pages['/whiteboard/']());
|
||||
$('body').append($div.html());
|
||||
}));
|
||||
SFCommon.create(waitFor(function (c) { APP.common = common = c; }));
|
||||
}).nThen(function (/*waitFor*/) {
|
||||
Cryptpad.onError(function (info) {
|
||||
if (info && info.type === "store") {
|
||||
onConnectError();
|
||||
}
|
||||
});
|
||||
andThen(common);
|
||||
});
|
||||
};
|
||||
main();
|
||||
});
|
|
@ -1,511 +1,41 @@
|
|||
// Load #1, load as little as possible because we are in a race to get the loading screen up.
|
||||
define([
|
||||
'jquery',
|
||||
'/bower_components/nthen/index.js',
|
||||
'/api/config',
|
||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/common/toolbar2.js',
|
||||
'/bower_components/textpatcher/TextPatcher.amd.js',
|
||||
'json.sortify',
|
||||
'/bower_components/chainpad-json-validator/json-ot.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/cryptget.js',
|
||||
'/whiteboard/colors.js',
|
||||
'/customize/application_config.js',
|
||||
'/common/common-thumbnail.js',
|
||||
'/bower_components/secure-fabric.js/dist/fabric.min.js',
|
||||
'/bower_components/file-saver/FileSaver.min.js',
|
||||
'jquery',
|
||||
'/common/requireconfig.js',
|
||||
'/common/sframe-common-outer.js'
|
||||
], function (nThen, ApiConfig, $, RequireConfig, SFCommonO) {
|
||||
var requireConfig = RequireConfig();
|
||||
|
||||
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
|
||||
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
|
||||
'less!/customize/src/less/cryptpad.less',
|
||||
'less!/whiteboard/whiteboard.less',
|
||||
'less!/customize/src/less/toolbar.less',
|
||||
], function ($, Config, Realtime, Crypto, Toolbar, TextPatcher, JSONSortify, JsonOT, Cryptpad, Cryptget, Colors, AppConfig, Thumb) {
|
||||
var saveAs = window.saveAs;
|
||||
var Messages = Cryptpad.Messages;
|
||||
|
||||
var module = window.APP = { $:$ };
|
||||
var Fabric = module.Fabric = window.fabric;
|
||||
|
||||
$(function () {
|
||||
Cryptpad.addLoadingScreen();
|
||||
var onConnectError = function () {
|
||||
Cryptpad.errorLoadingScreen(Messages.websocketError);
|
||||
};
|
||||
var toolbar;
|
||||
|
||||
var secret = Cryptpad.getSecrets();
|
||||
var readOnly = secret.keys && !secret.keys.editKeyStr;
|
||||
if (!secret.keys) {
|
||||
secret.keys = secret.key;
|
||||
}
|
||||
|
||||
var andThen = function () {
|
||||
/* Initialize Fabric */
|
||||
var canvas = module.canvas = new Fabric.Canvas('canvas');
|
||||
var $canvas = $('canvas');
|
||||
var $controls = $('#controls');
|
||||
var $canvasContainer = $('canvas').parents('.canvas-container');
|
||||
var $pickers = $('#pickers');
|
||||
var $colors = $('#colors');
|
||||
var $cursors = $('#cursors');
|
||||
var $deleteButton = $('#delete');
|
||||
|
||||
var brush = {
|
||||
color: '#000000',
|
||||
opacity: 1
|
||||
// Loaded in load #2
|
||||
nThen(function (waitFor) {
|
||||
$(waitFor());
|
||||
}).nThen(function (waitFor) {
|
||||
var req = {
|
||||
cfg: requireConfig,
|
||||
req: [ '/common/loading.js' ],
|
||||
pfx: window.location.origin
|
||||
};
|
||||
window.rc = requireConfig;
|
||||
window.apiconf = ApiConfig;
|
||||
$('#sbox-iframe').attr('src',
|
||||
ApiConfig.httpSafeOrigin + '/whiteboard/inner.html?' + requireConfig.urlArgs +
|
||||
'#' + encodeURIComponent(JSON.stringify(req)));
|
||||
|
||||
var $toggle = $('#toggleDraw');
|
||||
var $width = $('#width');
|
||||
var $widthLabel = $('label[for="width"]');
|
||||
var $opacity = $('#opacity');
|
||||
var $opacityLabel = $('label[for="opacity"]');
|
||||
window.canvas = canvas;
|
||||
var createCursor = function () {
|
||||
var w = canvas.freeDrawingBrush.width;
|
||||
var c = canvas.freeDrawingBrush.color;
|
||||
var size = w > 30 ? w+2 : w+32;
|
||||
$cursors.html('<canvas width="'+size+'" height="'+size+'"></canvas>');
|
||||
var $ccanvas = $cursors.find('canvas');
|
||||
var ccanvas = $ccanvas[0];
|
||||
|
||||
var ctx = ccanvas.getContext('2d');
|
||||
var centerX = size / 2;
|
||||
var centerY = size / 2;
|
||||
var radius = w/2;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
|
||||
ctx.fillStyle = c;
|
||||
ctx.fill();
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = brush.color;
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(size/2, 0); ctx.lineTo(size/2, 10);
|
||||
ctx.moveTo(size/2, size); ctx.lineTo(size/2, size-10);
|
||||
ctx.moveTo(0, size/2); ctx.lineTo(10, size/2);
|
||||
ctx.moveTo(size, size/2); ctx.lineTo(size-10, size/2);
|
||||
ctx.strokeStyle = '#000000';
|
||||
ctx.stroke();
|
||||
|
||||
var img = ccanvas.toDataURL("image/png");
|
||||
$controls.find('.selected > img').attr('src', img);
|
||||
canvas.freeDrawingCursor = 'url('+img+') '+size/2+' '+size/2+', crosshair';
|
||||
// This is a cheap trick to avoid loading sframe-channel in parallel with the
|
||||
// loading screen setup.
|
||||
var done = waitFor();
|
||||
var onMsg = function (msg) {
|
||||
var data = JSON.parse(msg.data);
|
||||
if (data.q !== 'READY') { return; }
|
||||
window.removeEventListener('message', onMsg);
|
||||
var _done = done;
|
||||
done = function () { };
|
||||
_done();
|
||||
};
|
||||
|
||||
var updateBrushWidth = function () {
|
||||
var val = $width.val();
|
||||
canvas.freeDrawingBrush.width = Number(val);
|
||||
$widthLabel.text(Cryptpad.Messages._getKey("canvas_widthLabel", [val]));
|
||||
$('#width-val').text(val + 'px');
|
||||
createCursor();
|
||||
};
|
||||
updateBrushWidth();
|
||||
|
||||
$width.on('change', updateBrushWidth);
|
||||
|
||||
var updateBrushOpacity = function () {
|
||||
var val = $opacity.val();
|
||||
brush.opacity = Number(val);
|
||||
canvas.freeDrawingBrush.color = Colors.hex2rgba(brush.color, brush.opacity);
|
||||
$opacityLabel.text(Cryptpad.Messages._getKey("canvas_opacityLabel", [val]));
|
||||
$('#opacity-val').text((Number(val) * 100) + '%');
|
||||
createCursor();
|
||||
};
|
||||
updateBrushOpacity();
|
||||
|
||||
$opacity.on('change', updateBrushOpacity);
|
||||
|
||||
var pickColor = function (current, cb) {
|
||||
var $picker = $('<input>', {
|
||||
type: 'color',
|
||||
value: '#FFFFFF',
|
||||
})
|
||||
// TODO confirm that this is safe to remove
|
||||
//.css({ visibility: 'hidden' })
|
||||
.on('change', function () {
|
||||
var color = this.value;
|
||||
cb(color);
|
||||
}).appendTo($pickers);
|
||||
setTimeout(function () {
|
||||
$picker.val(current);
|
||||
$picker.click();
|
||||
});
|
||||
};
|
||||
|
||||
var setColor = function (c) {
|
||||
c = Colors.rgb2hex(c);
|
||||
brush.color = c;
|
||||
canvas.freeDrawingBrush.color = Colors.hex2rgba(brush.color, brush.opacity);
|
||||
module.$color.css({
|
||||
'color': c,
|
||||
});
|
||||
createCursor();
|
||||
};
|
||||
|
||||
|
||||
var palette = AppConfig.whiteboardPalette || [
|
||||
'red', 'blue', 'green', 'white', 'black', 'purple',
|
||||
'gray', 'beige', 'brown', 'cyan', 'darkcyan', 'gold', 'yellow', 'pink'
|
||||
];
|
||||
|
||||
$('.palette-color').on('click', function () {
|
||||
var color = $(this).css('background-color');
|
||||
setColor(color);
|
||||
});
|
||||
|
||||
module.draw = true;
|
||||
var toggleDrawMode = function () {
|
||||
module.draw = !module.draw;
|
||||
canvas.isDrawingMode = module.draw;
|
||||
$toggle.text(module.draw ? Messages.canvas_disable : Messages.canvas_enable);
|
||||
if (module.draw) { $deleteButton.hide(); }
|
||||
else { $deleteButton.show(); }
|
||||
};
|
||||
$toggle.click(toggleDrawMode);
|
||||
|
||||
var deleteSelection = function () {
|
||||
if (canvas.getActiveObject()) {
|
||||
canvas.getActiveObject().remove();
|
||||
}
|
||||
if (canvas.getActiveGroup()) {
|
||||
canvas.getActiveGroup()._objects.forEach(function (el) {
|
||||
el.remove();
|
||||
});
|
||||
canvas.discardActiveGroup();
|
||||
}
|
||||
canvas.renderAll();
|
||||
module.onLocal();
|
||||
};
|
||||
$deleteButton.click(deleteSelection);
|
||||
$(window).on('keyup', function (e) {
|
||||
if (e.which === 46) { deleteSelection (); }
|
||||
});
|
||||
|
||||
var setEditable = function (bool) {
|
||||
if (readOnly && bool) { return; }
|
||||
if (bool) { $controls.css('display', 'flex'); }
|
||||
else { $controls.hide(); }
|
||||
|
||||
canvas.isDrawingMode = bool ? module.draw : false;
|
||||
if (!bool) {
|
||||
canvas.deactivateAll();
|
||||
canvas.renderAll();
|
||||
}
|
||||
canvas.forEachObject(function (object) {
|
||||
object.selectable = bool;
|
||||
});
|
||||
$canvasContainer.find('canvas').css('border-color', bool? 'black': 'red');
|
||||
};
|
||||
|
||||
var saveImage = module.saveImage = function () {
|
||||
var defaultName = "pretty-picture.png";
|
||||
Cryptpad.prompt(Messages.exportPrompt, defaultName, function (filename) {
|
||||
if (!(typeof(filename) === 'string' && filename)) { return; }
|
||||
$canvas[0].toBlob(function (blob) {
|
||||
saveAs(blob, filename);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.FM = Cryptpad.createFileManager({});
|
||||
module.upload = function (title) {
|
||||
var canvas = $canvas[0];
|
||||
var finish = function (thumb) {
|
||||
canvas.toBlob(function (blob) {
|
||||
blob.name = title;
|
||||
module.FM.handleFile(blob, void 0, thumb);
|
||||
});
|
||||
};
|
||||
|
||||
Thumb.fromCanvas(canvas, function (e, blob) {
|
||||
// carry on even if you can't get a thumbnail
|
||||
if (e) { console.error(e); }
|
||||
finish(blob);
|
||||
});
|
||||
};
|
||||
|
||||
var initializing = true;
|
||||
|
||||
var $bar = $('#toolbar');
|
||||
|
||||
var Title;
|
||||
var UserList;
|
||||
var Metadata;
|
||||
|
||||
var config = module.config = {
|
||||
initialState: '{}',
|
||||
websocketURL: Cryptpad.getWebsocketURL(),
|
||||
validateKey: secret.keys.validateKey,
|
||||
readOnly: readOnly,
|
||||
channel: secret.channel,
|
||||
crypto: Crypto.createEncryptor(secret.keys),
|
||||
transformFunction: JsonOT.transform,
|
||||
};
|
||||
|
||||
var addColorToPalette = function (color, i) {
|
||||
if (readOnly) { return; }
|
||||
var $color = $('<span>', {
|
||||
'class': 'palette-color',
|
||||
})
|
||||
.css({
|
||||
'background-color': color,
|
||||
})
|
||||
.click(function () {
|
||||
var c = Colors.rgb2hex($color.css('background-color'));
|
||||
setColor(c);
|
||||
})
|
||||
.on('dblclick', function (e) {
|
||||
e.preventDefault();
|
||||
pickColor(Colors.rgb2hex($color.css('background-color')), function (c) {
|
||||
$color.css({
|
||||
'background-color': c,
|
||||
});
|
||||
palette.splice(i, 1, c);
|
||||
config.onLocal();
|
||||
setColor(c);
|
||||
});
|
||||
});
|
||||
|
||||
$colors.append($color);
|
||||
};
|
||||
|
||||
var metadataCfg = {};
|
||||
var updatePalette = metadataCfg.updatePalette = function (newPalette) {
|
||||
palette = newPalette;
|
||||
$colors.html('<div class="hidden"> </div>');
|
||||
palette.forEach(addColorToPalette);
|
||||
};
|
||||
updatePalette(palette);
|
||||
|
||||
var makeColorButton = function ($container) {
|
||||
var $testColor = $('<input>', { type: 'color', value: '!' });
|
||||
|
||||
// if colors aren't supported, bail out
|
||||
if ($testColor.attr('type') !== 'color' ||
|
||||
$testColor.val() === '!') {
|
||||
console.log("Colors aren't supported. Aborting");
|
||||
return;
|
||||
}
|
||||
|
||||
var $color = module.$color = $('<button>', {
|
||||
id: "color-picker",
|
||||
title: Messages.canvas_chooseColor,
|
||||
'class': "fa fa-square rightside-button",
|
||||
})
|
||||
.on('click', function () {
|
||||
pickColor($color.css('background-color'), function (color) {
|
||||
setColor(color);
|
||||
});
|
||||
});
|
||||
|
||||
setColor('#000');
|
||||
|
||||
$container.append($color);
|
||||
|
||||
return $color;
|
||||
};
|
||||
|
||||
config.onInit = function (info) {
|
||||
UserList = Cryptpad.createUserList(info, config.onLocal, Cryptget, Cryptpad);
|
||||
|
||||
Title = Cryptpad.createTitle({}, config.onLocal, Cryptpad);
|
||||
|
||||
Metadata = Cryptpad.createMetadata(UserList, Title, metadataCfg, Cryptpad);
|
||||
|
||||
var configTb = {
|
||||
displayed: ['title', 'useradmin', 'spinner', 'lag', 'state', 'share', 'userlist', 'newpad', 'limit', 'upgrade'],
|
||||
userList: UserList.getToolbarConfig(),
|
||||
share: {
|
||||
secret: secret,
|
||||
channel: info.channel
|
||||
},
|
||||
title: Title.getTitleConfig(),
|
||||
common: Cryptpad,
|
||||
readOnly: readOnly,
|
||||
ifrw: window,
|
||||
realtime: info.realtime,
|
||||
network: info.network,
|
||||
$container: $bar,
|
||||
$contentContainer: $('#canvas-area')
|
||||
};
|
||||
|
||||
toolbar = module.toolbar = Toolbar.create(configTb);
|
||||
|
||||
Title.setToolbar(toolbar);
|
||||
|
||||
var $rightside = toolbar.$rightside;
|
||||
|
||||
/* save as template */
|
||||
if (!Cryptpad.isTemplate(window.location.href)) {
|
||||
var templateObj = {
|
||||
rt: info.realtime,
|
||||
Crypt: Cryptget,
|
||||
getTitle: function () { return document.title; }
|
||||
};
|
||||
var $templateButton = Cryptpad.createButton('template', true, templateObj);
|
||||
$rightside.append($templateButton);
|
||||
}
|
||||
|
||||
var $export = Cryptpad.createButton('export', true, {}, saveImage);
|
||||
$rightside.append($export);
|
||||
|
||||
Cryptpad.createButton('savetodrive', true, {}, function () {})
|
||||
.click(function () {
|
||||
Cryptpad.prompt(Messages.exportPrompt, document.title + '.png',
|
||||
function (name) {
|
||||
if (name === null || !name.trim()) { return; }
|
||||
module.upload(name);
|
||||
});
|
||||
}).appendTo($rightside);
|
||||
|
||||
var $forget = Cryptpad.createButton('forget', true, {}, function (err) {
|
||||
if (err) { return; }
|
||||
setEditable(false);
|
||||
toolbar.failed();
|
||||
});
|
||||
$rightside.append($forget);
|
||||
|
||||
var editHash;
|
||||
|
||||
if (!readOnly) {
|
||||
editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys);
|
||||
makeColorButton($rightside);
|
||||
}
|
||||
if (!readOnly) { Cryptpad.replaceHash(editHash); }
|
||||
};
|
||||
|
||||
// used for debugging, feel free to remove
|
||||
var Catch = function (f) {
|
||||
return function () {
|
||||
try {
|
||||
f();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var onRemote = config.onRemote = Catch(function () {
|
||||
if (initializing) { return; }
|
||||
var userDoc = module.realtime.getUserDoc();
|
||||
|
||||
Metadata.update(userDoc);
|
||||
var json = JSON.parse(userDoc);
|
||||
var remoteDoc = json.content;
|
||||
|
||||
// TODO update palette if it has changed
|
||||
|
||||
canvas.loadFromJSON(remoteDoc);
|
||||
canvas.renderAll();
|
||||
|
||||
var content = canvas.toDatalessJSON();
|
||||
if (content !== remoteDoc) { Cryptpad.notify(); }
|
||||
if (readOnly) { setEditable(false); }
|
||||
});
|
||||
setEditable(false);
|
||||
|
||||
var stringifyInner = function (textValue) {
|
||||
var obj = {
|
||||
content: textValue,
|
||||
metadata: {
|
||||
users: UserList.userData,
|
||||
palette: palette,
|
||||
defaultTitle: Title.defaultTitle,
|
||||
type: 'whiteboard',
|
||||
}
|
||||
};
|
||||
if (!initializing) {
|
||||
obj.metadata.title = Title.title;
|
||||
}
|
||||
// stringify the json and send it into chainpad
|
||||
return JSONSortify(obj);
|
||||
};
|
||||
|
||||
|
||||
var onLocal = module.onLocal = config.onLocal = Catch(function () {
|
||||
if (initializing) { return; }
|
||||
if (readOnly) { return; }
|
||||
|
||||
var content = stringifyInner(canvas.toDatalessJSON());
|
||||
|
||||
module.patchText(content);
|
||||
});
|
||||
|
||||
config.onReady = function (info) {
|
||||
var realtime = module.realtime = info.realtime;
|
||||
module.patchText = TextPatcher.create({
|
||||
realtime: realtime
|
||||
});
|
||||
|
||||
var isNew = false;
|
||||
var userDoc = module.realtime.getUserDoc();
|
||||
if (userDoc === "" || userDoc === "{}") { isNew = true; }
|
||||
else {
|
||||
var hjson = JSON.parse(userDoc);
|
||||
if (typeof(hjson) !== 'object' || Array.isArray(hjson) ||
|
||||
(typeof(hjson.type) !== 'undefined' && hjson.type !== 'whiteboard')) {
|
||||
Cryptpad.errorLoadingScreen(Messages.typeError);
|
||||
throw new Error(Messages.typeError);
|
||||
}
|
||||
}
|
||||
|
||||
Cryptpad.removeLoadingScreen();
|
||||
setEditable(true);
|
||||
initializing = false;
|
||||
onRemote();
|
||||
|
||||
/* TODO: restore palette from metadata.palette */
|
||||
|
||||
if (readOnly) { return; }
|
||||
UserList.getLastName(toolbar.$userNameButton, isNew);
|
||||
};
|
||||
|
||||
config.onAbort = function () {
|
||||
setEditable(false);
|
||||
toolbar.failed();
|
||||
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
|
||||
};
|
||||
|
||||
// TODO onConnectionStateChange
|
||||
config.onConnectionChange = function (info) {
|
||||
setEditable(info.state);
|
||||
toolbar.failed();
|
||||
if (info.state) {
|
||||
initializing = true;
|
||||
toolbar.reconnecting(info.myId);
|
||||
Cryptpad.findOKButton().click();
|
||||
} else {
|
||||
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
|
||||
}
|
||||
};
|
||||
|
||||
module.rt = Realtime.start(config);
|
||||
|
||||
canvas.on('mouse:up', onLocal);
|
||||
|
||||
$('#clear').on('click', function () {
|
||||
canvas.clear();
|
||||
onLocal();
|
||||
});
|
||||
|
||||
$('#save').on('click', function () {
|
||||
saveImage();
|
||||
});
|
||||
};
|
||||
|
||||
Cryptpad.ready(function () {
|
||||
andThen();
|
||||
Cryptpad.reportAppUsage();
|
||||
});
|
||||
Cryptpad.onError(function (info) {
|
||||
if (info) {
|
||||
onConnectError();
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('message', onMsg);
|
||||
}).nThen(function (/*waitFor*/) {
|
||||
SFCommonO.start();
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue