create examples directory with old prototypes

This commit is contained in:
ansuz
2016-12-30 13:22:01 +01:00
parent 0abf45bdc5
commit ba4df1a22c
23 changed files with 26 additions and 10 deletions

243
www/examples/board/board.js Normal file
View 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;
});

View 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>

View 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!");
});
});
});

View 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
View 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);
});
});

View 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
View 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;
});

View 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
View 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
View 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
View 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.

View 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
View 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);
});
});

View 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
View 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); }
});

View 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
View 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);
});

View 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>

View 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);
});

View 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
View 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);
});
});

View 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">

View 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);
});
});