manual merge, still wip

pull/1/head
Caleb James DeLisle 7 years ago
commit 368a6b2406

@ -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,66 @@ define([
};
var appToolbar = function () {
return h('div#toolbar.toolbar-container');
return h('div#cp-toolbar.cp-toolbar-container');
};
Pages['/whiteboard/'] = Pages['/whiteboard/index.html'] = function () {
return [
appToolbar(),
h('div#canvas-area', h('canvas#canvas', {
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 +680,7 @@ define([
])
])
])
]),
loadingScreen()
])
];
};

@ -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 () {

@ -13,11 +13,12 @@
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 +26,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 +52,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 +75,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 +87,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;
* {

@ -658,6 +658,14 @@
}
}
}
p.cp-toolbar-account {
&> span {
font-weight: bold;
span {
font-weight: normal;
}
}
}
.cp-toolbar-backup {
margin: 0;
border-radius: 0;

@ -27,4 +27,6 @@ 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-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.5" 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") {

@ -120,6 +120,7 @@ define([
}
console.log('CACHE MISS ' + url);
((/\.less([\?\#].*)?$/.test(url)) ? loadLess : loadCSS)(url, function (err, css) {
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
});
var close = Util.once(function () {
var $t = $(tagger).fadeOut(150, function () { $t.remove(); });
UI.warn(Messages._getKey('tags_duplicate', [val]));
});
var listener = listenForKeys(function () {}, function () {
close();
stopListening(listener);
var listener;
var close = Util.once(function (result, ev) {
var $frame = $(tagger).fadeOut(150, function () {
stopListening(listener);
$frame.remove();
cb(result, ev);
});
});
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);

@ -129,6 +129,10 @@ define([
return messenger.range_requests[txid];
};
var deleteRangeRequest = function (txid) {
delete messenger.range_requests[txid];
};
messenger.getMoreHistory = function (curvePublic, hash, count, cb) {
if (typeof(cb) !== 'function') { return; }
@ -393,7 +397,8 @@ define([
});
orderMessages(curvePublic, decrypted, req.sig);
return void req.cb(void 0, decrypted);
req.cb(void 0, decrypted);
return deleteRangeRequest(txid);
} else {
console.log(parsed);
}

@ -16,7 +16,7 @@ define([], function () {
handlers.splice(handlers.indexOf(cb), 1);
},
fire: function () {
if (fired) { return; }
if (once && fired) { return; }
fired = true;
var args = Array.prototype.slice.call(arguments);
handlers.forEach(function (h) { h.apply(null, args); });
@ -203,5 +203,9 @@ define([], function () {
};
};
Util.slice = function (A) {
return Array.prototype.slice.call(A);
};
return Util;
});

@ -20,9 +20,10 @@ define([
'/common/pinpad.js',
'/customize/application_config.js',
'/common/media-tag.js',
'/bower_components/nthen/index.js',
], function ($, Config, Messages, Store, Util, Hash, UI, History, UserList, Title, Metadata,
Messaging, CodeMirror, Files, FileCrypto, Realtime, Clipboard,
Pinpad, AppConfig, MediaTag) {
Pinpad, AppConfig, MediaTag, Nthen) {
// Configure MediaTags to use our local viewer
if (MediaTag && MediaTag.PdfPlugin) {
@ -102,6 +103,7 @@ define([
common.getAppType = Util.getAppType;
common.notAgainForAnother = Util.notAgainForAnother;
common.uid = Util.uid;
common.slice = Util.slice;
// import hash utilities for export
var createRandomHash = common.createRandomHash = Hash.createRandomHash;
@ -182,6 +184,9 @@ define([
}
return;
};
common.getLanguage = function () {
return Messages._languageUsed;
};
common.getUserlist = function () {
if (store) {
if (store.getProxy() && store.getProxy().info) {
@ -531,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');
@ -2071,23 +2087,8 @@ define([
return function (f) {
if (initialized) {
return void window.setTimeout(function () {
f(void 0, env);
});
return void setTimeout(function () { f(void 0, env); });
}
var block = 0;
var cb = function () {
block--;
if (!block) {
initialized = true;
updateLocalVersion();
common.addTooltips();
f(void 0, env);
if (typeof(window.onhashchange) === 'function') { window.onhashchange(); }
}
};
if (sessionStorage[newPadNameKey]) {
common.initialName = sessionStorage[newPadNameKey];
@ -2097,7 +2098,6 @@ define([
common.initialPath = sessionStorage[newPadPathKey];
delete sessionStorage[newPadPathKey];
}
common.onFriendRequest = function (confirmText, cb) {
common.confirm(confirmText, cb, null, true);
};
@ -2105,20 +2105,9 @@ define([
common.log(data.logText);
};
Store.ready(function (err, storeObj) {
store = common.store = env.store = storeObj;
common.addDirectMessageHandler(common);
var proxy = getProxy();
var network = getNetwork();
network.on('disconnect', function () {
Realtime.setConnectionState(false);
});
network.on('reconnect', function () {
Realtime.setConnectionState(true);
});
var proxy;
var network;
var provideFeedback = function () {
if (Object.keys(proxy).length === 1) {
feedback("FIRST_APP_USE", true);
}
@ -2141,110 +2130,123 @@ define([
}
common.reportScreenDimensions();
common.reportLanguage();
};
$(function() {
// Race condition : if document.body is undefined when alertify.js is loaded, Alertify
// won't work. We have to reset it now to make sure it uses a correct "body"
UI.Alertify.reset();
// clear any tooltips that might get hung
setInterval(function () { common.clearTooltips(); }, 5000);
// Load the new pad when the hash has changed
var oldHref = document.location.href;
window.onhashchange = function () {
var newHref = document.location.href;
var parsedOld = parsePadUrl(oldHref).hashData;
var parsedNew = parsePadUrl(newHref).hashData;
if (parsedOld && parsedNew && (
parsedOld.type !== parsedNew.type
|| parsedOld.channel !== parsedNew.channel
|| parsedOld.mode !== parsedNew.mode
|| parsedOld.key !== parsedNew.key)) {
if (!parsedOld.channel) { oldHref = newHref; return; }
document.location.reload();
return;
}
if (parsedNew) {
oldHref = newHref;
}
};
if (PINNING_ENABLED && isLoggedIn()) {
console.log("logged in. pads will be pinned");
block++;
Pinpad.create(network, proxy, function (e, call) {
if (e) {
console.error(e);
return cb();
}
Nthen(function (waitFor) {
Store.ready(waitFor(function (err, storeObj) {
store = common.store = env.store = storeObj;
common.addDirectMessageHandler(common);
proxy = getProxy();
network = getNetwork();
network.on('disconnect', function () {
Realtime.setConnectionState(false);
});
network.on('reconnect', function () {
Realtime.setConnectionState(true);
});
provideFeedback();
}), common);
}).nThen(function (waitFor) {
$(waitFor());
}).nThen(function (waitFor) {
// Race condition : if document.body is undefined when alertify.js is loaded, Alertify
// won't work. We have to reset it now to make sure it uses a correct "body"
UI.Alertify.reset();
// clear any tooltips that might get hung
setInterval(function () { common.clearTooltips(); }, 5000);
// Load the new pad when the hash has changed
var oldHref = document.location.href;
window.onhashchange = function () {
var newHref = document.location.href;
var parsedOld = parsePadUrl(oldHref).hashData;
var parsedNew = parsePadUrl(newHref).hashData;
if (parsedOld && parsedNew && (
parsedOld.type !== parsedNew.type
|| parsedOld.channel !== parsedNew.channel
|| parsedOld.mode !== parsedNew.mode
|| parsedOld.key !== parsedNew.key)) {
if (!parsedOld.channel) { oldHref = newHref; return; }
document.location.reload();
return;
}
if (parsedNew) { oldHref = newHref; }
};
console.log('RPC handshake complete');
rpc = common.rpc = env.rpc = call;
if (PINNING_ENABLED && isLoggedIn()) {
console.log("logged in. pads will be pinned");
var w0 = waitFor();
Pinpad.create(network, proxy, function (e, call) {
if (e) {
console.error(e);
return w0();
}
common.getPinLimit(function (e, limit, plan, note) {
if (e) { return void console.error(e); }
common.account.limit = limit;
localStorage.plan = common.account.plan = plan;
common.account.note = note;
cb();
});
console.log('RPC handshake complete');
rpc = common.rpc = env.rpc = call;
common.arePinsSynced(function (err, yes) {
if (!yes) {
common.resetPins(function (err) {
if (err) {
console.error("Pin Reset Error");
return console.error(err);
}
console.log('RESET DONE');
});
}
});
common.getPinLimit(function (e, limit, plan, note) {
if (e) { return void console.error(e); }
common.account.limit = limit;
localStorage.plan = common.account.plan = plan;
common.account.note = note;
w0();
});
} else if (PINNING_ENABLED) {
console.log('not logged in. pads will not be pinned');
} else {
console.log('pinning disabled');
}
block++;
require([
'/common/rpc.js',
], function (Rpc) {
Rpc.createAnonymous(network, function (e, call) {
if (e) {
console.error(e);
return void cb();
common.arePinsSynced(function (err, yes) {
if (!yes) {
common.resetPins(function (err) {
if (err) {
console.error("Pin Reset Error");
return console.error(err);
}
console.log('RESET DONE');
});
}
anon_rpc = common.anon_rpc = env.anon_rpc = call;
cb();
});
});
} else if (PINNING_ENABLED) {
console.log('not logged in. pads will not be pinned');
} else {
console.log('pinning disabled');
}
// Everything's ready, continue...
if($('#pad-iframe').length) {
block++;
var $iframe = $('#pad-iframe');
var iframe = $iframe[0];
var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
if (iframeDoc.readyState === 'complete') {
cb();
return;
var w1 = waitFor();
require([
'/common/rpc.js',
], function (Rpc) {
Rpc.createAnonymous(network, function (e, call) {
if (e) {
console.error(e);
return void w1();
}
$iframe.load(cb);
return;
}
block++;
cb();
anon_rpc = common.anon_rpc = env.anon_rpc = call;
w1();
});
});
}, common);
// Everything's ready, continue...
if($('#pad-iframe').length) {
var w2 = waitFor();
var $iframe = $('#pad-iframe');
var iframe = $iframe[0];
var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
if (iframeDoc.readyState === 'complete') {
return void w2();
}
$iframe.load(w2); //cb);
}
}).nThen(function () {
updateLocalVersion();
common.addTooltips();
f(void 0, env);
if (typeof(window.onhashchange) === 'function') { window.onhashchange(); }
});
};
}());
// MAGIC that happens implicitly
$(function () {
Messages._applyTranslation();
});

@ -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) {

@ -77,5 +77,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;
}
};
});

@ -3,15 +3,11 @@ define([
'/bower_components/hyperjson/hyperjson.js',
'/common/toolbar3.js',
'/bower_components/chainpad-json-validator/json-ot.js',
'/common/TypingTests.js',
'json.sortify',
'/bower_components/textpatcher/TextPatcher.js',
'/common/cryptpad-common.js',
'/common/cryptget.js',
'/pad/links.js',
'/bower_components/nthen/index.js',
'/common/sframe-common.js',
'/api/config',
'/customize/messages.js',
'/common/common-util.js',
@ -23,15 +19,11 @@ define([
Hyperjson,
Toolbar,
JsonOT,
TypingTest,
JSONSortify,
TextPatcher,
Cryptpad,
Cryptget,
Links,
nThen,
SFCommon,
ApiConfig,
Messages,
Util)
{
@ -90,12 +82,8 @@ define([
return meta;
};
var isEditable = function () {
return (state === STATE.READY && !readOnly);
};
var stateChange = function (newState) {
var wasEditable = isEditable();
var wasEditable = (state === STATE.READY);
if (state === STATE.INFINITE_SPINNER) { return; }
if (newState === STATE.INFINITE_SPINNER) {
state = newState;
@ -106,6 +94,7 @@ define([
} else {
state = newState;
}
console.log(state + ' ' + wasEditable);
switch (state) {
case STATE.DISCONNECTED:
case STATE.INITIALIZING: {
@ -118,7 +107,10 @@ define([
}
default:
}
if (wasEditable !== isEditable()) { evEditableStateChange.fire(isEditable()); }
if (wasEditable !== (state === STATE.READY)) {
console.log("fire");
evEditableStateChange.fire(state === STATE.READY);
}
};
var onRemote = function () {

@ -42,6 +42,7 @@ var afterLoaded = function (req) {
updated: updated,
cache: data.cache
};
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;
});

@ -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();

@ -182,11 +182,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",
@ -294,7 +318,7 @@ define([
$userAdminContent.append($userName);
options.push({
tag: 'p',
attributes: {'class': 'accountData'},
attributes: {'class': 'cp-toolbar-account'},
content: $userAdminContent.html()
});
}

@ -51,7 +51,7 @@ define([
SFrameChannel.create($('#sbox-iframe')[0].contentWindow, waitFor(function (sfc) {
sframeChan = sfc;
}), false, { cache: cache });
}), false, { cache: cache, language: Cryptpad.getLanguage() });
Cryptpad.ready(waitFor());
}));
}).nThen(function (waitFor) {
@ -228,23 +228,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);
@ -370,6 +369,20 @@ define([
}
});
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);
});
if (cfg.addRpc) {
cfg.addRpc(sframeChan, Cryptpad);
}

@ -164,10 +164,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) {

@ -130,4 +130,9 @@ define({
// Put one or more entries to the cache which will go in localStorage.
'EV_CACHE_PUT': true,
// Set and get the tags using the tag prompt button
'Q_TAGS_GET': true,
'EV_TAGS_SET': 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);
};

@ -415,6 +415,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;
@ -446,6 +447,7 @@ define([
res.forEach(function (l) {
//var paths = findFile(l);
ret.push({
id: l,
paths: findFile(l),
data: exp.getFileData(l)
});

@ -6,19 +6,21 @@ define([
'/common/common-messenger.js',
'/contacts/messenger-ui.js',
'/bower_components/nthen/index.js',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/customize/src/less/cryptpad.less',
], function ($, Crypto, Toolbar, Cryptpad, Messenger, UI) {
], function ($, Crypto, Toolbar, Cryptpad, Messenger, UI, Nthen) {
var Messages = Cryptpad.Messages;
var APP = window.APP = {
Cryptpad: Cryptpad
};
$(function () {
var andThen = function () {
Nthen(function (waitFor) {
$(waitFor());
}).nThen(function (waitFor) {
Cryptpad.ready(waitFor(Cryptpad.reportAppUsage));
}).nThen(function () {
Cryptpad.addLoadingScreen();
var ifrw = $('#pad-iframe')[0].contentWindow;
@ -56,12 +58,5 @@ define([
var messenger = window.messenger = Messenger.messenger(Cryptpad);
UI.create(messenger, $list, $messages);
};
Cryptpad.ready(function () {
andThen();
Cryptpad.reportAppUsage();
});
});
});

@ -129,7 +129,7 @@ define([
console.error('No more messages to fetch');
channel.exhausted = true;
console.log(channel);
return;
return void $moreHistory.addClass('faded');
} else {
channel.TAIL = msg.sig;
}

@ -164,10 +164,11 @@ define([
var $unsortedIcon = $('<span>', {"class": "fa fa-files-o"});
var $templateIcon = $('<span>', {"class": "fa fa-cubes"});
var $recentIcon = $('<span>', {"class": "fa fa-clock-o"});
var $trashIcon = $('<span>', {"class": "fa fa-trash-o"});
var $trashIcon = $('<span>', {"class": "fa fa-trash"});
var $trashEmptyIcon = $('<span>', {"class": "fa fa-trash-o"});
//var $collapseIcon = $('<span>', {"class": "fa fa-minus-square-o expcol"});
var $expandIcon = $('<span>', {"class": "fa fa-plus-square-o expcol"});
var $emptyTrashIcon = $('<button>', {"class": "fa fa-ban"});
var $listIcon = $('<button>', {"class": "fa fa-list"});
var $gridIcon = $('<button>', {"class": "fa fa-th-large"});
var $sortAscIcon = $('<span>', {"class": "fa fa-angle-up sortasc"});
@ -696,6 +697,11 @@ define([
};
var updateContextButton = function () {
if (filesOp.isPathIn(currentPath, [TRASH])) {
$driveToolbar.find('cp-app-drive-toolbar-emptytrash').show();
} else {
$driveToolbar.find('cp-app-drive-toolbar-emptytrash').hide();
}
var $li = $content.find('.selected');
if ($li.length === 0) {
$li = findDataHolder($tree.find('.active'));
@ -1129,7 +1135,7 @@ define([
}
if (data.filename && data.filename !== data.title) {
var $renamed = $renamedIcon.clone().appendTo($state);
$renamed.attr('title', "TODO: you've set a custom name for this pad. Its shared title is:\n<b>{0}</b>");
$renamed.attr('title', Messages._getKey('fm_renamedPad', [data.title]));
}
var name = filesOp.getTitle(element);
@ -1198,6 +1204,13 @@ define([
draggable: true,
'class': 'element-row'
});
if (!isFolder && Array.isArray(APP.selectedFiles)) {
var idx = APP.selectedFiles.indexOf(element);
if (idx !== -1) {
$element.addClass('selected');
APP.selectedFiles.splice(idx, 1);
}
}
if (isFolder) {
addFolderData(element, key, $element);
} else {
@ -1382,6 +1395,18 @@ define([
$gridButton.attr('title', Messages.fm_viewGridButton);
$container.append($listButton).append($gridButton);
};
var createEmptyTrashButton = function ($container) {
var $button = $emptyTrashIcon.clone().addClass('element');
$button.addClass('cp-app-drive-toolbar-emptytrash');
$button.attr('title', Messages.fc_empty);
$button.click(function () {
Cryptpad.confirm(Messages.fm_emptyTrashDialog, function(res) {
if (!res) { return; }
filesOp.emptyTrash(refresh);
});
});
$container.append($button);
};
var getNewPadTypes = function () {
var arr = [];
@ -1751,6 +1776,13 @@ define([
'class': 'file-element element element-row' + roClass,
draggable: draggable
});
if (Array.isArray(APP.selectedFiles)) {
var sidx = APP.selectedFiles.indexOf(id);
if (sidx !== -1) {
$element.addClass('selected');
APP.selectedFiles.splice(sidx, 1);
}
}
addFileData(id, $element);
$element.prepend($icon).dblclick(function () {
openFile(id);
@ -1864,6 +1896,7 @@ define([
e.preventDefault();
if (filesOp.isInTrashRoot(parentPath)) { parentPath = [TRASH]; }
else { parentPath.pop(); }
APP.selectedFiles = [r.id];
module.displayDirectory(parentPath);
});
}
@ -1946,6 +1979,7 @@ define([
path = [ROOT];
}
var isInRoot = filesOp.isPathIn(path, [ROOT]);
var inTrash = filesOp.isPathIn(path, [TRASH]);
var isTrashRoot = filesOp.comparePath(path, [TRASH]);
var isTemplate = filesOp.comparePath(path, [TEMPLATE]);
var isAllFiles = filesOp.comparePath(path, [FILES_DATA]);
@ -1992,6 +2026,10 @@ define([
}
createViewModeButton($toolbar.find('.rightside'));
}
if (inTrash) {
createEmptyTrashButton($toolbar.find('.rightside'));
}
var $list = $('<ul>').appendTo($dirContent);
// NewButton can be undefined if we're in read only mode
@ -2370,6 +2408,13 @@ define([
}
}
if (data.tags && Array.isArray(data.tags)) {
$('<label>', {'for': 'cp-drive-tags'}).text(Messages.fm_prop_tagsList).appendTo($d);
$d.append(Cryptpad.dialog.selectable(data.tags.join(', '), {
id: 'cp-drive-tags',
}));
}
if (APP.loggedIn && AppConfig.enablePinning) {
// check the size of this file...
console.log(data.href);

@ -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.5" 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.5" 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,47 +0,0 @@
<!DOCTYPE html>
<html class="cp pad">
<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">
<link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css">
<script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script>
<link rel="icon" type="image/png"
href="/customize/main-favicon.png"
data-main-favicon="/customize/main-favicon.png"
data-alt-favicon="/customize/alt-favicon.png"
id="favicon" />
<link rel="stylesheet" href="/customize/main.css" />
<style>
html, body {
margin: 0px;
padding: 0px;
}
#pad-iframe {
position:fixed;
top:0px;
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>
<div id="loading">
<div class="loadingContainer">
<img class="cryptofist" src="/customize/cryptofist_small.png" />
<div class="spinnerContainer">
<span class="fa fa-spinner fa-pulse fa-4x fa-fw"></span>
</div>
<p data-localization="loading"></p>
</div>
</div>
</body>
</html>

@ -1,38 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css">
<script src="/bower_components/jquery/dist/jquery.min.js"></script>
<link rel="stylesheet" href="/bower_components/bootstrap/dist/css/bootstrap.min.css">
<style>
html, body {
margin: 0px;
height: 100%;
}
.cryptpad-toolbar {
margin-bottom: 1px;
padding: 0px;
display: inline-block;
}
media-tag * {
max-width: 100%;
margin: auto;
display: block;
}
media-tag *:not(button) {
height: 100%;
}
media-tag video {
min-width: 100%;
max-height: 100%;
}
</style>
</head>
<body>
<div id="toolbar" class="toolbar-container"></div>
<media-tag id="encryptedFile"></media-tag>
</body>
</html>

@ -1,139 +0,0 @@
define([
'jquery',
'/bower_components/chainpad-crypto/crypto.js',
'/bower_components/chainpad-netflux/chainpad-netflux.js',
'/common/toolbar.js',
'/common/cryptpad-common.js',
//'/common/visible.js',
//'/common/notify.js',
//'pdfjs-dist/build/pdf',
//'pdfjs-dist/build/pdf.worker',
'/bower_components/tweetnacl/nacl-fast.min.js',
'/bower_components/file-saver/FileSaver.min.js',
], function ($, Crypto, realtimeInput, Toolbar, Cryptpad /*, Visible, Notify*/) {
//var Messages = Cryptpad.Messages;
//var saveAs = window.saveAs;
//window.Nacl = window.nacl;
$(function () {
var ifrw = $('#pad-iframe')[0].contentWindow;
var $iframe = $('#pad-iframe').contents();
Cryptpad.addLoadingScreen();
var andThen = function () {
var $bar = $iframe.find('.toolbar-container');
var secret = Cryptpad.getSecrets();
if (!secret.keys) { throw new Error("You need a hash"); } // TODO
var cryptKey = secret.keys && secret.keys.fileKeyStr;
var fileId = secret.channel;
var hexFileName = Cryptpad.base64ToHex(fileId);
// var type = "image/png";
var parsed = Cryptpad.parsePadUrl(window.location.href);
var defaultName = Cryptpad.getDefaultName(parsed);
var getTitle = function () {
var pad = Cryptpad.getRelativeHref(window.location.href);
var fo = Cryptpad.getStore().getProxy().fo;
var data = fo.getFileData(pad);
return data ? data.title : undefined;
};
var updateTitle = function (newTitle) {
var title = document.title = newTitle;
$bar.find('.' + Toolbar.constants.title).find('span.title').text(title);
$bar.find('.' + Toolbar.constants.title).find('input').val(title);
};
var suggestName = function () {
return document.title || getTitle() || '';
};
var renameCb = function (err, title) {
document.title = title;
};
var $mt = $iframe.find('#encryptedFile');
$mt.attr('src', '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName);
$mt.attr('data-crypto-key', 'cryptpad:'+cryptKey);
// $mt.attr('data-type', type);
$(window.document).on('decryption', function (e) {
var decrypted = e.originalEvent;
var metadata = decrypted.metadata;
if (decrypted.callback) { decrypted.callback(); }
//console.log(metadata);
//console.log(defaultName);
if (!metadata || metadata.name !== defaultName) { return; }
var title = document.title = metadata.name;
updateTitle(title || defaultName);
})
.on('decryptionError', function (e) {
var error = e.originalEvent;
Cryptpad.alert(error.message);
})
.on('decryptionProgress', function (e) {
var progress = e.originalEvent;
console.log(progress.percent);
});
require(['/common/media-tag.js'], function (MediaTag) {
var configTb = {
displayed: ['useradmin', 'share', 'newpad'],
ifrw: ifrw,
common: Cryptpad,
title: {
onRename: renameCb,
defaultName: defaultName,
suggestName: suggestName
},
share: {
secret: secret,
channel: hexFileName
}
};
Toolbar.create($bar, null, null, null, null, configTb);
updateTitle(Cryptpad.initialName || getTitle() || defaultName);
/**
* Allowed mime types that have to be set for a rendering after a decryption.
*
* @type {Array}
*/
var allowedMediaTypes = [
'image/png',
'image/jpeg',
'image/jpg',
'image/gif',
'audio/mp3',
'audio/ogg',
'audio/wav',
'audio/webm',
'video/mp4',
'video/ogg',
'video/webm',
'application/pdf',
'application/dash+xml',
'download'
];
MediaTag.CryptoFilter.setAllowedMediaTypes(allowedMediaTypes);
MediaTag($mt[0]);
Cryptpad.removeLoadingScreen();
});
};
Cryptpad.ready(function () {
andThen();
Cryptpad.reportAppUsage();
});
});
});

@ -1,102 +0,0 @@
@import "/customize/src/less/variables.less";
@import "/customize/src/less/mixins.less";
@import "/common/markdown.less";
@import "/common/file-dialog.less";
html, body{
height: 100%;
width: 100%;
padding: 0px;
margin: 0px;
overflow: hidden;
box-sizing: border-box;
position: relative;
}
body {
display: flex;
flex-flow: column;
max-height: 100%;
min-height: auto;
}
@slideTime: 500ms;
.CodeMirror {
display: inline-block;
height: 100%;
width: 50%;
&.transition {
transition: width @slideTime, min-width @slideTime, max-width @slideTime;
}
min-width: 20%;
max-width: 80%;
resize: horizontal;
font-size: initial;
}
.CodeMirror.fullPage {
//min-width: 100%;
max-width: 100%;
resize: none;
flex: 1;
}
.CodeMirror-focused .cm-matchhighlight {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==);
background-position: bottom;
background-repeat: repeat-x;
}
#editorContainer {
flex: 1;
display: flex;
flex-flow: row;
height: 100%;
overflow: hidden;
}
#previewContainer {
flex: 1;
padding: 5px 20px;
overflow: auto;
display: inline-block;
height: 100%;
border-left: 1px solid black;
box-sizing: border-box;
font-family: Calibri,Ubuntu,sans-serif;
word-wrap: break-word;
position: relative;
media-tag {
* {
max-width:100%;
}
iframe[type="application/pdf"] {
max-height:50vh;
}
}
}
#preview {
max-width: 40vw;
margin: 1em auto;
.markdown_preformatted-code;
.markdown_gfm-table(black);
}
.cp-splitter {
position: absolute;
height: 100%;
width: 8px;
top: 0;
left: 0;
cursor: col-resize;
}
@media (max-width: @media-medium-screen) {
.CodeMirror {
flex: 1;
max-width: 100%;
resize: none;
}
#previewContainer {
display: none !important;
}
}

@ -1,41 +0,0 @@
<!DOCTYPE html>
<html class="cp code">
<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">
<meta name="referrer" content="no-referrer" />
<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 {
overflow-y: hidden;
}
#iframe-container {
position: fixed;
top: 0px;
bottom: 0px;
right: 0px;
left: 0px;
padding: 0px;
}
#pad-iframe {
width:100%;
height:100%;
border:none;
margin:0;
padding:0;
overflow:hidden;
box-sizing: border-box;
}
/* We use !important here to override the 96% set to the element in DecorateToolbar.js
when we enter fullscreen mode. It allows us to avoid changing the iframe's size in JS */
#pad-iframe.fullscreen {
top: 0px;
height: 100% !important;
}
</style>
</head>
<body>
<div id="iframe-container">
<iframe id="pad-iframe"></iframe><script src="/common/noscriptfix.js"></script>
</div>

@ -1,17 +0,0 @@
<!DOCTYPE html>
<html style="height: 100%;">
<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="inner.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style> .loading-hidden { display: none; } </style>
</head>
<body class="loading-hidden">
<div id="cme_toolbox" class="toolbar-container"></div>
<div id="editorContainer">
<textarea id="editor1" name="editor1"></textarea>
<div id="previewContainer"><div id="preview"></div></div>
</div>
</body>
</html>

@ -1,40 +0,0 @@
define([
'jquery',
'cm/lib/codemirror',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'less!/code/code.less',
'less!/customize/src/less/toolbar.less',
'less!/customize/src/less/cryptpad.less',
'css!cm/lib/codemirror.css',
'css!cm/addon/dialog/dialog.css',
'css!cm/addon/fold/foldgutter.css',
'cm/mode/markdown/markdown',
'cm/addon/mode/loadmode',
'cm/mode/meta',
'cm/addon/mode/overlay',
'cm/addon/mode/multiplex',
'cm/addon/mode/simple',
'cm/addon/edit/closebrackets',
'cm/addon/edit/matchbrackets',
'cm/addon/edit/trailingspace',
'cm/addon/selection/active-line',
'cm/addon/search/search',
'cm/addon/search/match-highlighter',
'cm/addon/search/searchcursor',
'cm/addon/dialog/dialog',
'cm/addon/fold/foldcode',
'cm/addon/fold/foldgutter',
'cm/addon/fold/brace-fold',
'cm/addon/fold/xml-fold',
'cm/addon/fold/markdown-fold',
'cm/addon/fold/comment-fold',
'cm/addon/display/placeholder',
], function ($, CMeditor) {
window.CodeMirror = CMeditor;
$('.loading-hidden').removeClass('loading-hidden');
});

@ -1,559 +0,0 @@
define([
'jquery',
'/bower_components/chainpad-crypto/crypto.js',
'/bower_components/chainpad-netflux/chainpad-netflux.js',
'/bower_components/textpatcher/TextPatcher.js',
'/common/toolbar2.js',
'json.sortify',
'/bower_components/chainpad-json-validator/json-ot.js',
'/common/cryptpad-common.js',
'/common/cryptget.js',
'/common/diffMarked.js',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/customize/src/less/cryptpad.less'
], function ($, Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad,
Cryptget, DiffMd) {
var Messages = Cryptpad.Messages;
var APP = window.APP = {
Cryptpad: Cryptpad,
};
$(function () {
Cryptpad.addLoadingScreen();
var ifrw = APP.ifrw = $('#pad-iframe')[0].contentWindow;
var stringify = function (obj) {
return JSONSortify(obj);
};
var toolbar;
var editor;
var secret = Cryptpad.getSecrets();
var readOnly = secret.keys && !secret.keys.editKeyStr;
if (!secret.keys) {
secret.keys = secret.key;
}
var onConnectError = function () {
Cryptpad.errorLoadingScreen(Messages.websocketError);
};
var andThen = function (CMeditor) {
var $iframe = $('#pad-iframe').contents();
var $contentContainer = $iframe.find('#editorContainer');
var $previewContainer = $iframe.find('#previewContainer');
var $preview = $iframe.find('#preview');
$preview.click(function (e) {
if (!e.target) { return; }
var $t = $(e.target);
if ($t.is('a') || $t.parents('a').length) {
e.preventDefault();
var $a = $t.is('a') ? $t : $t.parents('a').first();
var href = $a.attr('href');
window.open(href);
}
});
var CodeMirror = Cryptpad.createCodemirror(ifrw, Cryptpad, null, CMeditor);
$iframe.find('.CodeMirror').addClass('fullPage');
editor = CodeMirror.editor;
var setIndentation = APP.setIndentation = function (units, useTabs) {
if (typeof(units) !== 'number') { return; }
editor.setOption('indentUnit', units);
editor.setOption('tabSize', units);
editor.setOption('indentWithTabs', useTabs);
};
var indentKey = 'indentUnit';
var useTabsKey = 'indentWithTabs';
var proxy = Cryptpad.getProxy();
var updateIndentSettings = APP.updateIndentSettings = function () {
var indentUnit = proxy.settings[indentKey];
var useTabs = proxy.settings[useTabsKey];
setIndentation(
typeof(indentUnit) === 'number'? indentUnit: 2,
typeof(useTabs) === 'boolean'? useTabs: false);
};
proxy.on('change', ['settings', indentKey], updateIndentSettings);
proxy.on('change', ['settings', useTabsKey], updateIndentSettings);
var $bar = $('#pad-iframe')[0].contentWindow.$('#cme_toolbox');
var isHistoryMode = false;
var setEditable = APP.setEditable = function (bool) {
if (readOnly && bool) { return; }
editor.setOption('readOnly', !bool);
};
var Title;
var UserList;
var Metadata;
var config = {
initialState: '{}',
websocketURL: Cryptpad.getWebsocketURL(),
channel: secret.channel,
// our public key
validateKey: secret.keys.validateKey || undefined,
readOnly: readOnly,
crypto: Crypto.createEncryptor(secret.keys),
network: Cryptpad.getNetwork(),
transformFunction: JsonOT.validate,
};
var canonicalize = function (t) { return t.replace(/\r\n/g, '\n'); };
var setHistory = function (bool, update) {
isHistoryMode = bool;
setEditable(!bool);
if (!bool && update) {
config.onRemote();
}
};
var initializing = true;
var stringifyInner = function (textValue) {
var obj = {
content: textValue,
metadata: {
users: UserList.userData,
defaultTitle: Title.defaultTitle
}
};
if (!initializing) {
obj.metadata.title = Title.title;
}
// set mode too...
obj.highlightMode = CodeMirror.highlightMode;
// stringify the json and send it into chainpad
return stringify(obj);
};
var forceDrawPreview = function () {
try {
DiffMd.apply(DiffMd.render(editor.getValue()), $preview);
} catch (e) { console.error(e); }
};
var drawPreview = Cryptpad.throttle(function () {
if (CodeMirror.highlightMode !== 'markdown') { return; }
if (!$previewContainer.is(':visible')) { return; }
forceDrawPreview();
}, 150);
var onLocal = config.onLocal = function () {
if (initializing) { return; }
if (isHistoryMode) { return; }
if (readOnly) { return; }
editor.save();
drawPreview();
var textValue = canonicalize(CodeMirror.$textarea.val());
var shjson = stringifyInner(textValue);
APP.patchText(shjson);
if (APP.realtime.getUserDoc() !== shjson) {
console.error("realtime.getUserDoc() !== shjson");
}
};
var mediaTagModes = [
'markdown',
'html',
'htmlembedded',
'htmlmixed',
'index.html',
'php',
'velocity',
'xml',
];
var onModeChanged = function (mode) {
var $codeMirror = $iframe.find('.CodeMirror');
window.clearTimeout(APP.previewTo);
$codeMirror.addClass('transition');
APP.previewTo = window.setTimeout(function () {
$codeMirror.removeClass('transition');
}, 500);
if (mediaTagModes.indexOf(mode) !== -1) {
APP.$mediaTagButton.show();
} else { APP.$mediaTagButton.hide(); }
if (mode === "markdown") {
APP.$previewButton.show();
Cryptpad.getPadAttribute('previewMode', function (e, data) {
if (e) { return void console.error(e); }
if (data !== false) {
$previewContainer.show();
APP.$previewButton.addClass('active');
$codeMirror.removeClass('fullPage');
}
});
return;
}
APP.$previewButton.hide();
$previewContainer.hide();
APP.$previewButton.removeClass('active');
$codeMirror.addClass('fullPage');
if (typeof(APP.updateIndentSettings) === 'function') {
APP.updateIndentSettings();
}
};
config.onInit = function (info) {
UserList = Cryptpad.createUserList(info, config.onLocal, Cryptget, Cryptpad);
var titleCfg = { getHeadingText: CodeMirror.getHeadingText };
Title = Cryptpad.createTitle(titleCfg, config.onLocal, Cryptpad);
Metadata = Cryptpad.createMetadata(UserList, Title, null, 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: ifrw,
realtime: info.realtime,
network: info.network,
$container: $bar,
$contentContainer: $contentContainer
};
toolbar = APP.toolbar = Toolbar.create(configTb);
Title.setToolbar(toolbar);
CodeMirror.init(config.onLocal, Title, toolbar);
var $rightside = toolbar.$rightside;
var $drawer = toolbar.$drawer;
var editHash;
if (!readOnly) {
editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys);
}
/* add a history button */
var histConfig = {
onLocal: config.onLocal,
onRemote: config.onRemote,
setHistory: setHistory,
applyVal: function (val) {
var remoteDoc = JSON.parse(val || '{}').content;
editor.setValue(remoteDoc || '');
editor.save();
},
$toolbar: $bar
};
var $hist = Cryptpad.createButton('history', true, {histConfig: histConfig});
$drawer.append($hist);
/* save as template */
if (!Cryptpad.isTemplate(window.location.href)) {
var templateObj = {
rt: info.realtime,
Crypt: Cryptget,
getTitle: Title.getTitle
};
var $templateButton = Cryptpad.createButton('template', true, templateObj);
$rightside.append($templateButton);
}
/* add an export button */
var $export = Cryptpad.createButton('export', true, {}, CodeMirror.exportText);
$drawer.append($export);
if (!readOnly) {
/* add an import button */
var $import = Cryptpad.createButton('import', true, {}, CodeMirror.importText);
$drawer.append($import);
}
/* add a forget button */
var forgetCb = function (err) {
if (err) { return; }
setEditable(false);
};
var $forgetPad = Cryptpad.createButton('forget', true, {}, forgetCb);
$rightside.append($forgetPad);
var fileDialogCfg = {
$body: $iframe.find('body'),
onSelect: function (href) {
var parsed = Cryptpad.parsePadUrl(href);
var hexFileName = Cryptpad.base64ToHex(parsed.hashData.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '"></media-tag>';
editor.replaceSelection(mt);
},
data: APP
};
APP.$mediaTagButton = $('<button>', {
title: Messages.filePickerButton,
'class': 'rightside-button fa fa-picture-o',
style: 'font-size: 17px'
}).click(function () {
Cryptpad.createFileDialog(fileDialogCfg);
}).appendTo($rightside);
var $previewButton = APP.$previewButton = Cryptpad.createButton(null, true);
$previewButton.removeClass('fa-question').addClass('fa-eye');
$previewButton.attr('title', Messages.previewButtonTitle);
$previewButton.click(function () {
var $codeMirror = $iframe.find('.CodeMirror');
window.clearTimeout(APP.previewTo);
$codeMirror.addClass('transition');
APP.previewTo = window.setTimeout(function () {
$codeMirror.removeClass('transition');
}, 500);
if (CodeMirror.highlightMode !== 'markdown') {
$previewContainer.show();
}
$previewContainer.toggle();
if ($previewContainer.is(':visible')) {
forceDrawPreview();
$codeMirror.removeClass('fullPage');
Cryptpad.setPadAttribute('previewMode', true, function (e) {
if (e) { return console.log(e); }
});
$previewButton.addClass('active');
} else {
$codeMirror.addClass('fullPage');
$previewButton.removeClass('active');
Cryptpad.setPadAttribute('previewMode', false, function (e) {
if (e) { return console.log(e); }
});
}
});
$rightside.append($previewButton);
if (!readOnly) {
CodeMirror.configureTheme(function () {
CodeMirror.configureLanguage(null, onModeChanged);
});
}
else {
CodeMirror.configureTheme();
}
// set the hash
if (!readOnly) { Cryptpad.replaceHash(editHash); }
};
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;
if (userDoc === "" || userDoc === "{}") { isNew = true; }
var newDoc = "";
if(userDoc !== "") {
var hjson = JSON.parse(userDoc);
if (typeof (hjson) !== 'object' || Array.isArray(hjson) ||
(typeof(hjson.type) !== 'undefined' && hjson.type !== 'code')) {
var errorText = Messages.typeError;
Cryptpad.errorLoadingScreen(errorText);
throw new Error(errorText);
}
newDoc = hjson.content;
if (hjson.highlightMode) {
CodeMirror.setMode(hjson.highlightMode, onModeChanged);
}
}
if (!CodeMirror.highlightMode) {
CodeMirror.setMode('markdown', onModeChanged);
console.log("%s => %s", CodeMirror.highlightMode, CodeMirror.$language.val());
}
// Update the user list (metadata) from the hyperjson
Metadata.update(userDoc);
if (newDoc) {
editor.setValue(newDoc);
}
if (Cryptpad.initialName && Title.isDefaultTitle()) {
Title.updateTitle(Cryptpad.initialName);
}
Cryptpad.getPadAttribute('previewMode', function (e, data) {
if (e) { return void console.error(e); }
if (data === false && APP.$previewButton) {
APP.$previewButton.click();
}
});
/*
// add the splitter
if (!$iframe.has('.cp-splitter').length) {
var $preview = $iframe.find('#previewContainer');
var splitter = $('<div>', {
'class': 'cp-splitter'
}).appendTo($preview);
$preview.on('scroll', function() {
splitter.css('top', $preview.scrollTop() + 'px');
});
var $target = $iframe.find('.CodeMirror');
splitter.on('mousedown', function (e) {
e.preventDefault();
var x = e.pageX;
var w = $target.width();
$iframe.on('mouseup mousemove', function handler(evt) {
if (evt.type === 'mouseup') {
$iframe.off('mouseup mousemove', handler);
return;
}
$target.css('width', (w - x + evt.pageX) + 'px');
});
});
}
*/
Cryptpad.removeLoadingScreen();
setEditable(true);
initializing = false;
onLocal(); // push local state to avoid parse errors later.
if (readOnly) {
config.onRemote();
return;
}
UserList.getLastName(toolbar.$userNameButton, isNew);
var fmConfig = {
dropArea: $iframe.find('.CodeMirror'),
body: $iframe.find('body'),
onUploaded: function (ev, data) {
//var cursor = editor.getCursor();
//var cleanName = data.name.replace(/[\[\]]/g, '');
//var text = '!['+cleanName+']('+data.url+')';
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 src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '"></media-tag>';
editor.replaceSelection(mt);
}
};
APP.FM = Cryptpad.createFileManager(fmConfig);
};
config.onRemote = function () {
if (initializing) { return; }
if (isHistoryMode) { return; }
var oldDoc = canonicalize(CodeMirror.$textarea.val());
var shjson = APP.realtime.getUserDoc();
// Update the user list (metadata) from the hyperjson
Metadata.update(shjson);
var hjson = JSON.parse(shjson);
var remoteDoc = hjson.content;
var highlightMode = hjson.highlightMode;
if (highlightMode && highlightMode !== APP.highlightMode) {
CodeMirror.setMode(highlightMode, onModeChanged);
}
CodeMirror.setValueAndCursor(oldDoc, remoteDoc, TextPatcher);
drawPreview();
if (!readOnly) {
var textValue = canonicalize(CodeMirror.$textarea.val());
var shjson2 = stringifyInner(textValue);
if (shjson2 !== shjson) {
console.error("shjson2 !== shjson");
TextPatcher.log(shjson, TextPatcher.diff(shjson, shjson2));
APP.patchText(shjson2);
}
}
if (oldDoc !== remoteDoc) { Cryptpad.notify(); }
};
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);
toolbar.failed();
if (info.state) {
initializing = true;
toolbar.reconnecting(info.myId);
Cryptpad.findOKButton().click();
} else {
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
}
};
config.onError = onConnectError;
APP.realtime = Realtime.start(config);
editor.on('change', onLocal);
Cryptpad.onLogout(function () { setEditable(false); });
};
var interval = 100;
var second = function (CM) {
Cryptpad.ready(function () {
andThen(CM);
Cryptpad.reportAppUsage();
});
Cryptpad.onError(function (info) {
if (info && info.type === "store") {
onConnectError();
}
});
};
var first = function () {
if (ifrw.CodeMirror) {
second(ifrw.CodeMirror);
} else {
console.log("CodeMirror was not defined. Trying again in %sms", interval);
setTimeout(first, interval);
}
};
first();
});
});

@ -1,40 +0,0 @@
<!DOCTYPE html>
<html class="cp slide">
<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">
<meta name="referrer" content="no-referrer" />
<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 {
overflow-y: hidden;
}
#iframe-container {
position: fixed;
top: 0px;
bottom: 0px;
right: 0px;
left: 0px;
padding: 0px;
}
#pad-iframe {
width:100%;
height:100%;
border:none;
margin:0;
padding:0;
overflow:hidden;
box-sizing: border-box;
}
body #pad-iframe.fullscreen {
top: 0px;
height: 100%;
}
</style>
</head>
<body>
<div id="iframe-container">
<iframe id="pad-iframe", name="pad-iframe"></iframe><script src="/common/noscriptfix.js"></script>
</div>

@ -1,33 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css">
<script src="/bower_components/jquery/dist/jquery.min.js"></script>
<script async data-bootload="inner.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
<style>.loading-hidden { display: none; } </style>
</head>
<body class="loading-hidden">
<div id="bar"></div>
<!-- <textarea></textarea>-->
<div id="cme_toolbox" class="toolbar-container"></div>
<div id="editorContainer">
<textarea id="editor1" name="editor1"></textarea>
<div class="cp-app-slide-viewer slide" tabindex="2">
<div id="modal">
<div id="button_exit" class="button"><span class="fa fa-times"></span></div>
<div id="button_left" class="button"><span class="fa fa-chevron-left"></span></div>
<div id="button_right" class="button"><span class="fa fa-chevron-right"></span></div>
<div id="content"></div>
</div>
<div id="print"></div>
</div>
</div>
<div id="nope"></div>
<div id="colorPicker_check"></div>
</body>
</html>

@ -1,41 +0,0 @@
define([
'jquery',
'cm/lib/codemirror',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'less!/customize/src/less/toolbar.less',
'less!/customize/src/less/cryptpad.less',
'less!/slide/slide.less',
'css!cm/lib/codemirror.css',
'css!cm/addon/dialog/dialog.css',
'css!cm/addon/fold/foldgutter.css',
'cm/mode/markdown/markdown',
'cm/addon/mode/loadmode',
'cm/mode/meta',
'cm/addon/mode/overlay',
'cm/addon/mode/multiplex',
'cm/addon/mode/simple',
'cm/addon/edit/closebrackets',
'cm/addon/edit/matchbrackets',
'cm/addon/edit/trailingspace',
'cm/addon/selection/active-line',
'cm/addon/search/search',
'cm/addon/search/match-highlighter',
'cm/addon/search/searchcursor',
'cm/addon/dialog/dialog',
'cm/addon/fold/foldcode',
'cm/addon/fold/foldgutter',
'cm/addon/fold/brace-fold',
'cm/addon/fold/xml-fold',
'cm/addon/fold/markdown-fold',
'cm/addon/fold/comment-fold',
'cm/addon/display/placeholder',
], function ($, CMeditor) {
window.CodeMirror = CMeditor;
$('.loading-hidden').removeClass('loading-hidden');
});

@ -1,680 +0,0 @@
define([
'jquery',
'/bower_components/chainpad-crypto/crypto.js',
'/bower_components/chainpad-netflux/chainpad-netflux.js',
'/bower_components/textpatcher/TextPatcher.js',
'/common/toolbar2.js',
'json.sortify',
'/bower_components/chainpad-json-validator/json-ot.js',
'/common/cryptpad-common.js',
'/common/cryptget.js',
'/slide/slide.js',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/customize/src/less/cryptpad.less',
], function ($, Crypto, Realtime, TextPatcher, Toolbar, JSONSortify, JsonOT, Cryptpad, Cryptget, Slide) {
var Messages = Cryptpad.Messages;
var module = window.APP = {
Cryptpad: Cryptpad,
TextPatcher: TextPatcher,
Slide: Slide,
};
var APP = window.APP;
var SLIDE_BACKCOLOR_ID = "cryptpad-backcolor";
var SLIDE_COLOR_ID = "cryptpad-color";
$(function () {
Cryptpad.addLoadingScreen();
var stringify = function (obj) {
return JSONSortify(obj);
};
var ifrw = module.ifrw = $('#pad-iframe')[0].contentWindow;
var toolbar;
var editor;
var secret = Cryptpad.getSecrets();
var readOnly = secret.keys && !secret.keys.editKeyStr;
Slide.readOnly = readOnly;
if (!secret.keys) {
secret.keys = secret.key;
}
var presentMode = Slide.isPresentURL();
var onConnectError = function () {
Cryptpad.errorLoadingScreen(Messages.websocketError);
};
var andThen = function (CMeditor) {
var $iframe = $('#pad-iframe').contents();
var $contentContainer = $iframe.find('#editorContainer');
var CodeMirror = Cryptpad.createCodemirror(ifrw, Cryptpad, null, CMeditor);
editor = CodeMirror.editor;
var $bar = $('#pad-iframe')[0].contentWindow.$('#cme_toolbox');
var $pad = $('#pad-iframe');
var isHistoryMode = false;
var setEditable = module.setEditable = function (bool) {
if (readOnly && bool) { return; }
editor.setOption('readOnly', !bool);
};
var Title;
var UserList;
var Metadata;
var setTabTitle = function (title) {
var slideNumber = '';
if (Slide.shown) { //Slide.index && Slide.content.length) {
slideNumber = ' (' + Slide.index + '/' + Slide.content.length + ')';
}
document.title = title + slideNumber;
};
var initialState = Messages.slideInitialState;
var $modal = $pad.contents().find('#modal');
var $content = $pad.contents().find('#content');
var $print = $pad.contents().find('#print');
var slideOptions = {};
$content.click(function (e) {
if (!e.target) { return; }
var $t = $(e.target);
if ($t.is('a') || $t.parents('a').length) {
e.preventDefault();
var $a = $t.is('a') ? $t : $t.parents('a').first();
var href = $a.attr('href');
window.open(href);
}
});
Slide.setModal($modal, $content, $pad, ifrw, slideOptions, initialState);
var enterPresentationMode = function (shouldLog) {
Slide.show(true, editor.getValue());
if (shouldLog) {
Cryptpad.log(Messages.presentSuccess);
}
};
if (presentMode) {
enterPresentationMode(true);
}
var textColor;
var backColor;
var config = {
initialState: '{}',
websocketURL: Cryptpad.getWebsocketURL(),
channel: secret.channel,
// our public key
validateKey: secret.keys.validateKey || undefined,
readOnly: readOnly,
crypto: Crypto.createEncryptor(secret.keys),
transformFunction: JsonOT.validate,
network: Cryptpad.getNetwork()
};
var canonicalize = function (t) { return t.replace(/\r\n/g, '\n'); };
var setHistory = function (bool, update) {
isHistoryMode = bool;
setEditable(!bool);
if (!bool && update) {
config.onRemote();
}
};
var initializing = true;
var stringifyInner = function (textValue) {
var obj = {
content: textValue,
metadata: {
users: UserList.userData,
defaultTitle: Title.defaultTitle,
slideOptions: slideOptions
}
};
if (!initializing) {
obj.metadata.title = Title.title;
}
if (textColor) {
obj.metadata.color = textColor;
}
if (backColor) {
obj.metadata.backColor = backColor;
}
// stringify the json and send it into chainpad
return stringify(obj);
};
var onLocal = config.onLocal = function () {
if (initializing) { return; }
if (isHistoryMode) { return; }
if (readOnly) { return; }
editor.save();
var textValue = canonicalize(CodeMirror.$textarea.val());
var shjson = stringifyInner(textValue);
module.patchText(shjson);
Slide.update(textValue);
if (module.realtime.getUserDoc() !== shjson) {
console.error("realtime.getUserDoc() !== shjson");
}
};
var metadataCfg = {
slideOptions: function (newOpt) {
if (stringify(newOpt) !== stringify(slideOptions)) {
$.extend(slideOptions, newOpt);
// TODO: manage realtime + cursor in the "options" modal ??
Slide.updateOptions();
}
}
};
var updateColors = metadataCfg.slideColors = function (text, back) {
if (text) {
textColor = text;
$modal.css('color', text);
$modal.css('border-color', text);
$pad.contents().find('#' + SLIDE_COLOR_ID).css('color', text);
}
if (back) {
backColor = back;
$modal.css('background-color', back);
//$pad.contents().find('#' + SLIDE_COLOR_ID).css('background', back);
$pad.contents().find('#' + SLIDE_BACKCOLOR_ID).css('color', back);
}
};
var createPrintDialog = function () {
var slideOptionsTmp = {
title: false,
slide: false,
date: false,
transition: true,
style: ''
};
$.extend(slideOptionsTmp, slideOptions);
var $container = $('<div class="alertify">');
var $container2 = $('<div class="dialog">').appendTo($container);
var $div = $('<div id="printOptions">').appendTo($container2);
var $p = $('<p>', {'class': 'msg'}).appendTo($div);
$('<b>').text(Messages.printOptions).appendTo($p);
$p.append($('<br>'));
// Slide number
$('<input>', {type: 'checkbox', id: 'checkNumber', checked: slideOptionsTmp.slide}).on('change', function () {
var c = this.checked;
slideOptionsTmp.slide = c;
}).appendTo($p).css('width', 'auto');
$('<label>', {'for': 'checkNumber'}).text(Messages.printSlideNumber).appendTo($p);
$p.append($('<br>'));
// Date
$('<input>', {type: 'checkbox', id: 'checkDate', checked: slideOptionsTmp.date}).on('change', function () {
var c = this.checked;
slideOptionsTmp.date = c;
}).appendTo($p).css('width', 'auto');
$('<label>', {'for': 'checkDate'}).text(Messages.printDate).appendTo($p);
$p.append($('<br>'));
// Title
$('<input>', {type: 'checkbox', id: 'checkTitle', checked: slideOptionsTmp.title}).on('change', function () {
var c = this.checked;
slideOptionsTmp.title = c;
}).appendTo($p).css('width', 'auto');
$('<label>', {'for': 'checkTitle'}).text(Messages.printTitle).appendTo($p);
$p.append($('<br>'));
// Transition
$('<input>', {type: 'checkbox', id: 'checkTransition', checked: slideOptionsTmp.transition}).on('change', function () {
var c = this.checked;
slideOptionsTmp.transition = c;
}).appendTo($p).css('width', 'auto');
$('<label>', {'for': 'checkTransition'}).text(Messages.printTransition).appendTo($p);
$p.append($('<br>'));
// CSS
$('<label>', {'for': 'cssPrint'}).text(Messages.printCSS).appendTo($p);
$p.append($('<br>'));
var $textarea = $('<textarea>', {'id':'cssPrint'}).css({'width':'100%', 'height':'100px'}).appendTo($p)
.on('keydown keyup', function (e) {
e.stopPropagation();
});
$textarea.val(slideOptionsTmp.style);
window.setTimeout(function () { $textarea.focus(); }, 0);
var h;
var todo = function () {
$.extend(slideOptions, slideOptionsTmp);
slideOptions.style = $textarea.val();
onLocal();
$container.remove();
Cryptpad.stopListening(h);
};
var todoCancel = function () {
$container.remove();
Cryptpad.stopListening(h);
};
h = Cryptpad.listenForKeys(todo, todoCancel);
var $nav = $('<nav>').appendTo($div);
$('<button>', {'class': 'cancel'}).text(Messages.cancelButton).appendTo($nav).click(todoCancel);
$('<button>', {'class': 'ok'}).text(Messages.settings_save).appendTo($nav).click(todo);
return $container;
};
config.onInit = function (info) {
UserList = Cryptpad.createUserList(info, config.onLocal, Cryptget, Cryptpad);
var titleCfg = {
updateLocalTitle: setTabTitle,
getHeadingText: CodeMirror.getHeadingText
};
Title = Cryptpad.createTitle(titleCfg, config.onLocal, Cryptpad);
Slide.setTitle(Title);
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: ifrw,
realtime: info.realtime,
network: info.network,
$container: $bar,
$contentContainer: $contentContainer
};
toolbar = module.toolbar = Toolbar.create(configTb);
Title.setToolbar(toolbar);
CodeMirror.init(config.onLocal, Title, toolbar);
var $rightside = toolbar.$rightside;
var $drawer = toolbar.$drawer;
var editHash;
if (!readOnly) {
editHash = Cryptpad.getEditHashFromKeys(info.channel, secret.keys);
}
/* add a history button */
var histConfig = {
onLocal: config.onLocal,
onRemote: config.onRemote,
setHistory: setHistory,
applyVal: function (val) {
var remoteDoc = JSON.parse(val || '{}').content;
editor.setValue(remoteDoc || '');
editor.save();
},
$toolbar: $bar
};
var $hist = Cryptpad.createButton('history', true, {histConfig: histConfig});
$drawer.append($hist);
/* 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);
}
/* add an export button */
var $export = Cryptpad.createButton('export', true, {}, CodeMirror.exportText);
$drawer.append($export);
if (!readOnly) {
/* add an import button */
var $import = Cryptpad.createButton('import', true, {}, CodeMirror.importText);
$drawer.append($import);
}
/* add a forget button */
var forgetCb = function (err) {
if (err) { return; }
setEditable(false);
};
var $forgetPad = Cryptpad.createButton('forget', true, {}, forgetCb);
$rightside.append($forgetPad);
var fileDialogCfg = {
$body: $iframe.find('body'),
onSelect: function (href) {
var parsed = Cryptpad.parsePadUrl(href);
var hexFileName = Cryptpad.base64ToHex(parsed.hashData.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '"></media-tag>';
editor.replaceSelection(mt);
},
data: APP
};
$('<button>', {
title: Messages.filePickerButton,
'class': 'rightside-button fa fa-picture-o',
style: 'font-size: 17px'
}).click(function () {
Cryptpad.createFileDialog(fileDialogCfg);
}).appendTo($rightside);
var $previewButton = APP.$previewButton = Cryptpad.createButton(null, true);
$previewButton.removeClass('fa-question').addClass('fa-eye');
$previewButton.attr('title', Messages.previewButtonTitle);
$previewButton.click(function () {
var $c = $iframe.find('#editorContainer');
if ($c.hasClass('preview')) {
Cryptpad.setPadAttribute('previewMode', false, function (e) {
if (e) { return console.log(e); }
});
$previewButton.removeClass('active');
return void $c.removeClass('preview');
}
Cryptpad.setPadAttribute('previewMode', true, function (e) {
if (e) { return console.log(e); }
});
$c.addClass('preview');
$previewButton.addClass('active');
Slide.updateFontSize();
});
$rightside.append($previewButton);
var $printButton = $('<button>', {
title: Messages.printButtonTitle,
'class': 'rightside-button fa fa-print',
style: 'font-size: 17px'
}).click(function () {
Slide.update(editor.getValue(), true);
$print.html($content.html());
Cryptpad.confirm("Are you sure you want to print?", function (yes) {
if (yes) {
window.frames["pad-iframe"].focus();
window.frames["pad-iframe"].print();
}
}, {ok: Messages.printButton});
Cryptpad.feedback('PRINT_SLIDES');
//$('body').append(createPrintDialog());
}).append($('<span>', {'class': 'drawer'}).text(Messages.printText));
$drawer.append($printButton);
var $slideOptions = $('<button>', {
title: Messages.slideOptionsTitle,
'class': 'rightside-button fa fa-cog',
style: 'font-size: 17px'
}).click(function () {
$('body').append(createPrintDialog());
}).append($('<span>', {'class': 'drawer'}).text(Messages.slideOptionsText));
$drawer.append($slideOptions);
var $present = Cryptpad.createButton('present', true)
.click(function () {
enterPresentationMode(true);
});
if (presentMode) {
$present.hide();
}
$rightside.append($present);
var configureColors = function () {
var $back = $('<button>', {
id: SLIDE_BACKCOLOR_ID,
'class': 'fa fa-square rightside-button',
'style': 'font-family: FontAwesome; color: #000;',
title: Messages.backgroundButtonTitle
});
var $text = $('<button>', {
id: SLIDE_COLOR_ID,
'class': 'fa fa-i-cursor rightside-button',
'style': 'font-family: FontAwesome; font-weight: bold; color: #fff;',
title: Messages.colorButtonTitle
});
var $testColor = $('<input>', { type: 'color', value: '!' });
var $check = $pad.contents().find("#colorPicker_check");
if ($testColor.attr('type') !== "color" || $testColor.val() === '!') { return; }
$back.on('click', function() {
var $picker = $('<input>', { type: 'color', value: backColor })
.css({ display: 'none', })
.on('change', function() {
updateColors(undefined, this.value);
onLocal();
});
$check.append($picker);
setTimeout(function() {
$picker.click();
}, 0);
});
$text.on('click', function() {
var $picker = $('<input>', { type: 'color', value: textColor })
.css({ display: 'none', })
.on('change', function() {
updateColors(this.value, undefined);
onLocal();
$check.html('');
});
$check.append($picker);
setTimeout(function() {
$picker.click();
}, 0);
});
$rightside.append($back).append($text);
};
configureColors();
CodeMirror.configureTheme();
if (presentMode) {
$('#top-bar').hide();
}
// set the hash
if (!window.location.hash || window.location.hash === '#') {
Cryptpad.replaceHash(editHash);
}
};
config.onReady = function (info) {
if (module.realtime !== info.realtime) {
var realtime = module.realtime = info.realtime;
module.patchText = TextPatcher.create({
realtime: realtime,
//logging: true
});
}
var userDoc = module.realtime.getUserDoc();
var isNew = false;
if (userDoc === "" || userDoc === "{}") { isNew = true; }
var newDoc = "";
if(userDoc !== "") {
var hjson = JSON.parse(userDoc);
newDoc = hjson.content;
if (typeof (hjson) !== 'object' || Array.isArray(hjson) ||
(typeof(hjson.type) !== 'undefined' && hjson.type !== 'slide')) {
var errorText = Messages.typeError;
Cryptpad.errorLoadingScreen(errorText);
throw new Error(errorText);
}
if (hjson.highlightMode) {
CodeMirror.setMode(hjson.highlightMode);
}
}
if (!CodeMirror.highlightMode) {
CodeMirror.setMode('markdown');
}
// Update the user list (metadata) from the hyperjson
Metadata.update(userDoc);
editor.setValue(newDoc || initialState);
if (Cryptpad.initialName && Title.isDefaultTitle()) {
Title.updateTitle(Cryptpad.initialName);
}
Cryptpad.getPadAttribute('previewMode', function (e, data) {
if (e) { return void console.error(e); }
if ([true, undefined].indexOf(data) !== -1 && APP.$previewButton) {
APP.$previewButton.click();
}
});
Slide.onChange(function (o, n, l) {
var slideNumber = '';
if (n !== null) {
if (Slide.shown) { //Slide.index && Slide.content.length) {
slideNumber = ' (' + (++n) + '/' + l + ')';
}
}
document.title = Title.title + slideNumber;
});
Cryptpad.removeLoadingScreen();
setEditable(true);
initializing = false;
onLocal(); // push local state to avoid parse errors later.
Slide.update(editor.getValue());
if (readOnly) { return; }
UserList.getLastName(toolbar.$userNameButton, isNew);
var fmConfig = {
dropArea: $iframe.find('.CodeMirror'),
body: $iframe.find('body'),
onUploaded: function (ev, data) {
//var cursor = editor.getCursor();
//var cleanName = data.name.replace(/[\[\]]/g, '');
//var text = '!['+cleanName+']('+data.url+')';
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 src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '"></media-tag>';
editor.replaceSelection(mt);
}
};
APP.FM = Cryptpad.createFileManager(fmConfig);
};
config.onRemote = function () {
if (initializing) { return; }
if (isHistoryMode) { return; }
var oldDoc = canonicalize(CodeMirror.$textarea.val());
var shjson = module.realtime.getUserDoc();
// Update the user list (metadata) from the hyperjson
Metadata.update(shjson);
var hjson = JSON.parse(shjson);
var remoteDoc = hjson.content;
var highlightMode = hjson.highlightMode;
if (highlightMode && highlightMode !== CodeMirror.highlightMode) {
CodeMirror.setMode(highlightMode);
}
CodeMirror.setValueAndCursor(oldDoc, remoteDoc, TextPatcher);
if (!readOnly) {
var textValue = canonicalize(CodeMirror.$textarea.val());
var shjson2 = stringifyInner(textValue);
if (shjson2 !== shjson) {
console.error("shjson2 !== shjson");
TextPatcher.log(shjson, TextPatcher.diff(shjson, shjson2));
module.patchText(shjson2);
}
}
Slide.update(remoteDoc);
if (oldDoc !== remoteDoc) {
Cryptpad.notify();
}
};
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);
toolbar.failed();
if (info.state) {
initializing = true;
toolbar.reconnecting(info.myId);
Cryptpad.findOKButton().click();
} else {
if (!Slide.isPresentURL()) {
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
}
}
};
config.onError = onConnectError;
module.realtime = Realtime.start(config);
editor.on('change', onLocal);
Cryptpad.onLogout(function () { setEditable(false); });
};
var interval = 100;
var second = function (CM) {
Cryptpad.ready(function () {
andThen(CM);
Cryptpad.reportAppUsage();
});
Cryptpad.onError(function (info) {
if (info && info.type === "store") {
onConnectError();
}
});
};
var first = function () {
if (ifrw.CodeMirror) {
// it exists, call your continuation
second(ifrw.CodeMirror);
} else {
console.log("CodeMirror was not defined. Trying again in %sms", interval);
// try again in 'interval' ms
setTimeout(first, interval);
}
};
first();
});
});

@ -1,428 +0,0 @@
html,
body {
height: 100%;
width: 100%;
padding: 0px;
margin: 0px;
overflow: hidden;
box-sizing: border-box;
position: relative;
}
body {
display: flex;
flex-flow: column;
}
h1 {
font-size: 40px;
}
h2 {
font-size: 37px;
}
h3 {
font-size: 34px;
}
h4 {
font-size: 31px;
}
h5 {
font-size: 27px;
}
h6 {
font-size: 24px;
}
body .CodeMirror {
height: 100%;
}
body .CodeMirror-focused .cm-matchhighlight {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==);
background-position: bottom;
background-repeat: repeat-x;
}
#colorPicker_check {
display: block;
}
@media print {
@page {
margin: 0;
size: landscape;
}
body {
display: block;
}
body .CodeMirror,
body #cme_toolbox {
display: none;
}
body * {
visibility: hidden;
height: auto;
max-height: none;
}
html,
body {
max-height: none;
overflow: visible;
}
html .cp #print {
display: block;
visibility: visible;
}
html .cp #print * {
visibility: visible;
}
}
#cme_toolbox {
z-index: 10000;
}
#editorContainer {
flex: 1;
display: flex;
flex-flow: row;
height: 100%;
overflow: hidden;
}
#editorContainer .CodeMirror {
resize: none;
width: 100vw;
}
#editorContainer.preview .CodeMirror {
width: 50vw;
}
.preview .cp {
flex: 1;
overflow: hidden;
}
.preview .cp div#modal:not(.shown) {
position: relative;
top: auto;
left: auto;
width: auto;
display: block;
height: 100%;
}
.preview .cp div#modal:not(.shown) #content .slide-container {
width: 100%;
}
.preview .cp div#modal:not(.shown) #content .slide-frame {
width: 50vw;
height: 28.125vw;
max-height: 100vh;
max-width: 177.78vh;
}
.cp {
/* Slide position (print mode) */
/* Slide position (present mode) */
/* Slide content */
}
.cp #print {
position: relative;
display: none;
font-size: 10.125vw;
/*.slide-frame:first-child {
margin-top: ~"calc(((100vh - 56.25vw)/2))";
}*/
}
.cp #print .slide-frame {
display: block !important;
padding: 0.5em;
margin: auto;
border: 1px solid black;
height: 50.625vw;
width: 90vw;
page-break-after: always;
position: relative;
box-sizing: border-box;
overflow: hidden;
align-items: center;
justify-content: center;
}
.cp #print .slide-frame li {
min-width: 45vw;
}
.cp #print .slide-frame h1 {
padding-top: 0;
}
.cp #print .slide-container {
width: 90vw;
height: 100vh;
margin: 0vh 5vw;
display: flex;
}
.cp #print .slide-container:last-child {
height: calc(100vh - 2px);
}
.cp div.modal,
.cp div#modal {
background-color: black;
color: white;
/* Navigation buttons */
box-sizing: border-box;
z-index: 9001;
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100vh;
display: none;
background-color: #000;
}
.cp div.modal .button,
.cp div#modal .button {
position: absolute;
cursor: pointer;
font-size: 30px;
opacity: 0.6;
display: none;
z-index: 9001;
}
.cp div.modal .button:hover,
.cp div#modal .button:hover {
opacity: 1;
display: block !important;
}
.cp div.modal #button_exit,
.cp div#modal #button_exit {
left: 20px;
top: 20px;
}
.cp div.modal #button_left,
.cp div#modal #button_left {
left: 6vw;
bottom: 10vh;
}
.cp div.modal #button_right,
.cp div#modal #button_right {
right: 6vw;
bottom: 10vh;
}
.cp div.modal.shown,
.cp div#modal.shown {
display: block;
position: fixed;
top: 0px;
left: 0px;
z-index: 100000;
height: 100vh;
width: 100%;
}
.cp div.modal #content,
.cp div#modal #content {
font-size: 20vh;
position: relative;
height: 100%;
overflow: visible;
white-space: nowrap;
}
.cp div.modal #content .slide-frame,
.cp div#modal #content .slide-frame {
overflow: hidden;
display: inline-block;
box-sizing: border-box;
border: 1px solid;
white-space: normal;
vertical-align: middle;
/* center things as much as possible
margin-top: 50vh;
margin-bottom: 50vh;
transform: translateY(-50%);
*/
padding: 0.5em;
width: 100vw;
height: 56.25vw;
max-height: 100vh;
max-width: 177.78vh;
margin: auto;
}
.cp div.modal #content .slide-container,
.cp div#modal #content .slide-container {
display: inline-flex;
height: 100%;
width: 100vw;
text-align: center;
vertical-align: top;
}
.cp div.modal #content.transition .slide-container,
.cp div#modal #content.transition .slide-container {
transition: margin-left 1s;
}
.cp div.modal .center,
.cp div#modal .center {
position: relative;
width: 80%;
height: 80%;
margin: auto;
border: 1px solid #ffffff;
text-align: center;
}
.cp div.modal.shown,
.cp div#modal.shown {
display: block;
}
.cp div#modal #content .slide-frame,
.cp #print .slide-frame {
text-align: left;
position: relative;
}
.cp div#modal #content .slide-frame *,
.cp #print .slide-frame * {
font-size: 27.5%;
line-height: 110%;
}
.cp div#modal #content .slide-frame * *,
.cp #print .slide-frame * * {
font-size: 100%;
line-height: 1em;
}
.cp div#modal #content .slide-frame ul ul,
.cp #print .slide-frame ul ul,
.cp div#modal #content .slide-frame ol ul,
.cp #print .slide-frame ol ul,
.cp div#modal #content .slide-frame ul ol,
.cp #print .slide-frame ul ol,
.cp div#modal #content .slide-frame ol ol,
.cp #print .slide-frame ol ol {
margin: 0;
}
.cp div#modal #content .slide-frame h1,
.cp #print .slide-frame h1 {
font-size: 50%;
line-height: 110%;
}
.cp div#modal #content .slide-frame h2,
.cp #print .slide-frame h2 {
font-size: 42%;
line-height: 110%;
}
.cp div#modal #content .slide-frame h3,
.cp #print .slide-frame h3 {
font-size: 36%;
line-height: 110%;
}
.cp div#modal #content .slide-frame h4,
.cp #print .slide-frame h4 {
font-size: 30%;
line-height: 110%;
}
.cp div#modal #content .slide-frame h5,
.cp #print .slide-frame h5 {
font-size: 22%;
line-height: 110%;
}
.cp div#modal #content .slide-frame h6,
.cp #print .slide-frame h6 {
font-size: 16%;
line-height: 110%;
}
.cp div#modal #content .slide-frame h1,
.cp #print .slide-frame h1,
.cp div#modal #content .slide-frame h2,
.cp #print .slide-frame h2,
.cp div#modal #content .slide-frame h3,
.cp #print .slide-frame h3,
.cp div#modal #content .slide-frame h4,
.cp #print .slide-frame h4,
.cp div#modal #content .slide-frame h5,
.cp #print .slide-frame h5,
.cp div#modal #content .slide-frame h6,
.cp #print .slide-frame h6 {
color: inherit;
text-align: center;
padding-top: 0;
margin-bottom: 0.5em;
}
.cp div#modal #content .slide-frame pre > code,
.cp #print .slide-frame pre > code {
display: block;
position: relative;
border: 1px solid #333;
width: 90%;
margin: auto;
padding-left: .25vw;
overflow-x: auto;
overflow-y: hidden;
}
.cp div#modal #content .slide-frame ul,
.cp #print .slide-frame ul,
.cp div#modal #content .slide-frame ol,
.cp #print .slide-frame ol {
min-width: 50%;
max-width: 100%;
display: table;
margin: 0 auto;
padding-left: 0.3em;
}
.cp div#modal #content .slide-frame img,
.cp #print .slide-frame img {
position: relative;
min-width: 1%;
max-width: 90%;
max-height: 90%;
margin: auto;
}
.cp div#modal #content .slide-frame .slideNumber,
.cp #print .slide-frame .slideNumber {
position: absolute;
right: 5vh;
bottom: 5vh;
font-size: 10%;
line-height: 110%;
}
.cp div#modal #content .slide-frame .slideDate,
.cp #print .slide-frame .slideDate {
position: absolute;
left: 5vh;
bottom: 5vh;
font-size: 10%;
line-height: 110%;
}
.cp div#modal #content .slide-frame .slideTitle,
.cp #print .slide-frame .slideTitle {
position: absolute;
bottom: 5vh;
left: 0px;
right: 0px;
text-align: center;
font-size: 10%;
line-height: 110%;
}
#fileDialog {
position: absolute;
background-color: rgba(200, 200, 200, 0.8);
top: 15vh;
bottom: 15vh;
left: 10vw;
right: 10vw;
border: 1px solid black;
z-index: 10;
overflow: auto;
display: none;
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
font-size: 16px;
text-align: center;
}
#fileDialog .close {
position: absolute;
top: 0;
right: 0;
padding: 5px;
cursor: pointer;
}
#fileDialog .element {
cursor: pointer;
display: inline-flex;
width: 100px;
height: 100px;
border: 1px solid #ccc;
margin: 5px;
overflow: hidden;
word-wrap: break-word;
background-color: white;
padding: 5px;
align-items: center;
}
#fileDialog .element span {
width: 100px;
text-align: center;
}

