create examples directory with old prototypes
This commit is contained in:
243
www/examples/board/board.js
Normal file
243
www/examples/board/board.js
Normal file
@@ -0,0 +1,243 @@
|
||||
define([
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
],function () {
|
||||
var $ = window.jQuery;
|
||||
var Board = {};
|
||||
var proxy;
|
||||
|
||||
var Uid = function (prefix) {
|
||||
return function () {
|
||||
return prefix + Number(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER))
|
||||
.toString(32).replace(/\./g, '');
|
||||
};
|
||||
};
|
||||
|
||||
var removeUid = function (A, e) {
|
||||
var i = A.indexOf(e);
|
||||
if (i === -1) { return -1; }
|
||||
A.splice(i, 1);
|
||||
return i;
|
||||
};
|
||||
|
||||
var luid = Board.luid = Uid('l-'); // list-uid
|
||||
var cuid = Board.cuid = Uid('c-'); // card uid
|
||||
|
||||
var Input = Board.Input = function (opt) {
|
||||
return $('<input>', opt);
|
||||
};
|
||||
|
||||
/*
|
||||
populate the proxy with all the relevant fields
|
||||
return boolean whether you are the first user
|
||||
*/
|
||||
Board.initialize = function (_proxy) {
|
||||
proxy = _proxy;
|
||||
var first = false;
|
||||
|
||||
['listOrder'].forEach(function (k) {
|
||||
if (typeof(proxy[k]) === 'undefined') {
|
||||
first = true;
|
||||
proxy[k] = [];
|
||||
}
|
||||
});
|
||||
|
||||
['lists', 'cards'].forEach(function (k) {
|
||||
if (typeof(proxy[k]) === 'undefined') {
|
||||
proxy[k] = {};
|
||||
}
|
||||
});
|
||||
|
||||
return first;
|
||||
};
|
||||
|
||||
/*
|
||||
* a list is appended to the extant order
|
||||
*/
|
||||
var List = Board.List = function (id) {
|
||||
if (!id) {
|
||||
id = List.create();
|
||||
}
|
||||
|
||||
var $input = Input({
|
||||
type: 'text',
|
||||
placeholder: 'list title',
|
||||
})
|
||||
.addClass('list-title')
|
||||
.on('keyup change', function () {
|
||||
var val = $input.val();
|
||||
proxy.lists[id].title = val;
|
||||
});
|
||||
|
||||
var $cards = $('<div>', {
|
||||
|
||||
})
|
||||
.addClass('card-holder');
|
||||
|
||||
var $new = $('<a>', {
|
||||
|
||||
})
|
||||
.addClass('add-card')
|
||||
.text('add new card')
|
||||
.click(function () {
|
||||
// is this correct?
|
||||
$cards.append(Board.Card(id));
|
||||
});
|
||||
|
||||
var $list = $('<div>', {
|
||||
id: id,
|
||||
})
|
||||
.addClass('list-column')
|
||||
.append($input)
|
||||
.append($cards)
|
||||
.append($new);
|
||||
|
||||
return $list;
|
||||
};
|
||||
|
||||
/*
|
||||
*/
|
||||
List.create = function () {
|
||||
var id = luid();
|
||||
proxy.listOrder.push(id);
|
||||
proxy.lists[id] = {
|
||||
title: "",
|
||||
cards: [],
|
||||
};
|
||||
|
||||
return id;
|
||||
};
|
||||
|
||||
/*
|
||||
*/
|
||||
List.remove = function (id) {
|
||||
var i = removeUid(proxy.listOrder, id);
|
||||
if (i === -1) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
*/
|
||||
List.move = function () {
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
*/
|
||||
List.insert = function () {
|
||||
|
||||
};
|
||||
|
||||
List.draw = function ($lists, lid) {
|
||||
if (!lid) {
|
||||
console.log("List Id not supplied");
|
||||
}
|
||||
|
||||
|
||||
var $parent = $lists.find('#' + lid);
|
||||
if (!$parent.length) {
|
||||
console.log("Creating new list");
|
||||
// doesn't exist. draw it fresh
|
||||
|
||||
var $list = Board.List(lid);
|
||||
$lists.append($list);
|
||||
|
||||
//console.log("Updating list");
|
||||
|
||||
//var $list = Board.List(lid);
|
||||
var title = proxy.lists[lid].title;
|
||||
|
||||
console.log(title);
|
||||
|
||||
$list.find('input.list-title').val(title);
|
||||
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// else update
|
||||
};
|
||||
|
||||
/*
|
||||
* UI element
|
||||
*/
|
||||
var Card = Board.Card = function (pid) {
|
||||
// pid => parent id
|
||||
|
||||
var id = Card.create(pid);
|
||||
|
||||
var $input = Input({
|
||||
placeholder: 'card description',
|
||||
})
|
||||
.addClass('card-title');
|
||||
|
||||
var $card = $('<div>', {
|
||||
|
||||
})
|
||||
.addClass('card-container')
|
||||
.append($input);
|
||||
|
||||
return $card;
|
||||
};
|
||||
|
||||
/*
|
||||
* a card is instantiated within a parent list
|
||||
* .create(parent) adds the relevant attributes to the data structure
|
||||
* and returns the created id
|
||||
*/
|
||||
Card.create = function (pid) {
|
||||
var id = cuid();
|
||||
|
||||
if (typeof(proxy.lists[pid]) === 'undefined') {
|
||||
console.error("Trying to create card for list which does not exist");
|
||||
return id;
|
||||
}
|
||||
|
||||
proxy.lists[pid].cards.push(id);
|
||||
proxy.cards[id] = {
|
||||
// TODO what goes in a card
|
||||
parent: pid,
|
||||
title: "",
|
||||
};
|
||||
|
||||
return id;
|
||||
};
|
||||
|
||||
/*
|
||||
*/
|
||||
Card.move = function (uid, A, B) {
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
*/
|
||||
Card.insert = function () {
|
||||
|
||||
};
|
||||
|
||||
Card.draw = function ($lists, cid) {
|
||||
if (!cid) {
|
||||
console.error("card id not supplied");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!proxy.cards[cid]) {
|
||||
console.error("no such card: ", cid);
|
||||
return;
|
||||
}
|
||||
|
||||
var card = proxy.cards[cid];
|
||||
|
||||
|
||||
};
|
||||
|
||||
var Draw = Board.Draw = function ($lists) {
|
||||
proxy.listOrder.forEach(function (luid) {
|
||||
List.draw($lists, luid);
|
||||
});
|
||||
};
|
||||
|
||||
return Board;
|
||||
});
|
||||
103
www/examples/board/index.html
Normal file
103
www/examples/board/index.html
Normal file
@@ -0,0 +1,103 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="cp board">
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Zero Knowledge Date Picker</title>
|
||||
<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" />
|
||||
<script data-main="main" src="/bower_components/requirejs/require.js"></script>
|
||||
<script>
|
||||
require.config({
|
||||
waitSeconds: 60,
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
html, body {
|
||||
width: 100;
|
||||
}
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
#adduser, #addoption {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#lists {
|
||||
display: inline-block;
|
||||
border: 1px solid white;
|
||||
height: 80vh;
|
||||
}
|
||||
|
||||
#create-list {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
.list-column {
|
||||
vertical-align: top;
|
||||
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
width: 400px;
|
||||
height: 100%;
|
||||
border: 1px solid white;
|
||||
}
|
||||
/* input */
|
||||
input.list-title {
|
||||
margin: 15px;
|
||||
width: 80%;
|
||||
display: block;
|
||||
}
|
||||
.card-holder {
|
||||
border: 1px solid #ddd;
|
||||
width: 90%;
|
||||
margin: auto;
|
||||
}
|
||||
.add-card {
|
||||
background-color: green;
|
||||
display: block;
|
||||
height: 20px;
|
||||
width: 80%;
|
||||
cursor: pointer;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
border: 5px solid blue;
|
||||
|
||||
}
|
||||
|
||||
.card-container {
|
||||
display: block;
|
||||
height: 50px;
|
||||
width: 95%;
|
||||
margin: auto;
|
||||
padding: 5px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
#board {
|
||||
margin-left: 10vw;
|
||||
overflow-x: visible;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!--<div id="main"> -->
|
||||
|
||||
<div id="toolbar" class="buttons">
|
||||
<sub><a href="/"></a></sub>
|
||||
</div>
|
||||
|
||||
<div id="board">
|
||||
<div id="lists"></div>
|
||||
<span id="create-list">Add List</span>
|
||||
</div>
|
||||
|
||||
94
www/examples/board/main.js
Normal file
94
www/examples/board/main.js
Normal file
@@ -0,0 +1,94 @@
|
||||
define([
|
||||
'/api/config?cb=' + Math.random().toString(16).substring(2),
|
||||
'/customize/messages.js',
|
||||
'board.js',
|
||||
'/bower_components/textpatcher/TextPatcher.js',
|
||||
'/bower_components/chainpad-listmap/chainpad-listmap.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/common/visible.js',
|
||||
'/common/notify.js',
|
||||
'/bower_components/file-saver/FileSaver.min.js',
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
], function (Config, Messages, Board, TextPatcher, Listmap, Crypto, Cryptpad, Visible, Notify) {
|
||||
var $ = window.jQuery;
|
||||
var saveAs = window.saveAs;
|
||||
|
||||
Cryptpad.styleAlerts();
|
||||
console.log("Initializing your realtime session...");
|
||||
|
||||
var secret = Cryptpad.getSecrets();
|
||||
|
||||
var module = window.APP = {
|
||||
Board: Board,
|
||||
};
|
||||
|
||||
var unnotify = function () {
|
||||
if (!(module.tabNotification &&
|
||||
typeof(module.tabNotification.cancel) === 'function')) { return; }
|
||||
module.tabNotification.cancel();
|
||||
};
|
||||
|
||||
var notify = function () {
|
||||
if (!(Visible.isSupported() && !Visible.currently())) { return; }
|
||||
unnotify();
|
||||
module.tabNotification = Notify.tab(1000, 10);
|
||||
};
|
||||
|
||||
var setEditable = function (bool) {
|
||||
|
||||
};
|
||||
|
||||
setEditable(false);
|
||||
|
||||
|
||||
var $lists = $('#lists');
|
||||
|
||||
var $addList = $('#create-list').click(function () {
|
||||
Board.List.draw($lists);
|
||||
});
|
||||
|
||||
var firstUser = function () {
|
||||
Cryptpad.log("You are the first user to visit this board");
|
||||
};
|
||||
|
||||
var whenReady = function (opt) {
|
||||
var rt = module.rt;
|
||||
var proxy = rt.proxy;
|
||||
|
||||
var first = Board.initialize(proxy);
|
||||
|
||||
//var board = module.board = Board.create(proxy);
|
||||
|
||||
Board.Draw($lists);
|
||||
|
||||
if (first) { firstUser(); }
|
||||
|
||||
};
|
||||
|
||||
var config = {
|
||||
websocketURL: Config.websocketURL,
|
||||
channel: secret.channel,
|
||||
data: {},
|
||||
crypto: Crypto.createEncryptor(secret.key),
|
||||
};
|
||||
|
||||
Cryptpad.ready(function () {
|
||||
var rt = module.rt = Listmap.create(config);
|
||||
var proxy = rt.proxy;
|
||||
proxy
|
||||
.on('create', function (info) {
|
||||
var realtime = module.realtime = info.realtime;
|
||||
window.location.hash = info.channel + secret.key;
|
||||
})
|
||||
.on('ready', function (info) {
|
||||
Cryptpad.log("Ready!");
|
||||
whenReady({
|
||||
|
||||
});
|
||||
})
|
||||
.on('disconnect', function () {
|
||||
Cryptpad.warn("Disconnected!");
|
||||
});
|
||||
});
|
||||
});
|
||||
79
www/examples/form/index.html
Normal file
79
www/examples/form/index.html
Normal file
@@ -0,0 +1,79 @@
|
||||
<!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"/>
|
||||
<script data-main="main" src="/bower_components/requirejs/require.js"></script>
|
||||
<style>
|
||||
html, body{
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
form {
|
||||
border: 3px solid black;
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
font-weight: bold !important;
|
||||
font-size: 18px !important;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="password"],
|
||||
input[type="number"],
|
||||
input[type="range"],
|
||||
select
|
||||
{
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
width: 80%;
|
||||
}
|
||||
textarea {
|
||||
width: 80%;
|
||||
height: 40vh;
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<form>
|
||||
<input type="radio" name="radio" value="one" checked>One
|
||||
<input type="radio" name="radio" value="two">Two
|
||||
<input type="radio" name="radio" value="three">Three<br>
|
||||
|
||||
<input type="checkbox" name="checkbox1" value="1">Checkbox One
|
||||
<input type="checkbox" name="checkbox2" value="2">Checkbox Two<br>
|
||||
|
||||
<input type="text" name="text" placeholder="Text Input"><br>
|
||||
<input type="password" name="password" placeholder="Passwords"><br>
|
||||
|
||||
|
||||
<input type="number" name="number" min="1" max="5" placeholder="Numbers">Number<br>
|
||||
|
||||
<input type="range" name="range" min="0" max="100">Ranges<br>
|
||||
|
||||
<select name="select">
|
||||
<option value="one">One</option>
|
||||
<option value="two">Two</option>
|
||||
<option value="three">Three</option>
|
||||
<option value="four">Four</option>
|
||||
</select> Dropdowns<br>
|
||||
|
||||
<select name="select-multiple" multiple>
|
||||
<option value="pew">Pew</option>
|
||||
<option value="bang">Bang</option>
|
||||
<option value="kapow">Kapow</option>
|
||||
<option value="zing">Zing</option>
|
||||
</select>
|
||||
|
||||
<textarea name="textarea"></textarea><br>
|
||||
|
||||
</form>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
226
www/examples/form/main.js
Normal file
226
www/examples/form/main.js
Normal file
@@ -0,0 +1,226 @@
|
||||
require.config({ paths: { 'json.sortify': '/bower_components/json.sortify/dist/JSON.sortify' } });
|
||||
define([
|
||||
'/api/config?cb=' + Math.random().toString(16).substring(2),
|
||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/bower_components/textpatcher/TextPatcher.amd.js',
|
||||
'json.sortify',
|
||||
'ula.js',
|
||||
'/bower_components/chainpad-json-validator/json-ot.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
], function (Config, Realtime, Crypto, TextPatcher, Sortify, Formula, JsonOT, Cryptpad) {
|
||||
var $ = window.jQuery;
|
||||
|
||||
var secret = Cryptpad.getSecrets();
|
||||
|
||||
var module = window.APP = {
|
||||
TextPatcher: TextPatcher,
|
||||
Sortify: Sortify,
|
||||
Formula: Formula,
|
||||
};
|
||||
var initializing = true;
|
||||
|
||||
var uid = module.uid = Formula.uid;
|
||||
|
||||
var getInputType = Formula.getInputType;
|
||||
var $elements = module.elements = $('input, select, textarea');
|
||||
|
||||
var eventsByType = Formula.eventsByType;
|
||||
|
||||
var Map = module.Map = {};
|
||||
|
||||
var UI = module.UI = {
|
||||
ids: [],
|
||||
each: function (f) {
|
||||
UI.ids.forEach(function (id, i, list) {
|
||||
if (!UI[id]) { return; }
|
||||
f(UI[id], i, list);
|
||||
});
|
||||
},
|
||||
add: function (id, ui) {
|
||||
if (UI.ids.indexOf(id) === -1) {
|
||||
UI.ids.push(id);
|
||||
|
||||
UI[id] = ui;
|
||||
return true;
|
||||
} else {
|
||||
// it already exists
|
||||
|
||||
return false;
|
||||
}
|
||||
},
|
||||
remove: function (id) {
|
||||
delete UI[id];
|
||||
var idx = UI.ids.indexOf(id);
|
||||
if (idx > -1) {
|
||||
UI.ids.splice(idx, 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var cursorTypes = ['textarea', 'password', 'text'];
|
||||
|
||||
var canonicalize = function (text) { return text.replace(/\r\n/g, '\n'); };
|
||||
$elements.each(function (index, element) {
|
||||
var $this = $(this);
|
||||
|
||||
var id = uid();
|
||||
var type = getInputType($this);
|
||||
|
||||
// ignore hidden inputs, submit inputs, and buttons
|
||||
if (['button', 'submit', 'hidden'].indexOf(type) !== -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this // give each element a uid
|
||||
.data('rtform-uid', id)
|
||||
// get its type
|
||||
.data('rt-ui-type', type);
|
||||
|
||||
var component = {
|
||||
id: id,
|
||||
$: $this,
|
||||
element: element,
|
||||
type: type,
|
||||
preserveCursor: cursorTypes.indexOf(type) !== -1,
|
||||
name: $this.prop('name'),
|
||||
};
|
||||
|
||||
UI.add(id, component);
|
||||
|
||||
component.value = (function () {
|
||||
var checker = ['radio', 'checkbox'].indexOf(type) !== -1;
|
||||
|
||||
if (checker) {
|
||||
return function (content) {
|
||||
return typeof content !== 'undefined'?
|
||||
$this.prop('checked', !!content):
|
||||
$this.prop('checked');
|
||||
};
|
||||
} else {
|
||||
return function (content) {
|
||||
return typeof content !== 'undefined' ?
|
||||
$this.val(content):
|
||||
typeof($this.val()) === 'string'? canonicalize($this.val()): $this.val();
|
||||
};
|
||||
}
|
||||
}());
|
||||
|
||||
var update = component.update = function () { Map[id] = component.value(); };
|
||||
update();
|
||||
});
|
||||
|
||||
var config = module.config = {
|
||||
initialState: Sortify(Map) || '{}',
|
||||
websocketURL: Config.websocketURL,
|
||||
userName: Crypto.rand64(8),
|
||||
channel: secret.channel,
|
||||
crypto: Crypto.createEncryptor(secret.key),
|
||||
transformFunction: JsonOT.validate
|
||||
};
|
||||
|
||||
var setEditable = module.setEditable = function (bool) {
|
||||
/* (dis)allow editing */
|
||||
$elements.each(function () {
|
||||
$(this).attr('disabled', !bool);
|
||||
});
|
||||
};
|
||||
|
||||
setEditable(false);
|
||||
|
||||
var onInit = config.onInit = function (info) {
|
||||
var realtime = module.realtime = info.realtime;
|
||||
window.location.hash = info.channel + secret.key;
|
||||
|
||||
// create your patcher
|
||||
module.patchText = TextPatcher.create({
|
||||
realtime: realtime,
|
||||
logging: true,
|
||||
});
|
||||
};
|
||||
|
||||
var readValues = function () {
|
||||
UI.each(function (ui, i, list) {
|
||||
Map[ui.id] = ui.value();
|
||||
});
|
||||
};
|
||||
|
||||
var onLocal = config.onLocal = function () {
|
||||
if (initializing) { return; }
|
||||
/* serialize local changes */
|
||||
readValues();
|
||||
module.patchText(Sortify(Map));
|
||||
};
|
||||
|
||||
var updateValues = function () {
|
||||
var userDoc = module.realtime.getUserDoc();
|
||||
var parsed = JSON.parse(userDoc);
|
||||
|
||||
console.log(userDoc);
|
||||
|
||||
// flush received values to the map
|
||||
// but only if you don't have them locally
|
||||
// this *shouldn't* break cursors
|
||||
Object.keys(parsed).forEach(function (key) {
|
||||
if (UI.ids.indexOf(key) === -1) { Map[key] = parsed[key]; }
|
||||
});
|
||||
|
||||
UI.each(function (ui, i, list) {
|
||||
var newval = parsed[ui.id];
|
||||
var oldval = ui.value();
|
||||
|
||||
if (typeof(newval) === 'undefined') { return; }
|
||||
if (newval === oldval) { return; }
|
||||
|
||||
var op;
|
||||
var selects;
|
||||
var element = ui.element;
|
||||
if (ui.preserveCursor) {
|
||||
op = TextPatcher.diff(oldval, newval);
|
||||
selects = ['selectionStart', 'selectionEnd'].map(function (attr) {
|
||||
var before = element[attr];
|
||||
var after = TextPatcher.transformCursor(element[attr], op);
|
||||
return after;
|
||||
});
|
||||
}
|
||||
|
||||
ui.value(newval);
|
||||
ui.update();
|
||||
|
||||
if (op && ui.preserveCursor) {
|
||||
//console.log(selects);
|
||||
element.selectionStart = selects[0];
|
||||
element.selectionEnd = selects[1];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var onRemote = config.onRemote = function (info) {
|
||||
if (initializing) { return; }
|
||||
/* integrate remote changes */
|
||||
updateValues();
|
||||
};
|
||||
|
||||
var onReady = config.onReady = function (info) {
|
||||
updateValues();
|
||||
|
||||
console.log("READY");
|
||||
setEditable(true);
|
||||
initializing = false;
|
||||
};
|
||||
|
||||
var onAbort = config.onAbort = function (info) {
|
||||
window.alert("Network Connection Lost");
|
||||
};
|
||||
|
||||
var rt = Realtime.start(config);
|
||||
|
||||
UI.each(function (ui, i, list) {
|
||||
var type = ui.type;
|
||||
var events = eventsByType[type];
|
||||
ui.$.on(events, onLocal);
|
||||
});
|
||||
|
||||
});
|
||||
14
www/examples/form/types.md
Normal file
14
www/examples/form/types.md
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
```Javascript
|
||||
/* elements that we need to listen to */
|
||||
/*
|
||||
* text => $(text).val()
|
||||
* password => $(password).val()
|
||||
* radio => $(radio).prop('checked')
|
||||
* checkbox => $(checkbox).prop('checked')
|
||||
* number => $(number).val() // returns string, no default
|
||||
* range => $(range).val()
|
||||
* select => $(select).val()
|
||||
* textarea => $(textarea).val()
|
||||
*/
|
||||
```
|
||||
25
www/examples/form/ula.js
Normal file
25
www/examples/form/ula.js
Normal file
@@ -0,0 +1,25 @@
|
||||
define([], function () {
|
||||
var ula = {};
|
||||
|
||||
var uid = ula.uid = (function () {
|
||||
var i = 0;
|
||||
var prefix = 'rt_';
|
||||
return function () { return prefix + i++; };
|
||||
}());
|
||||
|
||||
ula.getInputType = function ($el) { return $el[0].type; };
|
||||
|
||||
ula.eventsByType = {
|
||||
text: 'change keyup',
|
||||
password: 'change keyup',
|
||||
radio: 'change click',
|
||||
checkbox: 'change click',
|
||||
number: 'change',
|
||||
range: 'keyup change',
|
||||
'select-one': 'change',
|
||||
'select-multiple': 'change',
|
||||
textarea: 'change keyup',
|
||||
};
|
||||
|
||||
return ula;
|
||||
});
|
||||
78
www/examples/hack/index.html
Normal file
78
www/examples/hack/index.html
Normal file
@@ -0,0 +1,78 @@
|
||||
<!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"/>
|
||||
<script data-main="main" src="/bower_components/requirejs/require.js"></script>
|
||||
<style>
|
||||
html, body{
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
textarea{
|
||||
position: absolute;
|
||||
top: 5vh;
|
||||
left: 0px;
|
||||
border: 0px;
|
||||
|
||||
padding-top: 15px;
|
||||
width: 100%;
|
||||
height: 95vh;
|
||||
max-width: 100%;
|
||||
max-height: 100vh;
|
||||
|
||||
font-size: 30px;
|
||||
background-color: #073642;
|
||||
color: #839496;
|
||||
|
||||
overflow-x: hidden;
|
||||
|
||||
/* disallow textarea resizes */
|
||||
resize: none;
|
||||
}
|
||||
|
||||
textarea[disabled] {
|
||||
background-color: #275662;
|
||||
color: #637476;
|
||||
}
|
||||
|
||||
#panel {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
width: 100%;
|
||||
height: 5vh;
|
||||
z-index: 95;
|
||||
background-color: #777;
|
||||
/* min-height: 75px; */
|
||||
}
|
||||
#run {
|
||||
display: block;
|
||||
float: right;
|
||||
height: 100%;
|
||||
width: 10vw;
|
||||
z-index: 100;
|
||||
line-height: 5vw;
|
||||
font-size: 1.5em;
|
||||
background-color: #222;
|
||||
color: #CCC;
|
||||
text-align: center;
|
||||
border-radius: 5%;
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<textarea></textarea>
|
||||
<div id="panel">
|
||||
<!-- TODO update this element when new users join -->
|
||||
<span id="users"></span>
|
||||
<!-- what else should go in the panel? -->
|
||||
<a href="#" id="run">RUN</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
162
www/examples/hack/main.js
Normal file
162
www/examples/hack/main.js
Normal file
@@ -0,0 +1,162 @@
|
||||
define([
|
||||
'/api/config?cb=' + Math.random().toString(16).substring(2),
|
||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/bower_components/textpatcher/TextPatcher.amd.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/bower_components/jquery/dist/jquery.min.js'
|
||||
], function (Config, Realtime, Crypto, TextPatcher, Cryptpad) {
|
||||
var $ = window.jQuery;
|
||||
|
||||
var secret = Cryptpad.getSecrets();
|
||||
|
||||
var $textarea = $('textarea'),
|
||||
$run = $('#run');
|
||||
|
||||
var module = {};
|
||||
|
||||
var config = {
|
||||
initialState: '',
|
||||
websocketURL: Config.websocketURL,
|
||||
channel: secret.channel,
|
||||
crypto: Crypto.createEncryptor(secret.key),
|
||||
};
|
||||
var initializing = true;
|
||||
|
||||
var setEditable = function (bool) { $textarea.attr('disabled', !bool); };
|
||||
var canonicalize = function (text) { return text.replace(/\r\n/g, '\n'); };
|
||||
|
||||
setEditable(false);
|
||||
|
||||
var onInit = config.onInit = function (info) {
|
||||
window.location.hash = info.channel + secret.key;
|
||||
$(window).on('hashchange', function() { window.location.reload(); });
|
||||
};
|
||||
|
||||
var onRemote = config.onRemote = function (info) {
|
||||
if (initializing) { return; }
|
||||
|
||||
var userDoc = info.realtime.getUserDoc();
|
||||
var current = canonicalize($textarea.val());
|
||||
|
||||
var op = TextPatcher.diff(current, userDoc);
|
||||
|
||||
var elem = $textarea[0];
|
||||
|
||||
var selects = ['selectionStart', 'selectionEnd'].map(function (attr) {
|
||||
return TextPatcher.transformCursor(elem[attr], op);
|
||||
});
|
||||
|
||||
$textarea.val(userDoc);
|
||||
elem.selectionStart = selects[0];
|
||||
elem.selectionEnd = selects[1];
|
||||
|
||||
// TODO do something on external messages
|
||||
// http://webdesign.tutsplus.com/tutorials/how-to-display-update-notifications-in-the-browser-tab--cms-23458
|
||||
};
|
||||
|
||||
var onReady = config.onReady = function (info) {
|
||||
module.patchText = TextPatcher.create({
|
||||
realtime: info.realtime
|
||||
// logging: true
|
||||
});
|
||||
initializing = false;
|
||||
setEditable(true);
|
||||
$textarea.val(info.realtime.getUserDoc());
|
||||
};
|
||||
|
||||
var onAbort = config.onAbort = function (info) {
|
||||
setEditable(false);
|
||||
window.alert("Server Connection Lost");
|
||||
};
|
||||
|
||||
var onLocal = config.onLocal = function () {
|
||||
if (initializing) { return; }
|
||||
module.patchText(canonicalize($textarea.val()));
|
||||
};
|
||||
|
||||
var rt = window.rt = Realtime.start(config);
|
||||
|
||||
var splice = function (str, index, chars) {
|
||||
var count = chars.length;
|
||||
return str.slice(0, index) + chars + str.slice((index -1) + count);
|
||||
};
|
||||
|
||||
var setSelectionRange = function (input, start, end) {
|
||||
if (input.setSelectionRange) {
|
||||
input.focus();
|
||||
input.setSelectionRange(start, end);
|
||||
} else if (input.createTextRange) {
|
||||
var range = input.createTextRange();
|
||||
range.collapse(true);
|
||||
range.moveEnd('character', end);
|
||||
range.moveStart('character', start);
|
||||
range.select();
|
||||
}
|
||||
};
|
||||
|
||||
var setCursor = function (el, pos) {
|
||||
setSelectionRange(el, pos, pos);
|
||||
};
|
||||
|
||||
var state = {};
|
||||
|
||||
// TODO
|
||||
$textarea.on('keydown', function (e) {
|
||||
// track when control keys are pushed down
|
||||
//switch (e.key) { }
|
||||
});
|
||||
|
||||
// TODO
|
||||
$textarea.on('keyup', function (e) {
|
||||
// track when control keys are released
|
||||
});
|
||||
|
||||
//$textarea.on('change', onLocal);
|
||||
$textarea.on('keypress', function (e) {
|
||||
onLocal();
|
||||
switch (e.key) {
|
||||
case 'Tab':
|
||||
// insert a tab wherever the cursor is...
|
||||
var start = $textarea.prop('selectionStart');
|
||||
var end = $textarea.prop('selectionEnd');
|
||||
if (typeof start !== 'undefined') {
|
||||
if (start === end) {
|
||||
$textarea.val(function (i, val) {
|
||||
return splice(val, start, "\t");
|
||||
});
|
||||
setCursor($textarea[0], start +1);
|
||||
} else {
|
||||
// indentation?? this ought to be fun.
|
||||
|
||||
}
|
||||
}
|
||||
// simulate a keypress so the event goes through..
|
||||
// prevent default behaviour for tab
|
||||
e.preventDefault();
|
||||
|
||||
onLocal();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
['cut', 'paste', 'change', 'keyup', 'keydown', 'select', 'textInput']
|
||||
.forEach(function (evt) {
|
||||
$textarea.on(evt, onLocal);
|
||||
});
|
||||
|
||||
$run.click(function (e) {
|
||||
e.preventDefault();
|
||||
var content = $textarea.val();
|
||||
|
||||
try {
|
||||
eval(content); // jshint ignore:line
|
||||
} catch (err) {
|
||||
// FIXME don't use alert, make an errorbox
|
||||
window.alert(err.message);
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
22
www/examples/index.html
Normal file
22
www/examples/index.html
Normal file
@@ -0,0 +1,22 @@
|
||||
<!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"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>What can be built on top of CryptPad?</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="/examples/form/">forms</a></li>
|
||||
<li><a href="/examples/text/">text</a></li>
|
||||
<li><a href="/examples/hack/">textareas with executable content</a></li>
|
||||
<li><a href="/examples/board/">kanban board</a></li>
|
||||
<li><a href="/examples/json/">json objects</a></li>
|
||||
<li><a href="/examples/read/">ajax-like get/put behaviour</a></li>
|
||||
<li><a href="/examples/render/">render markdown content as html</a></li>
|
||||
<li><a href="/examples/style/">edit a page's style tag</a></li>
|
||||
<li><a href="/examples/upload/">upload content</a></li>
|
||||
</ul>
|
||||
|
||||
125
www/examples/json/README.md
Normal file
125
www/examples/json/README.md
Normal file
@@ -0,0 +1,125 @@
|
||||
# Realtime Lists and Maps
|
||||
|
||||
Our realtime list/map API has some limitations.
|
||||
|
||||
## Datatype Serialization
|
||||
|
||||
Only datatypes which can be serialized via `JSON.parse(JSON.stringify(yourObject))` will be preserved.
|
||||
|
||||
This means the following types can be serialized:
|
||||
|
||||
1. strings
|
||||
2. objects
|
||||
3. arrays
|
||||
4. booleans
|
||||
5. numbers
|
||||
6. null
|
||||
|
||||
While these cannot be serialized:
|
||||
|
||||
1. undefined
|
||||
2. symbol
|
||||
|
||||
## Object Interaction
|
||||
|
||||
Only 'get' and 'set' methods are supported.
|
||||
This is because we need to limit the operations we support to those supported by all browsers we might use.
|
||||
|
||||
Currently that means we can't rely on `in`, `delete`, or anything other than a `get`/`set` operation to behave as expected.
|
||||
Treat all other features as `Undefined Behaviour`.
|
||||
|
||||
> Your mileage may vary
|
||||
|
||||
`set` methods include all of the [assignment operators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Assignment_Operators#Exponentiation_assignment).
|
||||
|
||||
```
|
||||
// where 'x' is the realtime object `{}`
|
||||
|
||||
// assignment
|
||||
x.n = 5;
|
||||
|
||||
x.n += 3;
|
||||
x.n++;
|
||||
++x.n;
|
||||
|
||||
x.a = 5;
|
||||
x.b = 3;
|
||||
x.a *= x.b++;
|
||||
x // {a: 15, b: 4, n: 10}
|
||||
```
|
||||
|
||||
Instead of `delete`, assign `undefined`.
|
||||
`delete` will remove an attribute locally, but the deletion will not propogate to other clients until your next serialization.
|
||||
This is potentially problematic, as it can result in poorly formed patches.
|
||||
|
||||
### Object and array methods
|
||||
|
||||
methods which do not directly use setters and getters can be problematic:
|
||||
|
||||
`Array.push` behaves correctly, however, `Array.pop` does not.
|
||||
|
||||
|
||||
## Deep Equality
|
||||
|
||||
Normally in Javascript objects are passed by reference.
|
||||
That means you can do things like this:
|
||||
|
||||
```
|
||||
var a = {x: 5};
|
||||
var b = a;
|
||||
|
||||
// true
|
||||
console.log(a === b);
|
||||
```
|
||||
|
||||
Using the realtime list/map API, objects are serialized, and are therefore copied by value.
|
||||
|
||||
Since objects are deserialized and created on each client, you will not be able to rely on this kind of equality across objects, despite their having been created in this fashion.
|
||||
|
||||
Object equality _might_ work if the comparison is performed on the same client that initially created the object, but relying on this kind of behaviour is not advisable.
|
||||
|
||||
## Listeners
|
||||
|
||||
You can add a listener to an attribute (via its path relative to the root realtime object).
|
||||
|
||||
There are various types of listeners
|
||||
|
||||
* change
|
||||
* remove
|
||||
* disconnect
|
||||
* ready
|
||||
|
||||
### Semantics
|
||||
|
||||
Suppose you have a realtime object `A` containing nested structures.
|
||||
|
||||
```
|
||||
{
|
||||
a: {
|
||||
b: {
|
||||
c: 5
|
||||
}
|
||||
},
|
||||
d: {
|
||||
e: [
|
||||
1,
|
||||
4,
|
||||
9
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you want to be alerted whenever the second element in the array `e` within `d` changes, you can attach a listener like so:
|
||||
|
||||
```
|
||||
A.on('change', ['d', 'e', 1], function (oldval, newval, path, rootObject) {
|
||||
/* do something with these values */
|
||||
console.log("value changes from %s to %s", oldval, newval);
|
||||
});
|
||||
```
|
||||
|
||||
## Known Bugs
|
||||
|
||||
there is currently an issue with popping the last element of an array.
|
||||
|
||||
51
www/examples/json/index.html
Normal file
51
www/examples/json/index.html
Normal file
@@ -0,0 +1,51 @@
|
||||
<!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"/>
|
||||
<script data-main="main" src="/bower_components/requirejs/require.js"></script>
|
||||
<style>
|
||||
html, body{
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
form {
|
||||
border: 3px solid black;
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
font-weight: bold !important;
|
||||
font-size: 18px !important;
|
||||
}
|
||||
|
||||
input[type="text"]
|
||||
{
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
width: 80%;
|
||||
height: 3em;
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
|
||||
}
|
||||
textarea {
|
||||
width: 80%;
|
||||
height: 40vh;
|
||||
}
|
||||
div#content {
|
||||
width: 80%;
|
||||
margin: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="content">
|
||||
<p>The field below behaves like a <a target="_blank" href="https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop">REPL</a>, with the realtime object created by this page exposed as the value <code>x</code></p>
|
||||
<p>Open your browser's console to see the output.</p>
|
||||
<input type="text" name="repl" placeholder="Value" autofocus><br>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
78
www/examples/json/main.js
Normal file
78
www/examples/json/main.js
Normal file
@@ -0,0 +1,78 @@
|
||||
define([
|
||||
'/api/config?cb=' + Math.random().toString(16).substring(2),
|
||||
'/bower_components/chainpad-listmap/chainpad-listmap.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
], function (Config, RtListMap, Crypto, Common) {
|
||||
var $ = window.jQuery;
|
||||
|
||||
var secret = Common.getSecrets();
|
||||
|
||||
var config = {
|
||||
websocketURL: Config.websocketURL,
|
||||
channel: secret.channel,
|
||||
//cryptKey: secret.key,
|
||||
data: {},
|
||||
crypto: Crypto.createEncryptor(secret.key)
|
||||
};
|
||||
|
||||
var module = window.APP = {};
|
||||
|
||||
var $repl = $('[name="repl"]');
|
||||
|
||||
var setEditable = module.setEditable = function (bool) {
|
||||
[$repl].forEach(function ($el) {
|
||||
$el.attr('disabled', !bool);
|
||||
});
|
||||
};
|
||||
|
||||
var initializing = true;
|
||||
|
||||
setEditable(false);
|
||||
|
||||
var rt = module.rt = RtListMap.create(config);
|
||||
rt.proxy.on('create', function (info) {
|
||||
console.log("initializing...");
|
||||
window.location.hash = info.channel + secret.key;
|
||||
}).on('ready', function (info) {
|
||||
console.log("...your realtime object is ready");
|
||||
|
||||
rt.proxy
|
||||
// on(event, path, cb)
|
||||
.on('change', [], function (o, n, p) {
|
||||
console.log("root change event firing for path [%s]: %s => %s", p.join(','), o, n);
|
||||
})
|
||||
.on('remove', [], function (o, p, root) {
|
||||
console.log("Removal of value [%s] at path [%s]", o, p.join(','));
|
||||
})
|
||||
.on('change', ['a', 'b', 'c'], function (o, n, p) {
|
||||
console.log("Deeper change event at [%s]: %s => %s", p.join(','), o, n);
|
||||
console.log("preventing propogation...");
|
||||
return false;
|
||||
})
|
||||
// on(event, cb)
|
||||
.on('disconnect', function (info) {
|
||||
setEditable(false);
|
||||
window.alert("Network connection lost");
|
||||
});
|
||||
|
||||
// set up user interface hooks
|
||||
$repl.on('keyup', function (e) {
|
||||
if (e.which === 13 /* enter keycode */) {
|
||||
var value = $repl.val();
|
||||
|
||||
if (!value.trim()) { return; }
|
||||
|
||||
console.log("evaluating `%s`", value);
|
||||
var x = rt.proxy;
|
||||
|
||||
console.log('> ', eval(value)); // jshint ignore:line
|
||||
console.log();
|
||||
$repl.val('');
|
||||
}
|
||||
});
|
||||
|
||||
setEditable(true);
|
||||
});
|
||||
});
|
||||
38
www/examples/read/index.html
Normal file
38
www/examples/read/index.html
Normal file
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<script data-main="main" src="/bower_components/requirejs/require.js"></script>
|
||||
<script>
|
||||
require.config({
|
||||
waitSeconds: 60,
|
||||
});
|
||||
</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" />
|
||||
<style>
|
||||
input {
|
||||
width: 50vw;
|
||||
padding: 15px;
|
||||
}
|
||||
pre {
|
||||
max-width: 90vw;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<input id="target" type="text" value="/1/edit/xvhI6k6n7qYEtNL8cAv5zw/a4KKGGDY0S8GDj6m9iumX5E4"></input>
|
||||
<button id="get">get</button>
|
||||
<hr />
|
||||
|
||||
<textarea id="putter" type="text"></textarea>
|
||||
<button id="put">put</button>
|
||||
|
||||
<button id="open">open</button>
|
||||
35
www/examples/read/main.js
Normal file
35
www/examples/read/main.js
Normal file
@@ -0,0 +1,35 @@
|
||||
define([
|
||||
'/common/cryptget.js',
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
], function (Crypt) {
|
||||
var $ = window.jQuery;
|
||||
|
||||
var $target = $('#target');
|
||||
var $dest = $('#dest');
|
||||
|
||||
var useDoc = function (err, doc) {
|
||||
if (err) { return console.error(err); }
|
||||
//console.log(doc);
|
||||
$('#putter').val(doc);
|
||||
};
|
||||
|
||||
$('#get').click(function () {
|
||||
var val = $target.val();
|
||||
if (!val.trim()) { return; }
|
||||
Crypt.get(val, useDoc);
|
||||
});
|
||||
|
||||
$('#put').click(function () {
|
||||
var hash = $target.val().trim();
|
||||
Crypt.put(hash, $('#putter').val(), function (e) {
|
||||
if (e) { console.error(e); }
|
||||
$('#get').click();
|
||||
});
|
||||
});
|
||||
|
||||
$('#open').click(function () {
|
||||
window.open('/code/#' + $target.val());
|
||||
});
|
||||
|
||||
if (window.location.hash) { Crypt.get(void 0, useDoc); }
|
||||
});
|
||||
42
www/examples/render/index.html
Normal file
42
www/examples/render/index.html
Normal file
@@ -0,0 +1,42 @@
|
||||
<!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="/common/render-sd.css" />
|
||||
<script data-main="main" src="/bower_components/requirejs/require.js"></script>
|
||||
<style>
|
||||
html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body { overflow-y: auto; }
|
||||
|
||||
#inner {
|
||||
display: fixed;
|
||||
width: 95%;
|
||||
height: 100%;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
|
||||
margin: 0px auto;
|
||||
padding: 0px;
|
||||
}
|
||||
img { max-width: 100%; }
|
||||
code { font-family: monospace; }
|
||||
|
||||
blockquote, p, pre, code, li { font-size: 20px; }
|
||||
table, thead, tbody, th, tr, td{
|
||||
border: 1pt solid #586e75;
|
||||
background-color: #002b36;
|
||||
padding: 15px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="target">
|
||||
<div id="inner"></div>
|
||||
</div>
|
||||
105
www/examples/render/main.js
Normal file
105
www/examples/render/main.js
Normal file
@@ -0,0 +1,105 @@
|
||||
define([
|
||||
'/api/config?cb=' + Math.random().toString(16).substring(2),
|
||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/bower_components/marked/marked.min.js',
|
||||
'/bower_components/hyperjson/hyperjson.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
'/bower_components/diff-dom/diffDOM.js',
|
||||
], function (Config, Realtime, Crypto, Marked, Hyperjson, Cryptpad) {
|
||||
var $ = window.jQuery;
|
||||
var DiffDom = window.diffDOM;
|
||||
|
||||
var secret = Cryptpad.getSecrets();
|
||||
|
||||
// set markdown rendering options :: strip html to prevent XSS
|
||||
Marked.setOptions({
|
||||
sanitize: true
|
||||
});
|
||||
|
||||
var module = window.APP = { };
|
||||
|
||||
var $target = module.$target = $('#target');
|
||||
|
||||
var config = {
|
||||
websocketURL: Config.websocketURL,
|
||||
channel: secret.channel,
|
||||
crypto: Crypto.createEncryptor(secret.key)
|
||||
};
|
||||
|
||||
var draw = window.draw = (function () {
|
||||
var target = $target[0],
|
||||
inner = $target.find('#inner')[0];
|
||||
|
||||
if (!target) { throw new Error(); }
|
||||
var DD = new DiffDom({});
|
||||
|
||||
return function (md) {
|
||||
var rendered = Marked(md||"");
|
||||
// make a dom
|
||||
var New = $('<div id="inner">'+rendered+'</div>')[0];
|
||||
|
||||
var patches = (DD).diff(inner, New);
|
||||
DD.apply(inner, patches);
|
||||
return patches;
|
||||
};
|
||||
}());
|
||||
|
||||
var redrawTimeout;
|
||||
var lazyDraw = function (md) {
|
||||
if (redrawTimeout) { clearTimeout(redrawTimeout); }
|
||||
redrawTimeout = setTimeout(function () {
|
||||
draw(md);
|
||||
}, 450);
|
||||
};
|
||||
|
||||
var initializing = true;
|
||||
|
||||
var onInit = config.onInit = function (info) {
|
||||
window.location.hash = info.channel + secret.key;
|
||||
module.realtime = info.realtime;
|
||||
};
|
||||
|
||||
var getContent = function (userDoc) {
|
||||
try {
|
||||
var parsed = JSON.parse(userDoc);
|
||||
if (typeof(parsed.content) !== 'string') {
|
||||
throw new Error();
|
||||
}
|
||||
return parsed.content;
|
||||
} catch (err) {
|
||||
return userDoc;
|
||||
}
|
||||
};
|
||||
|
||||
// when your editor is ready
|
||||
var onReady = config.onReady = function (info) {
|
||||
console.log("Realtime is ready!");
|
||||
var userDoc = module.realtime.getUserDoc();
|
||||
lazyDraw(getContent(userDoc));
|
||||
initializing = false;
|
||||
};
|
||||
|
||||
// when remote editors do things...
|
||||
var onRemote = config.onRemote = function () {
|
||||
if (initializing) { return; }
|
||||
var userDoc = module.realtime.getUserDoc();
|
||||
lazyDraw(getContent(userDoc));
|
||||
};
|
||||
|
||||
var onLocal = config.onLocal = function () {
|
||||
// we're not really expecting any local events for this editor...
|
||||
/* but we might add a second pane in the future so that you don't need
|
||||
a second window to edit your markdown */
|
||||
if (initializing) { return; }
|
||||
var userDoc = module.realtime.getUserDoc();
|
||||
lazyDraw(userDoc);
|
||||
};
|
||||
|
||||
var onAbort = config.onAbort = function () {
|
||||
window.alert("Network Connection Lost");
|
||||
};
|
||||
|
||||
var rts = Realtime.start(config);
|
||||
});
|
||||
61
www/examples/style/index.html
Normal file
61
www/examples/style/index.html
Normal file
@@ -0,0 +1,61 @@
|
||||
<!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"/>
|
||||
<script data-main="main" src="/bower_components/requirejs/require.js"></script>
|
||||
<style></style>
|
||||
</head>
|
||||
<body>
|
||||
<a id="edit" href="#" target="_blank">Edit this document's style</a>
|
||||
|
||||
<h1>HTML Ipsum Presents</h1>
|
||||
|
||||
<p><strong>Pellentesque habitant morbi tristique</strong> senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. <em>Aenean ultricies mi vitae est.</em> Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, <code>commodo vitae</code>, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. <a href="#">Donec non enim</a> in turpis pulvinar facilisis. Ut felis.</p>
|
||||
|
||||
<h2>Header Level 2</h2>
|
||||
|
||||
<ol>
|
||||
<li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li>
|
||||
<li>Aliquam tincidunt mauris eu risus.</li>
|
||||
</ol>
|
||||
|
||||
<blockquote><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est.</p></blockquote>
|
||||
|
||||
<h3>Header Level 3</h3>
|
||||
|
||||
<ul>
|
||||
<li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li>
|
||||
<li>Aliquam tincidunt mauris eu risus.</li>
|
||||
</ul>
|
||||
|
||||
<pre><code>
|
||||
#header h1 a {
|
||||
display: block;
|
||||
width: 300px;
|
||||
height: 80px;
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>th 1</th>
|
||||
<th>th 2</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>one</td>
|
||||
<td>two</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>three</td>
|
||||
<td>four</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
78
www/examples/style/main.js
Normal file
78
www/examples/style/main.js
Normal file
@@ -0,0 +1,78 @@
|
||||
define([
|
||||
'/api/config?cb=' + Math.random().toString(16).substring(2),
|
||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/bower_components/textpatcher/TextPatcher.amd.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
], function (Config, Realtime, Crypto, TextPatcher, Cryptpad) {
|
||||
// TODO consider adding support for less.js
|
||||
var $ = window.jQuery;
|
||||
|
||||
var $style = $('style').first(),
|
||||
$edit = $('#edit');
|
||||
|
||||
var module = window.APP = {};
|
||||
|
||||
var secret = Cryptpad.getSecrets();
|
||||
var config = {
|
||||
websocketURL: Config.websocketURL,
|
||||
channel: secret.channel,
|
||||
crypto: Crypto.createEncryptor(secret.key),
|
||||
};
|
||||
|
||||
var userName = module.userName = config.userName = Crypto.rand64(8);
|
||||
|
||||
var lazyDraw = (function () {
|
||||
var to,
|
||||
delay = 500;
|
||||
return function (content) {
|
||||
if (to) { clearTimeout(to); }
|
||||
to = setTimeout(function () {
|
||||
$style.text(content);
|
||||
},delay);
|
||||
};
|
||||
}());
|
||||
|
||||
var draw = function (content) { lazyDraw(content); };
|
||||
|
||||
var initializing = true;
|
||||
|
||||
var onInit = config.onInit = function (info) {
|
||||
window.location.hash = info.channel + secret.key;
|
||||
var realtime = module.realtime = info.realtime;
|
||||
module.patchText = TextPatcher.create({
|
||||
realtime: realtime,
|
||||
logging: true,
|
||||
});
|
||||
|
||||
$(window).on('hashchange', function() {
|
||||
window.location.reload();
|
||||
});
|
||||
};
|
||||
|
||||
var onReady = config.onReady = function (info) {
|
||||
var userDoc = module.realtime.getUserDoc();
|
||||
draw(userDoc);
|
||||
console.log("Ready");
|
||||
initializing = false;
|
||||
};
|
||||
|
||||
var onRemote = config.onRemote = function () {
|
||||
draw(module.realtime.getUserDoc());
|
||||
};
|
||||
|
||||
var onAbort = config.onAbort = function (info) {
|
||||
// notify the user of the abort
|
||||
window.alert("Network Connection Lost");
|
||||
};
|
||||
|
||||
var onLocal = config.onLocal = function () {
|
||||
// nope
|
||||
};
|
||||
|
||||
|
||||
$edit.attr('href', '/examples/text/'+ window.location.hash);
|
||||
|
||||
var rt = Realtime.start(config);
|
||||
});
|
||||
39
www/examples/text/index.html
Normal file
39
www/examples/text/index.html
Normal file
@@ -0,0 +1,39 @@
|
||||
<!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"/>
|
||||
<script data-main="main" src="/bower_components/requirejs/require.js"></script>
|
||||
<style>
|
||||
html, body{
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
textarea{
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
max-width: 100%;
|
||||
max-height: 100vh;
|
||||
|
||||
font-size: 18px;
|
||||
background-color: #073642;
|
||||
color: #839496;
|
||||
|
||||
overflow-x: hidden;
|
||||
|
||||
/* disallow textarea resizes */
|
||||
resize: none;
|
||||
}
|
||||
textarea[disabled] {
|
||||
background-color: #275662;
|
||||
color: #637476;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<textarea></textarea>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
95
www/examples/text/main.js
Normal file
95
www/examples/text/main.js
Normal file
@@ -0,0 +1,95 @@
|
||||
define([
|
||||
'/api/config?cb=' + Math.random().toString(16).substring(2),
|
||||
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/bower_components/textpatcher/TextPatcher.amd.js',
|
||||
'/common/cryptpad-common.js',
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
], function (Config, Realtime, Crypto, TextPatcher, Cryptpad) {
|
||||
var $ = window.jQuery;
|
||||
|
||||
var secret = Cryptpad.getSecrets();
|
||||
|
||||
var module = window.APP = {
|
||||
TextPatcher: TextPatcher
|
||||
};
|
||||
|
||||
var userName = module.userName = Crypto.rand64(8);
|
||||
|
||||
var initializing = true;
|
||||
var $textarea = $('textarea');
|
||||
|
||||
var config = module.config = {
|
||||
initialState: '',
|
||||
websocketURL: Config.websocketURL,
|
||||
channel: secret.channel,
|
||||
crypto: Crypto.createEncryptor(secret.key),
|
||||
};
|
||||
|
||||
var setEditable = function (bool) { $textarea.attr('disabled', !bool); };
|
||||
var canonicalize = function (text) { return text.replace(/\r\n/g, '\n'); };
|
||||
|
||||
setEditable(false);
|
||||
|
||||
var onInit = config.onInit = function (info) {
|
||||
window.location.hash = info.channel + secret.key;
|
||||
$(window).on('hashchange', function() {
|
||||
window.location.reload();
|
||||
});
|
||||
};
|
||||
|
||||
var onRemote = config.onRemote = function (info) {
|
||||
if (initializing) { return; }
|
||||
var userDoc = module.realtime.getUserDoc();
|
||||
var content = canonicalize($textarea.val());
|
||||
|
||||
var op = TextPatcher.diff(content, userDoc);
|
||||
var elem = $textarea[0];
|
||||
|
||||
var selects = ['selectionStart', 'selectionEnd'].map(function (attr) {
|
||||
return TextPatcher.transformCursor(elem[attr], op);
|
||||
});
|
||||
|
||||
$textarea.val(userDoc);
|
||||
elem.selectionStart = selects[0];
|
||||
elem.selectionEnd = selects[1];
|
||||
};
|
||||
|
||||
var onLocal = config.onLocal = function () {
|
||||
if (initializing) { return; }
|
||||
module.patchText(canonicalize($textarea.val()));
|
||||
};
|
||||
|
||||
var onReady = config.onReady = function (info) {
|
||||
var realtime = module.realtime = info.realtime;
|
||||
module.patchText = TextPatcher.create({
|
||||
realtime: realtime
|
||||
});
|
||||
|
||||
$textarea.val(realtime.getUserDoc());
|
||||
|
||||
setEditable(true);
|
||||
initializing = false;
|
||||
};
|
||||
|
||||
var onAbort = config.onAbort = function (info) {
|
||||
setEditable(false);
|
||||
window.alert("Server Connection Lost");
|
||||
};
|
||||
|
||||
var onConnectionChange = config.onConnectionChange = function (info) {
|
||||
if (info.state) {
|
||||
initializing = true;
|
||||
} else {
|
||||
setEditable(false);
|
||||
window.alert("Server Connection Lost. Trying to reconnect...");
|
||||
}
|
||||
};
|
||||
|
||||
var rt = Realtime.start(config);
|
||||
|
||||
['cut', 'paste', 'change', 'keyup', 'keydown', 'select', 'textInput']
|
||||
.forEach(function (evt) {
|
||||
$textarea.on(evt, onLocal);
|
||||
});
|
||||
});
|
||||
34
www/examples/upload/index.html
Normal file
34
www/examples/upload/index.html
Normal file
@@ -0,0 +1,34 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<script data-main="main" src="/bower_components/requirejs/require.js"></script>
|
||||
<script>
|
||||
require.config({
|
||||
waitSeconds: 60,
|
||||
});
|
||||
</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" />
|
||||
<style>
|
||||
input {
|
||||
width: 50vw;
|
||||
padding: 15px;
|
||||
}
|
||||
pre {
|
||||
max-width: 90vw;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Upload</h1>
|
||||
|
||||
<input type="file">
|
||||
|
||||
75
www/examples/upload/main.js
Normal file
75
www/examples/upload/main.js
Normal file
@@ -0,0 +1,75 @@
|
||||
define([
|
||||
'/common/cryptget.js',
|
||||
'/bower_components/chainpad-crypto/crypto.js',
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
], function (Crypt, Crypto) {
|
||||
var $ = window.jQuery;
|
||||
var Nacl = window.nacl;
|
||||
|
||||
var key = Nacl.randomBytes(32);
|
||||
|
||||
var handleFile = function (body) {
|
||||
//console.log("plaintext");
|
||||
//console.log(body);
|
||||
|
||||
0 && Crypt.put(body, function (e, out) {
|
||||
if (e) { return void console.error(e); }
|
||||
if (out) {
|
||||
console.log(out);
|
||||
}
|
||||
});
|
||||
|
||||
var data = {};
|
||||
|
||||
(_ => {
|
||||
var cyphertext = data.payload = Crypto.encrypt(body, key);
|
||||
console.log("encrypted");
|
||||
console.log(cyphertext);
|
||||
|
||||
console.log(data);
|
||||
|
||||
var decrypted = Crypto.decrypt(cyphertext, key);
|
||||
//console.log('decrypted');
|
||||
//console.log(decrypted);
|
||||
|
||||
|
||||
if (decrypted !== body) {
|
||||
throw new Error("failed to maintain integrity with round trip");
|
||||
}
|
||||
|
||||
// finding... files are entirely too large.
|
||||
|
||||
|
||||
console.log(data.payload.length, body.length); // 1491393, 588323
|
||||
console.log(body.length / data.payload.length); // 0.3944788529918003
|
||||
console.log(data.payload.length / body.length); // 2.534990132971174
|
||||
|
||||
/*
|
||||
|
||||
http://stackoverflow.com/questions/19959072/sending-binary-data-in-javascript-over-http
|
||||
|
||||
// Since we deal with Firefox and Chrome only
|
||||
var bytesToSend = [253, 0, 128, 1];
|
||||
var bytesArray = new Uint8Array(bytesToSend);
|
||||
|
||||
$.ajax({
|
||||
url: '%your_service_url%',
|
||||
type: 'POST',
|
||||
contentType: 'application/octet-stream',
|
||||
data: bytesArray,
|
||||
processData: false
|
||||
});
|
||||
*/
|
||||
})();
|
||||
};
|
||||
|
||||
var $file = $('input[type="file"]');
|
||||
$file.on('change', function (e) {
|
||||
var file = e.target.files[0];
|
||||
var reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
handleFile(e.target.result);
|
||||
};
|
||||
reader.readAsText(file);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user