Merge pull request #65 from amark/develop

Develop 0.2.0!
This commit is contained in:
Mark Nadal 2015-07-04 01:05:50 -06:00
commit a3fcc2912e
68 changed files with 15555 additions and 3368 deletions

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
node_modules/* node_modules/*
npm-debug.log npm-debug.log
*data.json *data.json
*.db
.idea/

View File

@ -1,5 +1,7 @@
language: node_js language: node_js
node_js: node_js:
- 0.6
- 0.8 - 0.8
- 0.10 - 0.10
- 0.11 - 0.11
- 0.12

103
README.md
View File

@ -1,8 +1,12 @@
gun [![Build Status](https://travis-ci.org/amark/gun.svg?branch=master)](https://travis-ci.org/amark/gun) gun [![Build Status](https://travis-ci.org/amark/gun.svg?branch=master)](https://travis-ci.org/amark/gun) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/amark/gun?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
=== ===
GUN is a realtime, decentralized, embedded, graph database engine.
## Getting Started ## Getting Started
For the browser, try out this [tutorial](https://dl.dropboxusercontent.com/u/4374976/gun/web/think.html). This README is for GUN servers.
If you do not have [node](http://nodejs.org/) or [npm](https://www.npmjs.com/), read [this](https://github.com/amark/gun/blob/master/examples/install.sh) first. If you do not have [node](http://nodejs.org/) or [npm](https://www.npmjs.com/), read [this](https://github.com/amark/gun/blob/master/examples/install.sh) first.
Then in your terminal, run: Then in your terminal, run:
@ -33,42 +37,26 @@ These are the default persistence layers, they are modular and can be replaced o
Using S3 is recommended for deployment, and using a file is recommended for local development. Using S3 is recommended for deployment, and using a file is recommended for local development.
Now you can save your first object, and create a reference to it. ## Demos
```javascript The examples included in this repo are online [here](http://gunjs.herokuapp.com/), you can run them locally by:
gun.set({ hello: 'world' }).key('my/first/data');
```
Altogether, try it with the node hello world web server which will reply with your data.
```javascript
var Gun = require('gun');
var gun = Gun({ file: 'data.json' });
gun.set({ hello: 'world' }).key('my/first/data');
var http = require('http');
http.createServer(function(req, res){
gun.load('my/first/data', function(err, data){
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify(data));
});
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');
```
Fire up your browser and hit that URL - you'll see your data, plus some gun specific metadata.
## Examples
Try out some online [examples](http://gunjs.herokuapp.com/) or run them yourself with the following command:
```bash ```bash
git clone http://github.com/amark/gun sudo npm install gun
cd gun/examples && npm install cd node_modules/gun
node express.js 8080 node examples/http.js 8080
``` ```
Then visit [http://localhost:8080](http://localhost:8080) in your browser.
Then visit [http://localhost:8080](http://localhost:8080) in your browser. If that did not work it is probably because npm installed to a global directory, to fix this try `mkdir node_modules` in your desired directory and re-run the above commands.
***
## WARNINGS
### v0.2.0 [![Queued](https://badge.waffle.io/amark/gun.svg?label=Queue&title=Queue)](http://waffle.io/amark/gun) [![In Progress](https://badge.waffle.io/amark/gun.svg?label=InProgress&title=In%20Progress)](http://waffle.io/amark/gun) [![Pending Deploy](https://badge.waffle.io/amark/gun.svg?label=Pending&title=Done)](http://waffle.io/amark/gun) Status
Version 0.2.0 is currently in alpha. Important changes include `.get` to `.val`, `.load` to `.get`, and `.set` to `.put`. Documentation is our current focus, and `.all` functionality will be coming soon. The latest documentation can be found at https://github.com/amark/gun/wiki/0.2.0-API-and-How-to. Please report any issues via https://github.com/amark/gun/issues.
GUN is not stable, and therefore should not be trusted in a production environment.
***
## API ## API
@ -85,20 +73,20 @@ In gun, it can be helpful to think of everything as field/value pairs. For examp
"email": "mark@gunDB.io" "email": "mark@gunDB.io"
} }
``` ```
Now, we want to save this object to a key called `usernames/marknadal`. We can do that like this: Now, we want to save this object to a key called `'usernames/marknadal'`. We can do that like this:
```javascript ```javascript
gun.set({ gun.put({
username: "marknadal", username: "marknadal",
name: "Mark Nadal", name: "Mark Nadal",
email: "mark@gunDB.io" email: "mark@gunDB.io"
}).key('usernames/marknadal'); }).key('usernames/marknadal');
``` ```
We can also pass `set` a callback that can be used to handle errors: We can also pass `put` a callback that can be used to handle errors:
```javascript ```javascript
gun.set({ gun.put({
username: "marknadal", username: "marknadal",
name: "Mark Nadal", name: "Mark Nadal",
email: "mark@gunDB.io" email: "mark@gunDB.io"
@ -112,33 +100,40 @@ gun.set({
Once we have some data stored in gun, we need a way to get them out again. Retrieving the data that we just stored would look like this: Once we have some data stored in gun, we need a way to get them out again. Retrieving the data that we just stored would look like this:
```javascript ```javascript
gun.load('usernames/marknadal').get(function(user){ gun.get('usernames/marknadal').val(function(user){
console.log(user.name); // Prints `Mark Nadal` to the console console.log(user.name); // Prints `Mark Nadal` to the console
}); });
``` ```
Basically, this tells gun to check `usernames/marknadal`, and then return the object it finds associated with it. For more information, including how to save relational or document based data, [check out the wiki](https://github.com/amark/gun/wiki). Basically, this tells gun to check `'usernames/marknadal'`, and then return the object it finds associated with it. For more information, including how to save relational or document based data, [check out the wiki](https://github.com/amark/gun/wiki).
--- ---
## YOU ## YOU
We're just getting started, so join us! Being lonely is never any fun, especially when programming. Being lonely is never any fun, especially when programming.
I want to help you, because my goal is for GUN to be the easiest database ever. Our goal is for GUN to be the easiest database ever,
That means if you ever get stuck on something for longer than 5 minutes, which means if you ever get stuck on something for longer than 5 minutes,
you should talk to me so I can help you solve it. let us know so we can help you. Your input is invaluable,
Your input will then help me improve gun. as it enables us where to refine GUN. So drop us a line in the [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/amark/gun?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)! Or join the [mail list](https://groups.google.com/forum/#!forum/g-u-n).
We are also really open to contributions! GUN is easy to extend and customize:
Thanks to the following people who have contributed to GUN, via code, issues, or conversation:
[agborkowski](https://github.com/agborkowski), [alexlafroscia](https://github.com/alexlafroscia), [anubiann00b](https://github.com/anubiann00b), [bromagosa](https://github.com/bromagosa), [coolaj86](https://github.com/coolaj86), [d-oliveros](https://github.com/d-oliveros), [danscan](https://github.com/danscan), [forrestjt](https://github.com/forrestjt), [gedw99](https://github.com/gedw99), [HelloCodeMing](https://github.com/HelloCodeMing), [JosePedroDias](https://github.com/josepedrodias), [onetom](https://github.com/onetom), [ndarilek](https://github.com/ndarilek), [phpnode](https://github.com/phpnode), [riston](https://github.com/riston), [rootsical](https://github.com/rootsical), [rrrene](https://github.com/rrrene), [ssr1ram](https://github.com/ssr1ram), [Xe](https://github.com/Xe), [zot](https://github.com/zot)
This list of contributors was manually compiled, alphabetically sorted. If we missed you, please submit an issue so we can get you added!
## Contribute
Extending GUN or writing modules for it is as simple as:
`Gun.on('opt').event(function(gun, opt){ /* Your module here! */ })` `Gun.on('opt').event(function(gun, opt){ /* Your module here! */ })`
It is also important to us that your database is not a magical black box. We also want our database to be comprehensible, not some magical black box.
So often our questions get dismissed with "its complicated hard low level stuff, let the experts handle it." So often database questions get dismissed with "its complicated hard low level stuff, let the experts handle it".
And we do not think that attitude will generate any progress for people. That attitude prevents progress, instead we welcome teaching people and listening to new perspectives.
Instead, we want to make everyone an expert by actually getting really good at explaining the concepts. Join along side us in a quest to learn cool things and help others build awesome technology!
So join our community, in the quest of learning cool things and helping yourself and others build awesome technology.
- [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/amark/gun?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) (all chats relating to GUN and development should be here! IRC style) We need help on the following roadmap.
- Google Group: https://groups.google.com/forum/#!forum/g-u-n (for slower threaded discussions)
## Ahead ## Ahead
- ~~Realtime push to the browser~~ - ~~Realtime push to the browser~~
@ -155,9 +150,7 @@ So join our community, in the quest of learning cool things and helping yourself
- LRU or some Expiry (so RAM doesn't asplode) - LRU or some Expiry (so RAM doesn't asplode)
- Bug fixes - Bug fixes
- Data Structures: - Data Structures:
- ~~Groups~~ - ~~Sets~~ (Table/Collections, Unordered Lists)
- Linked Lists
- Collections (hybrid: linked-groups/paginated-lists)
- CRDTs - CRDTs
- OT - OT
- Locking / Strong Consistency (sacrifices Availability) - Locking / Strong Consistency (sacrifices Availability)

View File

@ -1 +0,0 @@
web: node app.js

View File

@ -1,18 +0,0 @@
console.log("If modules not found, run `npm install` in example/admin folder!"); // git subtree push -P examples/admin heroku master
var port = process.env.OPENSHIFT_NODEJS_PORT || process.env.VCAP_APP_PORT || process.env.PORT || 8888;
var express = require('express');
var bodyParser = require('body-parser');
var app = express();
var Gun = require('gun');
var gun = Gun({
s3: (process.env.NODE_ENV === 'production')? null : require('../../test/shotgun') // replace this with your own keys!
});
app.use(gun.server)
.use(express.static(__dirname))
app.listen(port);
console.log('Express started on port ' + port + ' with /gun');
gun.load('blob/data').blank(function(){ // in case there is no data on this key
console.log("blankety blank");
gun.set({ hello: "world", from: "Mark Nadal",_:{'#':'0DFXd0ckJ9cXGczusNf1ovrE'}}).key('blob/data'); // save some sample data
});

View File

@ -1,85 +0,0 @@
<!DOCTYPE html>
<html ng-app="admin">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://ajax.cdnjs.com/ajax/libs/json2/20110223/json2.js"></script>
<!--
-->
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.js"></script>
<script src="../../gun.js"></script>
</head>
<body ng-controller="editor">
<style>
html, body {
font-family: Verdana, Geneva, sans-serif;
}
a {
color: skyblue;
text-decoration: none;
cursor: poiner;
}
ul, li {
list-style-type: none;
}
ul:hover, li:hover {
list-style-type: inherit;
}
input {
border: none;
border-bottom: dashed 1px gainsboro;
}
.none {
display: none;
}
</style>
<h3>Admin Data Editor</h3>
This is a live view of your JSON data, you can edit it in realtime or add new key/values.
<!--
<form method="post" action="http://localhost:8888/gun">
First name: <input type="text" name="firstname"><br>
Last name: <input type="text" name="lastname">
<input type="submit" value="Submit">
</form>
-->
<ul name="list">
<li ng-repeat="(key, val) in data">
<div ng-if="key != '_'">
<b>{{key}}</b>:
<span contenteditable="true" gun>{{val}}</span>
</div>
</li>
<li>
<form ng-submit="add()">
<label>
<input ng-model="field" placeholder="key" ng-submit="add()">
<a ng-click="add()">add</a>
<input type="submit" class="none"/>
</label>
</form>
</li>
</ul>
<script>
var gun = Gun([location.origin + '/gun']);
angular.module('admin', []).controller('editor', function($scope){
$scope.data = {};
$scope.$data = gun.load('blob/data').get(function(data){
Gun.obj.map(data, function(val, field){
if(val === $scope.data[field]){ return }
$scope.data[field] = val;
});
$scope.$apply();
});
$scope.add = function(a,b,c){
$scope.$data.path($scope.field).set( $scope.data[$scope.field] = 'value' );
$scope.field = '';
};
}).directive('gun', function(){
return function(scope, elem){
elem.on('keyup', function(){
scope.$data.path(scope.key).set( scope.data[scope.key] = elem.text() );
});
};
});
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +0,0 @@
{
"name": "admin",
"main": "app.js",
"description": "Example gun app, using Express & Angular."
, "version": "0.0.1"
, "engines": {
"node": "~>0.6.6"
}
, "dependencies": {
"express": "~>4.9.0",
"body-parser": "~>1.8.1",
"gun": "0.0.7"
}
, "scripts": {
"start": "node app.js",
"test": "mocha"
}
}

View File

@ -1,229 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://ajax.cdnjs.com/ajax/libs/json2/20110223/json2.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="../../gun.js"></script>
</head>
<body><center>
<style>
html, body {
font-family: Papyrus, fantasy;
font-size: 18pt;
}
.start .player {
color: white;
border: none;
padding: 1.5em;
background: skyblue;
font-family: Papyrus, fantasy;
}
.screen {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
}
.off {
display: none;
}
.white {
color: white;
}
.winner input, .winner button {
font-size: 18pt;
}
</style>
<div class="start screen">
<!--<pre>
¦¦¦¦¦¦+ ¦¦+ ¦¦+ ¦¦¦+ ¦¦+ ¦¦¦¦¦¦¦+ ¦¦+ ¦¦+ ¦¦¦+ ¦¦+ ¦¦¦¦¦¦+ ¦¦¦¦¦¦¦+ ¦¦¦¦¦¦+
¦¦+----+ ¦¦¦ ¦¦¦ ¦¦¦¦+ ¦¦¦ ¦¦+----+ ¦¦¦ ¦¦¦ ¦¦¦¦+ ¦¦¦ ¦¦+----+ ¦¦+----+ ¦¦+--¦¦+
¦¦¦ ¦¦¦+ ¦¦¦ ¦¦¦ ¦¦+¦¦+ ¦¦¦ ¦¦¦¦¦¦¦+ ¦¦¦ ¦¦¦ ¦¦+¦¦+ ¦¦¦ ¦¦¦ ¦¦¦+ ¦¦¦¦¦+ ¦¦¦¦¦¦++
¦¦¦ ¦¦¦ ¦¦¦ ¦¦¦ ¦¦¦+¦¦+¦¦¦ +----¦¦¦ ¦¦¦ ¦¦¦ ¦¦¦+¦¦+¦¦¦ ¦¦¦ ¦¦¦ ¦¦+--+ ¦¦+--¦¦+
+¦¦¦¦¦¦++ +¦¦¦¦¦¦++ ¦¦¦ +¦¦¦¦¦ ¦¦¦¦¦¦¦¦ ¦¦¦¦¦¦¦+ ¦¦¦ ¦¦¦ +¦¦¦¦¦ +¦¦¦¦¦¦++ ¦¦¦¦¦¦¦+ ¦¦¦ ¦¦¦
+-----+ +-----+ +-+ +---+ +------+ +------+ +-+ +-+ +---+ +-----+ +------+ +-+ +-+
</pre>-->
<h1>GUN SLINGER</h1>
<h3>Select!</h3>
<button id="one" class="player">Player 1</button>
<button id="two" class="player">Player 2</button>
<h5>Next game available in 15 seconds or less...<h5>
<h4>Fastest draw in the west, <span id="fastest">no</span> seconds, by <span id="slinger">nobody</span>.</h4>
<h5>Previous duel won in <span id="previous">no</span> seconds, by <span id="last">no one</span>.</h5>
</div>
<div class="shoot screen off white" style="background: tan;">
<h3>GET READY!</h3>
</div>
<div class="fire screen off" style="background: lime;">
<h3>FIRE!</h3>
<h5>by tapping this screen</h5>
</div>
<div class="stop screen off" style="background: yellow;">
<h3>STOP!</h3>
<h5>...waiting for the other player...</h5>
</div>
<div class="disqualified screen off white" style="background: red;">
<h3>DISQUALIFIED!</h3>
</div>
<div class="loser screen off white" style="background: red;">
<h3>YOU DIED!</h3>
</div>
<div class="draw screen off white" style="background: red;">
<h3>YOU BOTH DIED!</h3>
<button onclick="game.reset()">Reset Game</button>
</div>
<div class="default screen off white" style="background: skyblue;">
<h3>YOU WON!</h3>
<button onclick="game.reset()">Reset</button>
</div>
<div class="winner screen off white" style="background: skyblue;">
<h3>YOU WON!</h3>
<form onsubmit="game.rank()">
<input placeholder="nickname">
<button onclick="game.rank()">Rank</button>
</form>
</div>
<script>
$(function(){
var game = window.game = {me:{}}
//, gun = Gun('https://gunjs.herokuapp.com/gun')
, gun = window.gun = Gun('http://localhost:8888' + '/gun')
.load('game/duel')
;
gun.path('name').get(function(val){
console.log('gunslinger has results', val);
});
gun.get(function(player){
console.log("Game update", player);
if(game.timeout){
clearTimeout(game.timeout);
} else {
game.timeout = setTimeout(game.reset, 15 * 1000);
}
$('.start').find('#one').text(player.one? 'Taken!' : 'Player 1').attr('disabled', player.one? true : false);
$('.start').find('#two').text(player.two? 'Taken!' : 'Player 2').attr('disabled', player.two? true : false);
$('.start').find('#fastest').text(player.fastest || 'no');
$('.start').find('#slinger').text(player.slinger || 'nobody');
$('.start').find('#previous').text(player.previous || 'no');
$('.start').find('#last').text(player.last || 'no one');
game.start(player);
game.compare(player);
});
gun.path('dqed').get(function(){
if(!game.me.player || game.me.dqed){ return }
game.me.default = true;
game.screen('default');
});
gun.path('one').get(function(val){
console.log('gun player one', val);
if(null === val){
game.cancel();
}
})
game.start = function(player){
if(game.me.started){ return }
if(!player.one || !player.two){ return }
if(!player || !game.me || !game.me.player){ return }
console.log("start?", player, game.me);
if(player[game.me.player] == game.me[game.me.player]){
game.screen('shoot');
game.coordinate();
} else {
game.cancel();
}
}
gun.path('coordinate').get(game.coordinate = function(at){
if(!game.me.player){ return }
var started = game.me.started = Gun.roulette();
if(at){
Gun.schedule(at, function(){
if(game.me.dqed || game.me.default || started != game.me.started){ return }
game.screen('fire');
game.me.draw = (+new Date());
});
return;
}
if(game.me.player != 'two'){ return }
game.me.coordinate = (+new Date()) + Math.round(Math.random() * 2000 + 2700); // MIN is the right number, and MAX is the SUM of both numbers.
gun.path('coordinate').set(game.me.coordinate);
});
game.compare = function(score){
if(!game.me.player || game.me.over || !score.onespeed || !score.twospeed){ return }
if(score.onespeed < score.twospeed){
if(game.me.one){
win(score.onespeed);
} else {
game.screen('loser');
}
} else
if(score.twospeed < score.onespeed){
if(game.me.two){
win(score.twospeed);
} else {
game.screen('loser');
}
} else {
game.screen('draw');
}
function win(speed){
game.me.over = true;
game.screen('winner');
speed = speed / 1000; // convert to seconds
if(score.fastest && speed < score.fastest){
gun.path('fastest').set(speed);
game.me.fastest = true;
}
gun.path('previous').set(speed);
}
}
game.rank = function(){
var nick = $(".winner").find('input').val() || 'Masked Cowboy';
gun.path('last').set(nick);
if(game.me.fastest){
gun.path('slinger').set(nick);
}
game.reset();
}
game.reset = function(){
console.log("Resetting game");
gun.path('one').set(null);
gun.path('two').set(null);
gun.path('dqed').set(null);
gun.path('coordinate').set(null);
gun.path('onespeed').set(null);
gun.path('twospeed').set(null);
game.cancel();
}
game.cancel = function(){
game.screen();
game.me = {};
}
game.screen = function(screen){
$('.screen').addClass('off').filter('.' + (screen || 'start')).removeClass('off');
}
$('.player').on('click', function(){
if(game.me.player){ return }
game.me = {};
gun.path(game.me.player = this.id).set(game.me[game.me.player] = Gun.roulette());
});
$('.shoot').on('click', function(){
if(!game.me.player){ return }
game.me.dqed = true;
game.screen('disqualified');
gun.path('dqed').set(game.me[game.me.player]);
});
$('.fire').on('click', function(){
if(!game.me.player || game.me.fired){ return }
game.me.fired = (+new Date());
game.me.speed = game.me.fired - game.me.draw;
gun.path(game.me.player + 'speed').set(game.me.speed);
game.screen('stop');
});
})
</script>
</center></body>
</html>

View File

@ -1,229 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://ajax.cdnjs.com/ajax/libs/json2/20110223/json2.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="old_gun_for_slinger.js"></script>
</head>
<body><center>
<style>
html, body {
font-family: Papyrus, fantasy;
font-size: 18pt;
}
.start .player {
color: white;
border: none;
padding: 1.5em;
background: skyblue;
font-family: Papyrus, fantasy;
}
.screen {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
}
.off {
display: none;
}
.white {
color: white;
}
.winner input, .winner button {
font-size: 18pt;
}
</style>
<div class="start screen">
<!--<pre>
¦¦¦¦¦¦+ ¦¦+ ¦¦+ ¦¦¦+ ¦¦+ ¦¦¦¦¦¦¦+ ¦¦+ ¦¦+ ¦¦¦+ ¦¦+ ¦¦¦¦¦¦+ ¦¦¦¦¦¦¦+ ¦¦¦¦¦¦+
¦¦+----+ ¦¦¦ ¦¦¦ ¦¦¦¦+ ¦¦¦ ¦¦+----+ ¦¦¦ ¦¦¦ ¦¦¦¦+ ¦¦¦ ¦¦+----+ ¦¦+----+ ¦¦+--¦¦+
¦¦¦ ¦¦¦+ ¦¦¦ ¦¦¦ ¦¦+¦¦+ ¦¦¦ ¦¦¦¦¦¦¦+ ¦¦¦ ¦¦¦ ¦¦+¦¦+ ¦¦¦ ¦¦¦ ¦¦¦+ ¦¦¦¦¦+ ¦¦¦¦¦¦++
¦¦¦ ¦¦¦ ¦¦¦ ¦¦¦ ¦¦¦+¦¦+¦¦¦ +----¦¦¦ ¦¦¦ ¦¦¦ ¦¦¦+¦¦+¦¦¦ ¦¦¦ ¦¦¦ ¦¦+--+ ¦¦+--¦¦+
+¦¦¦¦¦¦++ +¦¦¦¦¦¦++ ¦¦¦ +¦¦¦¦¦ ¦¦¦¦¦¦¦¦ ¦¦¦¦¦¦¦+ ¦¦¦ ¦¦¦ +¦¦¦¦¦ +¦¦¦¦¦¦++ ¦¦¦¦¦¦¦+ ¦¦¦ ¦¦¦
+-----+ +-----+ +-+ +---+ +------+ +------+ +-+ +-+ +---+ +-----+ +------+ +-+ +-+
</pre>-->
<h1>GUN SLINGER</h1>
<h3>Select!</h3>
<button id="one" class="player">Player 1</button>
<button id="two" class="player">Player 2</button>
<h5>Next game available in 15 seconds or less...<h5>
<h4>Fastest draw in the west, <span id="fastest">no</span> seconds, by <span id="slinger">nobody</span>.</h4>
<h5>Previous duel won in <span id="previous">no</span> seconds, by <span id="last">no one</span>.</h5>
</div>
<div class="shoot screen off white" style="background: tan;">
<h3>GET READY!</h3>
</div>
<div class="fire screen off" style="background: lime;">
<h3>FIRE!</h3>
<h5>by tapping this screen</h5>
</div>
<div class="stop screen off" style="background: yellow;">
<h3>STOP!</h3>
<h5>...waiting for the other player...</h5>
</div>
<div class="disqualified screen off white" style="background: red;">
<h3>DISQUALIFIED!</h3>
</div>
<div class="loser screen off white" style="background: red;">
<h3>YOU DIED!</h3>
</div>
<div class="draw screen off white" style="background: red;">
<h3>YOU BOTH DIED!</h3>
<button onclick="game.reset()">Reset Game</button>
</div>
<div class="default screen off white" style="background: skyblue;">
<h3>YOU WON!</h3>
<button onclick="game.reset()">Reset</button>
</div>
<div class="winner screen off white" style="background: skyblue;">
<h3>YOU WON!</h3>
<form onsubmit="game.rank()">
<input placeholder="nickname">
<button onclick="game.rank()">Rank</button>
</form>
</div>
<script>
$(function(){
var game = window.game = {me:{}}
, gun = Gun('https://gunjs.herokuapp.com/gun')
//, gun = window.gun = Gun('http://localhost:8888' + '/gun')
.load('game/duel')
;
gun.path('name').get(function(val){
console.log('gunslinger has results', val);
});
gun.get(function(player){
console.log("Game update", player);
if(game.timeout){
clearTimeout(game.timeout);
} else {
game.timeout = setTimeout(game.reset, 15 * 1000);
}
$('.start').find('#one').text(player.one? 'Taken!' : 'Player 1').attr('disabled', player.one? true : false);
$('.start').find('#two').text(player.two? 'Taken!' : 'Player 2').attr('disabled', player.two? true : false);
$('.start').find('#fastest').text(player.fastest || 'no');
$('.start').find('#slinger').text(player.slinger || 'nobody');
$('.start').find('#previous').text(player.previous || 'no');
$('.start').find('#last').text(player.last || 'no one');
game.start(player);
game.compare(player);
});
gun.path('dqed').get(function(){
if(!game.me.player || game.me.dqed){ return }
game.me.default = true;
game.screen('default');
});
gun.path('one').get(function(val){
console.log('gun player one', val);
if(null === val){
game.cancel();
}
})
game.start = function(player){
if(game.me.started){ return }
if(!player.one || !player.two){ return }
if(!player || !game.me || !game.me.player){ return }
console.log("start?", player, game.me);
if(player[game.me.player] == game.me[game.me.player]){
game.screen('shoot');
game.coordinate();
} else {
game.cancel();
}
}
gun.path('coordinate').get(game.coordinate = function(at){
if(!game.me.player){ return }
var started = game.me.started = Gun.roulette();
if(at){
Gun.schedule(at, function(){
if(game.me.dqed || game.me.default || started != game.me.started){ return }
game.screen('fire');
game.me.draw = (+new Date());
});
return;
}
if(game.me.player != 'two'){ return }
game.me.coordinate = (+new Date()) + Math.round(Math.random() * 2000 + 2700); // MIN is the right number, and MAX is the SUM of both numbers.
gun.path('coordinate').set(game.me.coordinate);
});
game.compare = function(score){
if(!game.me.player || game.me.over || !score.onespeed || !score.twospeed){ return }
if(score.onespeed < score.twospeed){
if(game.me.one){
win(score.onespeed);
} else {
game.screen('loser');
}
} else
if(score.twospeed < score.onespeed){
if(game.me.two){
win(score.twospeed);
} else {
game.screen('loser');
}
} else {
game.screen('draw');
}
function win(speed){
game.me.over = true;
game.screen('winner');
speed = speed / 1000; // convert to seconds
if(score.fastest && speed < score.fastest){
gun.path('fastest').set(speed);
game.me.fastest = true;
}
gun.path('previous').set(speed);
}
}
game.rank = function(){
var nick = $(".winner").find('input').val() || 'Masked Cowboy';
gun.path('last').set(nick);
if(game.me.fastest){
gun.path('slinger').set(nick);
}
game.reset();
}
game.reset = function(){
console.log("Resetting game");
gun.path('one').set(null);
gun.path('two').set(null);
gun.path('dqed').set(null);
gun.path('coordinate').set(null);
gun.path('onespeed').set(null);
gun.path('twospeed').set(null);
game.cancel();
}
game.cancel = function(){
game.screen();
game.me = {};
}
game.screen = function(screen){
$('.screen').addClass('off').filter('.' + (screen || 'start')).removeClass('off');
}
$('.player').on('click', function(){
if(game.me.player){ return }
game.me = {};
gun.path(game.me.player = this.id).set(game.me[game.me.player] = Gun.roulette());
});
$('.shoot').on('click', function(){
if(!game.me.player){ return }
game.me.dqed = true;
game.screen('disqualified');
gun.path('dqed').set(game.me[game.me.player]);
});
$('.fire').on('click', function(){
if(!game.me.player || game.me.fired){ return }
game.me.fired = (+new Date());
game.me.speed = game.me.fired - game.me.draw;
gun.path(game.me.player + 'speed').set(game.me.speed);
game.screen('stop');
});
})
</script>
</center></body>
</html>

View File

@ -1,195 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://ajax.cdnjs.com/ajax/libs/json2/20110223/json2.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="../../gun.js"></script>
</head>
<body><center>
<style>
html, body {
font-family: Papyrus, fantasy;
font-size: 18pt;
}
.start .player {
color: white;
border: none;
padding: 1.5em;
background: skyblue;
font-family: Papyrus, fantasy;
}
.screen {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
}
.off {
display: none;
}
.white {
color: white;
}
</style>
<div class="start screen">
<!--<pre>
¦¦¦¦¦¦+ ¦¦+ ¦¦+ ¦¦¦+ ¦¦+ ¦¦¦¦¦¦¦+ ¦¦+ ¦¦+ ¦¦¦+ ¦¦+ ¦¦¦¦¦¦+ ¦¦¦¦¦¦¦+ ¦¦¦¦¦¦+
¦¦+----+ ¦¦¦ ¦¦¦ ¦¦¦¦+ ¦¦¦ ¦¦+----+ ¦¦¦ ¦¦¦ ¦¦¦¦+ ¦¦¦ ¦¦+----+ ¦¦+----+ ¦¦+--¦¦+
¦¦¦ ¦¦¦+ ¦¦¦ ¦¦¦ ¦¦+¦¦+ ¦¦¦ ¦¦¦¦¦¦¦+ ¦¦¦ ¦¦¦ ¦¦+¦¦+ ¦¦¦ ¦¦¦ ¦¦¦+ ¦¦¦¦¦+ ¦¦¦¦¦¦++
¦¦¦ ¦¦¦ ¦¦¦ ¦¦¦ ¦¦¦+¦¦+¦¦¦ +----¦¦¦ ¦¦¦ ¦¦¦ ¦¦¦+¦¦+¦¦¦ ¦¦¦ ¦¦¦ ¦¦+--+ ¦¦+--¦¦+
+¦¦¦¦¦¦++ +¦¦¦¦¦¦++ ¦¦¦ +¦¦¦¦¦ ¦¦¦¦¦¦¦¦ ¦¦¦¦¦¦¦+ ¦¦¦ ¦¦¦ +¦¦¦¦¦ +¦¦¦¦¦¦++ ¦¦¦¦¦¦¦+ ¦¦¦ ¦¦¦
+-----+ +-----+ +-+ +---+ +------+ +------+ +-+ +-+ +---+ +-----+ +------+ +-+ +-+
</pre>-->
<h1>GUN SLINGER</h1>
<h3>Select!</h3>
<button id="one" class="player">Player 1</button>
<button id="two" class="player">Player 2</button>
<h5>Next game available in 15 seconds or less...<h5>
</div>
<div class="shoot screen off white" style="background: tan;">
<h3>GET READY!</h3>
</div>
<div class="fire screen off" style="background: lime;">
<h3>FIRE!</h3>
<h5>by tapping this screen</h5>
</div>
<div class="stop screen off" style="background: yellow;">
<h3>STOP!</h3>
<h5>...waiting for the other player...</h5>
</div>
<div class="disqualified screen off white" style="background: red;">
<h3>DISQUALIFIED!</h3>
</div>
<div class="loser screen off white" style="background: red;">
<h3>YOU DIED!</h3>
</div>
<div class="draw screen off white" style="background: red;">
<h3>YOU BOTH DIED!</h3>
<button onclick="game.reset()">Reset Game</button>
</div>
<div class="winner screen off white" style="background: skyblue;">
<h3>YOU WON!</h3>
<button onclick="game.reset()">Reset Game</button>
</div>
<script>
$(function(){
var game = window.game = {me:{}}
, gun = Gun('https://gunjs.herokuapp.com/gun')
//, gun = window.gun = Gun('http://localhost:8888' + '/gun')
.load('game/duel')
;
gun.path('name').get(function(val){
console.log('gunslinger has results', val);
});
gun.get(function(player){
console.log("Game update", player);
if(game.timeout){
clearTimeout(game.timeout);
} else {
game.timeout = setTimeout(game.reset, 15 * 1000);
}
$('.start').find('#one').text(player.one? 'Taken!' : 'Player 1').attr('disabled', player.one? true : false);
$('.start').find('#two').text(player.two? 'Taken!' : 'Player 2').attr('disabled', player.two? true : false);
game.start(player);
game.compare(player);
});
gun.path('dqed').get(function(){
if(!game.me.player || game.me.dqed){ return }
game.me.default = true;
game.screen('winner');
});
gun.path('one').get(function(val){
console.log('gun player one', val);
if(null === val){
game.cancel();
}
})
game.start = function(player){
if(game.me.started){ return }
if(!player.one || !player.two){ return }
if(!player || !game.me || !game.me.player){ return }
console.log("start?", player, game.me);
if(player[game.me.player] == game.me[game.me.player]){
game.screen('shoot');
game.coordinate();
} else {
game.cancel();
}
}
gun.path('coordinate').get(game.coordinate = function(at){
if(!game.me.player){ return }
var started = game.me.started = Gun.roulette();
if(at){
Gun.schedule(at, function(){
if(game.me.dqed || game.me.default || started != game.me.started){ return }
game.screen('fire');
game.me.draw = (+new Date());
});
return;
}
if(game.me.player != 'two'){ return }
game.me.coordinate = (+new Date()) + Math.round(Math.random() * 2000 + 2700); // MIN is the right number, and MAX is the SUM of both numbers.
gun.path('coordinate').set(game.me.coordinate);
});
game.compare = function(score){
if(!game.me.player || !score.onespeed || !score.twospeed){ return }
if(score.onespeed < score.twospeed){
if(game.me.one){
game.screen('winner');
} else {
game.screen('loser');
}
} else
if(score.twospeed < score.onespeed){
if(game.me.two){
game.screen('winner');
} else {
game.screen('loser');
}
} else {
game.screen('draw');
}
}
game.reset = function(){
console.log("Resetting game");
gun.path('one').set(null);
gun.path('two').set(null);
gun.path('dqed').set(null);
gun.path('coordinate').set(null);
gun.path('onespeed').set(null);
gun.path('twospeed').set(null);
game.cancel();
}
game.cancel = function(){
game.screen();
game.me = {};
}
game.screen = function(screen){
$('.screen').addClass('off').filter('.' + (screen || 'start')).removeClass('off');
}
$('.player').on('click', function(){
if(game.me.player){ return }
game.me = {};
gun.path(game.me.player = this.id).set(game.me[game.me.player] = Gun.roulette());
});
$('.shoot').on('click', function(){
if(!game.me.player){ return }
game.me.dqed = true;
game.screen('disqualified');
gun.path('dqed').set(game.me[game.me.player]);
});
$('.fire').on('click', function(){
if(!game.me.player || game.me.fired){ return }
game.me.fired = (+new Date());
game.me.speed = game.me.fired - game.me.draw;
gun.path(game.me.player + 'speed').set(game.me.speed);
game.screen('stop');
});
})
</script>
</center></body>
</html>

View File

@ -1,79 +0,0 @@
<!DOCTYPE html>
<html ng-app="admin">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://ajax.cdnjs.com/ajax/libs/json2/20110223/json2.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.js"></script>
<script src="../../gun.js"></script>
</head>
<body ng-controller="editor">
<style>
html, body {
font-family: Verdana, Geneva, sans-serif;
}
a {
color: skyblue;
text-decoration: none;
cursor: poiner;
}
ul, li {
list-style-type: none;
}
ul:hover, li:hover {
list-style-type: inherit;
}
input {
border: none;
border-bottom: dashed 1px gainsboro;
}
.none {
display: none;
}
</style>
<h3>Admin JSON Editor</h3>
This is a live view of your data, you can edit it in realtime or add new key/values.
<ul name="list">
<li ng-repeat="(key, val) in data">
<div ng-if="key != '_'">
<b>{{key}}</b>:
<span contenteditable="true" gun>{{val}}</span>
</div>
</li>
<li>
<form ng-submit="add()">
<label>
<input ng-model="field" placeholder="key" ng-submit="add()">
<a ng-click="add()">add</a>
<input type="submit" class="none"/>
</label>
</form>
</li>
</ul>
<script>
var gun = Gun(location.origin + '/gun');
angular.module('admin', []).controller('editor', function($scope){
$scope.data = {};
$scope.$data = gun.load('example/angular/data').blank(function(){
console.log("Initializing Data!");
this.set({});
}).on(function(data){
Gun.obj.map(data, function(val, field){
if(val === $scope.data[field]){ return }
$scope.data[field] = val;
});
$scope.$apply();
});
$scope.add = function(){
$scope.$data.path($scope.field).set( $scope.data[$scope.field] = 'value' );
$scope.field = '';
};
}).directive('gun', function(){
return function(scope, elem){
elem.on('keyup', function(){
scope.$data.path(scope.key).set( scope.data[scope.key] = elem.text() );
});
};
});
</script>
</body>
</html>

View File

@ -1,5 +0,0 @@
var gun = require('gun')({
s3: (process.env.NODE_ENV === 'production')? null : require('../../test/shotgun') // replace this with your own keys!
});
gun.load('kitten/hobbes').path('servant.cat.servant.name').get(function(name){ console.log(name) })

View File

@ -1,13 +0,0 @@
var gun = require('gun')({
s3: (process.env.NODE_ENV === 'production')? null : require('../../test/shotgun') // replace this with your own keys!
});
gun.load('email/mark@gundb.io').get(function(Mark){
console.log("Hello ", Mark);
this.path('username').set('amark'); // because we hadn't saved it yet!
this.path('cat').get(function(Hobbes){ // `this` is context of the nodes you explore via path
console.log(Hobbes);
this.set({ servant: Mark, coat: "tabby" }); // oh no! Hobbes has become Mark's master.
this.key('kitten/hobbes'); // cats are taking over the internet! Better make an index for them.
});
});

View File

@ -1,7 +0,0 @@
var gun = require('gun')({
s3: (process.env.NODE_ENV === 'production')? null : require('../../test/shotgun') // replace this with your own keys!
});
gun.set({ name: "Mark Nadal", email: "mark@gunDB.io", cat: { name: "Hobbes", species: "kitty" } })
.key('email/mark@gundb.io')
;

60
examples/chat/index.html Normal file
View File

@ -0,0 +1,60 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="../../gun.js"></script>
</head>
<body>
<style>
html, body { font-size: 14pt; }
.hide { display: none; }
form .who { width: 10%; }
form .what { width: 80%; }
ul { list-style: none; padding: 0; }
</style>
<ul><li class="hide">
<i class="when" style="color: #555; font-size: 12pt;">0</i>
<b class="who"></b>:
<span class="what"></span>
<u class="hide sort">0</u>
</li></ul>
<form>
<input class="who" placeholder="alias">
<input class="what" placeholder="message">
<button>send</button>
</form>
<script>
var chat = Gun(location.origin + '/gun').get('example/chat/data').not(function(){
return this.put({1: {who: 'Welcome', what: "to the chat app!", when: 1}}).key('example/chat/data');
});
chat.map().val(function(msg, field){
var $ul = $('ul'), $last = $.sort(field, $ul.lastChild), $msg;
($msg = $("#msg-" + field) || $ul.insertBefore($.model.cloneNode(true), $last.nextSibling)).id = 'msg-' + field;
$('.who', $msg)[$.text] = msg.who;
$('.what', $msg)[$.text] = msg.what;
$('.when', $msg)[$.text] = new Date(msg.when).toLocaleTimeString().toLowerCase();
$('.sort', $msg)[$.text] = field;
if(document.body.scrollHeight - (window.scrollY + window.innerHeight) <= $ul.lastChild.scrollHeight + 50){
window.scrollTo(0, document.body.scrollHeight);
}
});
var $ = function(s, e){ return (e || document).querySelector(s) } // make native look like jQuery.
$.sort = function(when, e){ return (when > ($('.sort', e)[$.text] || 0))? e : $.sort(when, e.previousSibling) }
$.text = document.body.textContent? 'textContent' : 'innerText'; // because browsers are stupid.
($.model = $('ul li').cloneNode(true)).removeAttribute('class');
$('.who', $('form')).value = (document.cookie.match(/alias\=(.*?)(\&|$|\;)/i)||[])[1]||'';
$('.what', $('form')).focus();
$('form').onsubmit = function(e){
var msg = {};
msg.when = Gun.time.is();
document.cookie = ('alias=' + (msg.who = $('.who', this).value || 'user' + Gun.text.random(6)));
msg.what = $('.what', this).value || '';
chat.path(msg.when + '_' + Gun.text.random(4)).put(msg);
$('.what', this).value = '';
return (e && e.preventDefault()), false;
};
</script>
</body>
</html>

10
examples/chat/spam.js Normal file
View File

@ -0,0 +1,10 @@
function spam(){
spam.start = true; spam.lock = false;
if(spam.count >= 100){ return }
var $f = $('form');
$('.what', $f).value = ++spam.count;
$f.onsubmit();
setTimeout(spam, 0);
}; spam.count = 0; spam.lock = true;
alert("ADD THIS LINE TO THE TOP OF THE MAP.VAL CALLBACK: `if(!spam.lock && !spam.start){ spam() }`");

View File

@ -17,4 +17,4 @@ var gun = Gun({
gun.attach(app); gun.attach(app);
app.use(express.static(__dirname)).listen(port); app.use(express.static(__dirname)).listen(port);
console.log('Server started on port ' + port + ' with /gun'); console.log('Server started on port ' + port + ' with /gun');

View File

@ -6,11 +6,11 @@ var gun = Gun({
bucket: '' // The bucket you want to save into bucket: '' // The bucket you want to save into
} }
}); });
gun.set({ hello: 'world' }).key('my/first/data'); gun.put({ hello: 'world' }).key('my/first/data');
var http = require('http'); var http = require('http');
http.createServer(function(req, res){ http.createServer(function(req, res){
gun.load('my/first/data', function(err, data){ gun.get('my/first/data', function(err, data){
res.writeHead(200, {'Content-Type': 'application/json'}); res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify(data)); res.end(JSON.stringify(data));
}); });

View File

@ -1,7 +1,5 @@
var port = process.env.OPENSHIFT_NODEJS_PORT || process.env.VCAP_APP_PORT || process.env.PORT || process.argv[2] || 80; var port = process.env.OPENSHIFT_NODEJS_PORT || process.env.VCAP_APP_PORT || process.env.PORT || process.argv[2] || 80;
var http = require('http');
var Gun = require('gun'); var Gun = require('gun');
var gun = Gun({ var gun = Gun({
file: 'data.json', file: 'data.json',
@ -12,10 +10,16 @@ var gun = Gun({
} }
}); });
var server = http.createServer(function(req, res){ var server = require('http').createServer(function(req, res){
gun.server(req, res); if(gun.server(req, res)){
return; // filters gun requests!
}
require('fs').createReadStream(require('path').join(__dirname, req.url)).on('error',function(){ // static files!
res.writeHead(200, {'Content-Type': 'text/html'});
res.end(require('fs').readFileSync(require('path').join(__dirname, 'index.html'))); // or default to index
}).pipe(res); // stream
}); });
gun.attach(server); gun.attach(server);
server.listen(port); server.listen(port);
console.log('Server started on port ' + port + ' with /gun'); console.log('Server started on port ' + port + ' with /gun');

View File

@ -1,6 +1,6 @@
<html> <html>
<body> <body>
<h1>Examples Directory</h1> <h1>Examples Directory <button style="float: right;" onclick="localStorage.clear()">Clear Local Storage</button></h1>
<style> <style>
iframe { iframe {
width: 100%; width: 100%;
@ -9,8 +9,9 @@
border-top: ridge 2em skyblue; border-top: ridge 2em skyblue;
} }
</style> </style>
<a href="todo/index.html"><iframe src="todo/index.html"></iframe></a> <a href="/todo/index.html"><iframe src="/todo/index.html"></iframe></a>
<a href="angular/index.html"><iframe src="angular/index.html"></iframe></a> <a href="/json/index.html"><iframe src="/json/index.html"></iframe></a>
<script src="../../gun.js"></script> <a href="/chat/index.html"><iframe src="/chat/index.html"></iframe></a>
<script src="../gun.js"></script>
</body> </body>
</html> </html>

75
examples/json/index.html Normal file
View File

@ -0,0 +1,75 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="../../gun.js"></script>
</head>
<body>
<h3>Admin JSON Editor</h3>
This is a live view of your data, you can edit it in realtime or add new field/values.
<ul id="data">
</ul>
<li id="model">
<b>field</b>:
<span contenteditable="true">val</span>
</li>
<ul><li>
<form id="form" onsubmit="add()">
<label>
<input id="field" placeholder="field">
<button type="submit">add</button>
</label>
</form>
</li></ul>
<script>
var ref = Gun(location.origin + '/gun').get('example/json/data');
ref.not(function(){
return this.put({hello: "world!"}).key('example/json/data');
}).on(function(data){
for(var field in data){
if(field === '_'){ continue } // skip meta data!
var val = (data[field = field || ''] || '').toString(), id = field.replace(/[^A-z]/ig, ''), elem; // make data safe.
(elem = $('#' + id) || $('#data').appendChild($('#model').cloneNode(true))).id = id; // reuse or make element, set id.
$('b', elem).innerHTML = field.replace(/\</ig, '&lt;'); // escape and display field
$('span', elem).innerHTML = val.replace(/\</ig, '&lt;'); // escape and display value
}
});
var $ = function(s, e){ return (e || document).querySelector(s) } // make native look like jQuery.
document.onkeyup = function(e){
if(!e || !e.target){ return } // ignore if no element!
if(!e.target.attributes.contenteditable){ return } // ignore if element content isn't editable!
ref.path((e.target.previousElementSibling.innerHTML||'').toString().replace(/\</ig, '&lt;')) // grab the label, which is in the previous element.
.put( (e.target.innerHTML||'').toString().replace(/\</ig, '&lt;') ); // insert the value of the text in the current element.
}
$('#form').onsubmit = function add(e){
return ref.path($('#field').value || '').put("value"), false; // add a new field, and cancel the form submit.
}
</script>
<style>
html, body {
font-family: Verdana, Geneva, sans-serif;
}
a, button {
border: none;
color: skyblue;
background: transparent;
text-decoration: none;
cursor: poiner;
}
ul, li {
list-style-type: none;
}
ul:hover, li:hover {
list-style-type: inherit;
}
input {
border: none;
border-bottom: dashed 1px gainsboro;
}
.none, #model {
display: none;
}
</style>
</body>
</html>

View File

@ -1,6 +0,0 @@
<html>
<body>
<script src="../../gun.js"></script>
<script src="../../lib/list.js"></script>
</body>
</html>

View File

@ -8,7 +8,7 @@
} }
, "dependencies": { , "dependencies": {
"express": "~>4.9.0", "express": "~>4.9.0",
"gun": "0.1.5" "gun": "file:../"
} }
, "scripts": { , "scripts": {
"start": "node express.js", "start": "node express.js",

View File

@ -1,135 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h1>Social Network</h1>
<form name="sign">
<input name="email" placeholder="email">
<input name="password" type="password" placeholder="password">
<input type="submit" name="it" value="sign in or up">
</form>
<script src="../../deps/jquery.js"></script>
<script src="../../../as/as.js"></script>
<script src="../../gun.js"></script>
<script>
$(function(){
$.as($.as('sign'));
var social = {};
social.server = 'http://localhost:8888/';
gun = Gun(social.server + 'gun');
$.as('sign.email').on('keyup', function(e){
e.val = $(this).val();
if(!e || !e.val || $(this).data('val') == e.val){ return }
$(this).data('val', e.val);
$.post(social.server + 'sign', JSON.stringify({email: e.val}), function(reply){
console.log(reply);
if(!reply){ return }
$.as('sign.it').val(reply.err || reply.ok);
},'json');
});
$('form').on('submit', function(e){
e.preventDefault(); e.stopPropagation();
var auth = {
email: $.as('sign.email').val()
,password: $.as('sign.password').val()
}
if(!auth.email){
return $.as('sign.email').val("No email!");
}
if(!auth.password){
return $.as('sign.password').val("No password!");
}
$.post(social.server + 'sign', JSON.stringify(auth), function(reply){
if(!reply){ return }
$.as('sign.it').val(reply.err || reply.ok);
},'json');
return false;
});
});
</script>
<script>
var load = function(b,c){
var d = document,
j = "script",
s = d.createElement(j)
;
var e = 2166136261,
g = b.length,
h = c,
i = /=\?/,
w = window.setTimeout,
x,
y,
a = function(z){
document.body && (z=z||x) && s && document.body[z]? document.body[y=z](s) : w(a,0);
};
if(i.test(b)){
for(;g--;) e = e * 16777619 ^ b.charCodeAt(g);
window[j+=e<0?-e:e] = function(){
h.apply(h,arguments);
delete window[j]};
b = b.replace(i,"="+j);
c = 0
};
s.onload = s.onreadystatechange = function(){
if(y&&/de|m/.test(s.readyState||"m")){
c&&c();a(x='removeChild');try{for(c in s)delete s[c]}catch(b){}
}
};
s.src = b; c && a(x='appendChild');
}
</script>
<script>
function send(url, data){
var $ = function(t){ return document.createElement(t) },
frame = $('iframe'),
form = $('form'),
input = $("input");
form.method = "POST";
form.target = 'cb';
form.enctype = 'multipart/form-data';
form.action = url;
form.style = "display:none";
form.style.display = "none";
input.name = "d";
input.value = JSON.stringify(data);
input.type = "hidden";
form.appendChild(input);
frame.name = 'cb';
frame.src = 'javascript:false;';
frame.onload = function(){
frame.onload = function(){
console.log(frame);
return;
var doc = this.contentWindow ? this.contentWindow.document :(this.contentDocument ? this.contentDocument : this.document),
root = doc.documentElement ? doc.documentElement : doc.body,
textarea = root.getElementsByTagName("textarea")[0],
type = textarea && textarea.getAttribute("data-type") || null,
status = textarea && textarea.getAttribute("data-status") || 200,
statusText = textarea && textarea.getAttribute("data-statusText") || "OK",
content = {
html: root.innerHTML,
text: type ?
textarea.value :
root ? (root.textContent || root.innerText) : null
};
console.log(doc, root, textarea, type, status, statusText, content);
return;
cleanUp();
completeCallback(status, statusText, content, type ?("Content-Type: " + type) : null);
}
form.submit();
}
form.appendChild(frame);
document.body.appendChild(form);
}
</script>
</body>
</html>

View File

@ -1,58 +0,0 @@
var fs = require('fs');
var http = require('http');
var qs = require('querystring');
var Gun = require('gun');
var gun = Gun({
peers: 'http://localhost:8888/gun'
,s3: require('../../test/shotgun') // replace this with your own keys!
});
http.route = function(url){
console.log(url);
var path = __dirname + url;
if(!url){ return http.route }
if(gun.server.regex.test(url)){
return gun;
}
if(fs.existsSync(path)){
return ((path = require(path)) && path.server)? path : http.route;
} else
if(url.slice(-3) !== '.js'){
return http.route(url + '.js');
}
return http.route;
}
http.route.server = function(req, res){
console.log("/ no route found");
}
http.createServer(function(req, res){
console.log(req.headers);
console.log(req.method, req.url);
var body = {};
body.length = 0;
body.data = new require('buffer').Buffer('');
req.on('data', function(buffer){
if(body.data.length >= body.length + buffer.length){
buffer.copy(body.data, body.length);
} else {
body.data = Buffer.concat([body.data, buffer]);
}
body.length += buffer.length;
});
req.on('end', function(x){
body.text = body.data.toString('utf8');
try{body.json = JSON.parse(body.text);
}catch(e){}
delete body.data;
req.body = body.json || body.text;
http.route(req.url).server(req, res);
});
res.on('data', function(data){
res.write(JSON.stringify(data) + '\n');
});
res.on('end', function(data){
res.end(JSON.stringify(data));
});
}).listen(8888);
console.log("listening");
//process.on("uncaughtException", function(e){console.log('!!!!!!!!!!!!!!!!!!!!!!');console.log(e);console.log('!!!!!!!!!!!!!!!!!!!!!!')});

View File

@ -1,77 +0,0 @@
var sign = {};
var Gun = require('gun');
var gun = Gun({
peers: 'http://localhost:8888/gun'
,s3: require('../../test/shotgun') // replace this with your own keys!
});
sign.user = {}
sign.user.create = function(form, cb, shell){
sign.crypto(form, function(err, user){
if(err || !user){ return cb(err) }
user = {key: user.key, salt: user.salt};
user.account = {email: form.email, registered: new Date().getTime()};
gun.set(user).key('email/' + user.account.email);
cb(null, user);
});
};
sign.server = function(req, res){
console.log("sign.server", req.headers, req.body);
if(!req.body || !req.body.email){ return res.emit('end', {err: "That email does not exist."}) }
var user = gun.load('email/' + req.body.email, function(data){ // this callback is called the magazine, since it holds the clip
console.log("data from key", data);
if(!req.body.password){
return res.emit('end', {ok: 'sign in'});
}
crypto({password: req.body.password, salt: data.salt }, function(error, valid){
if(error){ return res.emit('end', {err: "Something went wrong! Try again."}) }
if(data.key === valid.key){ // authorized
return res.emit('end', {ok: 'Signed in!'});
} else { // unauthorized
return res.emit('end', {err: "Wrong password."});
}
});
}).blank(function(){
if(!req.body.password){
return res.emit('end', {ok: 'sign up'});
}
return sign.user.create(req.body, function(err, user){
if(err || !user){ return res.emit('end', {err: "Something went wrong, please try again."}) }
console.log('yay we made the user', user);
res.emit('end', {err: "Registered!"});
}, user);
});
}
;var crypto = function(context, callback, option){
option = option || {};
option.hash = option.hash || 'sha1';
option.strength = option.strength || 10000;
option.crypto = option.crypto || require('crypto');
if(!context.password){
option.crypto.randomBytes(8,function(error, buffer){
if(error){ return callback(error) }
context.pass = buffer.toString('base64');
crypto(context, callback);
}); return callback;
}
if(!context.salt){
option.crypto.randomBytes(64, function(error, buffer){
if(error){ return callback(error) }
context.salt = buffer.toString('base64');
crypto(context, callback);
});
return callback;
}
option.crypto.pbkdf2(context.password, context.salt, option.strength, context.salt.length, function(error, buffer){
if(!buffer || error){ return callback(error) }
delete context.password;
context.key = buffer.toString('base64');
callback(null, context);
});
return callback;
};
sign.crypto = crypto;
module.exports = sign;

View File

@ -10,24 +10,24 @@
// by Forrest Tait! Edited by Mark Nadal. // by Forrest Tait! Edited by Mark Nadal.
function ready(){ function ready(){
var $ = document.querySelector.bind(document); var $ = document.querySelector.bind(document);
var gun = Gun(location.origin + '/gun').load('example/todo/data').set({}); var gun = Gun(location.origin + '/gun').get('example/todo/data');
gun.on(function renderToDo(val){ gun.on(function renderToDo(val){
var todoHTML = ''; var todoHTML = '';
for(key in val) { for(field in val) {
if(!val[key] || key == '_') continue; if(!val[field] || field == '_') continue;
todoHTML += '<li style="width:400px;height:2em;">' + (val[key]||'').toString().replace(/\</ig, '&lt;') + todoHTML += '<li style="width:400px;height:2em;">'
'<button style="float:right;" onclick=removeToDo("'+key+'")>X</button></li>'; + (val[field]||'').toString().replace(/\</ig, '&lt;')
+ '<button style="float:right;" onclick=removeToDo("'+field+'")>X</button></li>';
} }
$("#todos").innerHTML = todoHTML; $("#todos").innerHTML = todoHTML;
}); });
$("#addToDo").onsubmit = function(){ $("#addToDo").onsubmit = function(){
var id = randomId(); gun.set(($("#todoItem").value||'').toString().replace(/\</ig, '&lt;'));
gun.path(id).set(($("#todoItem").value||'').toString().replace(/\</ig, '&lt;'));
$("#todoItem").value = ""; $("#todoItem").value = "";
return false; return false;
}; };
window.removeToDo = function(id){ window.removeToDo = function(id){
gun.path(id).set(null); gun.path(id).put(null);
} }
} }
function randomId(){ function randomId(){
@ -35,4 +35,4 @@
} }
</script> </script>
</body> </body>
</html> </html>

1464
gun.js

File diff suppressed because it is too large Load Diff

View File

@ -380,6 +380,11 @@ html, body {
</div> <!-- END FOLD --> </div> <!-- END FOLD -->
<a href="https://github.com/amark/gun" target="_blank"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://camo.githubusercontent.com/365986a132ccd6a44c23a9169022c0b5c890c387/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f7265645f6161303030302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png"></a> <a href="https://github.com/amark/gun" target="_blank"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://camo.githubusercontent.com/365986a132ccd6a44c23a9169022c0b5c890c387/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f7265645f6161303030302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png"></a>
<script type="text/javascript">
window.heap=window.heap||[],heap.load=function(t,e){window.heap.appid=t,window.heap.config=e;var a=document.createElement("script");a.type="text/javascript",a.async=!0,a.src=("https:"===document.location.protocol?"https:":"http:")+"//cdn.heapanalytics.com/js/heap-"+t+".js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(a,n);for(var o=function(t){return function(){heap.push([t].concat(Array.prototype.slice.call(arguments,0)))}},p=["clearEventProperties","identify","setEventProperties","track","unsetEventProperty"],c=0;c<p.length;c++)heap[p[c]]=o(p[c])};
heap.load("2174172773");
</script>
<!-- BEGIN TUTORIAL --> <!-- BEGIN TUTORIAL -->
<div class="tutorial"> <div class="tutorial">
<script src="https://gunjs.herokuapp.com/gun.js"></script> <script src="https://gunjs.herokuapp.com/gun.js"></script>

View File

@ -6,7 +6,6 @@
} }
var s = this; var s = this;
s.on = a.on.create(); s.on = a.on.create();
s.mime = require('mime');
s.AWS = require('aws-sdk'); s.AWS = require('aws-sdk');
s.config = {}; s.config = {};
opt = opt || {}; opt = opt || {};
@ -31,14 +30,14 @@
}; };
s3.id = function(m){ return m.Bucket +'/'+ m.Key } s3.id = function(m){ return m.Bucket +'/'+ m.Key }
s3.chain = s3.prototype; s3.chain = s3.prototype;
s3.chain.put = function(key, o, cb, m){ s3.chain.PUT = function(key, o, cb, m){
if(!key){ return } if(!key){ return }
m = m || {} m = m || {}
m.Bucket = m.Bucket || this.config.bucket; m.Bucket = m.Bucket || this.config.bucket;
m.Key = m.Key || key; m.Key = m.Key || key;
if(a.obj.is(o) || a.list.is(o)){ if(a.obj.is(o) || a.list.is(o)){
m.Body = a.text.ify(o); m.Body = a.text.ify(o);
m.ContentType = this.mime.lookup('json') m.ContentType = 'application/json';
} else { } else {
m.Body = a.text.is(o)? o : a.text.ify(o); m.Body = a.text.is(o)? o : a.text.ify(o);
} }
@ -49,7 +48,7 @@
}); });
return this; return this;
} }
s3.chain.get = function(key, cb, o){ s3.chain.GET = function(key, cb, o){
if(!key){ return } if(!key){ return }
var s = this var s = this
, m = { , m = {
@ -74,7 +73,7 @@
if(e || !r){ return s.on(id).emit(e) } if(e || !r){ return s.on(id).emit(e) }
r.Text = r.text = t = (r.Body||r.body||'').toString('utf8'); r.Text = r.text = t = (r.Body||r.body||'').toString('utf8');
r.Type = r.type = r.ContentType || (r.headers||{})['content-type']; r.Type = r.type = r.ContentType || (r.headers||{})['content-type'];
if(r.type && 'json' === s.mime.extension(r.type)){ if(r.type && 'application/json' === r.type){
d = a.obj.ify(t); d = a.obj.ify(t);
} }
m = r.Metadata; m = r.Metadata;
@ -119,7 +118,7 @@
} }
this.S3().listObjects(m, function(e,r){ this.S3().listObjects(m, function(e,r){
//a.log('list',e); //a.log('list',e);
a.list.each((r||{}).Contents, function(v){console.log(v)}); a.list.map((r||{}).Contents, function(v){console.log(v)});
//a.log('---end list---'); //a.log('---end list---');
if(!a.fns.is(cb)) return; if(!a.fns.is(cb)) return;
cb(e,r); cb(e,r);

View File

@ -2,39 +2,87 @@
// modified by Mark to be part of core for convenience // modified by Mark to be part of core for convenience
// twas not designed for production use // twas not designed for production use
// only simple local development. // only simple local development.
var Gun = require('../gun'),
file = {};
var Gun = require('../gun'), file = {}; Gun.on('opt').event(function(gun, opts) {
if ((opts.file === false) || (opts.s3 && opts.s3.key)) {
Gun.on('opt').event(function(gun, opts){ return; // don't use this plugin if S3 is being used.
if(opts.s3 && opts.s3.key){ return } // don't use this plugin if S3 is being used. }
console.log("WARNING! This `file.js` module for gun is intended only for local development testing!")
opts.file = opts.file || 'data.json'; opts.file = opts.file || 'data.json';
var fs = require('fs'); var fs = require('fs');
file.raw = file.raw || (fs.existsSync||require('path').existsSync)(opts.file)? fs.readFileSync(opts.file).toString() : null; file.raw = file.raw || (fs.existsSync || require('path').existsSync)(opts.file) ? fs.readFileSync(opts.file).toString() : null;
var all = file.all = file.all || Gun.obj.ify(file.raw || {nodes: {}, keys: {}}); var all = file.all = file.all || Gun.obj.ify(file.raw || {
nodes: {},
keys: {}
});
all.keys = all.keys || {};
all.nodes = all.nodes || {};
gun.opt({hooks: { gun.opt({hooks: {
load: function(key, cb, options){ get: function get(key, cb, o){
if(Gun.obj.is(key) && key[Gun._.soul]){ var graph, soul;
return cb(null, all.nodes[key[Gun._.soul]]); if(soul = Gun.is.soul(key)){
} if(all.nodes[soul]){
cb(null, all.nodes[all.keys[key]]); (graph = {})[soul] = all.nodes[soul];
} cb(null, graph);
,set: function(graph, cb){ (graph = {})[soul] = Gun.union.pseudo(soul);
all.nodes = gun.__.graph; cb(null, graph); // end.
/*for(n in all.nodes){ // this causes some divergence problems, so removed for now till later when it can be fixed. }
for(k in all.nodes[n]){ return;
if(all.nodes[n][k] === null){ }
delete all.nodes[n][k]; Gun.obj.map(all.keys[key], function(rel){
if(Gun.is.soul(rel)){ get(soul = rel, cb, o) }
});
return soul? cb(null, {}) : cb(null, null);
},
put: function(graph, cb, o){
all.nodes = gun.__.graph;
fs.writeFile(opts.file, Gun.text.ify(all), cb);
},
key: function(key, soul, cb, o){
var meta = {};
meta[Gun._.soul] = soul = Gun.is.soul(soul) || soul;
((all.keys = all.keys || {})[key] = all.keys[key] || {})[soul] = meta;
fs.writeFile(opts.file, Gun.text.ify(all), cb);
},
all: function(list, opt, cb) {
opt = opt || {};
opt.from = opt.from || '';
opt.start = opt.from + (opt.start || '');
if(opt.end){ opt.end = opt.from + opt.end }
var match = {};
cb = cb || function(){};
Gun.obj.map(list, function(soul, key){
var end = opt.end || key;
if(key.indexOf(opt.from) === 0 && opt.start <= key && (key <= end || key.indexOf(end) === 0)){
if(opt.upto){
if(key.slice(opt.from.length).indexOf(opt.upto) === -1){
yes(soul, key);
}
} else {
yes(soul, key);
} }
} }
}*/ });
fs.writeFile(opts.file, Gun.text.ify(all), cb); function yes(soul, key){
} cb(key);
,key: function(key, soul, cb){ match[key] = {};
all.keys[key] = soul; match[key][Gun._.soul] = soul;
fs.writeFile(opts.file, Gun.text.ify(all), cb); }
} return match;
}
}}, true); }}, true);
gun.all = gun.all || function(url, cb) {
}); url = require('url').parse(url, true);
var r = gun.__.opt.hooks.all(all.keys, {
from: url.pathname,
upto: url.query['*'],
start: url.query['*>'],
end: url.query['*<']
});
console.log("All please", url.pathname, url.query['*'], r);
cb = cb || function() {};
cb(null, r);
}
});

View File

@ -1,18 +0,0 @@
var Gun = Gun || require('../gun');
Gun.chain.group = function(obj, cb, opt){
var gun = this;
opt = opt || {};
cb = cb || function(){};
gun = gun.set({}); // insert assumes a graph node. So either create it or merge with the existing one.
var error, item = gun.chain().set(obj, function(err){ // create the new item in its own context.
error = err; // if this happens, it should get called before the .get
}).get(function(val){
if(error){ return cb.call(gun, error) } // which in case it is, allows us to fail fast.
var add = {}, soul = Gun.is.soul.on(val);
if(!soul){ return cb.call(gun, {err: Gun.log("No soul!")}) }
add[soul] = val; // other wise, let's then
gun.set(add, cb); // merge with the graph node.
});
return gun;
};

View File

@ -21,7 +21,7 @@ module.exports = function(req, res, next){
res.statusCode = reply.statusCode || reply.status; res.statusCode = reply.statusCode || reply.status;
} }
if(reply.headers){ if(reply.headers){
if(!res._headerSent){ if(!(res.headersSent || res.headerSent || res._headerSent || res._headersSent)){
Gun.obj.map(reply.headers, function(val, field){ Gun.obj.map(reply.headers, function(val, field){
res.setHeader(field, val); res.setHeader(field, val);
}); });
@ -47,4 +47,4 @@ module.exports = function(req, res, next){
post(null, body); post(null, body);
}); });
form.parse(req); form.parse(req);
} }

View File

@ -3,20 +3,20 @@ var Gun = Gun || require('../gun');
Gun.chain.list = function(cb, opt){ Gun.chain.list = function(cb, opt){
opt = opt || {}; opt = opt || {};
cb = cb || function(){}; cb = cb || function(){};
var gun = this.set({}); // insert assumes a graph node. So either create it or merge with the existing one. var gun = this.put({}); // insert assumes a graph node. So either create it or merge with the existing one.
gun.last = function(obj, cb){ gun.last = function(obj, cb){
var last = gun.path('last'); var last = gun.path('last');
if(!arguments.length){ return last } if(!arguments.length){ return last }
return gun.path('last').set(null).set(obj).get(function(val){ // warning! these are not transactional! They could be. return gun.path('last').put(null).put(obj).val(function(val){ // warning! these are not transactional! They could be.
console.log("last is", val); console.log("last is", val);
last.path('next').set(this._.node, cb); last.path('next').put(this._.node, cb);
}); });
} }
gun.first = function(obj, cb){ gun.first = function(obj, cb){
var first = gun.path('first'); var first = gun.path('first');
if(!arguments.length){ return first } if(!arguments.length){ return first }
return gun.path('first').set(null).set(obj).get(function(){ // warning! these are not transactional! They could be. return gun.path('first').put(null).put(obj).val(function(){ // warning! these are not transactional! They could be.
first.path('prev').set(this._.node, cb); first.path('prev').put(this._.node, cb);
}); });
} }
return gun; return gun;
@ -29,24 +29,24 @@ Gun.chain.list = function(cb, opt){
Gun.log.verbose = true; Gun.log.verbose = true;
var list = gun.list(); var list = gun.list();
list.last({name: "Mark Nadal", type: "human", age: 23}).get(function(val){ list.last({name: "Mark Nadal", type: "human", age: 23}).val(function(val){
//console.log("oh yes?", val, '\n', this.__.graph); //console.log("oh yes?", val, '\n', this.__.graph);
}); });
list.last({name: "Amber Cazzell", type: "human", age: 23}).get(function(val){ list.last({name: "Amber Cazzell", type: "human", age: 23}).val(function(val){
//console.log("oh yes?", val, '\n', this.__.graph); //console.log("oh yes?", val, '\n', this.__.graph);
}); });
list.list().last({name: "Hobbes", type: "kitten", age: 4}).get(function(val){ list.list().last({name: "Hobbes", type: "kitten", age: 4}).val(function(val){
//console.log("oh yes?", val, '\n', this.__.graph); //console.log("oh yes?", val, '\n', this.__.graph);
}); });
list.list().last({name: "Skid", type: "kitten", age: 2}).get(function(val){ list.list().last({name: "Skid", type: "kitten", age: 2}).val(function(val){
//console.log("oh yes?", val, '\n', this.__.graph); //console.log("oh yes?", val, '\n', this.__.graph);
}); });
setTimeout(function(){ list.get(function(val){ setTimeout(function(){ list.val(function(val){
console.log("the list!", list.__.graph); console.log("the list!", list.__.graph);
return; return;
list.path('first').get(Gun.log) list.path('first').val(Gun.log)
.path('next').get(Gun.log) .path('next').val(Gun.log)
.path('next').get(Gun.log); .path('next').val(Gun.log);
})}, 1000); })}, 1000);
return; return;

104
lib/radix.js Normal file
View File

@ -0,0 +1,104 @@
function radix(r){
r = r || {};
var u, n = null, c = 0;
function get(p){
var v = match(p, r);
return v;
}
function match(path, tree, v){
if(!Gun.obj.map(tree, function(val, key){
if(key[0] !== path[0]){ return }
var i = 1;
while(key[i] === path[i] && path[i]){ i++ }
if(key = key.slice(i)){ // recurse
console.log("match", key, i)
v = {sub: tree, pre: path.slice(0, i), val: val, post: key, path: path.slice(i) };
} else { // replace
console.log("matching", path, key, i);
v = match(path.slice(i), val);
}
return true;
})){ console.log("matched", tree, path); v = {sub: tree, path: path} } // insert
return v;
}
function rebalance(ctx, val){
console.log("rebalance", ctx, val);
if(!ctx.post){ return ctx.sub[ctx.path] = val }
ctx.sub[ctx.pre] = ctx.sub[ctx.pre] || (ctx.post? {} : ctx.val || {});
ctx.sub[ctx.pre][ctx.path] = val;
if(ctx.post){
ctx.sub[ctx.pre][ctx.post] = ctx.val;
delete ctx.sub[ctx.pre + ctx.post];
}
}
function set(p, val){
rebalance(match(p, r), val);
console.log('-------------------------');
return r;
}
return function(p, val){
return (1 < arguments.length)? set(p, val) : get(p || '');
}
} // IT WORKS!!!!!!
var rad = radix({});
rad('user/marknadal', {'#': 'asdf'});
rad('user/ambercazzell', {'#': 'dafs'});
rad('user/taitforrest', {'#': 'sadf'});
rad('user/taitveronika', {'#': 'fdsa'});
rad('user/marknadal', {'#': 'foo'});
/*
function radix(r){
var u, n = null, c = 0;
r = r || {};
function get(){
}
function match(p, l, cb){
cb = cb || function(){};
console.log("LETS DO THIS", p, l);
if(!Gun.obj.map(l, function(v, k){
if(k[0] === p[0]){
var i = 1;
while(k[i] === p[i] && p[i]){ i++ }
k = k.slice(i);
if(k){
cb(p.slice(0, i), v, k, l, p.slice(i));
} else {
match(p.slice(i), v, cb);
}
return 1;
}
})){ cb(p, l, null, l) }
}
function set(p, val){
match(p, r, function(pre, data, f, rr, pp){
if(f === null){
rr[pre] = val;
return;
}
console.log("Match?", c, pre, data, f);
rr[pre] = r[pre] || (f? {} : data || {});
rr[pre][pp] = val;
if(f){
rr[pre][f] = data;
delete rr[pre + f];
}
});
return r;
}
return function(p, val){
return (1 < arguments.length)? set(p, val) : get(p || '');
}
} // IT WORKS!!!!!!
var rad = radix({});
rad('user/marknadal', {'#': 'asdf'});
//rad('user/ambercazzell', {'#': 'dafs'});
//rad('user/taitforrest', {'#': 'sadf'});
//rad('user/taitveronika', {'#': 'fdsa'});
*/

View File

@ -12,28 +12,58 @@
gun.__.opt.batch = opt.batch || gun.__.opt.batch || 10; gun.__.opt.batch = opt.batch || gun.__.opt.batch || 10;
gun.__.opt.throttle = opt.throttle || gun.__.opt.throttle || 15; gun.__.opt.throttle = opt.throttle || gun.__.opt.throttle || 15;
gun.__.opt.disconnect = opt.disconnect || gun.__.opt.disconnect || 5; gun.__.opt.disconnect = opt.disconnect || gun.__.opt.disconnect || 5;
s3.load = s3.load || function(key, cb, opt){ s3.get = s3.get || function(key, cb, opt){
if(!key){ return } if(!key){ return }
cb = cb || function(){}; cb = cb || function(){};
opt = opt || {}; (opt = opt || {}).ctx = opt.ctx || {};
opt.ctx.load = opt.ctx.load || {};
if(key[Gun._.soul]){ if(key[Gun._.soul]){
key = s3.prefix + s3.prenode + key[Gun._.soul]; key = s3.prefix + s3.prenode + Gun.is.soul(key);
} else { } else {
key = s3.prefix + s3.prekey + key; key = s3.prefix + s3.prekey + key;
} }
s3.get(key, function(err, data, text, meta){ s3.GET(key, function(err, data, text, meta){
Gun.log('via s3', key, err); Gun.log('via s3', key, err);
if(meta && meta[Gun._.soul]){
return s3.load(meta, cb); // SUPER SUPER IMPORTANT TODO!!!! Make this load via GUN in case soul is already cached!
// HUGE HUGE HUGE performance gains could come from the above line being updated! (not that this module is performant).
}
if(err && err.statusCode == 404){ if(err && err.statusCode == 404){
err = null; // we want a difference between 'unfound' (data is null) and 'error' (auth is wrong). err = null; // we want a difference between 'unfound' (data is null) and 'error' (auth is wrong).
} }
cb(err, data); // TODO: optimize KEY command to not write data if there is only one soul (which is common).
if(meta && (meta.key || meta[Gun._.soul])){
if(err){ return cb(err) }
if(meta.key && Gun.obj.is(data) && !Gun.is.node(data)){
return Gun.obj.map(data, function(rel, soul){
if(!(soul = Gun.is.soul(rel))){ return }
opt.ctx.load[soul] = false;
s3.get(rel, cb, {next: 's3', ctx: opt.ctx}); // TODO: way faster if you use cache.
});
}
if(meta[Gun._.soul]){
return s3.get(meta, cb); // TODO: way faster if you use cache.
}
return cb({err: Gun.log('Cannot determine S3 key data!')});
}
if(data){
meta.soul = Gun.is.soul.on(data);
if(!meta.soul){
err = {err: Gun.log('No soul on node S3 data!')};
}
} else {
return cb(err, null);
}
if(err){ return cb(err) }
opt.ctx.load[meta.soul] = true;
var graph = {};
graph[meta.soul] = data;
cb(null, graph);
(graph = {})[meta.soul] = Gun.union.pseudo(meta.soul);
cb(null, graph);
if(Gun.obj.map(opt.ctx.load, function(loaded, soul){
if(!loaded){ return true }
})){ return } // return IF we have nodes still loading.
cb(null, {});
}); });
} }
s3.set = s3.set || function(nodes, cb){ s3.put = s3.put || function(nodes, cb){
s3.batching += 1; s3.batching += 1;
cb = cb || function(){}; cb = cb || function(){};
cb.count = 0; cb.count = 0;
@ -44,7 +74,7 @@
Gun.obj.map(nodes, function(node, soul){ Gun.obj.map(nodes, function(node, soul){
cb.count += 1; cb.count += 1;
batch[soul] = (batch[soul] || 0) + 1; batch[soul] = (batch[soul] || 0) + 1;
//Gun.log("set listener for", next + ':' + soul, batch[soul], cb.count); //Gun.log("put listener for", next + ':' + soul, batch[soul], cb.count);
s3.on(next + ':' + soul).event(function(){ s3.on(next + ':' + soul).event(function(){
cb.count -= 1; cb.count -= 1;
//Gun.log("transaction", cb.count); //Gun.log("transaction", cb.count);
@ -55,14 +85,14 @@
}); });
}); });
if(gun.__.opt.batch < s3.batching){ if(gun.__.opt.batch < s3.batching){
return s3.set.now(); return s3.put.now();
} }
if(!gun.__.opt.throttle){ if(!gun.__.opt.throttle){
return s3.set.now(); return s3.put.now();
} }
s3.wait = s3.wait || setTimeout(s3.set.now, gun.__.opt.throttle * 1000); // in seconds s3.wait = s3.wait || setTimeout(s3.put.now, gun.__.opt.throttle * 1000); // in seconds
} }
s3.set.now = s3.set.now || function(){ s3.put.now = s3.put.now || function(){
clearTimeout(s3.wait); clearTimeout(s3.wait);
s3.batching = 0; s3.batching = 0;
s3.wait = null; s3.wait = null;
@ -71,7 +101,7 @@
s3.next = Gun.time.is(); s3.next = Gun.time.is();
Gun.obj.map(batch, function put(exists, soul){ Gun.obj.map(batch, function put(exists, soul){
var node = gun.__.graph[soul]; // the batch does not actually have the nodes, but what happens when we do cold data? Could this be gone? var node = gun.__.graph[soul]; // the batch does not actually have the nodes, but what happens when we do cold data? Could this be gone?
s3.put(s3.prefix + s3.prenode + soul, node, function(err, reply){ s3.PUT(s3.prefix + s3.prenode + soul, node, function(err, reply){
Gun.log("s3 put reply", soul, err, reply); Gun.log("s3 put reply", soul, err, reply);
if(err || !reply){ if(err || !reply){
put(exists, soul); // naive implementation of retry TODO: BUG: need backoff and anti-infinite-loop! put(exists, soul); // naive implementation of retry TODO: BUG: need backoff and anti-infinite-loop!
@ -90,25 +120,31 @@
s3.wait = s3.wait || null; s3.wait = s3.wait || null;
s3.key = s3.key || function(key, soul, cb){ s3.key = s3.key || function(key, soul, cb){
var meta = {}; if(!key){
meta[Gun._.soul] = soul = Gun.text.is(soul)? soul : (soul||{})[Gun._.soul]; return cb({err: "No key!"});
}
if(!soul){ if(!soul){
return cb({err: "No soul!"}); return cb({err: "No soul!"});
} }
s3.put(s3.prefix + s3.prekey + key, '', function(err, reply){ // key is 2 bytes??? Should be smaller. Wait HUH? What did I mean by this? var path = s3.prefix + s3.prekey + key, meta = {key: '0.2'}, rel = {};
Gun.log("s3 put reply", soul, err, reply); meta[Gun._.soul] = rel[Gun._.soul] = soul = Gun.is.soul(soul) || soul;
if(err || !reply){ s3.GET(path, function(err, data, text, _){
s3.key(key, soul, cb); // naive implementation of retry TODO: BUG: need backoff and anti-infinite-loop! var souls = data || {};
return; souls[soul] = rel;
} s3.PUT(path, souls, function(err, reply){
cb(); Gun.log("s3 key reply", soul, err, reply);
}, {Metadata: meta}); if(err || !reply){
return s3.key(key, soul, cb); // naive implementation of retry TODO: BUG: need backoff and anti-infinite-loop!
}
cb();
}, {Metadata: meta});
});
} }
opt.hooks = opt.hooks || {}; opt.hooks = opt.hooks || {};
gun.opt({hooks: { gun.opt({hooks: {
load: opt.hooks.load || s3.load get: opt.hooks.get || s3.get
,set: opt.hooks.set || s3.set ,put: opt.hooks.put || s3.put
,key: opt.hooks.key || s3.key ,key: opt.hooks.key || s3.key
}}, true); }}, true);
}); });

35
lib/set.js Normal file
View File

@ -0,0 +1,35 @@
var Gun = Gun || require('../gun');
/*
Gun.chain.set = function(obj, cb, opt){
var set = this;
opt = opt || {};
cb = cb || function(){};
set = set.put({}); // insert assumes a graph node. So either create it or merge with the existing one.
var error, item = set.chain().put(obj, function(err){ // create the new item in its own context.
error = err; // if this happens, it should get called before the .val
}).val(function(val){
if(error){ return cb.call(set, error) } // which in case it is, allows us to fail fast.
var add = {}, soul = Gun.is.soul.on(val);
if(!soul){ return cb.call(set, {err: Gun.log("No soul!")}) }
add[soul] = val; // other wise, let's then
set.put(add, cb); // merge with the graph node.
});
return item;
};*/
Gun.chain.set = function(val, cb, opt){
var gun = this, ctx = {}, drift = Gun.time.now();
cb = cb || function(){};
opt = opt || {};
if(!gun.back){ gun = gun.put({}) }
gun = gun.not(function(next, key){
return key? this.put({}).key(key) : this.put({});
});
if(!val && !Gun.is.value(val)){ return gun }
var obj = {};
obj['I' + drift + 'R' + Gun.text.random(5)] = val;
return gun.put(obj, cb);
}

View File

@ -32,21 +32,21 @@
} }
return gun; return gun;
} }
gun.server = gun.server || function(req, res, next){ gun.server = gun.server || function(req, res, next){ // http
//Gun.log("\n\n GUN SERVER!", req); //Gun.log("\n\n GUN SERVER!", req);
next = next || function(){}; next = next || function(){};
if(!req || !res){ return next() } if(!req || !res){ return next(), false }
if(!req.url){ return next() } if(!req.url){ return next(), false }
if(!req.method){ return next() } if(!req.method){ return next(), false }
var msg = {}; var msg = {};
msg.url = url.parse(req.url, true); msg.url = url.parse(req.url, true);
if(!gun.server.regex.test(msg.url.pathname)){ return next() } if(!gun.server.regex.test(msg.url.pathname)){ return next(), false }
if(msg.url.pathname.replace(gun.server.regex,'').slice(0,3).toLowerCase() === '.js'){ if(msg.url.pathname.replace(gun.server.regex,'').slice(0,3).toLowerCase() === '.js'){
res.writeHead(200, {'Content-Type': 'text/javascript'}); res.writeHead(200, {'Content-Type': 'text/javascript'});
res.end(gun.server.js = gun.server.js || require('fs').readFileSync(__dirname + '/../gun.js')); // gun server is caching the gun library for the client res.end(gun.server.js = gun.server.js || require('fs').readFileSync(__dirname + '/../gun.js')); // gun server is caching the gun library for the client
return; return true;
} }
http(req, res, function(req, res){ return http(req, res, function(req, res){
if(!req){ return next() } if(!req){ return next() }
var tab, cb = res = require('./jsonp')(req, res); var tab, cb = res = require('./jsonp')(req, res);
if(req.headers && (tab = req.headers['gun-sid'])){ if(req.headers && (tab = req.headers['gun-sid'])){
@ -72,7 +72,7 @@
} }
} }
gun.__.opt.hooks.transport(req, cb); gun.__.opt.hooks.transport(req, cb);
}); }), true;
} }
gun.server.on = gun.server.on || Gun.on.create(); gun.server.on = gun.server.on || Gun.on.create();
gun.__.opt.poll = gun.__.opt.poll || opt.poll || 1; gun.__.opt.poll = gun.__.opt.poll || opt.poll || 1;
@ -87,56 +87,94 @@
return req.headers['x-forwarded-for'] || req.connection.remoteAddress || req.socket.remoteAddress || (req.connection.socket || {}).remoteAddress || ''; return req.headers['x-forwarded-for'] || req.connection.remoteAddress || req.socket.remoteAddress || (req.connection.socket || {}).remoteAddress || '';
} */ } */
gun.server.transport = gun.server.transport || (function(){ gun.server.transport = gun.server.transport || (function(){
// all streams, technically PATCH but implemented as POST, are forwarded to other trusted peers // all streams, technically PATCH but implemented as PUT or POST, are forwarded to other trusted peers
// except for the ones that are listed in the message as having already been sending to. // except for the ones that are listed in the message as having already been sending to.
// all states, implemented with GET, are replied to the source that asked for it. // all states, implemented with GET, are replied to the source that asked for it.
function tran(req, cb){ function tran(req, cb){
//Gun.log("gun.server", req); //Gun.log("gun.server", req);
req.method = req.body? 'post' : 'get'; // post or get is based on whether there is a body or not req.method = req.body? 'put' : 'get'; // put or get is based on whether there is a body or not
req.url.key = req.url.pathname.replace(gun.server.regex,'').replace(/^\//i,'') || ''; req.url.key = req.url.pathname.replace(gun.server.regex,'').replace(/^\//i,'') || '';
if('get' == req.method){ return tran.load(req, cb) } if('get' == req.method){ return tran.get(req, cb) }
if('post' == req.method){ return tran.post(req, cb) } if('put' == req.method || 'post' == req.method){ return tran.put(req, cb) }
cb({body: {hello: 'world'}}); cb({body: {hello: 'world'}});
} }
tran.load = function(req, cb){ tran.get = function(req, cb){
var key = req.url.key var key = req.url.key
, reply = {headers: {'Content-Type': tran.json}}; , reply = {headers: {'Content-Type': tran.json}};
//console.log(req);
if(req && req.url && Gun.obj.has(req.url.query, '*')){
return gun.all(req.url.key + req.url.search, function(err, list){
cb({headers: reply.headers, body: (err? (err.err? err : {err: err || "Unknown error."}) : list || null ) })
});
}
if(!key){ if(!key){
if(!Gun.obj.has(req.url.query, Gun._.soul)){ if(!Gun.obj.has(req.url.query, Gun._.soul)){
return cb({headers: reply.headers, body: {err: "No key or soul to load."}}); return cb({headers: reply.headers, body: {err: "No key or soul to get."}});
} }
key = {}; key = {};
key[Gun._.soul] = req.url.query[Gun._.soul]; key[Gun._.soul] = req.url.query[Gun._.soul];
} }
//Gun.log("transport.loading key ->", key, gun.__.graph, gun.__.keys); console.log("tran.get", key);
gun.load(key, function(err, node){ gun.get(key, function(err, graph){
//tran.sub.scribe(req.tab, node._[Gun._.soul]); //tran.sub.scribe(req.tab, graph._[Gun._.soul]);
cb({headers: reply.headers, body: (err? (err.err? err : {err: err || "Unknown error."}) : node || null)}); console.log("tran.get", key, "<---", err, graph);
if(err || !graph){
return cb({headers: reply.headers, body: (err? (err.err? err : {err: err || "Unknown error."}) : null)});
}
if(Gun.obj.empty(graph)){ return cb({headers: reply.headers, body: graph}) } // we're out of stuff!
// TODO: chunk the graph even if it is already chunked. pseudo code below!
/*Gun.is.graph(graph, function(node, soul){
if(Object.keys(node).length > 100){
// split object into many objects that have a fixed size
// iterate over each object
// cb({headers: reply.headers, chunk: {object} );
}
});*/
return cb({headers: reply.headers, chunk: graph }); // keep streaming
}); });
} }
tran.post = function(req, cb){ tran.put = function(req, cb){
// NOTE: It is highly recommended you do your own POSTs through your own API that then saves to gun manually. // NOTE: It is highly recommended you do your own PUT/POSTs through your own API that then saves to gun manually.
// This will give you much more fine-grain control over security, transactions, and what not. // This will give you much more fine-grain control over security, transactions, and what not.
var reply = {headers: {'Content-Type': tran.json}}; var reply = {headers: {'Content-Type': tran.json}};
if(!req.body){ return cb({headers: reply.headers, body: {err: "No body"}}) } if(!req.body){ return cb({headers: reply.headers, body: {err: "No body"}}) }
if(tran.post.key(req, cb)){ return } gun.server.on('network').emit(req);
if(tran.put.key(req, cb)){ return }
// some NEW code that should get revised.
if(Gun.is.node(req.body) || Gun.is.graph(req.body)){
//console.log("tran.put", req.body);
if(req.err = Gun.union(gun, req.body, function(err, ctx){ // TODO: BUG? Probably should give me ctx.graph
if(err){ return cb({headers: reply.headers, body: {err: err || "Union failed."}}) }
var ctx = ctx || {}; ctx.graph = {};
Gun.is.graph(req.body, function(node, soul){
ctx.graph[soul] = gun.__.graph[soul]; // TODO: BUG? Probably should be delta fields
})
gun.__.opt.hooks.put(ctx.graph, function(err, ok){
if(err){ return cb({headers: reply.headers, body: {err: err || "Failed."}}) }
cb({headers: reply.headers, body: {ok: ok || "Persisted."}});
});
}).err){ cb({headers: reply.headers, body: {err: req.err || "Union failed."}}) }
}
return;
//return;
// saving // saving
Gun.obj.map(req.body, function(node, soul){ // iterate over every node Gun.obj.map(req.body, function(node, soul){ // iterate over every node
if(soul != Gun.is.soul.on(node)){ return this.end("No soul!") } if(soul != Gun.is.soul.on(node)){ return this.end("No soul!") }
gun.load(node._, this.add(soul)); // and begin loading it in case it is not cached. gun.get({'#': soul}, this.add(soul)); // and begin getting it in case it is not cached.
}, Gun.fns.sum(function(err){ }, Gun.fns.sum(function(err){
if(err){ return reply.err = err } if(err){ return reply.err = err }
reply.loaded = true; reply.got = true;
})); }));
// could there be a timing error somewhere in here? // could there be a timing error somewhere in here?
var setImmediate = setImmediate || setTimeout; // TODO: BUG: This should be cleaned up, but I want Heroku to work. var setImmediate = setImmediate || setTimeout; // TODO: BUG: This should be cleaned up, but I want Heroku to work.
setImmediate(function(){ // do not do it right away because gun.load above is async, this should be cleaner. setImmediate(function(){ // do not do it right away because gun.get above is async, this should be cleaner.
var context = gun.union(req.body, function check(err, ctx){ // check if the body is valid, and get it into cache immediately. var context = Gun.union(gun, req.body, function check(err, ctx){ // check if the body is valid, and get it into cache immediately.
context = ctx || context; context = ctx || context;
if(err || reply.err || context.err || !context.nodes){ return cb({headers: reply.headers, body: {err: err || reply.err || context.err || "Union failed." }}) } if(err || reply.err || context.err || !context.nodes){ return cb({headers: reply.headers, body: {err: err || reply.err || context.err || "Union failed." }}) }
if(!Gun.fns.is(gun.__.opt.hooks.set)){ return cb({headers: reply.headers, body: {err: "Persistence not supported." }}) } if(!Gun.fns.is(gun.__.opt.hooks.put)){ return cb({headers: reply.headers, body: {err: "Persistence not supported." }}) }
if(!reply.loaded){ return setTimeout(check, 2) } // only persist if all nodes have been loaded into cache. if(!reply.got){ return setTimeout(check, 2) } // only persist if all nodes are in cache.
gun.__.opt.hooks.set(context.nodes, function(err, data){ // since we've already manually done the union, we can now directly call the persistence layer. gun.__.opt.hooks.put(context.nodes, function(err, data){ // since we've already manually done the union, we can now directly call the persistence layer.
if(err){ return cb({headers: reply.headers, body: {err: err || "Persistence failed." }}) } if(err){ return cb({headers: reply.headers, body: {err: err || "Persistence failed." }}) }
cb({headers: reply.headers, body: {ok: "Persisted."}}); // TODO: Fix so we know what the reply is. cb({headers: reply.headers, body: {ok: "Persisted."}}); // TODO: Fix so we know what the reply is.
}); });
@ -144,14 +182,14 @@
}, 0); }, 0);
gun.server.on('network').emit(req); gun.server.on('network').emit(req);
} }
tran.post.key = function(req, cb){ // key hook! tran.put.key = function(req, cb){ // key hook!
if(!req || !req.url || !req.url.key || !Gun.obj.has(req.body, Gun._.soul)){ return } if(!req || !req.url || !req.url.key || !Gun.obj.has(req.body, Gun._.soul)){ return }
var load = {}, index = {}, soul; var index = req.url.key, soul = Gun.is.soul(req.body);
soul = load[Gun._.soul] = index[req.url.key] = req.body[Gun._.soul]; console.log("tran.key", index, req.body);
gun.load(load).key(index, function(err, reply){ gun.key(index, function(err, reply){
if(err){ return cb({headers: {'Content-Type': tran.json}, body: {err: err}}) } if(err){ return cb({headers: {'Content-Type': tran.json}, body: {err: err}}) }
cb({headers: {'Content-Type': tran.json}, body: reply}); // TODO: Fix so we know what the reply is. cb({headers: {'Content-Type': tran.json}, body: reply}); // TODO: Fix so we know what the reply is.
}); }, soul);
return true; return true;
} }
gun.server.on('network').event(function(req){ gun.server.on('network').event(function(req){

View File

@ -1,24 +1,51 @@
{ {
"name": "gun", "name": "gun",
"version": "0.1.5", "version": "0.2.0-alpha-1",
"author": "Mark Nadal", "description": "Graph engine",
"description": "Graph engine.", "main": "index.js",
"engines": { "scripts": {
"node": "~>0.6.6" "start": "node examples/http.js 8080",
}, "test": "mocha"
"dependencies": { },
"mime": "~>1.2.11", "repository": {
"aws-sdk": "~>2.0.0", "type": "git",
"formidable": "~>1.0.15", "url": "git+https://github.com/amark/gun.git"
"ws": "~>0.4.32", },
"request": "~>2.39.0" "keywords": [
}, "graph",
"devDependencies": { "document",
"mocha": "~>1.9.0" "key",
}, "value",
"scripts": { "relational",
"start": "node examples/express.js 8080", "datastore",
"prestart": "npm install ./examples", "database",
"test": "mocha" "engine",
} "realtime",
} "decentralized",
"peer-to-peer",
"P2P",
"OSS",
"distributed",
"embedded",
"localstorage",
"S3"
],
"author": "Mark Nadal",
"license": "(Zlib OR MIT OR Apache-2.0)",
"bugs": {
"url": "https://github.com/amark/gun/issues"
},
"homepage": "https://github.com/amark/gun#readme",
"engines": {
"node": ">=0.6.6",
"iojs": ">=0.0.1"
},
"dependencies": {
"aws-sdk": "~>2.0.0",
"formidable": "~>1.0.15",
"ws": "~>0.4.32"
},
"devDependencies": {
"mocha": "~>1.9.0"
}
}

2
test/abc.js Normal file
View File

@ -0,0 +1,2 @@
var expect = global.expect = require("./expect");
require('./common');

View File

@ -1,4 +1,79 @@
console.log("MAKE SURE TO DELETE `data.json` BEFORE RUNNING TESTS!"); describe('All', function(){
return;
var expect = global.expect = require("./expect"); var expect = global.expect = require("./expect");
require('./common');
var Gun = Gun || require('../gun');
(typeof window === 'undefined') && require('../lib/file');
var gun = Gun({file: 'data.json'});
var keys = {
'emails/aquiva@gmail.com': 'asdf',
'emails/mark@gunDB.io': 'asdf',
'user/marknadal': 'asdf',
'emails/amber@cazzell.com': 'fdsa',
'user/ambernadal': 'fdsa',
'user/forrest': 'abcd',
'emails/banana@gmail.com': 'qwert',
'user/marknadal/messages/asdf': 'rti',
'user/marknadal/messages/fobar': 'yuoi',
'user/marknadal/messages/lol': 'hjkl',
'user/marknadal/messages/nano': 'vbnm',
'user/marknadal/messages/sweet': 'xcvb',
'user/marknadal/posts': 'qvtxz',
'emails/for@rest.com': 'abcd'
};
it('from', function() {
var r = gun.__.opt.hooks.all(keys, {from: 'user/'});
//console.log(r);
expect(r).to.be.eql({
'user/marknadal': { '#': 'asdf' },
'user/ambernadal': { '#': 'fdsa' },
'user/forrest': { '#': 'abcd' },
'user/marknadal/messages/asdf': { '#': 'rti' },
'user/marknadal/messages/fobar': { '#': 'yuoi' },
'user/marknadal/messages/lol': { '#': 'hjkl' },
'user/marknadal/messages/nano': { '#': 'vbnm' },
'user/marknadal/messages/sweet': { '#': 'xcvb' },
'user/marknadal/posts': { '#': 'qvtxz' }
});
});
it('from and upto', function() {
var r = gun.__.opt.hooks.all(keys, {from: 'user/', upto: '/'});
//console.log('upto', r);
expect(r).to.be.eql({
'user/marknadal': { '#': 'asdf' },
'user/ambernadal': { '#': 'fdsa' },
'user/forrest': { '#': 'abcd' }
});
});
it('from and upto and start and end', function() {
var r = gun.__.opt.hooks.all(keys, {from: 'user/', upto: '/', start: "c", end: "f"});
//console.log('upto and start and end', r);
expect(r).to.be.eql({
'user/forrest': { '#': 'abcd' }
});
});
it('map', function(done) { return done();
var users = gun.put({
a: {name: "Mark Nadal"},
b: {name: "Amber Nadal"},
c: {name: "Charlie Chapman"},
d: {name: "Johnny Depp"},
e: {name: "Santa Clause"}
});
//console.log("map:");
users.map().val(function(user){
//console.log("each user:", user);
}).path("ohboy");
return;
users.map(function(){
});
});
});

File diff suppressed because it is too large Load Diff

View File

@ -1,24 +0,0 @@
(function(){ // group test
var Gun = require('../index');
require('../lib/group');
var gun = Gun({file: 'data.json'});
gun.group({
name: "Mark Nadal",
age: 23,
type: "human"
}).group({
name: "Amber Nadal",
age: 23,
type: "human"
}).group({
name: "Hobbes",
age: 4,
type: "kitten"
}).get(function(g){
//console.log("GOT", g, this.__.graph);
}).map(function(val, id){
//console.log("map", id, val);
});
}());

480
test/json2.js Normal file
View File

@ -0,0 +1,480 @@
/*
http://www.JSON.org/json2.js
2011-02-23
Public Domain.
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
See http://www.JSON.org/js.html
This code should be minified before deployment.
See http://javascript.crockford.com/jsmin.html
USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
NOT CONTROL.
This file creates a global JSON object containing two methods: stringify
and parse.
JSON.stringify(value, replacer, space)
value any JavaScript value, usually an object or array.
replacer an optional parameter that determines how object
values are stringified for objects. It can be a
function or an array of strings.
space an optional parameter that specifies the indentation
of nested structures. If it is omitted, the text will
be packed without extra whitespace. If it is a number,
it will specify the number of spaces to indent at each
level. If it is a string (such as '\t' or '&nbsp;'),
it contains the characters used to indent at each level.
This method produces a JSON text from a JavaScript value.
When an object value is found, if the object contains a toJSON
method, its toJSON method will be called and the result will be
stringified. A toJSON method does not serialize: it returns the
value represented by the name/value pair that should be serialized,
or undefined if nothing should be serialized. The toJSON method
will be passed the key associated with the value, and this will be
bound to the value
For example, this would serialize Dates as ISO strings.
Date.prototype.toJSON = function (key) {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
return this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z';
};
You can provide an optional replacer method. It will be passed the
key and value of each member, with this bound to the containing
object. The value that is returned from your method will be
serialized. If your method returns undefined, then the member will
be excluded from the serialization.
If the replacer parameter is an array of strings, then it will be
used to select the members to be serialized. It filters the results
such that only members with keys listed in the replacer array are
stringified.
Values that do not have JSON representations, such as undefined or
functions, will not be serialized. Such values in objects will be
dropped; in arrays they will be replaced with null. You can use
a replacer function to replace those with JSON values.
JSON.stringify(undefined) returns undefined.
The optional space parameter produces a stringification of the
value that is filled with line breaks and indentation to make it
easier to read.
If the space parameter is a non-empty string, then that string will
be used for indentation. If the space parameter is a number, then
the indentation will be that many spaces.
Example:
text = JSON.stringify(['e', {pluribus: 'unum'}]);
// text is '["e",{"pluribus":"unum"}]'
text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
// text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
text = JSON.stringify([new Date()], function (key, value) {
return this[key] instanceof Date ?
'Date(' + this[key] + ')' : value;
});
// text is '["Date(---current time---)"]'
JSON.parse(text, reviver)
This method parses a JSON text to produce an object or array.
It can throw a SyntaxError exception.
The optional reviver parameter is a function that can filter and
transform the results. It receives each of the keys and values,
and its return value is used instead of the original value.
If it returns what it received, then the structure is not modified.
If it returns undefined then the member is deleted.
Example:
// Parse the text. Values that look like ISO date strings will
// be converted to Date objects.
myData = JSON.parse(text, function (key, value) {
var a;
if (typeof value === 'string') {
a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
if (a) {
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+a[5], +a[6]));
}
}
return value;
});
myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
var d;
if (typeof value === 'string' &&
value.slice(0, 5) === 'Date(' &&
value.slice(-1) === ')') {
d = new Date(value.slice(5, -1));
if (d) {
return d;
}
}
return value;
});
This is a reference implementation. You are free to copy, modify, or
redistribute.
*/
/*jslint evil: true, strict: false, regexp: false */
/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
lastIndex, length, parse, prototype, push, replace, slice, stringify,
test, toJSON, toString, valueOf
*/
// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.
var JSON;
if (!JSON) {
JSON = {};
}
(function () {
"use strict";
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
if (typeof Date.prototype.toJSON !== 'function') {
Date.prototype.toJSON = function (key) {
return isFinite(this.valueOf()) ?
this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z' : null;
};
String.prototype.toJSON =
Number.prototype.toJSON =
Boolean.prototype.toJSON = function (key) {
return this.valueOf();
};
}
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
gap,
indent,
meta = { // table of character substitutions
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
},
rep;
function quote(string) {
// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.
escapable.lastIndex = 0;
return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
var c = meta[a];
return typeof c === 'string' ? c :
'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' : '"' + string + '"';
}
function str(key, holder) {
// Produce a string from holder[key].
var i, // The loop counter.
k, // The member key.
v, // The member value.
length,
mind = gap,
partial,
value = holder[key];
// If the value has a toJSON method, call it to obtain a replacement value.
if (value && typeof value === 'object' &&
typeof value.toJSON === 'function') {
value = value.toJSON(key);
}
// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.
if (typeof rep === 'function') {
value = rep.call(holder, key, value);
}
// What happens next depends on the value's type.
switch (typeof value) {
case 'string':
return quote(value);
case 'number':
// JSON numbers must be finite. Encode non-finite numbers as null.
return isFinite(value) ? String(value) : 'null';
case 'boolean':
case 'null':
// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.
return String(value);
// If the type is 'object', we might be dealing with an object or an array or
// null.
case 'object':
// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.
if (!value) {
return 'null';
}
// Make an array to hold the partial results of stringifying this object value.
gap += indent;
partial = [];
// Is the value an array?
if (Object.prototype.toString.apply(value) === '[object Array]') {
// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.
length = value.length;
for (i = 0; i < length; i += 1) {
partial[i] = str(i, value) || 'null';
}
// Join all of the elements together, separated with commas, and wrap them in
// brackets.
v = partial.length === 0 ? '[]' : gap ?
'[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' :
'[' + partial.join(',') + ']';
gap = mind;
return v;
}
// If the replacer is an array, use it to select the members to be stringified.
if (rep && typeof rep === 'object') {
length = rep.length;
for (i = 0; i < length; i += 1) {
if (typeof rep[i] === 'string') {
k = rep[i];
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
} else {
// Otherwise, iterate through all of the keys in the object.
for (k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
}
// Join all of the member texts together, separated with commas,
// and wrap them in braces.
v = partial.length === 0 ? '{}' : gap ?
'{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' :
'{' + partial.join(',') + '}';
gap = mind;
return v;
}
}
// If the JSON object does not yet have a stringify method, give it one.
if (typeof JSON.stringify !== 'function') {
JSON.stringify = function (value, replacer, space) {
// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.
var i;
gap = '';
indent = '';
// If the space parameter is a number, make an indent string containing that
// many spaces.
if (typeof space === 'number') {
for (i = 0; i < space; i += 1) {
indent += ' ';
}
// If the space parameter is a string, it will be used as the indent string.
} else if (typeof space === 'string') {
indent = space;
}
// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.
rep = replacer;
if (replacer && typeof replacer !== 'function' &&
(typeof replacer !== 'object' ||
typeof replacer.length !== 'number')) {
throw new Error('JSON.stringify');
}
// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.
return str('', {'': value});
};
}
// If the JSON object does not yet have a parse method, give it one.
if (typeof JSON.parse !== 'function') {
JSON.parse = function (text, reviver) {
// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.
var j;
function walk(holder, key) {
// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.
var k, v, value = holder[key];
if (value && typeof value === 'object') {
for (k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
v = walk(value, k);
if (v !== undefined) {
value[k] = v;
} else {
delete value[k];
}
}
}
}
return reviver.call(holder, key, value);
}
// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.
text = String(text);
cx.lastIndex = 0;
if (cx.test(text)) {
text = text.replace(cx, function (a) {
return '\\u' +
('0000' + a.charCodeAt(0).toString(16)).slice(-4);
});
}
// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.
// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
if (/^[\],:{}\s]*$/
.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
.replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.
j = eval('(' + text + ')');
// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.
return typeof reviver === 'function' ?
walk({'': j}, '') : j;
}
// If the text is not JSON parseable, then a SyntaxError is thrown.
throw new SyntaxError('JSON.parse');
};
}
}());

View File

@ -2,6 +2,7 @@
<head> <head>
<title>Gun Tests</title> <title>Gun Tests</title>
<link rel="stylesheet" href="./mocha.css"/> <link rel="stylesheet" href="./mocha.css"/>
<script src="./json2.js"></script>
<script src="./mocha.js"></script> <script src="./mocha.js"></script>
<script>mocha.setup({ui:'bdd',globals:[]});</script> <script>mocha.setup({ui:'bdd',globals:[]});</script>
<script src="./expect.js"></script> <script src="./expect.js"></script>

49
test/set.js Normal file
View File

@ -0,0 +1,49 @@
(function(){ return;
var Gun = require('../gun');
var done = function(){};
var gun = Gun().get('set').set(), i = 0;
gun.val(function(val){
console.log('t1', val);
}).set(1).set(2).set(3).set(4) // if you set an object you'd have to do a `.back`
.map().val(function(val){ // TODO! BUG? If we do gun.set it immediately calls and we get stale data. Is this wrong?
console.log('t2', val, ++i);
if(4 === i){
console.log("TODO? BUG! Double soul?", gun.__.graph);
done()
}
});
return; // TODO! BUG! Causes tests to crash and burn badly.
require('../lib/set');
var gun = Gun();
var list = gun.get('thoughts');
list.set('a');
list.set('b');
list.set('c');
list.set('d').val(function(val){
console.log('what', val, '\n\n');
console.log(gun.__.graph);
})
return;
gun.set({
name: "Mark Nadal",
age: 23,
type: "human"
}).back.set({
name: "Amber Nadal",
age: 23,
type: "human"
}).back.set({
name: "Hobbes",
age: 4,
type: "kitten"
}).back.val(function(g){
console.log("GOT", g, this.__.graph);
}).map(function(val, id){
console.log("map", id, val);
});
}());

View File

@ -5,15 +5,15 @@
Gun.log.verbose = true; Gun.log.verbose = true;
/* /*
gun.set({foo: "bar"}).get(function(val){ gun.put({foo: "bar"}).val(function(val){
console.log("POWR HOUSE", val); console.log("POWR HOUSE", val);
this.set({lol: 'pancakes'}).get(function(v){ this.put({lol: 'pancakes'}).val(function(v){
console.log("YEAH CAKES", v); console.log("YEAH CAKES", v);
}) })
}); });
*/ */
gun.load('hello/world').set({hello: 'Mark'}).path('hello').get(function(val){ gun.get('hello/world').put({hello: 'Mark'}).path('hello').val(function(val){
console.log("YO", val); console.log("YO", val);
expect(val).to.be('Mark'); expect(val).to.be('Mark');
done(); done();

57
web/2015/15.html Normal file
View File

@ -0,0 +1,57 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<a href="https://github.com/amark/gun" target="_blank"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://camo.githubusercontent.com/365986a132ccd6a44c23a9169022c0b5c890c387/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f7265645f6161303030302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png"></a>
<a href="https://gunDB.io/" style="font-family: Arial;">Home</a>
<!-- BLOG POST HERE -->
<style>
#main {
min-width: 250px;
max-width: 700px;
width: 75%;
margin: 7% auto;
padding: 2% 5%;
background: white;
background: rgba(100%,100%,100%,.6);
font-family: Arial;
font-size: 18pt;
text-shadow: 0px 0px 7px #DDD;
line-height: 20pt;
}
#main p {
text-indent: 2em;
}
</style>
<div id="main">
<h3>Do You have Time to Chat?</h3>
<p>
Let's build a chat app. But we're going to do it in a mind boggling way.
Conversations take time to have, therefore rather than storing every message
individually, we are going to store them in time. What does that even mean?
</p>
<p>
The first requirement is understanding immutable data.
Most database systems overwrite old data with new data when there is an update.
This preserves data in space, but loses its history.
Immutable data is the idea of never changing data,
but appending a new record every time. Such approach preserves history.
</p>
<p>
</p>
</div>
<!-- END OLD BLOG POST -->
</body>
</html>

View File

@ -0,0 +1,325 @@
/* BASICS */
.CodeMirror {
/* Set height, width, borders, and global font properties here */
font-family: monospace;
height: 300px;
color: black;
}
/* PADDING */
.CodeMirror-lines {
padding: 4px 0; /* Vertical padding around content */
}
.CodeMirror pre {
padding: 0 4px; /* Horizontal padding of content */
}
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
background-color: white; /* The little square between H and V scrollbars */
}
/* GUTTER */
.CodeMirror-gutters {
border-right: 1px solid #ddd;
background-color: #f7f7f7;
white-space: nowrap;
}
.CodeMirror-linenumbers {}
.CodeMirror-linenumber {
padding: 0 3px 0 5px;
min-width: 20px;
text-align: right;
color: #999;
white-space: nowrap;
}
.CodeMirror-guttermarker { color: black; }
.CodeMirror-guttermarker-subtle { color: #999; }
/* CURSOR */
.CodeMirror div.CodeMirror-cursor {
border-left: 1px solid black;
}
/* Shown when moving in bi-directional text */
.CodeMirror div.CodeMirror-secondarycursor {
border-left: 1px solid silver;
}
.CodeMirror.cm-fat-cursor div.CodeMirror-cursor {
width: auto;
border: 0;
background: #7e7;
}
.CodeMirror.cm-fat-cursor div.CodeMirror-cursors {
z-index: 1;
}
.cm-animate-fat-cursor {
width: auto;
border: 0;
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
}
@-moz-keyframes blink {
0% { background: #7e7; }
50% { background: none; }
100% { background: #7e7; }
}
@-webkit-keyframes blink {
0% { background: #7e7; }
50% { background: none; }
100% { background: #7e7; }
}
@keyframes blink {
0% { background: #7e7; }
50% { background: none; }
100% { background: #7e7; }
}
/* Can style cursor different in overwrite (non-insert) mode */
div.CodeMirror-overwrite div.CodeMirror-cursor {}
.cm-tab { display: inline-block; text-decoration: inherit; }
.CodeMirror-ruler {
border-left: 1px solid #ccc;
position: absolute;
}
/* DEFAULT THEME */
.cm-s-default .cm-keyword {color: #708;}
.cm-s-default .cm-atom {color: #219;}
.cm-s-default .cm-number {color: #164;}
.cm-s-default .cm-def {color: #00f;}
.cm-s-default .cm-variable,
.cm-s-default .cm-punctuation,
.cm-s-default .cm-property,
.cm-s-default .cm-operator {}
.cm-s-default .cm-variable-2 {color: #05a;}
.cm-s-default .cm-variable-3 {color: #085;}
.cm-s-default .cm-comment {color: #a50;}
.cm-s-default .cm-string {color: #a11;}
.cm-s-default .cm-string-2 {color: #f50;}
.cm-s-default .cm-meta {color: #555;}
.cm-s-default .cm-qualifier {color: #555;}
.cm-s-default .cm-builtin {color: #30a;}
.cm-s-default .cm-bracket {color: #997;}
.cm-s-default .cm-tag {color: #170;}
.cm-s-default .cm-attribute {color: #00c;}
.cm-s-default .cm-header {color: blue;}
.cm-s-default .cm-quote {color: #090;}
.cm-s-default .cm-hr {color: #999;}
.cm-s-default .cm-link {color: #00c;}
.cm-negative {color: #d44;}
.cm-positive {color: #292;}
.cm-header, .cm-strong {font-weight: bold;}
.cm-em {font-style: italic;}
.cm-link {text-decoration: underline;}
.cm-strikethrough {text-decoration: line-through;}
.cm-s-default .cm-error {color: #f00;}
.cm-invalidchar {color: #f00;}
.CodeMirror-composing { border-bottom: 2px solid; }
/* Default styles for common addons */
div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
.CodeMirror-activeline-background {background: #e8f2ff;}
/* STOP */
/* The rest of this file contains styles related to the mechanics of
the editor. You probably shouldn't touch them. */
.CodeMirror {
position: relative;
overflow: hidden;
background: white;
}
.CodeMirror-scroll {
overflow: scroll !important; /* Things will break if this is overridden */
/* 30px is the magic margin used to hide the element's real scrollbars */
/* See overflow: hidden in .CodeMirror */
margin-bottom: -30px; margin-right: -30px;
padding-bottom: 30px;
height: 100%;
outline: none; /* Prevent dragging from highlighting the element */
position: relative;
}
.CodeMirror-sizer {
position: relative;
border-right: 30px solid transparent;
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
before actuall scrolling happens, thus preventing shaking and
flickering artifacts. */
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
position: absolute;
z-index: 6;
display: none;
}
.CodeMirror-vscrollbar {
right: 0; top: 0;
overflow-x: hidden;
overflow-y: scroll;
}
.CodeMirror-hscrollbar {
bottom: 0; left: 0;
overflow-y: hidden;
overflow-x: scroll;
}
.CodeMirror-scrollbar-filler {
right: 0; bottom: 0;
}
.CodeMirror-gutter-filler {
left: 0; bottom: 0;
}
.CodeMirror-gutters {
position: absolute; left: 0; top: 0;
z-index: 3;
}
.CodeMirror-gutter {
white-space: normal;
height: 100%;
display: inline-block;
margin-bottom: -30px;
/* Hack to make IE7 behave */
*zoom:1;
*display:inline;
}
.CodeMirror-gutter-wrapper {
position: absolute;
z-index: 4;
height: 100%;
}
.CodeMirror-gutter-elt {
position: absolute;
cursor: default;
z-index: 4;
}
.CodeMirror-gutter-wrapper {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.CodeMirror-lines {
cursor: text;
min-height: 1px; /* prevents collapsing before first draw */
}
.CodeMirror pre {
/* Reset some styles that the rest of the page might have set */
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
border-width: 0;
background: transparent;
font-family: inherit;
font-size: inherit;
margin: 0;
white-space: pre;
word-wrap: normal;
line-height: inherit;
color: inherit;
z-index: 2;
position: relative;
overflow: visible;
-webkit-tap-highlight-color: transparent;
}
.CodeMirror-wrap pre {
word-wrap: break-word;
white-space: pre-wrap;
word-break: normal;
}
.CodeMirror-linebackground {
position: absolute;
left: 0; right: 0; top: 0; bottom: 0;
z-index: 0;
}
.CodeMirror-linewidget {
position: relative;
z-index: 2;
overflow: auto;
}
.CodeMirror-widget {}
.CodeMirror-code {
outline: none;
}
/* Force content-box sizing for the elements where we expect it */
.CodeMirror-scroll,
.CodeMirror-sizer,
.CodeMirror-gutter,
.CodeMirror-gutters,
.CodeMirror-linenumber {
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.CodeMirror-measure {
position: absolute;
width: 100%;
height: 0;
overflow: hidden;
visibility: hidden;
}
.CodeMirror-measure pre { position: static; }
.CodeMirror div.CodeMirror-cursor {
position: absolute;
border-right: none;
width: 0;
}
div.CodeMirror-cursors {
visibility: hidden;
position: relative;
z-index: 3;
}
.CodeMirror-focused div.CodeMirror-cursors {
visibility: visible;
}
.CodeMirror-selected { background: #d9d9d9; }
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
.CodeMirror-crosshair { cursor: crosshair; }
.CodeMirror ::selection { background: #d7d4f0; }
.CodeMirror ::-moz-selection { background: #d7d4f0; }
.cm-searching {
background: #ffa;
background: rgba(255, 255, 0, .4);
}
/* IE7 hack to prevent it from returning funny offsetTops on the spans */
.CodeMirror span { *vertical-align: text-bottom; }
/* Used to force a border model for a node */
.cm-force-border { padding-right: .1px; }
@media print {
/* Hide the cursor when printing */
.CodeMirror div.CodeMirror-cursors {
visibility: hidden;
}
}
/* See issue #2901 */
.cm-tab-wrap-hack:after { content: ''; }
/* Help users use markselection to safely style text background */
span.CodeMirror-selectedtext { background: none; }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,33 @@
.cm-s-colorforth.CodeMirror { background: #000000; color: #f8f8f8; }
.cm-s-colorforth .CodeMirror-gutters { background: #0a001f; border-right: 1px solid #aaa; }
.cm-s-colorforth .CodeMirror-guttermarker { color: #FFBD40; }
.cm-s-colorforth .CodeMirror-guttermarker-subtle { color: #78846f; }
.cm-s-colorforth .CodeMirror-linenumber { color: #bababa; }
.cm-s-colorforth .CodeMirror-cursor { border-left: 1px solid white !important; }
.cm-s-colorforth span.cm-comment { color: #ededed; }
.cm-s-colorforth span.cm-def { color: #ff1c1c; font-weight:bold; }
.cm-s-colorforth span.cm-keyword { color: #ffd900; }
.cm-s-colorforth span.cm-builtin { color: #00d95a; }
.cm-s-colorforth span.cm-variable { color: #73ff00; }
.cm-s-colorforth span.cm-string { color: #007bff; }
.cm-s-colorforth span.cm-number { color: #00c4ff; }
.cm-s-colorforth span.cm-atom { color: #606060; }
.cm-s-colorforth span.cm-variable-2 { color: #EEE; }
.cm-s-colorforth span.cm-variable-3 { color: #DDD; }
.cm-s-colorforth span.cm-property {}
.cm-s-colorforth span.cm-operator {}
.cm-s-colorforth span.cm-meta { color: yellow; }
.cm-s-colorforth span.cm-qualifier { color: #FFF700; }
.cm-s-colorforth span.cm-tag { color: lime; }
.cm-s-colorforth span.cm-bracket { color: green; }
.cm-s-colorforth span.cm-attribute { color: #FFF700; }
.cm-s-colorforth span.cm-error { color: #f00; }
.cm-s-colorforth .CodeMirror-selected { background: #333d53 !important; }
.cm-s-colorforth span.cm-compilation { background: rgba(255, 255, 255, 0.12); }
.cm-s-colorforth .CodeMirror-activeline-background {background: #253540 !important;}

View File

@ -0,0 +1,33 @@
.cm-s-colorforth.CodeMirror { background: #000000; color: #f8f8f8; }
.cm-s-colorforth .CodeMirror-gutters { background: #0a001f; border-right: 1px solid #aaa; }
.cm-s-colorforth .CodeMirror-guttermarker { color: #FFBD40; }
.cm-s-colorforth .CodeMirror-guttermarker-subtle { color: #78846f; }
.cm-s-colorforth .CodeMirror-linenumber { color: #bababa; }
.cm-s-colorforth .CodeMirror-cursor { border-left: 1px solid white !important; }
.cm-s-colorforth span.cm-comment { color: #ededed; }
.cm-s-colorforth span.cm-def { color: #ff1c1c; font-weight:bold; }
.cm-s-colorforth span.cm-keyword { color: #ffd900; }
.cm-s-colorforth span.cm-builtin { color: #00d95a; }
.cm-s-colorforth span.cm-variable { color: #73ff00; }
.cm-s-colorforth span.cm-string { color: #007bff; }
.cm-s-colorforth span.cm-number { color: #00c4ff; }
.cm-s-colorforth span.cm-atom { color: #606060; }
.cm-s-colorforth span.cm-variable-2 { color: #EEE; }
.cm-s-colorforth span.cm-variable-3 { color: #DDD; }
.cm-s-colorforth span.cm-property {}
.cm-s-colorforth span.cm-operator {}
.cm-s-colorforth span.cm-meta { color: yellow; }
.cm-s-colorforth span.cm-qualifier { color: #FFF700; }
.cm-s-colorforth span.cm-bracket { color: #cc7; }
.cm-s-colorforth span.cm-tag { color: #FFBD40; }
.cm-s-colorforth span.cm-attribute { color: #FFF700; }
.cm-s-colorforth span.cm-error { color: #f00; }
.cm-s-colorforth .CodeMirror-selected { background: #333d53 !important; }
.cm-s-colorforth span.cm-compilation { background: rgba(255, 255, 255, 0.12); }
.cm-s-colorforth .CodeMirror-activeline-background {background: #253540 !important;}

754
web/dep/codemirror/css.js Normal file
View File

@ -0,0 +1,754 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineMode("css", function(config, parserConfig) {
if (!parserConfig.propertyKeywords) parserConfig = CodeMirror.resolveMode("text/css");
var indentUnit = config.indentUnit,
tokenHooks = parserConfig.tokenHooks,
documentTypes = parserConfig.documentTypes || {},
mediaTypes = parserConfig.mediaTypes || {},
mediaFeatures = parserConfig.mediaFeatures || {},
propertyKeywords = parserConfig.propertyKeywords || {},
nonStandardPropertyKeywords = parserConfig.nonStandardPropertyKeywords || {},
fontProperties = parserConfig.fontProperties || {},
counterDescriptors = parserConfig.counterDescriptors || {},
colorKeywords = parserConfig.colorKeywords || {},
valueKeywords = parserConfig.valueKeywords || {},
allowNested = parserConfig.allowNested;
var type, override;
function ret(style, tp) { type = tp; return style; }
// Tokenizers
function tokenBase(stream, state) {
var ch = stream.next();
if (tokenHooks[ch]) {
var result = tokenHooks[ch](stream, state);
if (result !== false) return result;
}
if (ch == "@") {
stream.eatWhile(/[\w\\\-]/);
return ret("def", stream.current());
} else if (ch == "=" || (ch == "~" || ch == "|") && stream.eat("=")) {
return ret(null, "compare");
} else if (ch == "\"" || ch == "'") {
state.tokenize = tokenString(ch);
return state.tokenize(stream, state);
} else if (ch == "#") {
stream.eatWhile(/[\w\\\-]/);
return ret("atom", "hash");
} else if (ch == "!") {
stream.match(/^\s*\w*/);
return ret("keyword", "important");
} else if (/\d/.test(ch) || ch == "." && stream.eat(/\d/)) {
stream.eatWhile(/[\w.%]/);
return ret("number", "unit");
} else if (ch === "-") {
if (/[\d.]/.test(stream.peek())) {
stream.eatWhile(/[\w.%]/);
return ret("number", "unit");
} else if (stream.match(/^-[\w\\\-]+/)) {
stream.eatWhile(/[\w\\\-]/);
if (stream.match(/^\s*:/, false))
return ret("variable-2", "variable-definition");
return ret("variable-2", "variable");
} else if (stream.match(/^\w+-/)) {
return ret("meta", "meta");
}
} else if (/[,+>*\/]/.test(ch)) {
return ret(null, "select-op");
} else if (ch == "." && stream.match(/^-?[_a-z][_a-z0-9-]*/i)) {
return ret("qualifier", "qualifier");
} else if (/[:;{}\[\]\(\)]/.test(ch)) {
return ret(null, ch);
} else if ((ch == "u" && stream.match(/rl(-prefix)?\(/)) ||
(ch == "d" && stream.match("omain(")) ||
(ch == "r" && stream.match("egexp("))) {
stream.backUp(1);
state.tokenize = tokenParenthesized;
return ret("property", "word");
} else if (/[\w\\\-]/.test(ch)) {
stream.eatWhile(/[\w\\\-]/);
return ret("property", "word");
} else {
return ret(null, null);
}
}
function tokenString(quote) {
return function(stream, state) {
var escaped = false, ch;
while ((ch = stream.next()) != null) {
if (ch == quote && !escaped) {
if (quote == ")") stream.backUp(1);
break;
}
escaped = !escaped && ch == "\\";
}
if (ch == quote || !escaped && quote != ")") state.tokenize = null;
return ret("string", "string");
};
}
function tokenParenthesized(stream, state) {
stream.next(); // Must be '('
if (!stream.match(/\s*[\"\')]/, false))
state.tokenize = tokenString(")");
else
state.tokenize = null;
return ret(null, "(");
}
// Context management
function Context(type, indent, prev) {
this.type = type;
this.indent = indent;
this.prev = prev;
}
function pushContext(state, stream, type) {
state.context = new Context(type, stream.indentation() + indentUnit, state.context);
return type;
}
function popContext(state) {
state.context = state.context.prev;
return state.context.type;
}
function pass(type, stream, state) {
return states[state.context.type](type, stream, state);
}
function popAndPass(type, stream, state, n) {
for (var i = n || 1; i > 0; i--)
state.context = state.context.prev;
return pass(type, stream, state);
}
// Parser
function wordAsValue(stream) {
var word = stream.current().toLowerCase();
if (valueKeywords.hasOwnProperty(word))
override = "atom";
else if (colorKeywords.hasOwnProperty(word))
override = "keyword";
else
override = "variable";
}
var states = {};
states.top = function(type, stream, state) {
if (type == "{") {
return pushContext(state, stream, "block");
} else if (type == "}" && state.context.prev) {
return popContext(state);
} else if (/@(media|supports|(-moz-)?document)/.test(type)) {
return pushContext(state, stream, "atBlock");
} else if (/@(font-face|counter-style)/.test(type)) {
state.stateArg = type;
return "restricted_atBlock_before";
} else if (/^@(-(moz|ms|o|webkit)-)?keyframes$/.test(type)) {
return "keyframes";
} else if (type && type.charAt(0) == "@") {
return pushContext(state, stream, "at");
} else if (type == "hash") {
override = "builtin";
} else if (type == "word") {
override = "tag";
} else if (type == "variable-definition") {
return "maybeprop";
} else if (type == "interpolation") {
return pushContext(state, stream, "interpolation");
} else if (type == ":") {
return "pseudo";
} else if (allowNested && type == "(") {
return pushContext(state, stream, "parens");
}
return state.context.type;
};
states.block = function(type, stream, state) {
if (type == "word") {
var word = stream.current().toLowerCase();
if (propertyKeywords.hasOwnProperty(word)) {
override = "property";
return "maybeprop";
} else if (nonStandardPropertyKeywords.hasOwnProperty(word)) {
override = "string-2";
return "maybeprop";
} else if (allowNested) {
override = stream.match(/^\s*:(?:\s|$)/, false) ? "property" : "tag";
return "block";
} else {
override += " error";
return "maybeprop";
}
} else if (type == "meta") {
return "block";
} else if (!allowNested && (type == "hash" || type == "qualifier")) {
override = "error";
return "block";
} else {
return states.top(type, stream, state);
}
};
states.maybeprop = function(type, stream, state) {
if (type == ":") return pushContext(state, stream, "prop");
return pass(type, stream, state);
};
states.prop = function(type, stream, state) {
if (type == ";") return popContext(state);
if (type == "{" && allowNested) return pushContext(state, stream, "propBlock");
if (type == "}" || type == "{") return popAndPass(type, stream, state);
if (type == "(") return pushContext(state, stream, "parens");
if (type == "hash" && !/^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/.test(stream.current())) {
override += " error";
} else if (type == "word") {
wordAsValue(stream);
} else if (type == "interpolation") {
return pushContext(state, stream, "interpolation");
}
return "prop";
};
states.propBlock = function(type, _stream, state) {
if (type == "}") return popContext(state);
if (type == "word") { override = "property"; return "maybeprop"; }
return state.context.type;
};
states.parens = function(type, stream, state) {
if (type == "{" || type == "}") return popAndPass(type, stream, state);
if (type == ")") return popContext(state);
if (type == "(") return pushContext(state, stream, "parens");
if (type == "interpolation") return pushContext(state, stream, "interpolation");
if (type == "word") wordAsValue(stream);
return "parens";
};
states.pseudo = function(type, stream, state) {
if (type == "word") {
override = "variable-3";
return state.context.type;
}
return pass(type, stream, state);
};
states.atBlock = function(type, stream, state) {
if (type == "(") return pushContext(state, stream, "atBlock_parens");
if (type == "}") return popAndPass(type, stream, state);
if (type == "{") return popContext(state) && pushContext(state, stream, allowNested ? "block" : "top");
if (type == "word") {
var word = stream.current().toLowerCase();
if (word == "only" || word == "not" || word == "and" || word == "or")
override = "keyword";
else if (documentTypes.hasOwnProperty(word))
override = "tag";
else if (mediaTypes.hasOwnProperty(word))
override = "attribute";
else if (mediaFeatures.hasOwnProperty(word))
override = "property";
else if (propertyKeywords.hasOwnProperty(word))
override = "property";
else if (nonStandardPropertyKeywords.hasOwnProperty(word))
override = "string-2";
else if (valueKeywords.hasOwnProperty(word))
override = "atom";
else
override = "error";
}
return state.context.type;
};
states.atBlock_parens = function(type, stream, state) {
if (type == ")") return popContext(state);
if (type == "{" || type == "}") return popAndPass(type, stream, state, 2);
return states.atBlock(type, stream, state);
};
states.restricted_atBlock_before = function(type, stream, state) {
if (type == "{")
return pushContext(state, stream, "restricted_atBlock");
if (type == "word" && state.stateArg == "@counter-style") {
override = "variable";
return "restricted_atBlock_before";
}
return pass(type, stream, state);
};
states.restricted_atBlock = function(type, stream, state) {
if (type == "}") {
state.stateArg = null;
return popContext(state);
}
if (type == "word") {
if ((state.stateArg == "@font-face" && !fontProperties.hasOwnProperty(stream.current().toLowerCase())) ||
(state.stateArg == "@counter-style" && !counterDescriptors.hasOwnProperty(stream.current().toLowerCase())))
override = "error";
else
override = "property";
return "maybeprop";
}
return "restricted_atBlock";
};
states.keyframes = function(type, stream, state) {
if (type == "word") { override = "variable"; return "keyframes"; }
if (type == "{") return pushContext(state, stream, "top");
return pass(type, stream, state);
};
states.at = function(type, stream, state) {
if (type == ";") return popContext(state);
if (type == "{" || type == "}") return popAndPass(type, stream, state);
if (type == "word") override = "tag";
else if (type == "hash") override = "builtin";
return "at";
};
states.interpolation = function(type, stream, state) {
if (type == "}") return popContext(state);
if (type == "{" || type == ";") return popAndPass(type, stream, state);
if (type == "word") override = "variable";
else if (type != "variable" && type != "(" && type != ")") override = "error";
return "interpolation";
};
return {
startState: function(base) {
return {tokenize: null,
state: "top",
stateArg: null,
context: new Context("top", base || 0, null)};
},
token: function(stream, state) {
if (!state.tokenize && stream.eatSpace()) return null;
var style = (state.tokenize || tokenBase)(stream, state);
if (style && typeof style == "object") {
type = style[1];
style = style[0];
}
override = style;
state.state = states[state.state](type, stream, state);
return override;
},
indent: function(state, textAfter) {
var cx = state.context, ch = textAfter && textAfter.charAt(0);
var indent = cx.indent;
if (cx.type == "prop" && (ch == "}" || ch == ")")) cx = cx.prev;
if (cx.prev &&
(ch == "}" && (cx.type == "block" || cx.type == "top" || cx.type == "interpolation" || cx.type == "restricted_atBlock") ||
ch == ")" && (cx.type == "parens" || cx.type == "atBlock_parens") ||
ch == "{" && (cx.type == "at" || cx.type == "atBlock"))) {
indent = cx.indent - indentUnit;
cx = cx.prev;
}
return indent;
},
electricChars: "}",
blockCommentStart: "/*",
blockCommentEnd: "*/",
fold: "brace"
};
});
function keySet(array) {
var keys = {};
for (var i = 0; i < array.length; ++i) {
keys[array[i]] = true;
}
return keys;
}
var documentTypes_ = [
"domain", "regexp", "url", "url-prefix"
], documentTypes = keySet(documentTypes_);
var mediaTypes_ = [
"all", "aural", "braille", "handheld", "print", "projection", "screen",
"tty", "tv", "embossed"
], mediaTypes = keySet(mediaTypes_);
var mediaFeatures_ = [
"width", "min-width", "max-width", "height", "min-height", "max-height",
"device-width", "min-device-width", "max-device-width", "device-height",
"min-device-height", "max-device-height", "aspect-ratio",
"min-aspect-ratio", "max-aspect-ratio", "device-aspect-ratio",
"min-device-aspect-ratio", "max-device-aspect-ratio", "color", "min-color",
"max-color", "color-index", "min-color-index", "max-color-index",
"monochrome", "min-monochrome", "max-monochrome", "resolution",
"min-resolution", "max-resolution", "scan", "grid"
], mediaFeatures = keySet(mediaFeatures_);
var propertyKeywords_ = [
"align-content", "align-items", "align-self", "alignment-adjust",
"alignment-baseline", "anchor-point", "animation", "animation-delay",
"animation-direction", "animation-duration", "animation-fill-mode",
"animation-iteration-count", "animation-name", "animation-play-state",
"animation-timing-function", "appearance", "azimuth", "backface-visibility",
"background", "background-attachment", "background-clip", "background-color",
"background-image", "background-origin", "background-position",
"background-repeat", "background-size", "baseline-shift", "binding",
"bleed", "bookmark-label", "bookmark-level", "bookmark-state",
"bookmark-target", "border", "border-bottom", "border-bottom-color",
"border-bottom-left-radius", "border-bottom-right-radius",
"border-bottom-style", "border-bottom-width", "border-collapse",
"border-color", "border-image", "border-image-outset",
"border-image-repeat", "border-image-slice", "border-image-source",
"border-image-width", "border-left", "border-left-color",
"border-left-style", "border-left-width", "border-radius", "border-right",
"border-right-color", "border-right-style", "border-right-width",
"border-spacing", "border-style", "border-top", "border-top-color",
"border-top-left-radius", "border-top-right-radius", "border-top-style",
"border-top-width", "border-width", "bottom", "box-decoration-break",
"box-shadow", "box-sizing", "break-after", "break-before", "break-inside",
"caption-side", "clear", "clip", "color", "color-profile", "column-count",
"column-fill", "column-gap", "column-rule", "column-rule-color",
"column-rule-style", "column-rule-width", "column-span", "column-width",
"columns", "content", "counter-increment", "counter-reset", "crop", "cue",
"cue-after", "cue-before", "cursor", "direction", "display",
"dominant-baseline", "drop-initial-after-adjust",
"drop-initial-after-align", "drop-initial-before-adjust",
"drop-initial-before-align", "drop-initial-size", "drop-initial-value",
"elevation", "empty-cells", "fit", "fit-position", "flex", "flex-basis",
"flex-direction", "flex-flow", "flex-grow", "flex-shrink", "flex-wrap",
"float", "float-offset", "flow-from", "flow-into", "font", "font-feature-settings",
"font-family", "font-kerning", "font-language-override", "font-size", "font-size-adjust",
"font-stretch", "font-style", "font-synthesis", "font-variant",
"font-variant-alternates", "font-variant-caps", "font-variant-east-asian",
"font-variant-ligatures", "font-variant-numeric", "font-variant-position",
"font-weight", "grid", "grid-area", "grid-auto-columns", "grid-auto-flow",
"grid-auto-position", "grid-auto-rows", "grid-column", "grid-column-end",
"grid-column-start", "grid-row", "grid-row-end", "grid-row-start",
"grid-template", "grid-template-areas", "grid-template-columns",
"grid-template-rows", "hanging-punctuation", "height", "hyphens",
"icon", "image-orientation", "image-rendering", "image-resolution",
"inline-box-align", "justify-content", "left", "letter-spacing",
"line-break", "line-height", "line-stacking", "line-stacking-ruby",
"line-stacking-shift", "line-stacking-strategy", "list-style",
"list-style-image", "list-style-position", "list-style-type", "margin",
"margin-bottom", "margin-left", "margin-right", "margin-top",
"marker-offset", "marks", "marquee-direction", "marquee-loop",
"marquee-play-count", "marquee-speed", "marquee-style", "max-height",
"max-width", "min-height", "min-width", "move-to", "nav-down", "nav-index",
"nav-left", "nav-right", "nav-up", "object-fit", "object-position",
"opacity", "order", "orphans", "outline",
"outline-color", "outline-offset", "outline-style", "outline-width",
"overflow", "overflow-style", "overflow-wrap", "overflow-x", "overflow-y",
"padding", "padding-bottom", "padding-left", "padding-right", "padding-top",
"page", "page-break-after", "page-break-before", "page-break-inside",
"page-policy", "pause", "pause-after", "pause-before", "perspective",
"perspective-origin", "pitch", "pitch-range", "play-during", "position",
"presentation-level", "punctuation-trim", "quotes", "region-break-after",
"region-break-before", "region-break-inside", "region-fragment",
"rendering-intent", "resize", "rest", "rest-after", "rest-before", "richness",
"right", "rotation", "rotation-point", "ruby-align", "ruby-overhang",
"ruby-position", "ruby-span", "shape-image-threshold", "shape-inside", "shape-margin",
"shape-outside", "size", "speak", "speak-as", "speak-header",
"speak-numeral", "speak-punctuation", "speech-rate", "stress", "string-set",
"tab-size", "table-layout", "target", "target-name", "target-new",
"target-position", "text-align", "text-align-last", "text-decoration",
"text-decoration-color", "text-decoration-line", "text-decoration-skip",
"text-decoration-style", "text-emphasis", "text-emphasis-color",
"text-emphasis-position", "text-emphasis-style", "text-height",
"text-indent", "text-justify", "text-outline", "text-overflow", "text-shadow",
"text-size-adjust", "text-space-collapse", "text-transform", "text-underline-position",
"text-wrap", "top", "transform", "transform-origin", "transform-style",
"transition", "transition-delay", "transition-duration",
"transition-property", "transition-timing-function", "unicode-bidi",
"vertical-align", "visibility", "voice-balance", "voice-duration",
"voice-family", "voice-pitch", "voice-range", "voice-rate", "voice-stress",
"voice-volume", "volume", "white-space", "widows", "width", "word-break",
"word-spacing", "word-wrap", "z-index",
// SVG-specific
"clip-path", "clip-rule", "mask", "enable-background", "filter", "flood-color",
"flood-opacity", "lighting-color", "stop-color", "stop-opacity", "pointer-events",
"color-interpolation", "color-interpolation-filters",
"color-rendering", "fill", "fill-opacity", "fill-rule", "image-rendering",
"marker", "marker-end", "marker-mid", "marker-start", "shape-rendering", "stroke",
"stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin",
"stroke-miterlimit", "stroke-opacity", "stroke-width", "text-rendering",
"baseline-shift", "dominant-baseline", "glyph-orientation-horizontal",
"glyph-orientation-vertical", "text-anchor", "writing-mode"
], propertyKeywords = keySet(propertyKeywords_);
var nonStandardPropertyKeywords_ = [
"scrollbar-arrow-color", "scrollbar-base-color", "scrollbar-dark-shadow-color",
"scrollbar-face-color", "scrollbar-highlight-color", "scrollbar-shadow-color",
"scrollbar-3d-light-color", "scrollbar-track-color", "shape-inside",
"searchfield-cancel-button", "searchfield-decoration", "searchfield-results-button",
"searchfield-results-decoration", "zoom"
], nonStandardPropertyKeywords = keySet(nonStandardPropertyKeywords_);
var fontProperties_ = [
"font-family", "src", "unicode-range", "font-variant", "font-feature-settings",
"font-stretch", "font-weight", "font-style"
], fontProperties = keySet(fontProperties_);
var counterDescriptors_ = [
"additive-symbols", "fallback", "negative", "pad", "prefix", "range",
"speak-as", "suffix", "symbols", "system"
], counterDescriptors = keySet(counterDescriptors_);
var colorKeywords_ = [
"aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige",
"bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown",
"burlywood", "cadetblue", "chartreuse", "chocolate", "coral", "cornflowerblue",
"cornsilk", "crimson", "cyan", "darkblue", "darkcyan", "darkgoldenrod",
"darkgray", "darkgreen", "darkkhaki", "darkmagenta", "darkolivegreen",
"darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen",
"darkslateblue", "darkslategray", "darkturquoise", "darkviolet",
"deeppink", "deepskyblue", "dimgray", "dodgerblue", "firebrick",
"floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite",
"gold", "goldenrod", "gray", "grey", "green", "greenyellow", "honeydew",
"hotpink", "indianred", "indigo", "ivory", "khaki", "lavender",
"lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral",
"lightcyan", "lightgoldenrodyellow", "lightgray", "lightgreen", "lightpink",
"lightsalmon", "lightseagreen", "lightskyblue", "lightslategray",
"lightsteelblue", "lightyellow", "lime", "limegreen", "linen", "magenta",
"maroon", "mediumaquamarine", "mediumblue", "mediumorchid", "mediumpurple",
"mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise",
"mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin",
"navajowhite", "navy", "oldlace", "olive", "olivedrab", "orange", "orangered",
"orchid", "palegoldenrod", "palegreen", "paleturquoise", "palevioletred",
"papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue",
"purple", "rebeccapurple", "red", "rosybrown", "royalblue", "saddlebrown",
"salmon", "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue",
"slateblue", "slategray", "snow", "springgreen", "steelblue", "tan",
"teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white",
"whitesmoke", "yellow", "yellowgreen"
], colorKeywords = keySet(colorKeywords_);
var valueKeywords_ = [
"above", "absolute", "activeborder", "additive", "activecaption", "afar",
"after-white-space", "ahead", "alias", "all", "all-scroll", "alphabetic", "alternate",
"always", "amharic", "amharic-abegede", "antialiased", "appworkspace",
"arabic-indic", "armenian", "asterisks", "attr", "auto", "avoid", "avoid-column", "avoid-page",
"avoid-region", "background", "backwards", "baseline", "below", "bidi-override", "binary",
"bengali", "blink", "block", "block-axis", "bold", "bolder", "border", "border-box",
"both", "bottom", "break", "break-all", "break-word", "bullets", "button", "button-bevel",
"buttonface", "buttonhighlight", "buttonshadow", "buttontext", "calc", "cambodian",
"capitalize", "caps-lock-indicator", "caption", "captiontext", "caret",
"cell", "center", "checkbox", "circle", "cjk-decimal", "cjk-earthly-branch",
"cjk-heavenly-stem", "cjk-ideographic", "clear", "clip", "close-quote",
"col-resize", "collapse", "column", "compact", "condensed", "contain", "content",
"content-box", "context-menu", "continuous", "copy", "counter", "counters", "cover", "crop",
"cross", "crosshair", "currentcolor", "cursive", "cyclic", "dashed", "decimal",
"decimal-leading-zero", "default", "default-button", "destination-atop",
"destination-in", "destination-out", "destination-over", "devanagari",
"disc", "discard", "disclosure-closed", "disclosure-open", "document",
"dot-dash", "dot-dot-dash",
"dotted", "double", "down", "e-resize", "ease", "ease-in", "ease-in-out", "ease-out",
"element", "ellipse", "ellipsis", "embed", "end", "ethiopic", "ethiopic-abegede",
"ethiopic-abegede-am-et", "ethiopic-abegede-gez", "ethiopic-abegede-ti-er",
"ethiopic-abegede-ti-et", "ethiopic-halehame-aa-er",
"ethiopic-halehame-aa-et", "ethiopic-halehame-am-et",
"ethiopic-halehame-gez", "ethiopic-halehame-om-et",
"ethiopic-halehame-sid-et", "ethiopic-halehame-so-et",
"ethiopic-halehame-ti-er", "ethiopic-halehame-ti-et", "ethiopic-halehame-tig",
"ethiopic-numeric", "ew-resize", "expanded", "extends", "extra-condensed",
"extra-expanded", "fantasy", "fast", "fill", "fixed", "flat", "flex", "footnotes",
"forwards", "from", "geometricPrecision", "georgian", "graytext", "groove",
"gujarati", "gurmukhi", "hand", "hangul", "hangul-consonant", "hebrew",
"help", "hidden", "hide", "higher", "highlight", "highlighttext",
"hiragana", "hiragana-iroha", "horizontal", "hsl", "hsla", "icon", "ignore",
"inactiveborder", "inactivecaption", "inactivecaptiontext", "infinite",
"infobackground", "infotext", "inherit", "initial", "inline", "inline-axis",
"inline-block", "inline-flex", "inline-table", "inset", "inside", "intrinsic", "invert",
"italic", "japanese-formal", "japanese-informal", "justify", "kannada",
"katakana", "katakana-iroha", "keep-all", "khmer",
"korean-hangul-formal", "korean-hanja-formal", "korean-hanja-informal",
"landscape", "lao", "large", "larger", "left", "level", "lighter",
"line-through", "linear", "linear-gradient", "lines", "list-item", "listbox", "listitem",
"local", "logical", "loud", "lower", "lower-alpha", "lower-armenian",
"lower-greek", "lower-hexadecimal", "lower-latin", "lower-norwegian",
"lower-roman", "lowercase", "ltr", "malayalam", "match", "matrix", "matrix3d",
"media-controls-background", "media-current-time-display",
"media-fullscreen-button", "media-mute-button", "media-play-button",
"media-return-to-realtime-button", "media-rewind-button",
"media-seek-back-button", "media-seek-forward-button", "media-slider",
"media-sliderthumb", "media-time-remaining-display", "media-volume-slider",
"media-volume-slider-container", "media-volume-sliderthumb", "medium",
"menu", "menulist", "menulist-button", "menulist-text",
"menulist-textfield", "menutext", "message-box", "middle", "min-intrinsic",
"mix", "mongolian", "monospace", "move", "multiple", "myanmar", "n-resize",
"narrower", "ne-resize", "nesw-resize", "no-close-quote", "no-drop",
"no-open-quote", "no-repeat", "none", "normal", "not-allowed", "nowrap",
"ns-resize", "numbers", "numeric", "nw-resize", "nwse-resize", "oblique", "octal", "open-quote",
"optimizeLegibility", "optimizeSpeed", "oriya", "oromo", "outset",
"outside", "outside-shape", "overlay", "overline", "padding", "padding-box",
"painted", "page", "paused", "persian", "perspective", "plus-darker", "plus-lighter",
"pointer", "polygon", "portrait", "pre", "pre-line", "pre-wrap", "preserve-3d",
"progress", "push-button", "radial-gradient", "radio", "read-only",
"read-write", "read-write-plaintext-only", "rectangle", "region",
"relative", "repeat", "repeating-linear-gradient",
"repeating-radial-gradient", "repeat-x", "repeat-y", "reset", "reverse",
"rgb", "rgba", "ridge", "right", "rotate", "rotate3d", "rotateX", "rotateY",
"rotateZ", "round", "row-resize", "rtl", "run-in", "running",
"s-resize", "sans-serif", "scale", "scale3d", "scaleX", "scaleY", "scaleZ",
"scroll", "scrollbar", "se-resize", "searchfield",
"searchfield-cancel-button", "searchfield-decoration",
"searchfield-results-button", "searchfield-results-decoration",
"semi-condensed", "semi-expanded", "separate", "serif", "show", "sidama",
"simp-chinese-formal", "simp-chinese-informal", "single",
"skew", "skewX", "skewY", "skip-white-space", "slide", "slider-horizontal",
"slider-vertical", "sliderthumb-horizontal", "sliderthumb-vertical", "slow",
"small", "small-caps", "small-caption", "smaller", "solid", "somali",
"source-atop", "source-in", "source-out", "source-over", "space", "spell-out", "square",
"square-button", "start", "static", "status-bar", "stretch", "stroke", "sub",
"subpixel-antialiased", "super", "sw-resize", "symbolic", "symbols", "table",
"table-caption", "table-cell", "table-column", "table-column-group",
"table-footer-group", "table-header-group", "table-row", "table-row-group",
"tamil",
"telugu", "text", "text-bottom", "text-top", "textarea", "textfield", "thai",
"thick", "thin", "threeddarkshadow", "threedface", "threedhighlight",
"threedlightshadow", "threedshadow", "tibetan", "tigre", "tigrinya-er",
"tigrinya-er-abegede", "tigrinya-et", "tigrinya-et-abegede", "to", "top",
"trad-chinese-formal", "trad-chinese-informal",
"translate", "translate3d", "translateX", "translateY", "translateZ",
"transparent", "ultra-condensed", "ultra-expanded", "underline", "up",
"upper-alpha", "upper-armenian", "upper-greek", "upper-hexadecimal",
"upper-latin", "upper-norwegian", "upper-roman", "uppercase", "urdu", "url",
"var", "vertical", "vertical-text", "visible", "visibleFill", "visiblePainted",
"visibleStroke", "visual", "w-resize", "wait", "wave", "wider",
"window", "windowframe", "windowtext", "words", "x-large", "x-small", "xor",
"xx-large", "xx-small"
], valueKeywords = keySet(valueKeywords_);
var allWords = documentTypes_.concat(mediaTypes_).concat(mediaFeatures_).concat(propertyKeywords_)
.concat(nonStandardPropertyKeywords_).concat(colorKeywords_).concat(valueKeywords_);
CodeMirror.registerHelper("hintWords", "css", allWords);
function tokenCComment(stream, state) {
var maybeEnd = false, ch;
while ((ch = stream.next()) != null) {
if (maybeEnd && ch == "/") {
state.tokenize = null;
break;
}
maybeEnd = (ch == "*");
}
return ["comment", "comment"];
}
CodeMirror.defineMIME("text/css", {
documentTypes: documentTypes,
mediaTypes: mediaTypes,
mediaFeatures: mediaFeatures,
propertyKeywords: propertyKeywords,
nonStandardPropertyKeywords: nonStandardPropertyKeywords,
fontProperties: fontProperties,
counterDescriptors: counterDescriptors,
colorKeywords: colorKeywords,
valueKeywords: valueKeywords,
tokenHooks: {
"/": function(stream, state) {
if (!stream.eat("*")) return false;
state.tokenize = tokenCComment;
return tokenCComment(stream, state);
}
},
name: "css"
});
CodeMirror.defineMIME("text/x-scss", {
mediaTypes: mediaTypes,
mediaFeatures: mediaFeatures,
propertyKeywords: propertyKeywords,
nonStandardPropertyKeywords: nonStandardPropertyKeywords,
colorKeywords: colorKeywords,
valueKeywords: valueKeywords,
fontProperties: fontProperties,
allowNested: true,
tokenHooks: {
"/": function(stream, state) {
if (stream.eat("/")) {
stream.skipToEnd();
return ["comment", "comment"];
} else if (stream.eat("*")) {
state.tokenize = tokenCComment;
return tokenCComment(stream, state);
} else {
return ["operator", "operator"];
}
},
":": function(stream) {
if (stream.match(/\s*\{/))
return [null, "{"];
return false;
},
"$": function(stream) {
stream.match(/^[\w-]+/);
if (stream.match(/^\s*:/, false))
return ["variable-2", "variable-definition"];
return ["variable-2", "variable"];
},
"#": function(stream) {
if (!stream.eat("{")) return false;
return [null, "interpolation"];
}
},
name: "css",
helperType: "scss"
});
CodeMirror.defineMIME("text/x-less", {
mediaTypes: mediaTypes,
mediaFeatures: mediaFeatures,
propertyKeywords: propertyKeywords,
nonStandardPropertyKeywords: nonStandardPropertyKeywords,
colorKeywords: colorKeywords,
valueKeywords: valueKeywords,
fontProperties: fontProperties,
allowNested: true,
tokenHooks: {
"/": function(stream, state) {
if (stream.eat("/")) {
stream.skipToEnd();
return ["comment", "comment"];
} else if (stream.eat("*")) {
state.tokenize = tokenCComment;
return tokenCComment(stream, state);
} else {
return ["operator", "operator"];
}
},
"@": function(stream) {
if (stream.eat("{")) return [null, "interpolation"];
if (stream.match(/^(charset|document|font-face|import|(-(moz|ms|o|webkit)-)?keyframes|media|namespace|page|supports)\b/, false)) return false;
stream.eatWhile(/[\w\\\-]/);
if (stream.match(/^\s*:/, false))
return ["variable-2", "variable-definition"];
return ["variable-2", "variable"];
},
"&": function() {
return ["atom", "atom"];
}
},
name: "css",
helperType: "less"
});
});

View File

@ -0,0 +1,121 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("../xml/xml"), require("../javascript/javascript"), require("../css/css"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "../xml/xml", "../javascript/javascript", "../css/css"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineMode("htmlmixed", function(config, parserConfig) {
var htmlMode = CodeMirror.getMode(config, {name: "xml",
htmlMode: true,
multilineTagIndentFactor: parserConfig.multilineTagIndentFactor,
multilineTagIndentPastTag: parserConfig.multilineTagIndentPastTag});
var cssMode = CodeMirror.getMode(config, "css");
var scriptTypes = [], scriptTypesConf = parserConfig && parserConfig.scriptTypes;
scriptTypes.push({matches: /^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^$/i,
mode: CodeMirror.getMode(config, "javascript")});
if (scriptTypesConf) for (var i = 0; i < scriptTypesConf.length; ++i) {
var conf = scriptTypesConf[i];
scriptTypes.push({matches: conf.matches, mode: conf.mode && CodeMirror.getMode(config, conf.mode)});
}
scriptTypes.push({matches: /./,
mode: CodeMirror.getMode(config, "text/plain")});
function html(stream, state) {
var tagName = state.htmlState.tagName;
if (tagName) tagName = tagName.toLowerCase();
var style = htmlMode.token(stream, state.htmlState);
if (tagName == "script" && /\btag\b/.test(style) && stream.current() == ">") {
// Script block: mode to change to depends on type attribute
var scriptType = stream.string.slice(Math.max(0, stream.pos - 100), stream.pos).match(/\btype\s*=\s*("[^"]+"|'[^']+'|\S+)[^<]*$/i);
scriptType = scriptType ? scriptType[1] : "";
if (scriptType && /[\"\']/.test(scriptType.charAt(0))) scriptType = scriptType.slice(1, scriptType.length - 1);
for (var i = 0; i < scriptTypes.length; ++i) {
var tp = scriptTypes[i];
if (typeof tp.matches == "string" ? scriptType == tp.matches : tp.matches.test(scriptType)) {
if (tp.mode) {
state.token = script;
state.localMode = tp.mode;
state.localState = tp.mode.startState && tp.mode.startState(htmlMode.indent(state.htmlState, ""));
}
break;
}
}
} else if (tagName == "style" && /\btag\b/.test(style) && stream.current() == ">") {
state.token = css;
state.localMode = cssMode;
state.localState = cssMode.startState(htmlMode.indent(state.htmlState, ""));
}
return style;
}
function maybeBackup(stream, pat, style) {
var cur = stream.current();
var close = cur.search(pat);
if (close > -1) stream.backUp(cur.length - close);
else if (cur.match(/<\/?$/)) {
stream.backUp(cur.length);
if (!stream.match(pat, false)) stream.match(cur);
}
return style;
}
function script(stream, state) {
if (stream.match(/^<\/\s*script\s*>/i, false)) {
state.token = html;
state.localState = state.localMode = null;
return null;
}
return maybeBackup(stream, /<\/\s*script\s*>/,
state.localMode.token(stream, state.localState));
}
function css(stream, state) {
if (stream.match(/^<\/\s*style\s*>/i, false)) {
state.token = html;
state.localState = state.localMode = null;
return null;
}
return maybeBackup(stream, /<\/\s*style\s*>/,
cssMode.token(stream, state.localState));
}
return {
startState: function() {
var state = htmlMode.startState();
return {token: html, localMode: null, localState: null, htmlState: state};
},
copyState: function(state) {
if (state.localState)
var local = CodeMirror.copyState(state.localMode, state.localState);
return {token: state.token, localMode: state.localMode, localState: local,
htmlState: CodeMirror.copyState(htmlMode, state.htmlState)};
},
token: function(stream, state) {
return state.token(stream, state);
},
indent: function(state, textAfter) {
if (!state.localMode || /^\s*<\//.test(textAfter))
return htmlMode.indent(state.htmlState, textAfter);
else if (state.localMode.indent)
return state.localMode.indent(state.localState, textAfter);
else
return CodeMirror.Pass;
},
innerMode: function(state) {
return {state: state.localState || state.htmlState, mode: state.localMode || htmlMode};
}
};
}, "xml", "javascript", "css");
CodeMirror.defineMIME("text/html", "htmlmixed");
});

View File

@ -0,0 +1,701 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
// TODO actually recognize syntax of TypeScript constructs
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineMode("javascript", function(config, parserConfig) {
var indentUnit = config.indentUnit;
var statementIndent = parserConfig.statementIndent;
var jsonldMode = parserConfig.jsonld;
var jsonMode = parserConfig.json || jsonldMode;
var isTS = parserConfig.typescript;
var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;
// Tokenizer
var keywords = function(){
function kw(type) {return {type: type, style: "keyword"};}
var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
var operator = kw("operator"), atom = {type: "atom", style: "atom"};
var jsKeywords = {
"if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
"return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C, "debugger": C,
"var": kw("var"), "const": kw("var"), "let": kw("var"),
"function": kw("function"), "catch": kw("catch"),
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
"in": operator, "typeof": operator, "instanceof": operator,
"true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
"this": kw("this"), "module": kw("module"), "class": kw("class"), "super": kw("atom"),
"yield": C, "export": kw("export"), "import": kw("import"), "extends": C
};
// Extend the 'normal' keywords with the TypeScript language extensions
if (isTS) {
var type = {type: "variable", style: "variable-3"};
var tsKeywords = {
// object-like things
"interface": kw("interface"),
"extends": kw("extends"),
"constructor": kw("constructor"),
// scope modifiers
"public": kw("public"),
"private": kw("private"),
"protected": kw("protected"),
"static": kw("static"),
// types
"string": type, "number": type, "bool": type, "any": type
};
for (var attr in tsKeywords) {
jsKeywords[attr] = tsKeywords[attr];
}
}
return jsKeywords;
}();
var isOperatorChar = /[+\-*&%=<>!?|~^]/;
var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;
function readRegexp(stream) {
var escaped = false, next, inSet = false;
while ((next = stream.next()) != null) {
if (!escaped) {
if (next == "/" && !inSet) return;
if (next == "[") inSet = true;
else if (inSet && next == "]") inSet = false;
}
escaped = !escaped && next == "\\";
}
}
// Used as scratch variables to communicate multiple values without
// consing up tons of objects.
var type, content;
function ret(tp, style, cont) {
type = tp; content = cont;
return style;
}
function tokenBase(stream, state) {
var ch = stream.next();
if (ch == '"' || ch == "'") {
state.tokenize = tokenString(ch);
return state.tokenize(stream, state);
} else if (ch == "." && stream.match(/^\d+(?:[eE][+\-]?\d+)?/)) {
return ret("number", "number");
} else if (ch == "." && stream.match("..")) {
return ret("spread", "meta");
} else if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
return ret(ch);
} else if (ch == "=" && stream.eat(">")) {
return ret("=>", "operator");
} else if (ch == "0" && stream.eat(/x/i)) {
stream.eatWhile(/[\da-f]/i);
return ret("number", "number");
} else if (/\d/.test(ch)) {
stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
return ret("number", "number");
} else if (ch == "/") {
if (stream.eat("*")) {
state.tokenize = tokenComment;
return tokenComment(stream, state);
} else if (stream.eat("/")) {
stream.skipToEnd();
return ret("comment", "comment");
} else if (state.lastType == "operator" || state.lastType == "keyword c" ||
state.lastType == "sof" || /^[\[{}\(,;:]$/.test(state.lastType)) {
readRegexp(stream);
stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/);
return ret("regexp", "string-2");
} else {
stream.eatWhile(isOperatorChar);
return ret("operator", "operator", stream.current());
}
} else if (ch == "`") {
state.tokenize = tokenQuasi;
return tokenQuasi(stream, state);
} else if (ch == "#") {
stream.skipToEnd();
return ret("error", "error");
} else if (isOperatorChar.test(ch)) {
stream.eatWhile(isOperatorChar);
return ret("operator", "operator", stream.current());
} else if (wordRE.test(ch)) {
stream.eatWhile(wordRE);
var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
return (known && state.lastType != ".") ? ret(known.type, known.style, word) :
ret("variable", "variable", word);
}
}
function tokenString(quote) {
return function(stream, state) {
var escaped = false, next;
if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){
state.tokenize = tokenBase;
return ret("jsonld-keyword", "meta");
}
while ((next = stream.next()) != null) {
if (next == quote && !escaped) break;
escaped = !escaped && next == "\\";
}
if (!escaped) state.tokenize = tokenBase;
return ret("string", "string");
};
}
function tokenComment(stream, state) {
var maybeEnd = false, ch;
while (ch = stream.next()) {
if (ch == "/" && maybeEnd) {
state.tokenize = tokenBase;
break;
}
maybeEnd = (ch == "*");
}
return ret("comment", "comment");
}
function tokenQuasi(stream, state) {
var escaped = false, next;
while ((next = stream.next()) != null) {
if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) {
state.tokenize = tokenBase;
break;
}
escaped = !escaped && next == "\\";
}
return ret("quasi", "string-2", stream.current());
}
var brackets = "([{}])";
// This is a crude lookahead trick to try and notice that we're
// parsing the argument patterns for a fat-arrow function before we
// actually hit the arrow token. It only works if the arrow is on
// the same line as the arguments and there's no strange noise
// (comments) in between. Fallback is to only notice when we hit the
// arrow, and not declare the arguments as locals for the arrow
// body.
function findFatArrow(stream, state) {
if (state.fatArrowAt) state.fatArrowAt = null;
var arrow = stream.string.indexOf("=>", stream.start);
if (arrow < 0) return;
var depth = 0, sawSomething = false;
for (var pos = arrow - 1; pos >= 0; --pos) {
var ch = stream.string.charAt(pos);
var bracket = brackets.indexOf(ch);
if (bracket >= 0 && bracket < 3) {
if (!depth) { ++pos; break; }
if (--depth == 0) break;
} else if (bracket >= 3 && bracket < 6) {
++depth;
} else if (wordRE.test(ch)) {
sawSomething = true;
} else if (/["'\/]/.test(ch)) {
return;
} else if (sawSomething && !depth) {
++pos;
break;
}
}
if (sawSomething && !depth) state.fatArrowAt = pos;
}
// Parser
var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true, "this": true, "jsonld-keyword": true};
function JSLexical(indented, column, type, align, prev, info) {
this.indented = indented;
this.column = column;
this.type = type;
this.prev = prev;
this.info = info;
if (align != null) this.align = align;
}
function inScope(state, varname) {
for (var v = state.localVars; v; v = v.next)
if (v.name == varname) return true;
for (var cx = state.context; cx; cx = cx.prev) {
for (var v = cx.vars; v; v = v.next)
if (v.name == varname) return true;
}
}
function parseJS(state, style, type, content, stream) {
var cc = state.cc;
// Communicate our context to the combinators.
// (Less wasteful than consing up a hundred closures on every call.)
cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style;
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = true;
while(true) {
var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
if (combinator(type, content)) {
while(cc.length && cc[cc.length - 1].lex)
cc.pop()();
if (cx.marked) return cx.marked;
if (type == "variable" && inScope(state, content)) return "variable-2";
return style;
}
}
}
// Combinator utils
var cx = {state: null, column: null, marked: null, cc: null};
function pass() {
for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
}
function cont() {
pass.apply(null, arguments);
return true;
}
function register(varname) {
function inList(list) {
for (var v = list; v; v = v.next)
if (v.name == varname) return true;
return false;
}
var state = cx.state;
if (state.context) {
cx.marked = "def";
if (inList(state.localVars)) return;
state.localVars = {name: varname, next: state.localVars};
} else {
if (inList(state.globalVars)) return;
if (parserConfig.globalVars)
state.globalVars = {name: varname, next: state.globalVars};
}
}
// Combinators
var defaultVars = {name: "this", next: {name: "arguments"}};
function pushcontext() {
cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
cx.state.localVars = defaultVars;
}
function popcontext() {
cx.state.localVars = cx.state.context.vars;
cx.state.context = cx.state.context.prev;
}
function pushlex(type, info) {
var result = function() {
var state = cx.state, indent = state.indented;
if (state.lexical.type == "stat") indent = state.lexical.indented;
else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev)
indent = outer.indented;
state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
};
result.lex = true;
return result;
}
function poplex() {
var state = cx.state;
if (state.lexical.prev) {
if (state.lexical.type == ")")
state.indented = state.lexical.indented;
state.lexical = state.lexical.prev;
}
}
poplex.lex = true;
function expect(wanted) {
function exp(type) {
if (type == wanted) return cont();
else if (wanted == ";") return pass();
else return cont(exp);
};
return exp;
}
function statement(type, value) {
if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex);
if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
if (type == "{") return cont(pushlex("}"), block, poplex);
if (type == ";") return cont();
if (type == "if") {
if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
cx.state.cc.pop()();
return cont(pushlex("form"), expression, statement, poplex, maybeelse);
}
if (type == "function") return cont(functiondef);
if (type == "for") return cont(pushlex("form"), forspec, statement, poplex);
if (type == "variable") return cont(pushlex("stat"), maybelabel);
if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
block, poplex, poplex);
if (type == "case") return cont(expression, expect(":"));
if (type == "default") return cont(expect(":"));
if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
statement, poplex, popcontext);
if (type == "module") return cont(pushlex("form"), pushcontext, afterModule, popcontext, poplex);
if (type == "class") return cont(pushlex("form"), className, poplex);
if (type == "export") return cont(pushlex("form"), afterExport, poplex);
if (type == "import") return cont(pushlex("form"), afterImport, poplex);
return pass(pushlex("stat"), expression, expect(";"), poplex);
}
function expression(type) {
return expressionInner(type, false);
}
function expressionNoComma(type) {
return expressionInner(type, true);
}
function expressionInner(type, noComma) {
if (cx.state.fatArrowAt == cx.stream.start) {
var body = noComma ? arrowBodyNoComma : arrowBody;
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(pattern, ")"), poplex, expect("=>"), body, popcontext);
else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
}
var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
if (type == "function") return cont(functiondef, maybeop);
if (type == "keyword c") return cont(noComma ? maybeexpressionNoComma : maybeexpression);
if (type == "(") return cont(pushlex(")"), maybeexpression, comprehension, expect(")"), poplex, maybeop);
if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
if (type == "{") return contCommasep(objprop, "}", null, maybeop);
if (type == "quasi") { return pass(quasi, maybeop); }
return cont();
}
function maybeexpression(type) {
if (type.match(/[;\}\)\],]/)) return pass();
return pass(expression);
}
function maybeexpressionNoComma(type) {
if (type.match(/[;\}\)\],]/)) return pass();
return pass(expressionNoComma);
}
function maybeoperatorComma(type, value) {
if (type == ",") return cont(expression);
return maybeoperatorNoComma(type, value, false);
}
function maybeoperatorNoComma(type, value, noComma) {
var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
var expr = noComma == false ? expression : expressionNoComma;
if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
if (type == "operator") {
if (/\+\+|--/.test(value)) return cont(me);
if (value == "?") return cont(expression, expect(":"), expr);
return cont(expr);
}
if (type == "quasi") { return pass(quasi, me); }
if (type == ";") return;
if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
if (type == ".") return cont(property, me);
if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
}
function quasi(type, value) {
if (type != "quasi") return pass();
if (value.slice(value.length - 2) != "${") return cont(quasi);
return cont(expression, continueQuasi);
}
function continueQuasi(type) {
if (type == "}") {
cx.marked = "string-2";
cx.state.tokenize = tokenQuasi;
return cont(quasi);
}
}
function arrowBody(type) {
findFatArrow(cx.stream, cx.state);
return pass(type == "{" ? statement : expression);
}
function arrowBodyNoComma(type) {
findFatArrow(cx.stream, cx.state);
return pass(type == "{" ? statement : expressionNoComma);
}
function maybelabel(type) {
if (type == ":") return cont(poplex, statement);
return pass(maybeoperatorComma, expect(";"), poplex);
}
function property(type) {
if (type == "variable") {cx.marked = "property"; return cont();}
}
function objprop(type, value) {
if (type == "variable" || cx.style == "keyword") {
cx.marked = "property";
if (value == "get" || value == "set") return cont(getterSetter);
return cont(afterprop);
} else if (type == "number" || type == "string") {
cx.marked = jsonldMode ? "property" : (cx.style + " property");
return cont(afterprop);
} else if (type == "jsonld-keyword") {
return cont(afterprop);
} else if (type == "[") {
return cont(expression, expect("]"), afterprop);
}
}
function getterSetter(type) {
if (type != "variable") return pass(afterprop);
cx.marked = "property";
return cont(functiondef);
}
function afterprop(type) {
if (type == ":") return cont(expressionNoComma);
if (type == "(") return pass(functiondef);
}
function commasep(what, end) {
function proceed(type) {
if (type == ",") {
var lex = cx.state.lexical;
if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
return cont(what, proceed);
}
if (type == end) return cont();
return cont(expect(end));
}
return function(type) {
if (type == end) return cont();
return pass(what, proceed);
};
}
function contCommasep(what, end, info) {
for (var i = 3; i < arguments.length; i++)
cx.cc.push(arguments[i]);
return cont(pushlex(end, info), commasep(what, end), poplex);
}
function block(type) {
if (type == "}") return cont();
return pass(statement, block);
}
function maybetype(type) {
if (isTS && type == ":") return cont(typedef);
}
function typedef(type) {
if (type == "variable"){cx.marked = "variable-3"; return cont();}
}
function vardef() {
return pass(pattern, maybetype, maybeAssign, vardefCont);
}
function pattern(type, value) {
if (type == "variable") { register(value); return cont(); }
if (type == "[") return contCommasep(pattern, "]");
if (type == "{") return contCommasep(proppattern, "}");
}
function proppattern(type, value) {
if (type == "variable" && !cx.stream.match(/^\s*:/, false)) {
register(value);
return cont(maybeAssign);
}
if (type == "variable") cx.marked = "property";
return cont(expect(":"), pattern, maybeAssign);
}
function maybeAssign(_type, value) {
if (value == "=") return cont(expressionNoComma);
}
function vardefCont(type) {
if (type == ",") return cont(vardef);
}
function maybeelse(type, value) {
if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex);
}
function forspec(type) {
if (type == "(") return cont(pushlex(")"), forspec1, expect(")"), poplex);
}
function forspec1(type) {
if (type == "var") return cont(vardef, expect(";"), forspec2);
if (type == ";") return cont(forspec2);
if (type == "variable") return cont(formaybeinof);
return pass(expression, expect(";"), forspec2);
}
function formaybeinof(_type, value) {
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
return cont(maybeoperatorComma, forspec2);
}
function forspec2(type, value) {
if (type == ";") return cont(forspec3);
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression); }
return pass(expression, expect(";"), forspec3);
}
function forspec3(type) {
if (type != ")") cont(expression);
}
function functiondef(type, value) {
if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
if (type == "variable") {register(value); return cont(functiondef);}
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, statement, popcontext);
}
function funarg(type) {
if (type == "spread") return cont(funarg);
return pass(pattern, maybetype);
}
function className(type, value) {
if (type == "variable") {register(value); return cont(classNameAfter);}
}
function classNameAfter(type, value) {
if (value == "extends") return cont(expression, classNameAfter);
if (type == "{") return cont(pushlex("}"), classBody, poplex);
}
function classBody(type, value) {
if (type == "variable" || cx.style == "keyword") {
if (value == "static") {
cx.marked = "keyword";
return cont(classBody);
}
cx.marked = "property";
if (value == "get" || value == "set") return cont(classGetterSetter, functiondef, classBody);
return cont(functiondef, classBody);
}
if (value == "*") {
cx.marked = "keyword";
return cont(classBody);
}
if (type == ";") return cont(classBody);
if (type == "}") return cont();
}
function classGetterSetter(type) {
if (type != "variable") return pass();
cx.marked = "property";
return cont();
}
function afterModule(type, value) {
if (type == "string") return cont(statement);
if (type == "variable") { register(value); return cont(maybeFrom); }
}
function afterExport(_type, value) {
if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
return pass(statement);
}
function afterImport(type) {
if (type == "string") return cont();
return pass(importSpec, maybeFrom);
}
function importSpec(type, value) {
if (type == "{") return contCommasep(importSpec, "}");
if (type == "variable") register(value);
if (value == "*") cx.marked = "keyword";
return cont(maybeAs);
}
function maybeAs(_type, value) {
if (value == "as") { cx.marked = "keyword"; return cont(importSpec); }
}
function maybeFrom(_type, value) {
if (value == "from") { cx.marked = "keyword"; return cont(expression); }
}
function arrayLiteral(type) {
if (type == "]") return cont();
return pass(expressionNoComma, maybeArrayComprehension);
}
function maybeArrayComprehension(type) {
if (type == "for") return pass(comprehension, expect("]"));
if (type == ",") return cont(commasep(maybeexpressionNoComma, "]"));
return pass(commasep(expressionNoComma, "]"));
}
function comprehension(type) {
if (type == "for") return cont(forspec, comprehension);
if (type == "if") return cont(expression, comprehension);
}
function isContinuedStatement(state, textAfter) {
return state.lastType == "operator" || state.lastType == "," ||
isOperatorChar.test(textAfter.charAt(0)) ||
/[,.]/.test(textAfter.charAt(0));
}
// Interface
return {
startState: function(basecolumn) {
var state = {
tokenize: tokenBase,
lastType: "sof",
cc: [],
lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
localVars: parserConfig.localVars,
context: parserConfig.localVars && {vars: parserConfig.localVars},
indented: 0
};
if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
state.globalVars = parserConfig.globalVars;
return state;
},
token: function(stream, state) {
if (stream.sol()) {
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = false;
state.indented = stream.indentation();
findFatArrow(stream, state);
}
if (state.tokenize != tokenComment && stream.eatSpace()) return null;
var style = state.tokenize(stream, state);
if (type == "comment") return style;
state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
return parseJS(state, style, type, content, stream);
},
indent: function(state, textAfter) {
if (state.tokenize == tokenComment) return CodeMirror.Pass;
if (state.tokenize != tokenBase) return 0;
var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical;
// Kludge to prevent 'maybelse' from blocking lexical scope pops
if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {
var c = state.cc[i];
if (c == poplex) lexical = lexical.prev;
else if (c != maybeelse) break;
}
if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev;
if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
lexical = lexical.prev;
var type = lexical.type, closing = firstChar == type;
if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info + 1 : 0);
else if (type == "form" && firstChar == "{") return lexical.indented;
else if (type == "form") return lexical.indented + indentUnit;
else if (type == "stat")
return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0);
else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
else if (lexical.align) return lexical.column + (closing ? 0 : 1);
else return lexical.indented + (closing ? 0 : indentUnit);
},
electricInput: /^\s*(?:case .*?:|default:|\{|\})$/,
blockCommentStart: jsonMode ? null : "/*",
blockCommentEnd: jsonMode ? null : "*/",
lineComment: jsonMode ? null : "//",
fold: "brace",
closeBrackets: "()[]{}''\"\"``",
helperType: jsonMode ? "json" : "javascript",
jsonldMode: jsonldMode,
jsonMode: jsonMode
};
});
CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/);
CodeMirror.defineMIME("text/javascript", "javascript");
CodeMirror.defineMIME("text/ecmascript", "javascript");
CodeMirror.defineMIME("application/javascript", "javascript");
CodeMirror.defineMIME("application/x-javascript", "javascript");
CodeMirror.defineMIME("application/ecmascript", "javascript");
CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
CodeMirror.defineMIME("application/x-json", {name: "javascript", json: true});
CodeMirror.defineMIME("application/ld+json", {name: "javascript", jsonld: true});
CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
});