@ -1,321 +0,0 @@
define([
'jquery',
'/common/diffMarked.js',
'/common/cryptpad-common.js',
],function ($, DiffMd, Cryptpad) {
var Slide = {
index: 0,
lastIndex: 0,
content: [],
changeHandlers: [],
};
var ifrw;
var $modal;
var $content;
var $pad;
var placeholder;
var options;
var separator = '<hr data-pewpew="pezpez">';
var separatorReg = /<hr data\-pewpew="pezpez">/g;
var slideClass = 'slide-frame';
var Title;
Slide.onChange = function (f) {
if (typeof(f) === 'function') {
Slide.changeHandlers.push(f);
}
};
var getNumberOfSlides = Slide.getNumberOfSlides = function () {
return $content.find('.' + slideClass).length;
};
var change = function (oldIndex, newIndex) {
if (Slide.changeHandlers.length) {
Slide.changeHandlers.some(function (f) {
f(oldIndex, newIndex, getNumberOfSlides());
});
}
};
var updateFontSize = Slide.updateFontSize = function () {
// 20vh
// 20 * 16 / 9vw
var wbase = 20;
var vh = 20;
var $elem = $(window);
if (!Slide.shown) {
wbase = 10;
vh *= $content.height()/$(window).height();
$elem = $content;
}
if ($elem.width() > 16/9*$elem.height()) {
$content.css('font-size', vh+'vh');
// $print.css('font-size', '20vh');
return;
}
$content.css('font-size', (wbase*9/16)+'vw');
// $print.css('font-size', (20*9/16)+'vw');
};
var fixCSS = function (css) {
var append = '.cp #print .slide-frame ';
var append2 = '.cp div#modal #content .slide-frame ';
return css.replace(/(\n*)([^\n}]+)\s*\{/g, '$1' + append + '$2,' + append2 + '$2 {');
};
var goTo = Slide.goTo = function (i) {
i = i || 0;
Slide.index = i;
$content.find('.slide-container').first().css('margin-left', -(i*100)+'%');
updateFontSize();
change(Slide.lastIndex, Slide.index);
$modal.find('#button_left > span').css({
opacity: Slide.index === 0? 0: 1
});
$modal.find('#button_right > span').css({
opacity: Slide.index === (getNumberOfSlides() -1)? 0: 1
});
};
var draw = Slide.draw = function (i) {
if (typeof(Slide.content) !== 'string') { return; }
var c = Slide.content;
var m = '<span class="slide-container"><span class="'+slideClass+'">'+DiffMd.render(c).replace(separatorReg, '</span></span><span class="slide-container"><span class="'+slideClass+'">')+'</span></span>';
DiffMd.apply(m, $content);
var length = getNumberOfSlides();
$modal.find('style.slideStyle').remove();
if (options.style && Slide.shown) {
$modal.prepend($('<style>', {'class': 'slideStyle'}).text(fixCSS(options.style)));
}
$content.find('.slide-frame').each(function (i, el) {
if (options.slide) {
$('<div>', {'class': 'slideNumber'}).text((i+1)+'/'+length).appendTo($(el));
}
if (options.date) {
$('<div>', {'class': 'slideDate'}).text(new Date().toLocaleDateString()).appendTo($(el));
}
if (options.title) {
$('<div>', {'class': 'slideTitle'}).text(Title.title).appendTo($(el));
}
});
$content.removeClass('transition');
if (options.transition || typeof(options.transition) === "undefined") {
$content.addClass('transition');
}
//$content.find('.' + slideClass).hide();
//$content.find('.' + slideClass + ':eq( ' + i + ' )').show();
//$content.css('margin-left', -(i*100)+'vw');
goTo(Math.min(i, getNumberOfSlides() - 1));
};
Slide.updateOptions = function () {
draw(Slide.index);
};
var isPresentURL = Slide.isPresentURL = function () {
var parsed = Cryptpad.parsePadUrl(window.location.href);
return parsed && parsed.hashData && parsed.hashData.present;
};
var show = Slide.show = function (bool, content) {
var parsed = Cryptpad.parsePadUrl(window.location.href);
var hashData = parsed.hashData || {};
Slide.shown = bool;
if (bool) {
Slide.update(content);
Slide.draw(Slide.index);
$modal.addClass('shown');
$(ifrw).focus();
change(null, Slide.index);
if (!isPresentURL()) {
window.location += parsed.getUrl({present: true, embed: hashData.embed});
}
$pad.contents().find('.cryptpad-present-button').hide();
$pad.contents().find('.cryptpad-source-button').show();
$pad.addClass('fullscreen');
$('#iframe-container').addClass('fullscreen');
$('.top-bar').hide();
updateFontSize();
return;
}
window.location = parsed.getUrl({embed: hashData.embed});
change(Slide.index, null);
$pad.contents().find('.cryptpad-present-button').show();
$pad.contents().find('.cryptpad-source-button').hide();
$pad.removeClass('fullscreen');
$('#iframe-container').removeClass('fullscreen');
$('.top-bar').show();
$modal.removeClass('shown');
updateFontSize();
};
Slide.update = function (content) {
updateFontSize();
//if (!init) { return; }
if (!content) { content = ''; }
var old = Slide.content;
Slide.content = content.replace(/\n\s*\-\-\-\s*\n/g, '\n\n'+separator+'\n\n');
if (old !== Slide.content) {
draw(Slide.index);
return;
}
change(Slide.lastIndex, Slide.index);
};
Slide.left = function () {
console.log('left');
Slide.lastIndex = Slide.index;
var i = Slide.index = Math.max(0, Slide.index - 1);
Slide.goTo(i);
};
Slide.right = function () {
console.log('right');
Slide.lastIndex = Slide.index;
var i = Slide.index = Math.min(getNumberOfSlides() -1, Slide.index + 1);
Slide.goTo(i);
};
Slide.first = function () {
console.log('first');
Slide.lastIndex = Slide.index;
var i = Slide.index = 0;
Slide.goTo(i);
};
Slide.last = function () {
console.log('end');
Slide.lastIndex = Slide.index;
var i = Slide.index = getNumberOfSlides() - 1;
Slide.goTo(i);
};
var addEvent = function () {
var icon_to;
$modal.mousemove(function () {
var $buttons = $modal.find('.button');
$buttons.show();
if (icon_to) { window.clearTimeout(icon_to); }
icon_to = window.setTimeout(function() {
$buttons.fadeOut();
}, 1000);
});
$modal.find('#button_exit').click(function () {
var ev = $.Event("keyup");
ev.which = 27;
$modal.trigger(ev);
});
$modal.find('#button_left').click(function () {
var ev = $.Event("keyup");
ev.which = 37;
$modal.trigger(ev);
});
$modal.find('#button_right').click(function () {
console.log('right');
var ev = $.Event("keyup");
ev.which = 39;
$modal.trigger(ev);
});
$pad.contents().find('.CodeMirror').keyup(function (e) { e.stopPropagation(); });
$(ifrw).on('keyup', function (e) {
//if (!Slide.shown) { return; }
if (e.ctrlKey) { return; }
switch(e.which) {
case 33: // pageup
case 38: // up
case 37: // left
Slide.left();
break;
case 34: // pagedown
case 32: // space
case 40: // down
case 39: // right
Slide.right();
break;
case 36: // home
Slide.first();
break;
case 35: // end
Slide.last();
break;
case 27: // esc
show(false);
break;
default:
console.log(e.which);
}
});
};
$(window).resize(Slide.updateFontSize);
// Swipe
var addSwipeEvents = function () {
var touch = {
maxTime: 2000,
minXDist: 150,
maxYDist: 100
};
var resetSwipe = function () {
touch.x = 0;
touch.y = 0;
touch.time = 0;
};
$content.on('touchstart', function (e) {
e.preventDefault();
resetSwipe();
var t = e.originalEvent.changedTouches[0];
touch.x = t.pageX;
touch.y = t.pageY;
touch.time = new Date().getTime();
});
$content.on('touchend', function (e) {
e.preventDefault();
var t = e.originalEvent.changedTouches[0];
var xDist = t.pageX - touch.x;
var yDist = t.pageY - touch.y;
var time = new Date().getTime() - touch.time;
if (time <= touch.maxTime && Math.abs(xDist) >= touch.minXDist && Math.abs(yDist) <= touch.maxYDist) {
if (xDist < 0) {
Slide.right();
return;
}
Slide.left();
}
});
$content.on('touchmove', function (e){
e.preventDefault();
});
};
Slide.setModal = function ($m, $c, $p, iframe, opt, ph) {
$modal = Slide.$modal = $m;
$content = Slide.$content = $c;
$pad = Slide.$pad = $p;
ifrw = Slide.ifrw = iframe;
placeholder = Slide.placeholder = ph;
options = Slide.options = opt;
addEvent();
addSwipeEvents();
};
Slide.setTitle = function (titleObj) {
Title = titleObj;
};
return Slide;
});