View File

@ -0,0 +1,120 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
var ie_lt8 = /MSIE \d/.test(navigator.userAgent) &&
(document.documentMode == null || document.documentMode < 8);
var Pos = CodeMirror.Pos;
var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
function findMatchingBracket(cm, where, strict, config) {
var line = cm.getLineHandle(where.line), pos = where.ch - 1;
var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];
if (!match) return null;
var dir = match.charAt(1) == ">" ? 1 : -1;
if (strict && (dir > 0) != (pos == where.ch)) return null;
var style = cm.getTokenTypeAt(Pos(where.line, pos + 1));
var found = scanForBracket(cm, Pos(where.line, pos + (dir > 0 ? 1 : 0)), dir, style || null, config);
if (found == null) return null;
return {from: Pos(where.line, pos), to: found && found.pos,
match: found && found.ch == match.charAt(0), forward: dir > 0};
}
// bracketRegex is used to specify which type of bracket to scan
// should be a regexp, e.g. /[[\]]/
//
// Note: If "where" is on an open bracket, then this bracket is ignored.
//
// Returns false when no bracket was found, null when it reached
// maxScanLines and gave up
function scanForBracket(cm, where, dir, style, config) {
var maxScanLen = (config && config.maxScanLineLength) || 10000;
var maxScanLines = (config && config.maxScanLines) || 1000;
var stack = [];
var re = config && config.bracketRegex ? config.bracketRegex : /[(){}[\]]/;
var lineEnd = dir > 0 ? Math.min(where.line + maxScanLines, cm.lastLine() + 1)
: Math.max(cm.firstLine() - 1, where.line - maxScanLines);
for (var lineNo = where.line; lineNo != lineEnd; lineNo += dir) {
var line = cm.getLine(lineNo);
if (!line) continue;
var pos = dir > 0 ? 0 : line.length - 1, end = dir > 0 ? line.length : -1;
if (line.length > maxScanLen) continue;
if (lineNo == where.line) pos = where.ch - (dir < 0 ? 1 : 0);
for (; pos != end; pos += dir) {
var ch = line.charAt(pos);
if (re.test(ch) && (style === undefined || cm.getTokenTypeAt(Pos(lineNo, pos + 1)) == style)) {
var match = matching[ch];
if ((match.charAt(1) == ">") == (dir > 0)) stack.push(ch);
else if (!stack.length) return {pos: Pos(lineNo, pos), ch: ch};
else stack.pop();
}
}
}
return lineNo - dir == (dir > 0 ? cm.lastLine() : cm.firstLine()) ? false : null;
}
function matchBrackets(cm, autoclear, config) {
// Disable brace matching in long lines, since it'll cause hugely slow updates
var maxHighlightLen = cm.state.matchBrackets.maxHighlightLineLength || 1000;
var marks = [], ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
var match = ranges[i].empty() && findMatchingBracket(cm, ranges[i].head, false, config);
if (match && cm.getLine(match.from.line).length <= maxHighlightLen) {
var style = match.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
marks.push(cm.markText(match.from, Pos(match.from.line, match.from.ch + 1), {className: style}));
if (match.to && cm.getLine(match.to.line).length <= maxHighlightLen)
marks.push(cm.markText(match.to, Pos(match.to.line, match.to.ch + 1), {className: style}));
}
}
if (marks.length) {
// Kludge to work around the IE bug from issue #1193, where text
// input stops going to the textare whever this fires.
if (ie_lt8 && cm.state.focused) cm.focus();
var clear = function() {
cm.operation(function() {
for (var i = 0; i < marks.length; i++) marks[i].clear();
});
};
if (autoclear) setTimeout(clear, 800);
else return clear;
}
}
var currentlyHighlighted = null;
function doMatchBrackets(cm) {
cm.operation(function() {
if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;}
currentlyHighlighted = matchBrackets(cm, false, cm.state.matchBrackets);
});
}
CodeMirror.defineOption("matchBrackets", false, function(cm, val, old) {
if (old && old != CodeMirror.Init)
cm.off("cursorActivity", doMatchBrackets);
if (val) {
cm.state.matchBrackets = typeof val == "object" ? val : {};
cm.on("cursorActivity", doMatchBrackets);
}
});
CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);});
CodeMirror.defineExtension("findMatchingBracket", function(pos, strict, config){
return findMatchingBracket(this, pos, strict, config);
});
CodeMirror.defineExtension("scanForBracket", function(pos, dir, style, config){
return scanForBracket(this, pos, dir, style, config);
});
});

384
web/dep/codemirror/xml.js Normal file
View File

@ -0,0 +1,384 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineMode("xml", function(config, parserConfig) {
var indentUnit = config.indentUnit;
var multilineTagIndentFactor = parserConfig.multilineTagIndentFactor || 1;
var multilineTagIndentPastTag = parserConfig.multilineTagIndentPastTag;
if (multilineTagIndentPastTag == null) multilineTagIndentPastTag = true;
var Kludges = parserConfig.htmlMode ? {
autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true,
'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true,
'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true,
'track': true, 'wbr': true, 'menuitem': true},
implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true,
'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true,
'th': true, 'tr': true},
contextGrabbers: {
'dd': {'dd': true, 'dt': true},
'dt': {'dd': true, 'dt': true},
'li': {'li': true},
'option': {'option': true, 'optgroup': true},
'optgroup': {'optgroup': true},
'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true,
'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true,
'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true,
'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true,
'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true},
'rp': {'rp': true, 'rt': true},
'rt': {'rp': true, 'rt': true},
'tbody': {'tbody': true, 'tfoot': true},
'td': {'td': true, 'th': true},
'tfoot': {'tbody': true},
'th': {'td': true, 'th': true},
'thead': {'tbody': true, 'tfoot': true},
'tr': {'tr': true}
},
doNotIndent: {"pre": true},
allowUnquoted: true,
allowMissing: true,
caseFold: true
} : {
autoSelfClosers: {},
implicitlyClosed: {},
contextGrabbers: {},
doNotIndent: {},
allowUnquoted: false,
allowMissing: false,
caseFold: false
};
var alignCDATA = parserConfig.alignCDATA;
// Return variables for tokenizers
var type, setStyle;
function inText(stream, state) {
function chain(parser) {
state.tokenize = parser;
return parser(stream, state);
}
var ch = stream.next();
if (ch == "<") {
if (stream.eat("!")) {
if (stream.eat("[")) {
if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>"));
else return null;
} else if (stream.match("--")) {
return chain(inBlock("comment", "-->"));
} else if (stream.match("DOCTYPE", true, true)) {
stream.eatWhile(/[\w\._\-]/);
return chain(doctype(1));
} else {
return null;
}
} else if (stream.eat("?")) {
stream.eatWhile(/[\w\._\-]/);
state.tokenize = inBlock("meta", "?>");
return "meta";
} else {
type = stream.eat("/") ? "closeTag" : "openTag";
state.tokenize = inTag;
return "tag bracket";
}
} else if (ch == "&") {
var ok;
if (stream.eat("#")) {
if (stream.eat("x")) {
ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";");
} else {
ok = stream.eatWhile(/[\d]/) && stream.eat(";");
}
} else {
ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";");
}
return ok ? "atom" : "error";
} else {
stream.eatWhile(/[^&<]/);
return null;
}
}
function inTag(stream, state) {
var ch = stream.next();
if (ch == ">" || (ch == "/" && stream.eat(">"))) {
state.tokenize = inText;
type = ch == ">" ? "endTag" : "selfcloseTag";
return "tag bracket";
} else if (ch == "=") {
type = "equals";
return null;
} else if (ch == "<") {
state.tokenize = inText;
state.state = baseState;
state.tagName = state.tagStart = null;
var next = state.tokenize(stream, state);
return next ? next + " tag error" : "tag error";
} else if (/[\'\"]/.test(ch)) {
state.tokenize = inAttribute(ch);
state.stringStartCol = stream.column();
return state.tokenize(stream, state);
} else {
stream.match(/^[^\s\u00a0=<>\"\']*[^\s\u00a0=<>\"\'\/]/);
return "word";
}
}
function inAttribute(quote) {
var closure = function(stream, state) {
while (!stream.eol()) {
if (stream.next() == quote) {
state.tokenize = inTag;
break;
}
}
return "string";
};
closure.isInAttribute = true;
return closure;
}
function inBlock(style, terminator) {
return function(stream, state) {
while (!stream.eol()) {
if (stream.match(terminator)) {
state.tokenize = inText;
break;
}
stream.next();
}
return style;
};
}
function doctype(depth) {
return function(stream, state) {
var ch;
while ((ch = stream.next()) != null) {
if (ch == "<") {
state.tokenize = doctype(depth + 1);
return state.tokenize(stream, state);
} else if (ch == ">") {
if (depth == 1) {
state.tokenize = inText;
break;
} else {
state.tokenize = doctype(depth - 1);
return state.tokenize(stream, state);
}
}
}
return "meta";
};
}
function Context(state, tagName, startOfLine) {
this.prev = state.context;
this.tagName = tagName;
this.indent = state.indented;
this.startOfLine = startOfLine;
if (Kludges.doNotIndent.hasOwnProperty(tagName) || (state.context && state.context.noIndent))
this.noIndent = true;
}
function popContext(state) {
if (state.context) state.context = state.context.prev;
}
function maybePopContext(state, nextTagName) {
var parentTagName;
while (true) {
if (!state.context) {
return;
}
parentTagName = state.context.tagName;
if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) ||
!Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
return;
}
popContext(state);
}
}
function baseState(type, stream, state) {
if (type == "openTag") {
state.tagStart = stream.column();
return tagNameState;
} else if (type == "closeTag") {
return closeTagNameState;
} else {
return baseState;
}
}
function tagNameState(type, stream, state) {
if (type == "word") {
state.tagName = stream.current();
setStyle = "tag";
return attrState;
} else {
setStyle = "error";
return tagNameState;
}
}
function closeTagNameState(type, stream, state) {
if (type == "word") {
var tagName = stream.current();
if (state.context && state.context.tagName != tagName &&
Kludges.implicitlyClosed.hasOwnProperty(state.context.tagName))
popContext(state);
if (state.context && state.context.tagName == tagName) {
setStyle = "tag";
return closeState;
} else {
setStyle = "tag error";
return closeStateErr;
}
} else {
setStyle = "error";
return closeStateErr;
}
}
function closeState(type, _stream, state) {
if (type != "endTag") {
setStyle = "error";
return closeState;
}
popContext(state);
return baseState;
}
function closeStateErr(type, stream, state) {
setStyle = "error";
return closeState(type, stream, state);
}
function attrState(type, _stream, state) {
if (type == "word") {
setStyle = "attribute";
return attrEqState;
} else if (type == "endTag" || type == "selfcloseTag") {
var tagName = state.tagName, tagStart = state.tagStart;
state.tagName = state.tagStart = null;
if (type == "selfcloseTag" ||
Kludges.autoSelfClosers.hasOwnProperty(tagName)) {
maybePopContext(state, tagName);
} else {
maybePopContext(state, tagName);
state.context = new Context(state, tagName, tagStart == state.indented);
}
return baseState;
}
setStyle = "error";
return attrState;
}
function attrEqState(type, stream, state) {
if (type == "equals") return attrValueState;
if (!Kludges.allowMissing) setStyle = "error";
return attrState(type, stream, state);
}
function attrValueState(type, stream, state) {
if (type == "string") return attrContinuedState;
if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return attrState;}
setStyle = "error";
return attrState(type, stream, state);
}
function attrContinuedState(type, stream, state) {
if (type == "string") return attrContinuedState;
return attrState(type, stream, state);
}
return {
startState: function() {
return {tokenize: inText,
state: baseState,
indented: 0,
tagName: null, tagStart: null,
context: null};
},
token: function(stream, state) {
if (!state.tagName && stream.sol())
state.indented = stream.indentation();
if (stream.eatSpace()) return null;
type = null;
var style = state.tokenize(stream, state);
if ((style || type) && style != "comment") {
setStyle = null;
state.state = state.state(type || style, stream, state);
if (setStyle)
style = setStyle == "error" ? style + " error" : setStyle;
}
return style;
},
indent: function(state, textAfter, fullLine) {
var context = state.context;
// Indent multi-line strings (e.g. css).
if (state.tokenize.isInAttribute) {
if (state.tagStart == state.indented)
return state.stringStartCol + 1;
else
return state.indented + indentUnit;
}
if (context && context.noIndent) return CodeMirror.Pass;
if (state.tokenize != inTag && state.tokenize != inText)
return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0;
// Indent the starts of attribute names.
if (state.tagName) {
if (multilineTagIndentPastTag)
return state.tagStart + state.tagName.length + 2;
else
return state.tagStart + indentUnit * multilineTagIndentFactor;
}
if (alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0;
var tagAfter = textAfter && /^<(\/)?([\w_:\.-]*)/.exec(textAfter);
if (tagAfter && tagAfter[1]) { // Closing tag spotted
while (context) {
if (context.tagName == tagAfter[2]) {
context = context.prev;
break;
} else if (Kludges.implicitlyClosed.hasOwnProperty(context.tagName)) {
context = context.prev;
} else {
break;
}
}
} else if (tagAfter) { // Opening tag spotted
while (context) {
var grabbers = Kludges.contextGrabbers[context.tagName];
if (grabbers && grabbers.hasOwnProperty(tagAfter[2]))
context = context.prev;
else
break;
}
}
while (context && !context.startOfLine)
context = context.prev;
if (context) return context.indent + indentUnit;
else return 0;
},
electricInput: /<\/[\s\w:]+>$/,
blockCommentStart: "<!--",
blockCommentEnd: "-->",
configuration: parserConfig.htmlMode ? "html" : "xml",
helperType: parserConfig.htmlMode ? "html" : "xml"
};
});
CodeMirror.defineMIME("text/xml", "xml");
CodeMirror.defineMIME("application/xml", "xml");
if (!CodeMirror.mimeModes.hasOwnProperty("text/html"))
CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true});
});