@ -1,398 +0,0 @@
@import "/customize/src/less/variables.less";
@import "/customize/src/less/mixins.less";
@import "/common/markdown.less";
// used for slides
.viewportRatio (@x, @y, @p: 100) {
width: @p * 100vw;
height: @y * (@p * 100vw) / @x;
max-width: @x / @y * (@p * 100vh);
max-height: (@p * 100vh);
}
html, body{
height: 100%;
width: 100%;
padding: 0px;
margin: 0px;
overflow: hidden;
box-sizing: border-box;
position: relative;
}
body {
font-size: unset;
display: flex;
flex-flow: column;
}
//.cp {
h1 { font-size: 40px; }
h2 { font-size: 37px; }
h3 { font-size: 34px; }
h4 { font-size: 31px; }
h5 { font-size: 27px; }
h6 { font-size: 24px; }
body {
.CodeMirror {
height: 100%;
font-size: initial;
}
.CodeMirror-focused .cm-matchhighlight {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAFklEQVQI12NgYGBgkKzc8x9CMDAwAAAmhwSbidEoSQAAAABJRU5ErkJggg==);
background-position: bottom;
background-repeat: repeat-x;
}
}
#colorPicker_check {
display: block;
}
@media print {
@page {
margin: 0;
size: landscape;
}
body {
.CodeMirror, #cme_toolbox {
display: none;
}
* {
visibility: hidden;
height: auto;
max-height: none;
}
display:block;
}
html, body {
height: auto;
max-height: none;
overflow: visible;
}
html {
.cp #print {
display: block;
visibility: visible;
* {
visibility: visible;
}
}
.cp #modal {
display: none !important;
}
.cp {
flex: 1 !important;
overflow: visible !important;
}
.userlist-drawer {
display: none !important;
}
#editorContainer {
height: auto;
display: block;
}
}
}
#cme_toolbox {
z-index: 10000;
}
#editorContainer {
flex: 1;
display: flex;
flex-flow: row;
height: 100%;
overflow: hidden;
.CodeMirror {
resize: none;
width: 100vw;
}
&.preview {
.CodeMirror {
//resize: horizontal;
width: 50vw;
}
}
}
.preview {
.cp {
width: 50vw;
overflow: hidden;
div#modal:not(.shown) {
position: relative;
top: auto;
left: auto;
width: auto;
display: block;
height: 100%;
#content {
.slide-container {
width: 100%;
}
.slide-frame {
width: 50vw;
height: 28.125vw; // height:width ratio = 9/16 = .5625
max-height: ~"calc(100vh - 96px)";
max-width: ~"calc(16 / 9 * (100vh - 96px))";
//max-height: 100vh;
//max-width: 177.78vh; // 16/9 = 1.778
}
}
#button_exit {
visibility: hidden;
}
}
}
.CodeMirror {
flex: 1;
}
}
.cp {
/* Slide position (print mode) */
@ratio:0.9;
#print {
position: relative;
display: none;
font-size: @ratio*11.25vw;
.slide-frame {
display: block !important;
padding: 0.5em;
margin: auto;
border: 1px solid black;
height: @ratio*56.25vw;
width: @ratio*100vw;
page-break-after: always;
position: relative;
box-sizing: border-box;
overflow: hidden;
li {
min-width: @ratio*50vw;
}
h1 {
padding-top: 0;
}
align-items: center;
justify-content: center;
}
.slide-container {
width: 90vw;
height: 100vh;
margin: 0vh 5vw !important;
display: flex;
&:last-child {
height: ~"calc(100vh - 2px)";
}
}
}
/* Slide position (present mode) */
div.modal, div#modal {
display: none;
background-color: black;
color: white;
/* Navigation buttons */
.button {
position: absolute;
cursor: pointer;
font-size: 30px;
opacity: 0.6;
display: none;
z-index: 9001;
}
.button:hover {
opacity: 1;
display: block !important;
}
#button_exit {
left: 20px;
top: 20px;
}
#button_left {
left: 6vw;
bottom: 10vh;
}
#button_right {
right: 6vw;
bottom: 10vh;
}
&.shown {
display: block;
position: fixed;
top: 0px;
left: 0px;
z-index: 100000;
height: 100vh;
width: 100%;
}
#content {
font-size: 20vh;
position: relative;
height: 100%;
overflow: visible;
white-space: nowrap;
.slide-frame {
overflow: hidden;
display: flex;
flex-flow: column !important;
box-sizing: border-box;
border: 1px solid;
white-space: normal;
vertical-align: middle;
padding: 0.5em;
width: 100vw;
height: 56.25vw; // height:width ratio = 9/16 = .5625
max-height: 100vh;
max-width: 177.78vh; // 16/9 = 1.778
margin: auto;
}
.slide-container {
display: inline-flex;
height: 100%; width: 100vw;
text-align: center;
vertical-align: top;
}
&.transition {
.slide-container {
transition: margin-left 1s;
}
}
media-tag button {
max-height: none;
}
}
box-sizing: border-box;
z-index: 9001;
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100vh;
display: none;
background-color: @slide-default-bg;
.center {
position: relative;
width: 80%;
height: 80%;
margin: auto;
border: 1px solid @light-base;
text-align: center;
}
&.shown {
display: block;
}
}
/* Slide content */
div#modal #content, #print {
.slide-frame {
* {
.size(2.75);
* {
font-size: 1em;
line-height: 1em;
}
}
ul, ol {
ul, ol {
margin: 0;
}
}
h1 { .size(5); }
h2 { .size(4.2); }
h3 { .size(3.6); }
h4 { .size (3); }
h5 { .size(2.2); }
h6 { .size(1.6); }
h1, h2, h3, h4, h5, h6 {
color: inherit;
text-align: center;
padding-top: 0;
margin-bottom: 0.5em;
}
.markdown_preformatted-code;
ul, ol {
min-width: 50%;
max-width: 100%;
display: table;
margin: 0 auto;
padding-left: 0.3em;
}
// fixes image overflowing
media-tag {
height: 100%;
& + * {
margin-top: 1rem;
}
}
img {
position: relative;
min-width: 1%;
margin: auto;
}
.slideNumber {
position: absolute;
right: 5vh;
bottom: 5vh;
.size(1);
}
.slideDate {
position: absolute;
left: 5vh;
bottom: 5vh;
.size(1);
}
.slideTitle {
position: absolute;
bottom: 5vh;
left: 0px; right: 0px;
text-align: center;
.size(1);
}
text-align: left;
position: relative;
}
}
}
@import "/common/file-dialog.less";
.slide-frame * {
max-width: 100%;
max-height: 100%;
}
p {
//display: flex;
//flex-flow: row wrap;
padding: 0;
margin: 0;
min-height:0;
min-width:0;
//flex: 1;
}
@import "../customize/src/less2/include/mediatag.less";
.mediatag_base();