5
web/dep/jquery.js vendored Normal file

File diff suppressed because one or more lines are too long

19
web/discover.html Normal file
View File

@ -0,0 +1,19 @@
<h2>Introduction to Data</h2>
<p>Your thoughts are information. Conversation explore interactions of ideas amongst yourself and others. We communicate by creating art via writing, drawing, or composing. Experiences are shared by recording reality.</p>
<p>All these things can be represented by data. If you want to build a tool that enriches any of these, you have to care about data. Specifically, you need a system that can store and move information.</p>
<p>You came to the right place, we will explain how to make a web app for each kind. Starting with the simplest and progressing to ever more complicated projects. It is important to know that everything we use will be based on free and open technology, including this tutorial itself.</p>
<a href="./think.html">Let us begin.</a>
Think
Converse
Contact
Find
Write
Draw
Compose
Record
Automate

55
web/editor.html Normal file
View File

@ -0,0 +1,55 @@
<html>
<body>
<link rel="stylesheet" href="./dep/codemirror/codemirror.css">
<link rel="stylesheet" href="./dep/codemirror/colorforth.css">
<style>
.edit {
border: 1px dashed;
overflow: hidden;
}
.CodeMirror {
float: left;
padding: .5em;
height: auto;
width: 49%;
}
iframe {
float: left;
width: 49%;
border: none;
}
</style>
<textarea style="height: 15em"></textarea>
<iframe></iframe>
<script src="./dep/codemirror/codemirror.js"></script>
<script src="./dep/codemirror/xml.js"></script>
<script src="./dep/codemirror/javascript.js"></script>
<script src="./dep/codemirror/css.js"></script>
<script src="./dep/codemirror/htmlmixed.js"></script>
<script src="./dep/codemirror/matchbrackets.js"></script>
<script>
var editor = CodeMirror.fromTextArea($('textarea')[0], {
theme: 'colorforth',
//lineNumbers: true,
matchBrackets: true,
mode: "text/html",
tabSize: 2
});
editor.live = function(cb){ editor.cb = cb || editor.cb }
editor.on("change", function change() {
clearTimeout(change.throttle);
change.throttle = setTimeout(live, 100);
});
function live() {
var frame = $('iframe').height($('.CodeMirror').height() * .95)[0];
frame = frame.contentDocument || frame.contentWindow.document;
frame.open();
frame.write(editor.getValue());
(editor.cb || function(){})(frame);
frame.close();
}
setTimeout(live, 100);
</script>
</html>
</body>

BIN
web/img/Thumbs.db Normal file

Binary file not shown.

BIN
web/img/knockout.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

65
web/intro.html Normal file
View File

@ -0,0 +1,65 @@
<div class="center fold stretch" style="background: url(./img/knockout.jpg) no-repeat 50% 50%; background-size: 100%;">
<div class="stretch stick" style="top: 50%; margin-top: -2.25em; font-family: arial;">
<div style="font-size: 4em; color: white; text-shadow: black 0px 0px 1em;">GAME OVER</div>
<button style="color: #400; font-size: 1.5em; background: red; padding: .75em; box-shadow: inset 0px 0px 2em 0px maroon; border: 1px solid #444;">
play again
</button>
<span>?</span>
</div>
</div>
<div style="float: left; max-width: 750px; width: 100%; padding: 2em; line-height: 1.25em; color: #ddd;">
<p>"You don't remember what happened, do you?"</p>
<p>Violence is a jarring thing. Imagine waking up and having no memory. No notion of who you are, where you came from, or why you were chosen.</p>
<p>Your identity, <i>erased</i>. This is your story, and you are about to discover how.</p>
<h2>Hi, I'm Plublious. Let me be your guide.</h2>
<p>"You've got a dreadful case of amnesia. I guess you're just gonna have to trust me."</p>
<p>"So hey, what's your name?"</p>
<div id="namethee"><button id="whoami">I don't know</button> or <input id="myname"> <button id="iam">is my name</button>.</div>
<p class="none" id="benamedbob">"You don't know? Well then, let's call you Bob."</p>
<p class="none" id="benamed">"Hi, <span class="name"></span>!"</p>
<div class="none" id="discover">
<p>"What is this all about?" <span class="name"></span> asks.
<div><button id="answers">Find out the answer</button> or <button class="upset">Be upset your questions are being written for you</button>.</div>
<div class="none" id="distributed">
<p>"Distributed systems."</p>
<p>"What?" <span class="name"></span> gawks.
<p>"Distributed systems."</p>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script>
$(function(){
$("#whoami").click(function(){ $("#namethee").addClass('none'); named("Bob"); });
$("#iam").click(function(){ $("#namethee").addClass('none'); named($('#myname').val()); });
function named(name){ $(".name").text(name); $("#benamed, #discover").removeClass('none'); };
$("#answers").click(function(){ $(this).closest('div').addClass('none'); $("#distributed").removeClass('none') })
})
</script>
<style>
html, body {
margin: 0;
padding: 0;
font-size: 16pt;
font-family: 'Palatino Linotype', Verdana;
background: black;
color: white;
}
.center {
text-align: center;
}
.fold {
height: 100%;
}
.stretch {
width: 100%;
}
.stick {
position: absolute;
}
.none {
display: none;
}
</style>