@ -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">&nbsp;</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.5" 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>

@ -26,7 +26,9 @@ define([
'/customize/messages.js',
'/pad/links.js',
'/bower_components/nthen/index.js',
'/common/media-tag.js',
'/api/config',
'/common/cryptpad-common.js',
'/bower_components/diff-dom/diffDOM.js',
@ -42,7 +44,9 @@ define([
Messages,
Links,
nThen,
ApiConfig)
MediaTag,
ApiConfig,
Cryptpad)
{
var DiffDom = window.diffDOM;
@ -78,9 +82,18 @@ define([
el.getAttribute('class').split(' ').indexOf('non-realtime') !== -1);
};
/* catch `type="_moz"` before it goes over the wire */
var brFilter = function (hj) {
if (hj[1].type === '_moz') { hj[1].type = undefined; }
var hjsonFilters = function (hj) {
/* catch `type="_moz"` before it goes over the wire */
var brFilter = function (hj) {
if (hj[1].type === '_moz') { hj[1].type = undefined; }
return hj;
};
var mediatagContentFilter = function (hj) {
if (hj[0] === 'MEDIA-TAG') { hj[2] = []; }
return hj;
};
brFilter(hj);
mediatagContentFilter(hj);
return hj;
};
@ -90,11 +103,11 @@ define([
var forbiddenTags = [
'SCRIPT',
'IFRAME',
//'IFRAME',
'OBJECT',
'APPLET',
'VIDEO',
'AUDIO'
//'VIDEO',
//'AUDIO'
];
var getHTML = function (inner) {
@ -308,11 +321,38 @@ define([
};
if (!framework.isReadOnly()) {
console.log('\n\n\n\n\nREGISTER\n\n\n\n\n');
framework.onEditableChange(function () {
console.log("Editable change");
var locked = framework.isLocked();
$(inner).css({ 'background-color': ((locked) ? '#aaa' : '') });
inner.setAttribute('contenteditable', !locked);
});
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;
}
}
};
framework._.sfCommon.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']
};
framework._.sfCommon.openFilePicker(pickerCfg);
}).appendTo(framework._.toolbar.$rightside);
var $tags = framework._.sfCommon.createButton('hashtag', true);
framework._.toolbar.$rightside.append($tags);
}
framework.setTitleRecommender(function () {
@ -328,15 +368,65 @@ define([
var DD = new DiffDom(mkDiffOptions(cursor, framework.isReadOnly()));
var mediaMap = {};
var restoreMediaTags = function (tempDom) {
var pattern = /(<media-tag contenteditable="false" data-crypto-key="([^"]*)" src="([^"]*)" tabindex="1">)<\/media-tag>/i;
var tags = tempDom.querySelectorAll('media-tag:empty');
Array.prototype.slice.call(tags).forEach(function (tag) {
if (pattern.length !== 4) { return; }
var src = pattern[3];
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');
console.log(Array.prototype.slice.call(tags));
Array.prototype.slice.call(tags).forEach(function (el) {
MediaTag(el);
$(el).on('keydown', function (e) {
if ([8,46].indexOf(e.which) !== -1) {
$(el).remove();
framework.localChange();
}
});
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!
framework.onContentUpdate(function (hjson) {
if (!Array.isArray(hjson)) {
var errorText = Messages.typeError;
Cryptpad.errorLoadingScreen(errorText);
throw new Error(errorText);
}
var userDocStateDom = hjsonToDom(hjson);
userDocStateDom.setAttribute("contenteditable",
inner.getAttribute('contenteditable'));
restoreMediaTags(userDocStateDom);
var patch = (DD).diff(inner, userDocStateDom);
(DD).apply(inner, patch);
displayMediaTags(inner);
if (framework.isReadOnly()) {
var $links = $(inner).find('a');
// off so that we don't end up with multiple identical handlers
@ -345,7 +435,7 @@ define([
});
framework.setContentGetter(function () {
return Hyperjson.fromDOM(inner, isNotMagicLine, brFilter);
return Hyperjson.fromDOM(inner, isNotMagicLine, hjsonFilters);
});
$bar.find('#cke_1_toolbar_collapser').hide();
@ -365,11 +455,22 @@ define([
editor.focus();
if (newPad) {
documentBody.innerHTML = Messages.initialState;
cursor.setToEnd();
} else if (framework.isReadOnly()) {
cursor.setToStart();
}
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 = framework._.sfCommon.createFileManager(fmConfig);
});
framework.onDefaultContentNeeded(function () {
@ -457,12 +558,18 @@ 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) {
Framework.create({}, waitFor(function (fw) { window.APP.framework = framework = fw; }));
editor.plugins.mediatag.translations = {
title: Messages.pad_mediatagTitle,
width: Messages.pad_mediatagWidth,
height: Messages.pad_mediatagHeight
};
Links.addSupportForOpeningLinksInNewTab(Ckeditor)({editor: editor});
}).nThen(function (/*waitFor*/) {
andThen2(editor, Ckeditor, framework);

@ -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
*/

@ -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.5" 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,148 @@
@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';
.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 {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
// 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.5" 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,551 @@
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">&nbsp;</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;
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);
}
} else {
Title.updateTitle(Cryptpad.initialName || Title.defaultTitle);
}
Cryptpad.removeLoadingScreen();
setEditable(!readOnly);
initializing = false;
config.onLocal();
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);
$('#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',
'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">&nbsp;</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();
}
});
'jquery',
'/common/requireconfig.js',
'/common/sframe-common-outer.js'
], function (nThen, ApiConfig, $, RequireConfig, SFCommonO) {
var requireConfig = RequireConfig();
// 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)));
// This is a cheap trick to avoid loading sframe-channel in parallel with the
// loading screen setup.
var done = waitFor();
var onMsg = function (msg) {
var data = JSON.parse(msg.data);
if (data.q !== 'READY') { return; }
window.removeEventListener('message', onMsg);
var _done = done;
done = function () { };
_done();
};
window.addEventListener('message', onMsg);
}).nThen(function (/*waitFor*/) {
SFCommonO.start();
});
});

Loading…
Cancel
Save