127
web/notes-keys.txt Normal file
View File

@ -0,0 +1,127 @@
Alice comes online and does
`var todo = gun.get('todo')`
However she is the first peer, objectively, to be around.
Therefore, very quickly her query returns empty. So when she
`todo.put({first: "buy groceries"}).key('todo')`
the put has to generate a soul and GUN is instructed to associate 'todo' with that soul.
A few hours later, Bob comes online and does
`var todo = gun.get('todo')`
and thankfully he was connected to Alice so he gets her soul and node. So when he
`todo.put({last: "sell leftovers"}).key('todo')`
this was the expected and intended result, producing the following graph:
```{
'ASDF': {
_: {'#': 'ASDF', '>': {
first: 1,
last: 2
}},
first: "buy groceries",
last: "sell leftovers"
}
}```
For purposes of clarity, we are using states as if they were linearizable (this is not actually the case though).
Then Carl comes online and tries to
`var todo = gun.get('todo')`
But since he is not connected to Alice or Bob, gets an empty result.
Carl does nothing with it, meaning no mutation, no soul, no generation.
Then Dave comes online and does the same as everyone else:
`var todo = gun.get('todo')`
But Dave is only connected to Carl as a peer, therefore his get is empty. Like Alice, he then
`todo.put({remember: "eat food!", last: "no leftovers!"}).key('todo')`
Which unfortunately causes a new soul to be generated. Meanwhile, everybody then does the following:
`todo.on(function(val){ console.log(val) })`
Alice and Bob immediately get:
```{
_: {'#': 'ASDF', '>': {
first: 1,
last: 2
}},
first: "buy groceries",
last: "sell leftovers"
}```
But Carl and Dave immediately get:
```{
_: {'#': 'FDSA', '>': {
remember: 3,
last: 3
}},
remember: "eat food!",
last: "no leftovers!"
}```
However, a few hours later everybody gets connected. This is the graph everyone then has:
```{
'ASDF': {
_: {'#': 'ASDF', '>': {
first: 1,
last: 2
}},
first: "buy groceries",
last: "sell leftovers"
},
'FDSA': {
_: {'#': 'FDSA', '>': {
remember: 3,
last: 3
}},
remember: "eat food!",
last: "no leftovers!"
}
}```
But their `on` function triggers again, with the following `val` locally for everyone:
```{
first: "buy groceries",
remember: "eat food!",
last: "no leftovers!"
}```
GUN merges all the nodes with matching keys into a temporary in-memory object.
This way it is safe to get empty results and still put data into it,
Everyone will see a unified view of the data when they do get connected, as intended.
This solves the null, singular, and plural problems for get all together.
However, if we intentionally do not want to see a potentially conflicting unified view, any peer can:
`var todos = gun.all('todo')`
And have the discrete data via:
`todos.map(function(todo, soul){ console.log(todo) })`
Which would currently get called twice, with the distinct nodes of 'ASDF' and 'FDSA'.
The only thing that this does not address is how write operations (put/key) would effect `get` nodes.
However, I feel like finding the answer to that question will be much easier than trying to solve `get` in any other way.

View File

@ -1,7 +1,7 @@
HOOKS: HOOKS:
- TRANSPORT: - TRANSPORT:
Has to basically re-implement set/load/key over some transport. Has to basically re-implement set/get/key/all over some transport.
REMEMBER that you have to subscribe on all set/load/key (especially key). REMEMBER that you have to subscribe on all set/get/key/all (especially key).
Notes: Notes:
@ -60,6 +60,16 @@ These are reserved for gun, to include meta-data on the data itself.
. represents walking the path through a node's scope . represents walking the path through a node's scope
_ represents non-human data in the graph that the amnesia machine uses to process stuff _ represents non-human data in the graph that the amnesia machine uses to process stuff
> and < are dedicated to comparing timestamp states, timelessly to avoid malicious abuse (this should always come last, because requests will be rejected without it) > and < are dedicated to comparing timestamp states, timelessly to avoid malicious abuse (this should always come last, because requests will be rejected without it)
~ is who
* relates to key prefixes
*> lexical constraint
*< lexical constraint
@ is where it should be delivered to.
@> is where it has been or will be rebroadcasted through.
% is for byte constraints.
: is time
$ is value
' is for escaping characters contained within '
{ {
who: {} who: {}
@ -85,9 +95,41 @@ How do we do memory management / clean up then of old dead bad data?
Init Init
You initialize with a node(s) you want to connect to. You initialize with a node(s) you want to connect to.
Evolution DISK
var node = gun.create('person/joe', {}); // this creates a 'new' node with an identifier to this node We might want to compact everything into a journal, with this type of format:
node('name', 'joe'); // changes properties #'ASDF'.'hello'>12743921$"world"
node('age', 24); Over the wire though it would include more information, like the ordering of where it has/will-be traversed through.
node('eyes', {}); // this internally creates a new node within the same subset as joe, @>'random','gunjs.herokuapp.com','127.0.0.1:8080':7894657#'ASDF'.'hello'>12743921$"world"
node('eyes.color', 'blue'); What about ACKs?
@'random':7894657#'ASDF'.'hello'>12743921
There is a limited amount of space on a machine, where we assume it is considered ephemeral as a user viewing device.
Such machines are the default requiring no configuration, machines that are permanent nodes can configure caps.
For instance, the browser would be a looping ephemeral device, where the 5mb localStorage gets recycled.
The localhost server of that device would be permanent and not loop, but halt when storage runs out.
By thus virtue of this, clients should be able to throttle (backpressure) their data intake.
The size of primitive values is fundamentally outside our control and left to the developer or multiparting.
If they decide to have a string that exceeds memory or local disk then their app will always fail regardless of multiparting.
However it is our duty to make sure that the rest of the structured data is streamable, including JSON.
Therefore networks and snapshotting should throttle to a size limit specified by the requesting agent.
Resuming these requests will be based soley on a lexical cursor included in the delivery, to avoid remembering state.
The asynchronicity of these lexical cursor could pose potential concurrency issues, and have to be considered ad hoc.
That is to say, if you are dealing with a large data set, you should always subscribe to updates it and never merely "get" it.
For the edge case of the singleton "get", the lexical cursor will progress until it receives the same or no cursor back from the server and terminate.
total over a range of time: {"alice", "bob", "carl", "david", "ed", "fred", "gary", "harry", "ian", "jake", "kim"}
client requests "users" at limit of 30 characters, server replies with (rounded down) {"alice", "bob", "carl"}.
after a while the client has processed this and goes for the next step, requesting /users?*>=carl&%=30 and {"david", "ed", "fred", "gary"} is returned.
then again /user?*>gary&%=30 with {"harry", "ian", "jake", "kim"} response.
then again /user?*>kim&%=30 with {} response. No subsequent cursor is possible, end.
Note: I do not know yet if the lexical cursor should be open-closed boundary, open-open, closed-closed, or closed-open - decide later.

406
web/think.html Normal file
View File

@ -0,0 +1,406 @@

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="./dep/jquery.js"></script>
</head>
<body>
<p>Before we can start building anything interesting, we should have a way to jot down our thoughts. Therefore the first thing we will build is a tool to keep track of what needs to be done. The infamous To-Do app, allowing us to keep temporary notes.</p>
<p>So what are the requirements? The ability to add a note, read our notes, and to clear them off. We will also need a space to keep these notes in, and a web page to access them through. Let's start with the page! You can edit the code below, which will update the live preview.</p>
<div class="edit">... loading editor and preview ...
<textarea style="height: 13em"><html>
<body>
<h1>Title</h1>
<form>
<input><button>Add</button>
</form>
<ul></ul>
</body>
</html></textarea>
</div>
<script>
var code = $('.edit').load('./editor.html', function(){setTimeout(function(){
var editor = $('.CodeMirror')[0].CodeMirror;
editor.live(function(frame){
$('body', frame).on('submit', 'form', function(e){e.preventDefault()});
});
editor.setValue(code);
},1)}).find('textarea').text();
</script>
<style>
.none { display: none }
.step-at { display: block }
textarea { border: none; width: 100%; }
</style>
<div id="step-1" class="step">
<p>What does this do? HTML is how we code the layout of a web page.</p>
<ul>
<li>We first must wrap all our code in an open and closing <u>html</u> tag so our computer knows it is a web page.</li>
<li>The <u>body</u> tag tells it to display the contents enclosed within.</li>
<li><u>h1</u> is one of many semantic tags for declaring a title, others include <u>h2</u>, <u>h3</u> and so on of different sizes.</li>
<li>A <u>form</u> is a container for getting information from a user.</li>
<li>Forms have <u>input</u>s which let the user type data in, it is a self-closing tag.</li>
<li>The <u>button</u> can be pressed, causing an action that we code to happen.</li>
<li><u>ul</u> is an unordered list which we will display our thoughts inside of.</li>
</ul>
<p>Now, try changing the <u>h1</u> text in the editor from "Title" to the name of our app, "Thoughts".</p>
<button class="none">I've done it!</button>
<textarea id="code-1" class="none" style="height: 14em"><html>
<body>
<h2>Thoughts</h2>
<form>
<input><button>Add</button>
</form>
<ul></ul>
<!-- Replace this Comment Line with the Code in the Step below! -->
</body>
</html></textarea>
</div>
<div id="step-2" class="step">
<p>HTML controls the layout, but how do we control what happens when a user presses the 'add' button? This is done with javascript. But using raw javascript quickly becomes verbose, so to keep things concise we will use a popular tool called jQuery. We also need a tool to store data, so we will include GUN as well.</p>
<p>Insert the following between the ending <u>ul</u> tag and the ending <u>body</u> tag, replacing the comment line:</p>
<textarea style="height: 6em;">
<script src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
<script src="https://rawgit.com/amark/gun/develop/gun.js"></script>
<script>
alert("Good job! You'll replace this line in the next step!");
</script></textarea>
<ul>
<li>The <u>script</u> tag tells the browser to use some javascript code, and <u>src</u> is where to load it from.</li>
<li>We can then test to see if our code worked with an <u>alert</u> message, which pops up and forces you to press ok.</li>
<li>In javascript, we denote text by wrapping it inside quotation marks, double <u>""</u> or single <u>''</u>.</li>
<li>We instruct the computer to notify us with that text by calling the <u>alert</u> function using parenthesis <u>()</u>.</li>
<li>A function is just a fancy word for a reusable piece of code that does something when we call its name, such as <u>alert</u>.</u>
<li>A semicolon <u>;</u> marks the end of a javascript sentence in the same way a period marks the end of a sentence.</li>
</ul>
<button class="none">It worked.</button>
<textarea id="code-2" class="none" style="height: 14em"><html>
<body>
<h2>Thoughts</h2>
<form>
<input><button>Add</button>
</form>
<ul></ul>
</script src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
</script src="https://rawgit.com/amark/gun/develop/gun.js"></script>
</script>
alert("Good job! You'll replace this line in the next step!");
</script>
</body>
</html></textarea>
</div>
<div id="step-3" class="step">
<p>Wonderful! You should have gotten the alert message, this means writing code works! Let's replace the alert line entirely with code that responds to user input.</p>
<textarea style="height: 5em;">
$('form').on('submit', function(event){
event.preventDefault();
alert("We got your thought! " + $('input').val());
});</textarea>
<p>What's going on here?</p>
<ul>
<li>jQuery is a function like <u>alert</u>, its name is <u>$</u> which can be called with parenthesis <u>()</u>.</li>
<li>Calling <u>$</u> with <u>'form'</u> as the input gives us a reference to the corresponding HTML form tag.</li>
<li>We then call <u>on</u> with two inputs. First the text name of an <u>event</u> we want to react to, and then a <u>function</u> we create.
<ul>
<li>Events are predefined ways we can interact with a user, such as <u>'mousemove'</u> or a <u>'keypress'</u>.</li>
<li>We use <u>'submit'</u> because it responds to both a button <u>'click'</u> and hitting enter on a form.</li>
<li>Our <u>function</u> will get called with the <u>event</u> every time the user does that action, allowing us to react to their input.</li>
</ul>
<li>The default behavior of a form is to cause the browser to change pages which is annoying, we prevent that by calling <u>preventDefault</u> on the <u>event</u>.</li>
<li>Finally, calling <u>$</u> with <u>'input'</u> will reference the HTML input tag which we then call <u>val</u> on, giving us the text the user typed in.</li>
</ul>
<button class="none">Got it.</button>
<textarea id="code-3" class="none" style="height: 14em"><html>
<body>
<h2>Thoughts</h2>
<form>
<input><button>Add</button>
</form>
<ul></ul>
</script src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
</script src="https://rawgit.com/amark/gun/develop/gun.js"></script>
</script>
$('form').on('submit', function(event){
event.preventDefault();
alert("We got your thought! " + $('input').val());
});
</script>
</body>
</html></textarea>
</div>
<div id="step-4" class="step">
<p>Now that users can jot down their thoughts, we need a place to save them. Let's start using GUN for just that.</p>
<textarea style="height: 1em;">
var gun = Gun().get('thoughts');</textarea>
<ul>
<li>The <u>var</u>iable keyword tells javascript that we want to create a reference named <u>gun</u> that we can reuse.</li>
<li>We call <u>Gun</u> to start the database, which only needs to be done once per page load.</li>
<li>Now we want to open up a reference to some data, so we call <u>get</u> with the name of the data we want.</li>
<li>However, no data has been saved to <u>'thoughts'</u> yet! Let's fix that in the next step by using <u>gun</u>.</li>
</ul>
<button class="none">Let's do it!</button>
<textarea id="code-4" class="none" style="height: 14em"><html>
<body>
<h2>Thoughts</h2>
<form>
<input><button>Add</button>
</form>
<ul></ul>
</script src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
</script src="https://rawgit.com/amark/gun/develop/gun.js"></script>
</script>
var gun = Gun().get('thoughts').set();
$('form').on('submit', function(event){
event.preventDefault();
alert("We got your thought! " + $('input').val());
});
</script>
</body>
</html></textarea>
</div>
<div id="step-5" class="step">
<p>Replace the alert line in the submit function with the following:</p>
<textarea style="height: 2.5em;">
gun.set($('input').val());
$('input').val("");</textarea>
<ul>
<li>We're telling gun to add the value of the input as an item in a <u>set</u> of thoughts.</li>
<li>Then we also want the input's <u>val</u>ue to become empty text, so we can add new thoughts later.</li>
</ul>
<button class="none">Show me the data!</button>
<textarea id="code-5" class="none" style="height: 14em"><html>
<body>
<h2>Thoughts</h2>
<form>
<input><button>Add</button>
</form>
<ul></ul>
</script src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
</script src="https://rawgit.com/amark/gun/develop/gun.js"></script>
</script>
var gun = Gun().get('thoughts').set();
$('form').on('submit', function(event){
event.preventDefault();
gun.set($('input').val());
$('input').val("");
});
</script>
</body>
</html></textarea>
</div>
<div id="step-6" class="step">
<p>Fantastic! Now that we can successfully store data, we want show the data! Replace the comment line in the editor with the following:</p>
<textarea style="height: 9em;">
gun.on().map(function(thought, id){
var li = $('#' + id).get(0) || $('<li>').attr('id', id).appendTo('ul');
if(thought){
$(li).text(thought);
} else {
$(li).hide();
}
});</textarea>
<ul>
<li>In the same way $'s <u>on</u> reacts to events, so does gun. It responds to any update on 'thoughts'.</li>
<li><u>map</u> calls the <u>function</u> you input into it for each item in the set, one at a time.</li>
<li>We get the <u>thought</u> value itself and a unique <u>id</u>entifier for the item in the set.</li>
<li>This next line looks scary, but read it like this, "make <u>var</u>iable <u>li</u> equal to X or Y".
<ul>
<li>The X part asks <u>$</u> to find the <u>id</u> in the HTML and <u>get</u> it.</li>
<li>In javascript, <u>||</u> means 'or', such that javascript will use X if it exist or it will use Y.</li>
<li>The Y part asks <u>$</u> to create a new <u>&lt;li&gt;</u> HTML tag, set its <u>id</u> <u>attr</u>ibute to our id and <u>append</u> it to the end of the HTML <u>ul</u> list.</li>
</ul></li>
<li>Finally, the javascript <u>if</u> statement either asks <u>$</u> to make <u>thought</u> be the text of the <u>li</u> if thought exists, <u>else</u> hide the <u>li</u> from being displayed.</li>
<li>Altogether it says "Create or reuse the HTML list item and make sure it is in the HTML list, then update the text or hide the item if there is no text".</li>
</ul>
<button class="none">I've tried adding a thought!</button>
<textarea id="code-6" class="none" style="height: 14em"><html>
<body>
<h2>Thoughts</h2>
<form>
<input><button>Add</button>
</form>
<ul></ul>
</script src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
</script src="https://rawgit.com/amark/gun/develop/gun.js"></script>
</script>
var gun = Gun().get('thoughts').set();
$('form').on('submit', function(event){
event.preventDefault();
gun.set($('input').val());
$('input').val("");
});
gun.on().map(function(thought, id){
var li = $('#' + id).get(0) || $('<li>').attr('id', id).appendTo('ul');
if(thought){
$(li).text(thought);
} else {
$(li).hide();
}
});
</script>
</body>
</html></textarea>
</div>
<div id="step-7" class="step">
<p>Finally we want to be able to clear off our thoughts when we are done with them. The interface for this could be done in many different ways, but for simplicity we will use a double tap as the gesture to clear it off. Replace the comment line in the editor with this code.</p>
<textarea style="height: 4em;">
$('body').on('dblclick', 'li', function(event){
gun.path(this.id).put(null);
});</textarea>
<ul>
<li>In order to react to any <u>'dblclick'</u> event rather than a specific one, we call <u>on</u> on the page's <u>'body'</u> as a whole.</li>
<li>But we want to filter the events to ones that happened only on any <u>'li'</u> tag. Fortunately, we can call <u>on</u> with an optional second input of <u>'li'</u> which does just that.</li>
<li>Inside a <u>function</u> we get a special <u>this</u> keyword in javascript, which <u>$</u> uses as a reference to the original HTML tag that caused the event.</li>
<li>Calling <u>path</u> tells <u>gun</u> to filter its data to just the <u>id</u> of the thought we want to clear off.</li>
<li>Then calling <u>put</u> on that tells gun to update that thought to <u>null</u>, so we no longer have the thought.</li>
<li>And whenever an update happens, <u>gun</u>'s <u>on</u> function from the previous step gets called again, which then hides the corresponding HTML list item.</u>
</ul>
<button class="none">Done!</button>
<textarea id="code-7" class="none" style="height: 14em"><html>
<body>
<h2>Thoughts</h2>
<form>
<input><button>Add</button>
</form>
<ul></ul>
</script src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
</script src="https://rawgit.com/amark/gun/develop/gun.js"></script>
</script>
var gun = Gun().get('thoughts').set();
$('form').on('submit', function(event){
event.preventDefault();
gun.set($('input').val());
$('input').val("");
});
gun.on().map(function(thought, id){
var li = $('#' + id).get(0) || $('<li>').attr('id', id).appendTo('ul');
if(thought){
$(li).text(thought);
} else {
$(li).hide();
}
});
$('body').on('dblclick', 'li', function(event){
$(this).remove();
gun.path(this.id).put(null);
});
</script>
</body>
</html></textarea>
</div>
<div id="step-8" class="step">
<p>Congratulations! You are all done, you have built your first GUN app!</p>
<p>In the next tutorial we will use GUN to synchronize data in realtime across multiple devices. We'll start by copying the app we made here and modifying it to become a chat app.</p>
<a href="#">Coming Soon!</a>
</div>
<script>
$('.step').addClass('none').first().addClass('step-at');
$('button').on('click', next).removeClass('none');
function next(){
$('.step-at').removeClass('step-at');
$('#step-' + (++next.at)).addClass('step-at');
(next.step[next.at] || function(){})();
}
next.at = 1;
next.comment = /\s*\/\*(.*?)\*\/\s*/i;
next.unwrap = /\s*<\/?(html|body)>\s*\s*/ig;
next.wrap = function(c){ return '<html>\n\t<body>\n\t\t' + c + '\n\t</body>\n</html>' }
next.code = function(){ return $('<div>' + next.mir.getValue().replace(next.wrap, '') + '</div>') }
next.step = [null, null, function(){
next.mir = $('.CodeMirror')[0].CodeMirror;
var code = next.code();
if(code.find('h1').text().toLowerCase() == 'title'){
code.find('h1').text('Thoughts');
}
next.mir.setValue(next.wrap($.trim(code.html()) + "\n<!-- Replace this Comment Line with the Code in the Step below! -->"));
}, function(){
var code = next.code();
var ctx = {$: true, gun: true, alert: true};
code.find('script').each(function(){
var src = ($(this).attr('src') || '').toLowerCase();
if(src.indexOf('jquery')){ ctx.$ = false }
if(src.indexOf('gun')){ ctx.gun = false }
if(($(this).text()||'').indexOf('alert')){ ctx.alert = false }
});
code.contents().filter(function(){ return this.nodeType == 8 }).remove();
var ul = code.find('ul');
if(ctx.alert){ ul.after($('<script>').text('\n\t\t\talert("Good job! Our code will go in this script tag!");\n\t\t')) }
if(ctx.gun){ ul.after($('<script>').attr('src', 'https://rawgit.com/amark/gun/develop/gun.js')) }
if(ctx.$){ ul.after($('<script>').attr('src','https://code.jquery.com/jquery-1.11.3.min.js')) }
if(ctx.alert || ctx.gun || ctx.$){ next.mir.setValue(next.wrap($.trim(code.html().replace(/<script/ig, '\n\t\t<script')))) }
}, function(){
var code = next.code();
var script = code.find('script').last();
if(0 > script.text().indexOf('.on')){
script.text('\n' + script.text().replace(/\s*alert.*\n/i, $('#step-3').find('textarea').first().text() + '\n'));
}
script.text('\n/* Replace this Comment Line with the Code in the Step below! */' + script.text());
next.mir.setValue(next.wrap($.trim(code.html())));
}, function(){
var code = next.code();
var script = code.find('script').last();
script.text(script.text().replace(next.comment, ''));
if(0 > script.text().indexOf('Gun')){
script.text('\n' + $('#step-4').find('textarea').first().text() + "\n\t\t\t" + script.text());
}
next.mir.setValue(next.wrap($.trim(code.html())));
}, function(){
var code = next.code();
var script = code.find('script').last();
if(0 < script.text().indexOf('alert')){
script.text(script.text().replace(/\s*alert.*\n/i, '\n' + $('#step-5').find('textarea').first().text() + '\n'));
}
script.text(script.text() + '\n/* Replace this Comment Line with the Code in the Step below! */\n\t\t');
next.mir.setValue(next.wrap($.trim(code.html())));
}, function(){
var code = next.code();
var script = code.find('script').last();
if(0 > script.text().indexOf('.map')){
script.text(script.text().replace(/\s*.*replace.*\n/i, '\n' + $('#step-6').find('textarea').first().text()));
}
script.text(script.text() + '\n/* Replace this Comment Line with the Code in the Step below! */\n\t\t');
next.mir.setValue(next.wrap($.trim(code.html())));
}, function(){
var code = next.code();
var script = code.find('script').last();
if(0 > script.text().indexOf('gun.path')){
script.text(script.text().replace(/\s*.*replace.*\n/i, '\n' + $('#step-7').find('textarea').first().text() + '\n'));
}
next.mir.setValue(next.wrap($.trim(code.html())));
}]
</script>
</body>
</html>

65
web/wire.txt Normal file
View File

@ -0,0 +1,65 @@
WIRE PROTOCOL
save/set/create/put/post/delete/update/change/mutate
get/open/load/call/location/address
name/reference/key/index/point
/gun {data: 'yay', #: "soul"}
/gun/key {#: 'soul'}
/gun/key
/gun/key?*=/&*<=a&*>=c
Reads are a GET and do not contain a body.
Writes have a body.
Reads may call the callback multiple times, and will end with an empty object.
Reads are either a singular pathname or pound query to get a key or soul's individual node.
Or a read is to retrieve information on many key's and their corresponding soul.
Query formats are allowed as:
?
* = /
star means the peer should reply with all the KEYS it has up to the character in the value of the query.
Ex. "/users/?*=/" would return {"users/marknadal": {"#": "ASDF"}, "users/ambernadal": {"#": "FDSA"}}
If there is no up to character, then all subkeys would be returned as well.
The peer does not have to reply with all of its keys, however if there are more keys for the receiving peer, it should give it a lexical cursor. (how?)
Ordering is not guaranteed, however if there is ordering, it should be lexical. Therefore things like limit or skip do not necessarily apply.
*> = a
*< = c
star greater than and star less than are lexical constraints on returning all peers KEYS.
Ex. "/users/?*=/&*>=a&*<=c" asks the peer to return users that start and end between 'a' and 'c',
thus {"users/alice": {"#": "DSAF"}, "users/bob": {"#": "DAFS"}, "user/carl": {"#": "SAFD"}}
# = ASDF
pound means the peer should reply with the node that has this exact soul. There should be no key prefixed path on this type of request. A special case of "#=*" indicates to literally dump the entire graph, as is, to the client. Lexical carets are okay.
% = 30
percent means byte constraint requested by the peer asking for data.
> = 1426007247399
< = 1426007248941
greater than and less than are time/state constraints, allowing a peer to request historical data or future data.
the peer processing the request ought to reply with such state data if it has it, but has no requirement to keep such data (but is encouraged to do so).
Using query constraints is generally not advised, as it is better to do all computation at the point of data, or have all data at the point of computation, not inbetween.
This protocol should work over HTTP, or the JSONP fallback specification, and emulated over WS or WebRTC.
On a separate note, it might be advantageous to chunk node objects into pieces as well, in case they grow too big or especially contain too many field/value pairs. If this was built into the behavior, to handle better streaming and lower memory use cases, it would also become ideal for groups of data without any extra logic or behavior.
There are three types of grouped related data:
1. Dependent Causality (videos, audio, etc.)
2. Relative Ordering (age, height, users, etc.)
3. Independent
Obviously independent data is fairly uninteresting, as the HAM can handle convergence just on states alone. Relative ordering requires only application specific logic to sort data for the view, or computing averages and such. They can always be streamed in efficiently by creating indices on the desired properties and then doing lexical loading. Any conflict in ordering on one property can be handled by cascading into other properties, like from sort order to then creation date. By this means, all data should be explicitly recorded, not implicit.
Dependent Causality is the most interesting, and unfortunately not conducive towards efficient means of streaming. This means the data must be sorted in a particular direction, and even if you receive a "later" chunk, you should not reveal it until all earlier chunks have been digested. Which might means you have to discard it from memory if space runs out, and then pull it back in again at the right time. Let's look at some differences here with some concrete examples.
If we have 4 people in the back room, and we want to sort them by height as they come on to stage, then order in which they come out from behind does not matter. Say their heights are 4, 5, 6, and 7 feet tall. When the first one comes out we do not need to do anything, where the 6 is the first one. Then the second one comes out and is 4, so we put them to the left of the first. Then third comes out the 7, and we put them to the right of the first. Finally, the fourth comes out as the 5, and we put them inbetween the first and second. This method allows us to incrementally sort and always see the correct ordering without waiting.
However, lets say we have collaborative text. We have the initial text of "Hello" and an editor named Alice adds ", World!" in which each letter (' ', ',', 'W', 'o', 'r', 'l', 'd', '!') is streamed, potentially out of order, to Bob. We are going to assert that relative ordering does not work because, for the sake of explanation, any individual letter could be lost in the mail as they are sent. The last thing we want is Bob receiving the three characters 'old', which has correct relative ordering but wrong dependency, and thinking Alice is insulting him for being senile. Therefore every letter should specify the message that comes before it, the letter that it depends upon.