resolve merge conflict
This commit is contained in:
commit
50695f41ed
2
.gitignore
vendored
2
.gitignore
vendored
@ -5,3 +5,5 @@ customization
|
|||||||
.*.swp
|
.*.swp
|
||||||
*.db
|
*.db
|
||||||
/customize/
|
/customize/
|
||||||
|
messages.log
|
||||||
|
.DS_Store
|
||||||
|
|||||||
27
.travis.yml
27
.travis.yml
@ -1,8 +1,25 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
|
env:
|
||||||
|
matrix:
|
||||||
|
- "BROWSER='firefox:19:Windows 2012'"
|
||||||
|
- "BROWSER='chrome::Windows 2008'"
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- master
|
- master
|
||||||
- diffdom
|
- diffdom
|
||||||
node_js:
|
- beta
|
||||||
- "0.12"
|
- netflux
|
||||||
|
node_js:
|
||||||
|
- "4.2.1"
|
||||||
|
before_script:
|
||||||
|
- npm run-script lint
|
||||||
|
- cp config.js.dist config.js
|
||||||
|
- npm install bower
|
||||||
|
- ./node_modules/bower/bin/bower install
|
||||||
|
- node ./server.js &
|
||||||
|
- sleep 2
|
||||||
|
addons:
|
||||||
|
sauce_connect:
|
||||||
|
username: "cjdelisle"
|
||||||
|
access_key:
|
||||||
|
secure: "pgGh8YGXLPq6fpdwwK2jnjRtwXPbVWQ/HIFvwX7E6HBpzxxcF2edE8sCdonWW9TP2LQisZFmVLqoSnZWMnjBr2CBAMKMFvaHQDJDQCo4v3BXkID7KgqyKmNcwW+FPfSJ5MxNBro8/GE/awkhZzJLYGUTS5zi/gVuIUwdi6cHI8s="
|
||||||
|
|||||||
196
ChainPadSrv.js
196
ChainPadSrv.js
@ -1,196 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014 XWiki SAS
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Affero General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// triggers jsh because we're not in a browser and WebSocket is a thing
|
|
||||||
var WebSocket = require('ws'); // jshint ignore:line
|
|
||||||
|
|
||||||
var REGISTER = 0;
|
|
||||||
var REGISTER_ACK = 1;
|
|
||||||
var PATCH = 2;
|
|
||||||
var DISCONNECT = 3;
|
|
||||||
var PING = 4;
|
|
||||||
var PONG = 5;
|
|
||||||
|
|
||||||
var parseMessage = function (msg) {
|
|
||||||
var res ={};
|
|
||||||
// two or more? use a for
|
|
||||||
['pass','user','channelId','content'].forEach(function(attr){
|
|
||||||
var len=msg.slice(0,msg.indexOf(':')),
|
|
||||||
// taking an offset lets us slice out the prop
|
|
||||||
// and saves us one string copy
|
|
||||||
o=len.length+1,
|
|
||||||
prop=res[attr]=msg.slice(o,Number(len)+o);
|
|
||||||
// slice off the property and its descriptor
|
|
||||||
msg = msg.slice(prop.length+o);
|
|
||||||
});
|
|
||||||
// content is the only attribute that's not a string
|
|
||||||
res.content=JSON.parse(res.content);
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
// get the password off the message before sending it to other clients.
|
|
||||||
var popPassword = function (msg) {
|
|
||||||
var passLen = msg.substring(0,msg.indexOf(':'));
|
|
||||||
return msg.substring(passLen.length+1 + Number(passLen));
|
|
||||||
};
|
|
||||||
|
|
||||||
var sendMsg = function (msg, socket) {
|
|
||||||
socket.send(msg);
|
|
||||||
};
|
|
||||||
|
|
||||||
var sendChannelMessage = function (ctx, channel, msg, cb) {
|
|
||||||
ctx.store.message(channel.name, msg, function () {
|
|
||||||
channel.forEach(function (user) {
|
|
||||||
try {
|
|
||||||
sendMsg(msg, user.socket);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e.stack);
|
|
||||||
try { user.socket.close(); } catch (e) { }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (cb) { cb(); }
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var mkMessage = function (user, channel, content) {
|
|
||||||
content = JSON.stringify(content);
|
|
||||||
return user.length + ':' + user +
|
|
||||||
channel.length + ':' + channel +
|
|
||||||
content.length + ':' + content;
|
|
||||||
};
|
|
||||||
|
|
||||||
var dropClient = function (ctx, userpass) {
|
|
||||||
var client = ctx.registeredClients[userpass];
|
|
||||||
if (client.socket.readyState !== WebSocket.CLOSING
|
|
||||||
&& client.socket.readyState !== WebSocket.CLOSED)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
client.socket.close();
|
|
||||||
} catch (e) {
|
|
||||||
console.log("Failed to disconnect ["+client.userName+"], attempting to terminate");
|
|
||||||
try {
|
|
||||||
client.socket.terminate();
|
|
||||||
} catch (ee) {
|
|
||||||
console.log("Failed to terminate ["+client.userName+"] *shrug*");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < client.channels.length; i++) {
|
|
||||||
var chanName = client.channels[i];
|
|
||||||
var chan = ctx.channels[chanName];
|
|
||||||
var idx = chan.indexOf(client);
|
|
||||||
if (idx < 0) { continue; }
|
|
||||||
console.log("Removing ["+client.userName+"] from channel ["+chanName+"]");
|
|
||||||
chan.splice(idx, 1);
|
|
||||||
if (chan.length === 0) {
|
|
||||||
console.log("Removing empty channel ["+chanName+"]");
|
|
||||||
delete ctx.channels[chanName];
|
|
||||||
} else {
|
|
||||||
sendChannelMessage(ctx, chan, mkMessage(client.userName, chanName, [DISCONNECT,0]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete ctx.registeredClients[userpass];
|
|
||||||
};
|
|
||||||
|
|
||||||
var handleMessage = function (ctx, socket, msg) {
|
|
||||||
var parsed = parseMessage(msg);
|
|
||||||
var userPass = parsed.user + ':' + parsed.pass;
|
|
||||||
msg = popPassword(msg);
|
|
||||||
|
|
||||||
if (parsed.content[0] === REGISTER) {
|
|
||||||
if (parsed.user.length === 0) { throw new Error(); }
|
|
||||||
console.log("[" + userPass + "] registered");
|
|
||||||
var user = ctx.registeredClients[userPass] = ctx.registeredClients[userPass] || {
|
|
||||||
channels: [parsed.channelId],
|
|
||||||
userName: parsed.user
|
|
||||||
};
|
|
||||||
if (user.socket && user.socket !== socket) { user.socket.close(); }
|
|
||||||
user.socket = socket;
|
|
||||||
|
|
||||||
var chan = ctx.channels[parsed.channelId] = ctx.channels[parsed.channelId] || [];
|
|
||||||
var newChan = (chan.length === 0);
|
|
||||||
chan.name = parsed.channelId;
|
|
||||||
|
|
||||||
// we send a register ack right away but then we fallthrough
|
|
||||||
// to let other users know that we were registered.
|
|
||||||
sendMsg(mkMessage('', parsed.channelId, [1,0]), socket);
|
|
||||||
|
|
||||||
var sendMsgs = function () {
|
|
||||||
sendChannelMessage(ctx, chan, msg, function () {
|
|
||||||
chan.push(user);
|
|
||||||
ctx.store.getMessages(chan.name, function (msg) {
|
|
||||||
try {
|
|
||||||
sendMsg(msg, socket);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e.stack);
|
|
||||||
try { socket.close(); } catch (e) { }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
if (newChan) {
|
|
||||||
sendChannelMessage(ctx, chan, mkMessage('', chan.name, [DISCONNECT,0]), sendMsgs);
|
|
||||||
} else {
|
|
||||||
sendMsgs();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parsed.content[0] === PING) {
|
|
||||||
// 31:xwiki:XWiki.Admin-141475016907510:RWJ5xF2+SL17:[5,1414752676547]
|
|
||||||
// 1:y31:xwiki:XWiki.Admin-141475016907510:RWJ5xF2+SL17:[4,1414752676547]
|
|
||||||
sendMsg(mkMessage(parsed.user, parsed.channelId, [ PONG, parsed.content[1] ]), socket);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var client = ctx.registeredClients[userPass];
|
|
||||||
if (typeof(client) === 'undefined') { throw new Error('unregistered'); }
|
|
||||||
|
|
||||||
var channel = ctx.channels[parsed.channelId];
|
|
||||||
if (typeof(channel) === 'undefined') { throw new Error('no such channel'); }
|
|
||||||
|
|
||||||
if (channel.indexOf(client) === -1) { throw new Error('client not in channel'); }
|
|
||||||
|
|
||||||
sendChannelMessage(ctx, channel, msg);
|
|
||||||
};
|
|
||||||
|
|
||||||
var create = module.exports.create = function (socketServer, store) {
|
|
||||||
var ctx = {
|
|
||||||
registeredClients: {},
|
|
||||||
channels: {},
|
|
||||||
store: store
|
|
||||||
};
|
|
||||||
|
|
||||||
socketServer.on('connection', function(socket) {
|
|
||||||
socket.on('message', function(message) {
|
|
||||||
try {
|
|
||||||
handleMessage(ctx, socket, message);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e.stack);
|
|
||||||
try { socket.close(); } catch (e) { }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
socket.on('close', function (evt) {
|
|
||||||
for (var client in ctx.registeredClients) {
|
|
||||||
if (ctx.registeredClients[client].socket === socket) {
|
|
||||||
dropClient(ctx, client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
259
NetfluxWebsocketSrv.js
Normal file
259
NetfluxWebsocketSrv.js
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
;(function () { 'use strict';
|
||||||
|
const Crypto = require('crypto');
|
||||||
|
const LogStore = require('./storage/LogStore');
|
||||||
|
|
||||||
|
|
||||||
|
const LAG_MAX_BEFORE_DISCONNECT = 30000;
|
||||||
|
const LAG_MAX_BEFORE_PING = 15000;
|
||||||
|
const HISTORY_KEEPER_ID = Crypto.randomBytes(8).toString('hex');
|
||||||
|
|
||||||
|
const USE_HISTORY_KEEPER = true;
|
||||||
|
const USE_FILE_BACKUP_STORAGE = true;
|
||||||
|
|
||||||
|
|
||||||
|
let dropUser;
|
||||||
|
|
||||||
|
const now = function () { return (new Date()).getTime(); };
|
||||||
|
|
||||||
|
const sendMsg = function (ctx, user, msg) {
|
||||||
|
try {
|
||||||
|
if (ctx.config.logToStdout) { console.log('<' + JSON.stringify(msg)); }
|
||||||
|
user.socket.send(JSON.stringify(msg));
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e.stack);
|
||||||
|
dropUser(ctx, user);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendChannelMessage = function (ctx, channel, msgStruct) {
|
||||||
|
msgStruct.unshift(0);
|
||||||
|
channel.forEach(function (user) {
|
||||||
|
if(msgStruct[2] !== 'MSG' || user.id !== msgStruct[1]) { // We don't want to send back a message to its sender, in order to save bandwidth
|
||||||
|
sendMsg(ctx, user, msgStruct);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (USE_HISTORY_KEEPER && msgStruct[2] === 'MSG') {
|
||||||
|
ctx.store.message(channel.id, JSON.stringify(msgStruct), function () { });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
dropUser = function (ctx, user) {
|
||||||
|
if (user.socket.readyState !== 2 /* WebSocket.CLOSING */
|
||||||
|
&& user.socket.readyState !== 3 /* WebSocket.CLOSED */)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
user.socket.close();
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Failed to disconnect ["+user.id+"], attempting to terminate");
|
||||||
|
try {
|
||||||
|
user.socket.terminate();
|
||||||
|
} catch (ee) {
|
||||||
|
console.log("Failed to terminate ["+user.id+"] *shrug*");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete ctx.users[user.id];
|
||||||
|
Object.keys(ctx.channels).forEach(function (chanName) {
|
||||||
|
let chan = ctx.channels[chanName];
|
||||||
|
let idx = chan.indexOf(user);
|
||||||
|
if (idx < 0) { return; }
|
||||||
|
console.log("Removing ["+user.id+"] from channel ["+chanName+"]");
|
||||||
|
chan.splice(idx, 1);
|
||||||
|
if (chan.length === 0) {
|
||||||
|
console.log("Removing empty channel ["+chanName+"]");
|
||||||
|
delete ctx.channels[chanName];
|
||||||
|
|
||||||
|
/* Call removeChannel if it is a function and channel removal is
|
||||||
|
set to true in the config file */
|
||||||
|
if (ctx.config.removeChannels) {
|
||||||
|
if (typeof(ctx.store.removeChannel) === 'function') {
|
||||||
|
ctx.timeouts[chanName] = setTimeout(function () {
|
||||||
|
ctx.store.removeChannel(chanName, function (err) {
|
||||||
|
if (err) { console.error("[removeChannelErr]: %s", err); }
|
||||||
|
else {
|
||||||
|
console.log("Deleted channel [%s] history from database...", chanName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, ctx.config.channelRemovalTimeout);
|
||||||
|
} else {
|
||||||
|
console.error("You have configured your server to remove empty channels, " +
|
||||||
|
"however, the database adaptor you are using has not implemented this behaviour.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendChannelMessage(ctx, chan, [user.id, 'LEAVE', chanName, 'Quit: [ dropUser() ]']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getHistory = function (ctx, channelName, handler, cb) {
|
||||||
|
var messageBuf = [];
|
||||||
|
ctx.store.getMessages(channelName, function (msgStr) {
|
||||||
|
messageBuf.push(JSON.parse(msgStr));
|
||||||
|
}, function () {
|
||||||
|
var startPoint;
|
||||||
|
var cpCount = 0;
|
||||||
|
var msgBuff2 = [];
|
||||||
|
for (startPoint = messageBuf.length - 1; startPoint >= 0; startPoint--) {
|
||||||
|
var msg = messageBuf[startPoint];
|
||||||
|
msgBuff2.push(msg);
|
||||||
|
if (msg[2] === 'MSG' && msg[4].indexOf('cp|') === 0) {
|
||||||
|
cpCount++;
|
||||||
|
if (cpCount >= 2) {
|
||||||
|
for (var x = msgBuff2.pop(); x; x = msgBuff2.pop()) { handler(x); }
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//console.log(messageBuf[startPoint]);
|
||||||
|
}
|
||||||
|
if (cpCount < 2) {
|
||||||
|
// no checkpoints.
|
||||||
|
for (var x = msgBuff2.pop(); x; x = msgBuff2.pop()) { handler(x); }
|
||||||
|
}
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const randName = function () { return Crypto.randomBytes(16).toString('hex'); };
|
||||||
|
|
||||||
|
const handleMessage = function (ctx, user, msg) {
|
||||||
|
let json = JSON.parse(msg);
|
||||||
|
let seq = json.shift();
|
||||||
|
let cmd = json[0];
|
||||||
|
let obj = json[1];
|
||||||
|
|
||||||
|
user.timeOfLastMessage = now();
|
||||||
|
user.pingOutstanding = false;
|
||||||
|
|
||||||
|
if (cmd === 'JOIN') {
|
||||||
|
if (obj && obj.length !== 32) {
|
||||||
|
sendMsg(ctx, user, [seq, 'ERROR', 'ENOENT', obj]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let chanName = obj || randName();
|
||||||
|
sendMsg(ctx, user, [seq, 'JACK', chanName]);
|
||||||
|
let chan = ctx.channels[chanName] = ctx.channels[chanName] || [];
|
||||||
|
|
||||||
|
// prevent removal of the channel if there is a pending timeout
|
||||||
|
if (ctx.config.removeChannels && ctx.timeouts[chanName]) {
|
||||||
|
clearTimeout(ctx.timeouts[chanName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
chan.id = chanName;
|
||||||
|
if (USE_HISTORY_KEEPER) {
|
||||||
|
sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'JOIN', chanName]);
|
||||||
|
}
|
||||||
|
chan.forEach(function (u) { sendMsg(ctx, user, [0, u.id, 'JOIN', chanName]); });
|
||||||
|
chan.push(user);
|
||||||
|
sendChannelMessage(ctx, chan, [user.id, 'JOIN', chanName]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cmd === 'MSG') {
|
||||||
|
if (obj === HISTORY_KEEPER_ID) {
|
||||||
|
let parsed;
|
||||||
|
try { parsed = JSON.parse(json[2]); } catch (err) { console.error(err); return; }
|
||||||
|
if (parsed[0] === 'GET_HISTORY') {
|
||||||
|
sendMsg(ctx, user, [seq, 'ACK']);
|
||||||
|
getHistory(ctx, parsed[1], function (msg) {
|
||||||
|
sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(msg)]);
|
||||||
|
}, function () {
|
||||||
|
sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, 0]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (obj && !ctx.channels[obj] && !ctx.users[obj]) {
|
||||||
|
sendMsg(ctx, user, [seq, 'ERROR', 'ENOENT', obj]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendMsg(ctx, user, [seq, 'ACK']);
|
||||||
|
let target;
|
||||||
|
json.unshift(user.id);
|
||||||
|
if ((target = ctx.channels[obj])) {
|
||||||
|
sendChannelMessage(ctx, target, json);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((target = ctx.users[obj])) {
|
||||||
|
json.unshift(0);
|
||||||
|
sendMsg(ctx, target, json);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cmd === 'LEAVE') {
|
||||||
|
let err;
|
||||||
|
let chan;
|
||||||
|
let idx;
|
||||||
|
if (!obj) { err = 'EINVAL'; obj = 'undefined';}
|
||||||
|
if (!err && !(chan = ctx.channels[obj])) { err = 'ENOENT'; }
|
||||||
|
if (!err && (idx = chan.indexOf(user)) === -1) { err = 'NOT_IN_CHAN'; }
|
||||||
|
if (err) {
|
||||||
|
sendMsg(ctx, user, [seq, 'ERROR', err, obj]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendMsg(ctx, user, [seq, 'ACK']);
|
||||||
|
json.unshift(user.id);
|
||||||
|
sendChannelMessage(ctx, chan, [user.id, 'LEAVE', chan.id]);
|
||||||
|
chan.splice(idx, 1);
|
||||||
|
}
|
||||||
|
if (cmd === 'PING') {
|
||||||
|
sendMsg(ctx, user, [seq, 'ACK']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let run = module.exports.run = function (storage, socketServer, config) {
|
||||||
|
/* Channel removal timeout defaults to 60000ms (one minute) */
|
||||||
|
config.channelRemovalTimeout =
|
||||||
|
typeof(config.channelRemovalTimeout) === 'number'?
|
||||||
|
config.channelRemovalTimeout:
|
||||||
|
60000;
|
||||||
|
|
||||||
|
let ctx = {
|
||||||
|
users: {},
|
||||||
|
channels: {},
|
||||||
|
timeouts: {},
|
||||||
|
store: (USE_FILE_BACKUP_STORAGE) ? LogStore.create('messages.log', storage) : storage,
|
||||||
|
config: config
|
||||||
|
};
|
||||||
|
setInterval(function () {
|
||||||
|
Object.keys(ctx.users).forEach(function (userId) {
|
||||||
|
let u = ctx.users[userId];
|
||||||
|
if (now() - u.timeOfLastMessage > LAG_MAX_BEFORE_DISCONNECT) {
|
||||||
|
dropUser(ctx, u);
|
||||||
|
} else if (!u.pingOutstanding && now() - u.timeOfLastMessage > LAG_MAX_BEFORE_PING) {
|
||||||
|
sendMsg(ctx, u, [0, '', 'PING', now()]);
|
||||||
|
u.pingOutstanding = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 5000);
|
||||||
|
socketServer.on('connection', function(socket) {
|
||||||
|
if(socket.upgradeReq.url !== '/cryptpad_websocket') { return; }
|
||||||
|
let conn = socket.upgradeReq.connection;
|
||||||
|
let user = {
|
||||||
|
addr: conn.remoteAddress + '|' + conn.remotePort,
|
||||||
|
socket: socket,
|
||||||
|
id: randName(),
|
||||||
|
timeOfLastMessage: now(),
|
||||||
|
pingOutstanding: false
|
||||||
|
};
|
||||||
|
ctx.users[user.id] = user;
|
||||||
|
sendMsg(ctx, user, [0, '', 'IDENT', user.id]);
|
||||||
|
socket.on('message', function(message) {
|
||||||
|
if (ctx.config.logToStdout) { console.log('>'+message); }
|
||||||
|
try {
|
||||||
|
handleMessage(ctx, user, message);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e.stack);
|
||||||
|
dropUser(ctx, user);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
socket.on('close', function (evt) {
|
||||||
|
for (let userId in ctx.users) {
|
||||||
|
if (ctx.users[userId].socket === socket) {
|
||||||
|
dropUser(ctx, ctx.users[userId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}());
|
||||||
30
TestSelenium.js
Normal file
30
TestSelenium.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/* global process */
|
||||||
|
var WebDriver = require("selenium-webdriver");
|
||||||
|
|
||||||
|
var driver;
|
||||||
|
if (process.env.SAUCE_USERNAME !== undefined) {
|
||||||
|
var browserArray = process.env.BROWSER.split(':');
|
||||||
|
driver = new WebDriver.Builder().usingServer(
|
||||||
|
'http://'+ process.env.SAUCE_USERNAME+':'+process.env.SAUCE_ACCESS_KEY+'@ondemand.saucelabs.com:80/wd/hub'
|
||||||
|
).withCapabilities({
|
||||||
|
"tunnel-identifier": process.env.TRAVIS_JOB_NUMBER,
|
||||||
|
"build": process.env.TRAVIS_JOB_NUMBER,
|
||||||
|
"username": process.env.SAUCE_USERNAME,
|
||||||
|
"accessKey": process.env.SAUCE_ACCESS_KEY,
|
||||||
|
}).forBrowser(browserArray[0], browserArray[1], browserArray[2]).build();
|
||||||
|
} else {
|
||||||
|
driver = new WebDriver.Builder().withCapabilities({ browserName: "chrome" }).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
driver.get('http://localhost:3000/assert/');
|
||||||
|
var report = driver.wait(WebDriver.until.elementLocated(WebDriver.By.className("report")), 5000);
|
||||||
|
report.getAttribute("class").then(function (cls) {
|
||||||
|
driver.quit();
|
||||||
|
if (!cls) {
|
||||||
|
throw new Error("cls is null");
|
||||||
|
} else if (cls.indexOf("failure") !== -1) {
|
||||||
|
throw new Error("cls contains the word failure");
|
||||||
|
} else if (cls.indexOf("success") === -1) {
|
||||||
|
throw new Error("cls does not contain the word success");
|
||||||
|
}
|
||||||
|
});
|
||||||
61
WebRTCSrv.js
Normal file
61
WebRTCSrv.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
'use strict'
|
||||||
|
let WebSocketServer = require('ws').Server
|
||||||
|
const UNSUPPORTED_DATA = 1007
|
||||||
|
const POLICY_VIOLATION = 1008
|
||||||
|
const CLOSE_UNSUPPORTED = 1003
|
||||||
|
|
||||||
|
var run = module.exports.run = function(server) {
|
||||||
|
server.on('connection', (socket) => {
|
||||||
|
if(socket.upgradeReq.url !== '/cryptpad_webrtc') { return; }
|
||||||
|
socket.on('message', (data) => {
|
||||||
|
try {
|
||||||
|
let msg = JSON.parse(data)
|
||||||
|
console.log(msg)
|
||||||
|
if (msg.hasOwnProperty('key')) {
|
||||||
|
for (let master of server.clients) {
|
||||||
|
if (master.key === msg.key) {
|
||||||
|
socket.close(POLICY_VIOLATION, 'The key already exists')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
socket.key = msg.key
|
||||||
|
socket.joiningClients = []
|
||||||
|
} else if (msg.hasOwnProperty('id')) {
|
||||||
|
for (let index in socket.joiningClients) {
|
||||||
|
if (index == msg.id) {
|
||||||
|
socket.joiningClients[index].send(JSON.stringify({data: msg.data}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
socket.close(POLICY_VIOLATION, 'Unknown id')
|
||||||
|
} else if (msg.hasOwnProperty('join')) {
|
||||||
|
for (let master of server.clients) {
|
||||||
|
if (master.key === msg.join) {
|
||||||
|
socket.master = master
|
||||||
|
master.joiningClients.push(socket)
|
||||||
|
let id = master.joiningClients.length - 1
|
||||||
|
master.send(JSON.stringify({id, data: msg.data}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
socket.close(POLICY_VIOLATION, 'Unknown key')
|
||||||
|
} else if (msg.hasOwnProperty('data') && socket.hasOwnProperty('master')) {
|
||||||
|
let id = socket.master.joiningClients.indexOf(socket)
|
||||||
|
socket.master.send(JSON.stringify({id, data: msg.data}))
|
||||||
|
} else {
|
||||||
|
socket.close(UNSUPPORTED_DATA, 'Unsupported message format')
|
||||||
|
}
|
||||||
|
} catch (event) {
|
||||||
|
socket.close(CLOSE_UNSUPPORTED, 'Server accepts only JSON')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('close', (event) => {
|
||||||
|
if (socket.hasOwnProperty('joiningClients')) {
|
||||||
|
for (let client of socket.joiningClients) {
|
||||||
|
client.close(POLICY_VIOLATION, 'The peer is no longer available')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
15
bower.json
15
bower.json
@ -18,8 +18,6 @@
|
|||||||
"tests"
|
"tests"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"markdown": "~0.5.0",
|
|
||||||
"jquery.sheet": "master",
|
|
||||||
"jquery": "~2.1.3",
|
"jquery": "~2.1.3",
|
||||||
"tweetnacl": "~0.12.2",
|
"tweetnacl": "~0.12.2",
|
||||||
"ckeditor": "~4.5.6",
|
"ckeditor": "~4.5.6",
|
||||||
@ -28,6 +26,17 @@
|
|||||||
"reconnectingWebsocket": "",
|
"reconnectingWebsocket": "",
|
||||||
"diff-dom": "https://github.com/fiduswriter/diffDOM.git#6fdb82c8a4f2096c07c129797a313fe13769e094",
|
"diff-dom": "https://github.com/fiduswriter/diffDOM.git#6fdb82c8a4f2096c07c129797a313fe13769e094",
|
||||||
"marked": "~0.3.5",
|
"marked": "~0.3.5",
|
||||||
"rangy": "rangy-release#~1.3.0"
|
"rangy": "rangy-release#~1.3.0",
|
||||||
|
"json.sortify": "~2.1.0",
|
||||||
|
"fabric.js": "fabric#~1.6.0",
|
||||||
|
"hyperjson": "~1.2.2",
|
||||||
|
"textpatcher": "^1.2.0",
|
||||||
|
"proxy-polyfill": "^0.1.5",
|
||||||
|
"chainpad": "^0.2.2",
|
||||||
|
"chainpad-json-validator": "^0.1.1",
|
||||||
|
"chainpad-crypto": "^0.1.1",
|
||||||
|
"netflux-websocket": "^0.1.0",
|
||||||
|
"chainpad-netflux": "^0.1.0",
|
||||||
|
"chainpad-listmap": "^0.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,21 @@ module.exports = {
|
|||||||
// the port on which your httpd will listen
|
// the port on which your httpd will listen
|
||||||
httpPort: 3000,
|
httpPort: 3000,
|
||||||
// the port used for websockets
|
// the port used for websockets
|
||||||
websocketPort: 3001,
|
websocketPort: 3000,
|
||||||
|
|
||||||
|
/* Cryptpad can log activity to stdout
|
||||||
|
* This may be useful for debugging
|
||||||
|
*/
|
||||||
|
logToStdout: false,
|
||||||
|
|
||||||
|
/* Cryptpad can be configured to remove channels some number of ms
|
||||||
|
after the last remaining client has disconnected.
|
||||||
|
|
||||||
|
Default behaviour is to keep channels forever.
|
||||||
|
If you enable channel removal, the default removal time is one minute
|
||||||
|
*/
|
||||||
|
removeChannels: false,
|
||||||
|
channelRemovalTimeout: 60000,
|
||||||
|
|
||||||
// You now have a choice of storage engines
|
// You now have a choice of storage engines
|
||||||
|
|
||||||
@ -19,7 +33,7 @@ module.exports = {
|
|||||||
* it will not scale well if your server stays alive for a long time.
|
* it will not scale well if your server stays alive for a long time.
|
||||||
* but it is completely dependency free
|
* but it is completely dependency free
|
||||||
*/
|
*/
|
||||||
storage: './storage/amnesia',
|
//storage: './storage/amnesia',
|
||||||
|
|
||||||
/* the 'lvl' storage module uses leveldb
|
/* the 'lvl' storage module uses leveldb
|
||||||
* it persists, and will perform better than amnesiadb
|
* it persists, and will perform better than amnesiadb
|
||||||
@ -31,8 +45,8 @@ module.exports = {
|
|||||||
*
|
*
|
||||||
* to delete all pads, run `rm -rf $YOUR_DB`
|
* to delete all pads, run `rm -rf $YOUR_DB`
|
||||||
*/
|
*/
|
||||||
// storage: './storage/lvl',
|
storage: './storage/lvl',
|
||||||
// levelPath: './test.level.db'
|
levelPath: './test.level.db'
|
||||||
|
|
||||||
/* mongo is the original storage engine for cryptpad
|
/* mongo is the original storage engine for cryptpad
|
||||||
* it has been more thoroughly tested, but requires a little more setup
|
* it has been more thoroughly tested, but requires a little more setup
|
||||||
|
|||||||
@ -122,9 +122,12 @@
|
|||||||
</p>
|
</p>
|
||||||
</noscript>
|
</noscript>
|
||||||
<script>
|
<script>
|
||||||
require(['/common/crypto.js'], function (Crypto) {
|
require(['/common/crypto.js', '/api/config?cb=' + Math.random().toString(16).substring(2)], function (Crypto, Config) {
|
||||||
document.getElementById('buttons').setAttribute('style', '');
|
document.getElementById('buttons').setAttribute('style', '');
|
||||||
document.getElementById('create-pad').setAttribute('href', '/pad/#' + Crypto.genKey());
|
document.getElementById('create-pad').setAttribute('href', '/pad/');
|
||||||
|
if(Config.webrtcURL !== '') {
|
||||||
|
document.getElementById('create-rtcpad').setAttribute('href', '/padrtc/');
|
||||||
|
}
|
||||||
document.getElementById('create-sheet').setAttribute('href', '/sheet/#' + Crypto.genKey());
|
document.getElementById('create-sheet').setAttribute('href', '/sheet/#' + Crypto.genKey());
|
||||||
document.getElementById('create-code').setAttribute('href', '/code/#' + Crypto.genKey());
|
document.getElementById('create-code').setAttribute('href', '/code/#' + Crypto.genKey());
|
||||||
});
|
});
|
||||||
@ -215,7 +218,8 @@
|
|||||||
|
|
||||||
<div id="buttons" class="buttons" style="display:none;">
|
<div id="buttons" class="buttons" style="display:none;">
|
||||||
<a id="create-pad" class="button create" href="pad">CREATE NEW PAD</a>
|
<a id="create-pad" class="button create" href="pad">CREATE NEW PAD</a>
|
||||||
<a id="create-sheet" class="button create" href="sheet">CREATE NEW SPREADSHEET</a>
|
<a id="create-rtcpad" class="button create" href="pad">CREATE NEW WEBRTC PAD</a>
|
||||||
|
<!--<a id="create-sheet" class="button create" href="sheet">CREATE NEW SPREADSHEET</a>-->
|
||||||
<a id="create-code" class="button create" href="code">CREATE NEW CODE COLLABORATION PAD</a>
|
<a id="create-code" class="button create" href="code">CREATE NEW CODE COLLABORATION PAD</a>
|
||||||
</div>
|
</div>
|
||||||
</center>
|
</center>
|
||||||
|
|||||||
@ -9,9 +9,11 @@
|
|||||||
"nthen": "~0.1.0"
|
"nthen": "~0.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jshint": "~2.9.1"
|
"jshint": "~2.9.1",
|
||||||
|
"selenium-webdriver": "^2.53.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "jshint --config .jshintrc --exclude-path .jshintignore ."
|
"lint": "jshint --config .jshintrc --exclude-path .jshintignore .",
|
||||||
|
"test": "node TestSelenium.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,7 +25,7 @@ To install:
|
|||||||
npm install -g bower ## if necessary
|
npm install -g bower ## if necessary
|
||||||
bower install
|
bower install
|
||||||
|
|
||||||
## copy config.js.dist to config.js
|
## copy config.js.dist to config.js
|
||||||
cp config.js.dist config.js
|
cp config.js.dist config.js
|
||||||
|
|
||||||
## modify configuration to use your own mongodb instance
|
## modify configuration to use your own mongodb instance
|
||||||
@ -67,6 +67,13 @@ rm -rf ./cryptpad.db
|
|||||||
|
|
||||||
If you are using the mongodb adaptor, [drop the relevant collection](https://docs.mongodb.org/manual/reference/method/db.collection.drop/#db.collection.drop).
|
If you are using the mongodb adaptor, [drop the relevant collection](https://docs.mongodb.org/manual/reference/method/db.collection.drop/#db.collection.drop).
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
To test CryptPad, go to http://your.server:3000/assert/
|
||||||
|
|
||||||
|
You can use WebDriver to run this test automatically by running TestSelenium.js but you will need chromedriver installed.
|
||||||
|
If you use Mac, you can `brew install chromedriver`.
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
CryptPad is *private*, not *anonymous*. Privacy protects your data, anonymity protects you.
|
CryptPad is *private*, not *anonymous*. Privacy protects your data, anonymity protects you.
|
||||||
|
|||||||
17
server.js
17
server.js
@ -6,7 +6,8 @@ var Http = require('http');
|
|||||||
var Https = require('https');
|
var Https = require('https');
|
||||||
var Fs = require('fs');
|
var Fs = require('fs');
|
||||||
var WebSocketServer = require('ws').Server;
|
var WebSocketServer = require('ws').Server;
|
||||||
var ChainPadSrv = require('./ChainPadSrv');
|
var NetfluxSrv = require('./NetfluxWebsocketSrv');
|
||||||
|
var WebRTCSrv = require('./WebRTCSrv');
|
||||||
|
|
||||||
var config = require('./config');
|
var config = require('./config');
|
||||||
config.websocketPort = config.websocketPort || config.httpPort;
|
config.websocketPort = config.websocketPort || config.httpPort;
|
||||||
@ -17,12 +18,6 @@ var Storage = require(config.storage||'./storage/mongo');
|
|||||||
var app = Express();
|
var app = Express();
|
||||||
app.use(Express.static(__dirname + '/www'));
|
app.use(Express.static(__dirname + '/www'));
|
||||||
|
|
||||||
// Bower is broken and does not allow components nested within components...
|
|
||||||
// And jquery.sheet expects it!
|
|
||||||
// *Workaround*
|
|
||||||
app.use("/bower_components/jquery.sheet/bower_components",
|
|
||||||
Express.static(__dirname + '/www/bower_components'));
|
|
||||||
|
|
||||||
var customize = "/customize";
|
var customize = "/customize";
|
||||||
if (!Fs.existsSync(__dirname + "/customize")) {
|
if (!Fs.existsSync(__dirname + "/customize")) {
|
||||||
customize = "/customize.dist";
|
customize = "/customize.dist";
|
||||||
@ -60,7 +55,9 @@ app.get('/api/config', function(req, res){
|
|||||||
res.setHeader('Content-Type', 'text/javascript');
|
res.setHeader('Content-Type', 'text/javascript');
|
||||||
res.send('define(' + JSON.stringify({
|
res.send('define(' + JSON.stringify({
|
||||||
websocketURL:'ws' + ((httpsOpts) ? 's' : '') + '://' + host + ':' +
|
websocketURL:'ws' + ((httpsOpts) ? 's' : '') + '://' + host + ':' +
|
||||||
config.websocketPort + '/cryptpad_websocket'
|
config.websocketPort + '/cryptpad_websocket',
|
||||||
|
webrtcURL:'ws' + ((httpsOpts) ? 's' : '') + '://' + host + ':' +
|
||||||
|
config.websocketPort + '/cryptpad_webrtc',
|
||||||
}) + ');');
|
}) + ');');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -75,9 +72,9 @@ if (config.websocketPort !== config.httpPort) {
|
|||||||
console.log("setting up a new websocket server");
|
console.log("setting up a new websocket server");
|
||||||
wsConfig = { port: config.websocketPort};
|
wsConfig = { port: config.websocketPort};
|
||||||
}
|
}
|
||||||
|
|
||||||
var wsSrv = new WebSocketServer(wsConfig);
|
var wsSrv = new WebSocketServer(wsConfig);
|
||||||
Storage.create(config, function (store) {
|
Storage.create(config, function (store) {
|
||||||
console.log('DB connected');
|
console.log('DB connected');
|
||||||
ChainPadSrv.create(wsSrv, store);
|
NetfluxSrv.run(store, wsSrv, config);
|
||||||
|
WebRTCSrv.run(wsSrv);
|
||||||
});
|
});
|
||||||
|
|||||||
19
storage/LogStore.js
Normal file
19
storage/LogStore.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
var Fs = require("fs");
|
||||||
|
|
||||||
|
var message = function(file, msg) {
|
||||||
|
file.write(msg+"\n");
|
||||||
|
};
|
||||||
|
|
||||||
|
var create = module.exports.create = function(filePath, backingStore) {
|
||||||
|
|
||||||
|
var file = Fs.createWriteStream(filePath, {flags: 'a+'});
|
||||||
|
|
||||||
|
var originalMessageFunction = backingStore.message;
|
||||||
|
|
||||||
|
backingStore.message = function(channel, msg, callback) {
|
||||||
|
message(file, msg);
|
||||||
|
originalMessageFunction(channel, msg, callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
return backingStore;
|
||||||
|
};
|
||||||
@ -21,21 +21,32 @@ That function must accept two arguments:
|
|||||||
|
|
||||||
## Methods
|
## Methods
|
||||||
|
|
||||||
### message(channelName, content, callback)
|
### message(channelName, content, handler)
|
||||||
|
|
||||||
When Cryptpad receives a message, it saves it into its datastore using its equivalent of a table for its channel name, and then relays the message to every other client which is participating in the same channel.
|
When Cryptpad receives a message, it saves it into its datastore using its equivalent of a table for its channel name, and then relays the message to every other client which is participating in the same channel.
|
||||||
|
|
||||||
Relaying logic exists outside of the storage module, you simply need to store the message then execute the callback on success.
|
Relaying logic exists outside of the storage module, you simply need to store the message then execute the handler on success.
|
||||||
|
|
||||||
### getMessages(channelName, callback)
|
### getMessages(channelName, handler, callback)
|
||||||
|
|
||||||
When a new client joins, they request the entire history of messages for a particular channel.
|
When a new client joins, they request the entire history of messages for a particular channel.
|
||||||
This method retreives those messages, and delivers them in order.
|
This method retreives those messages, and delivers them in order.
|
||||||
|
|
||||||
In theory, it should be possible for Chainpad to make sense of out of order messages, however, this has not yet been implemented.
|
In practice, out of order messages make your clientside application more likely to fail, however, they are generally tolerated.
|
||||||
In practice, out of order messages make your clientside application likely to fail.
|
|
||||||
As a channel accumulates a greater number of messages, the likelihood of the application receiving them in the wrong order becomes greater.
|
As a channel accumulates a greater number of messages, the likelihood of the application receiving them in the wrong order becomes greater.
|
||||||
This results in older sessions becoming less reliable
|
This results in older sessions becoming less reliable.
|
||||||
|
|
||||||
|
This function accepts the name of the channel in which the user is interested, the handler for each message, and the callback to be executed when the last message has been fetched and handled.
|
||||||
|
|
||||||
|
**Note**, the callback is a new addition to this API.
|
||||||
|
It is only implemented within the leveldb adaptor, making our latest code incompatible with the other back ends.
|
||||||
|
While we migrate to our new Netflux API, only the leveldb adaptor will be supported.
|
||||||
|
|
||||||
|
## removeChannel(channelName, callback)
|
||||||
|
|
||||||
|
This method is called (optionally, see config.js.dist for more info) some amount of time after the last client in a channel disconnects.
|
||||||
|
|
||||||
|
It should remove any history of that channel, and execute a callback which takes an error message as an argument.
|
||||||
|
|
||||||
## Documenting your adaptor
|
## Documenting your adaptor
|
||||||
|
|
||||||
|
|||||||
@ -18,6 +18,11 @@ module.exports.create = function(conf, cb){
|
|||||||
|
|
||||||
var db=[],
|
var db=[],
|
||||||
index=0;
|
index=0;
|
||||||
|
|
||||||
|
if (conf.removeChannels) {
|
||||||
|
console.log("Server is set to remove channels %sms after the last remaining client leaves.", conf.channelRemovalTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
cb({
|
cb({
|
||||||
message: function(channelName, content, cb){
|
message: function(channelName, content, cb){
|
||||||
var val = {
|
var val = {
|
||||||
@ -27,17 +32,25 @@ module.exports.create = function(conf, cb){
|
|||||||
time: new Date().getTime(),
|
time: new Date().getTime(),
|
||||||
};
|
};
|
||||||
db.push(val);
|
db.push(val);
|
||||||
cb();
|
if (cb) { cb(); }
|
||||||
},
|
},
|
||||||
getMessages: function(channelName, cb){
|
getMessages: function(channelName, handler, cb){
|
||||||
db.sort(function(a,b){
|
db.sort(function(a,b){
|
||||||
return a.id - b.id;
|
return a.id - b.id;
|
||||||
});
|
});
|
||||||
db.filter(function(val){
|
db.filter(function(val){
|
||||||
return val.chan === channelName;
|
return val.chan === channelName;
|
||||||
}).forEach(function(doc){
|
}).forEach(function(doc){
|
||||||
cb(doc.msg);
|
handler(doc.msg);
|
||||||
});
|
});
|
||||||
|
if (cb) { cb(); }
|
||||||
|
},
|
||||||
|
removeChannel: function (channelName, cb) {
|
||||||
|
var err = false;
|
||||||
|
db = db.filter(function (msg) {
|
||||||
|
return msg.chan !== channelName;
|
||||||
|
});
|
||||||
|
cb(err);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -17,39 +17,57 @@ var getIndex = function(db, cName, cb) {
|
|||||||
|
|
||||||
var insert = function (db, channelName, content, cb) {
|
var insert = function (db, channelName, content, cb) {
|
||||||
var index;
|
var index;
|
||||||
nThen(function (waitFor) {
|
var doIt = function () {
|
||||||
getIndex(db, channelName, waitFor(function (i) { index = i+1; }));
|
db.locked = true;
|
||||||
}).nThen(function (waitFor) {
|
nThen(function (waitFor) {
|
||||||
db.put(channelName+'=>'+index, content, waitFor(function (e) { if (e) { throw e; } }));
|
getIndex(db, channelName, waitFor(function (i) { index = i+1; }));
|
||||||
}).nThen(function (waitFor) {
|
}).nThen(function (waitFor) {
|
||||||
db.put(channelName+'=>index', ''+index, waitFor(function (e) { if (e) { throw e; } }));
|
db.put(channelName+'=>'+index, content, waitFor(function (e) { if (e) { throw e; } }));
|
||||||
}).nThen(cb);
|
}).nThen(function (waitFor) {
|
||||||
|
db.put(channelName+'=>index', ''+index, waitFor(function (e) { if (e) { throw e; } }));
|
||||||
|
}).nThen(function (waitFor) {
|
||||||
|
db.locked = false;
|
||||||
|
if (!db.queue.length) { return; }
|
||||||
|
db.queue.shift()();
|
||||||
|
}).nThen(cb);
|
||||||
|
};
|
||||||
|
if (db.locked) {
|
||||||
|
db.queue.push(doIt);
|
||||||
|
} else {
|
||||||
|
doIt();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var getMessages = function (db, channelName, msgHandler) {
|
var getMessages = function (db, channelName, msgHandler, cb) {
|
||||||
var index;
|
var index;
|
||||||
nThen(function (waitFor) {
|
nThen(function (waitFor) {
|
||||||
getIndex(db, channelName, waitFor(function (i) { index = i; }));
|
getIndex(db, channelName, waitFor(function (i) {
|
||||||
|
index = i;
|
||||||
|
}));
|
||||||
}).nThen(function (waitFor) {
|
}).nThen(function (waitFor) {
|
||||||
var again = function (i) {
|
var again = function (i) {
|
||||||
db.get(channelName + '=>' + i, waitFor(function (e, out) {
|
db.get(channelName + '=>' + i, waitFor(function (e, out) {
|
||||||
if (e) { throw e; }
|
if (e) { throw e; }
|
||||||
msgHandler(out);
|
msgHandler(out);
|
||||||
if (i < index) { again(i+1); }
|
if (i < index) { again(i+1); }
|
||||||
|
else if (cb) { cb(); }
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
if (index > -1) { again(0); }
|
if (index > -1) { again(0); }
|
||||||
|
else if (cb) { cb(); }
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.create = function (conf, cb) {
|
module.exports.create = function (conf, cb) {
|
||||||
var db = Level(conf.levelPath || './test.level.db');
|
var db = Level(conf.levelPath || './test.level.db');
|
||||||
|
db.locked = false;
|
||||||
|
db.queue = [];
|
||||||
cb({
|
cb({
|
||||||
message: function (channelName, content, cb) {
|
message: function (channelName, content, cb) {
|
||||||
insert(db, channelName, content, cb);
|
insert(db, channelName, content, cb);
|
||||||
},
|
},
|
||||||
getMessages: function (channelName, msgHandler) {
|
getMessages: function (channelName, msgHandler, cb) {
|
||||||
getMessages(db, channelName, msgHandler);
|
getMessages(db, channelName, msgHandler, cb);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,124 +0,0 @@
|
|||||||
define([], function () {
|
|
||||||
// this makes recursing a lot simpler
|
|
||||||
var isArray = function (A) {
|
|
||||||
return Object.prototype.toString.call(A)==='[object Array]';
|
|
||||||
};
|
|
||||||
|
|
||||||
var parseStyle = function(el){
|
|
||||||
var style = el.style;
|
|
||||||
var output = {};
|
|
||||||
for (var i = 0; i < style.length; ++i) {
|
|
||||||
var item = style.item(i);
|
|
||||||
output[item] = style[item];
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
};
|
|
||||||
|
|
||||||
var callOnHyperJSON = function (hj, cb) {
|
|
||||||
var children;
|
|
||||||
|
|
||||||
if (hj && hj[2]) {
|
|
||||||
children = hj[2].map(function (child) {
|
|
||||||
if (isArray(child)) {
|
|
||||||
// if the child is an array, recurse
|
|
||||||
return callOnHyperJSON(child, cb);
|
|
||||||
} else if (typeof (child) === 'string') {
|
|
||||||
// string nodes have leading and trailing quotes
|
|
||||||
return child.replace(/(^"|"$)/g,"");
|
|
||||||
} else {
|
|
||||||
// the above branches should cover all methods
|
|
||||||
// if we hit this, there is a problem
|
|
||||||
throw new Error();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
children = [];
|
|
||||||
}
|
|
||||||
// this should return the top level element of your new DOM
|
|
||||||
return cb(hj[0], hj[1], children);
|
|
||||||
};
|
|
||||||
|
|
||||||
var classify = function (token) {
|
|
||||||
return '.' + token.trim();
|
|
||||||
};
|
|
||||||
|
|
||||||
var isValidClass = function (x) {
|
|
||||||
if (x && /\S/.test(x)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var isTruthy = function (x) {
|
|
||||||
return x;
|
|
||||||
};
|
|
||||||
|
|
||||||
var DOM2HyperJSON = function(el, predicate, filter){
|
|
||||||
if(!el.tagName && el.nodeType === Node.TEXT_NODE){
|
|
||||||
return el.textContent;
|
|
||||||
}
|
|
||||||
if(!el.attributes){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (predicate) {
|
|
||||||
if (!predicate(el)) {
|
|
||||||
// shortcircuit
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var attributes = {};
|
|
||||||
|
|
||||||
var i = 0;
|
|
||||||
for(;i < el.attributes.length; i++){
|
|
||||||
var attr = el.attributes[i];
|
|
||||||
if(attr.name && attr.value){
|
|
||||||
if(attr.name === "style"){
|
|
||||||
attributes.style = parseStyle(el);
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
attributes[attr.name] = attr.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// this should never be longer than three elements
|
|
||||||
var result = [];
|
|
||||||
|
|
||||||
// get the element type, id, and classes of the element
|
|
||||||
// and push them to the result array
|
|
||||||
var sel = el.tagName;
|
|
||||||
|
|
||||||
if(attributes.id){
|
|
||||||
// we don't have to do much to validate IDs because the browser
|
|
||||||
// will only permit one id to exist
|
|
||||||
// unless we come across a strange browser in the wild
|
|
||||||
sel = sel +'#'+ attributes.id;
|
|
||||||
delete attributes.id;
|
|
||||||
}
|
|
||||||
result.push(sel);
|
|
||||||
|
|
||||||
// second element of the array is the element attributes
|
|
||||||
result.push(attributes);
|
|
||||||
|
|
||||||
// third element of the array is an array of child nodes
|
|
||||||
var children = [];
|
|
||||||
|
|
||||||
// js hint complains if we use 'var' here
|
|
||||||
i = 0;
|
|
||||||
for(; i < el.childNodes.length; i++){
|
|
||||||
children.push(DOM2HyperJSON(el.childNodes[i], predicate, filter));
|
|
||||||
}
|
|
||||||
result.push(children.filter(isTruthy));
|
|
||||||
|
|
||||||
if (filter) {
|
|
||||||
return filter(result);
|
|
||||||
} else {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
fromDOM: DOM2HyperJSON,
|
|
||||||
callOn: callOnHyperJSON
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@ -1,400 +0,0 @@
|
|||||||
define([], function () {
|
|
||||||
var Hyperscript;
|
|
||||||
|
|
||||||
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
|
||||||
var split = require('browser-split')
|
|
||||||
var ClassList = require('class-list')
|
|
||||||
require('html-element')
|
|
||||||
|
|
||||||
function context () {
|
|
||||||
|
|
||||||
var cleanupFuncs = []
|
|
||||||
|
|
||||||
function h() {
|
|
||||||
var args = [].slice.call(arguments), e = null
|
|
||||||
function item (l) {
|
|
||||||
var r
|
|
||||||
function parseClass (string) {
|
|
||||||
// Our minimal parser doesn’t understand escaping CSS special
|
|
||||||
// characters like `#`. Don’t use them. More reading:
|
|
||||||
// https://mathiasbynens.be/notes/css-escapes .
|
|
||||||
|
|
||||||
var m = split(string, /([\.#]?[^\s#.]+)/)
|
|
||||||
if(/^\.|#/.test(m[1]))
|
|
||||||
e = document.createElement('div')
|
|
||||||
forEach(m, function (v) {
|
|
||||||
var s = v.substring(1,v.length)
|
|
||||||
if(!v) return
|
|
||||||
if(!e)
|
|
||||||
e = document.createElement(v)
|
|
||||||
else if (v[0] === '.')
|
|
||||||
ClassList(e).add(s)
|
|
||||||
else if (v[0] === '#')
|
|
||||||
e.setAttribute('id', s)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if(l == null)
|
|
||||||
;
|
|
||||||
else if('string' === typeof l) {
|
|
||||||
if(!e)
|
|
||||||
parseClass(l)
|
|
||||||
else
|
|
||||||
e.appendChild(r = document.createTextNode(l))
|
|
||||||
}
|
|
||||||
else if('number' === typeof l
|
|
||||||
|| 'boolean' === typeof l
|
|
||||||
|| l instanceof Date
|
|
||||||
|| l instanceof RegExp ) {
|
|
||||||
e.appendChild(r = document.createTextNode(l.toString()))
|
|
||||||
}
|
|
||||||
//there might be a better way to handle this...
|
|
||||||
else if (isArray(l))
|
|
||||||
forEach(l, item)
|
|
||||||
else if(isNode(l))
|
|
||||||
e.appendChild(r = l)
|
|
||||||
else if(l instanceof Text)
|
|
||||||
e.appendChild(r = l)
|
|
||||||
else if ('object' === typeof l) {
|
|
||||||
for (var k in l) {
|
|
||||||
if('function' === typeof l[k]) {
|
|
||||||
if(/^on\w+/.test(k)) {
|
|
||||||
(function (k, l) { // capture k, l in the closure
|
|
||||||
if (e.addEventListener){
|
|
||||||
e.addEventListener(k.substring(2), l[k], false)
|
|
||||||
cleanupFuncs.push(function(){
|
|
||||||
e.removeEventListener(k.substring(2), l[k], false)
|
|
||||||
})
|
|
||||||
}else{
|
|
||||||
e.attachEvent(k, l[k])
|
|
||||||
cleanupFuncs.push(function(){
|
|
||||||
e.detachEvent(k, l[k])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})(k, l)
|
|
||||||
} else {
|
|
||||||
// observable
|
|
||||||
e[k] = l[k]()
|
|
||||||
cleanupFuncs.push(l[k](function (v) {
|
|
||||||
e[k] = v
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(k === 'style') {
|
|
||||||
if('string' === typeof l[k]) {
|
|
||||||
e.style.cssText = l[k]
|
|
||||||
}else{
|
|
||||||
for (var s in l[k]) (function(s, v) {
|
|
||||||
if('function' === typeof v) {
|
|
||||||
// observable
|
|
||||||
e.style.setProperty(s, v())
|
|
||||||
cleanupFuncs.push(v(function (val) {
|
|
||||||
e.style.setProperty(s, val)
|
|
||||||
}))
|
|
||||||
} else
|
|
||||||
e.style.setProperty(s, l[k][s])
|
|
||||||
})(s, l[k][s])
|
|
||||||
}
|
|
||||||
} else if (k.substr(0, 5) === "data-") {
|
|
||||||
e.setAttribute(k, l[k])
|
|
||||||
} else {
|
|
||||||
e.setAttribute(k, l[k])
|
|
||||||
if (e.getAttribute(k) !== l[k]) {
|
|
||||||
e[k] = l[k]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if ('function' === typeof l) {
|
|
||||||
//assume it's an observable!
|
|
||||||
var v = l()
|
|
||||||
e.appendChild(r = isNode(v) ? v : document.createTextNode(v))
|
|
||||||
|
|
||||||
cleanupFuncs.push(l(function (v) {
|
|
||||||
if(isNode(v) && r.parentElement)
|
|
||||||
r.parentElement.replaceChild(v, r), r = v
|
|
||||||
else
|
|
||||||
r.textContent = v
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
while(args.length)
|
|
||||||
item(args.shift())
|
|
||||||
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
h.cleanup = function () {
|
|
||||||
for (var i = 0; i < cleanupFuncs.length; i++){
|
|
||||||
cleanupFuncs[i]()
|
|
||||||
}
|
|
||||||
cleanupFuncs.length = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
var h = module.exports = context()
|
|
||||||
h.context = context
|
|
||||||
|
|
||||||
Hyperscript = h;
|
|
||||||
|
|
||||||
function isNode (el) {
|
|
||||||
return el && el.nodeName && el.nodeType
|
|
||||||
}
|
|
||||||
|
|
||||||
function forEach (arr, fn) {
|
|
||||||
if (arr.forEach) return arr.forEach(fn)
|
|
||||||
for (var i = 0; i < arr.length; i++) fn(arr[i], i)
|
|
||||||
}
|
|
||||||
|
|
||||||
function isArray (arr) {
|
|
||||||
return Object.prototype.toString.call(arr) == '[object Array]'
|
|
||||||
}
|
|
||||||
|
|
||||||
},{"browser-split":2,"class-list":3,"html-element":6}],2:[function(require,module,exports){
|
|
||||||
/*!
|
|
||||||
* Cross-Browser Split 1.1.1
|
|
||||||
* Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
|
|
||||||
* Available under the MIT License
|
|
||||||
* ECMAScript compliant, uniform cross-browser split method
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Splits a string into an array of strings using a regex or string separator. Matches of the
|
|
||||||
* separator are not included in the result array. However, if `separator` is a regex that contains
|
|
||||||
* capturing groups, backreferences are spliced into the result each time `separator` is matched.
|
|
||||||
* Fixes browser bugs compared to the native `String.prototype.split` and can be used reliably
|
|
||||||
* cross-browser.
|
|
||||||
* @param {String} str String to split.
|
|
||||||
* @param {RegExp|String} separator Regex or string to use for separating the string.
|
|
||||||
* @param {Number} [limit] Maximum number of items to include in the result array.
|
|
||||||
* @returns {Array} Array of substrings.
|
|
||||||
* @example
|
|
||||||
*
|
|
||||||
* // Basic use
|
|
||||||
* split('a b c d', ' ');
|
|
||||||
* // -> ['a', 'b', 'c', 'd']
|
|
||||||
*
|
|
||||||
* // With limit
|
|
||||||
* split('a b c d', ' ', 2);
|
|
||||||
* // -> ['a', 'b']
|
|
||||||
*
|
|
||||||
* // Backreferences in result array
|
|
||||||
* split('..word1 word2..', /([a-z]+)(\d+)/i);
|
|
||||||
* // -> ['..', 'word', '1', ' ', 'word', '2', '..']
|
|
||||||
*/
|
|
||||||
module.exports = (function split(undef) {
|
|
||||||
|
|
||||||
var nativeSplit = String.prototype.split,
|
|
||||||
compliantExecNpcg = /()??/.exec("")[1] === undef,
|
|
||||||
// NPCG: nonparticipating capturing group
|
|
||||||
self;
|
|
||||||
|
|
||||||
self = function(str, separator, limit) {
|
|
||||||
// If `separator` is not a regex, use `nativeSplit`
|
|
||||||
if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
|
|
||||||
return nativeSplit.call(str, separator, limit);
|
|
||||||
}
|
|
||||||
var output = [],
|
|
||||||
flags = (separator.ignoreCase ? "i" : "") + (separator.multiline ? "m" : "") + (separator.extended ? "x" : "") + // Proposed for ES6
|
|
||||||
(separator.sticky ? "y" : ""),
|
|
||||||
// Firefox 3+
|
|
||||||
lastLastIndex = 0,
|
|
||||||
// Make `global` and avoid `lastIndex` issues by working with a copy
|
|
||||||
separator = new RegExp(separator.source, flags + "g"),
|
|
||||||
separator2, match, lastIndex, lastLength;
|
|
||||||
str += ""; // Type-convert
|
|
||||||
if (!compliantExecNpcg) {
|
|
||||||
// Doesn't need flags gy, but they don't hurt
|
|
||||||
separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
|
|
||||||
}
|
|
||||||
/* Values for `limit`, per the spec:
|
|
||||||
* If undefined: 4294967295 // Math.pow(2, 32) - 1
|
|
||||||
* If 0, Infinity, or NaN: 0
|
|
||||||
* If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
|
|
||||||
* If negative number: 4294967296 - Math.floor(Math.abs(limit))
|
|
||||||
* If other: Type-convert, then use the above rules
|
|
||||||
*/
|
|
||||||
limit = limit === undef ? -1 >>> 0 : // Math.pow(2, 32) - 1
|
|
||||||
limit >>> 0; // ToUint32(limit)
|
|
||||||
while (match = separator.exec(str)) {
|
|
||||||
// `separator.lastIndex` is not reliable cross-browser
|
|
||||||
lastIndex = match.index + match[0].length;
|
|
||||||
if (lastIndex > lastLastIndex) {
|
|
||||||
output.push(str.slice(lastLastIndex, match.index));
|
|
||||||
// Fix browsers whose `exec` methods don't consistently return `undefined` for
|
|
||||||
// nonparticipating capturing groups
|
|
||||||
if (!compliantExecNpcg && match.length > 1) {
|
|
||||||
match[0].replace(separator2, function() {
|
|
||||||
for (var i = 1; i < arguments.length - 2; i++) {
|
|
||||||
if (arguments[i] === undef) {
|
|
||||||
match[i] = undef;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (match.length > 1 && match.index < str.length) {
|
|
||||||
Array.prototype.push.apply(output, match.slice(1));
|
|
||||||
}
|
|
||||||
lastLength = match[0].length;
|
|
||||||
lastLastIndex = lastIndex;
|
|
||||||
if (output.length >= limit) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (separator.lastIndex === match.index) {
|
|
||||||
separator.lastIndex++; // Avoid an infinite loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (lastLastIndex === str.length) {
|
|
||||||
if (lastLength || !separator.test("")) {
|
|
||||||
output.push("");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
output.push(str.slice(lastLastIndex));
|
|
||||||
}
|
|
||||||
return output.length > limit ? output.slice(0, limit) : output;
|
|
||||||
};
|
|
||||||
|
|
||||||
return self;
|
|
||||||
})();
|
|
||||||
|
|
||||||
},{}],3:[function(require,module,exports){
|
|
||||||
// contains, add, remove, toggle
|
|
||||||
var indexof = require('indexof')
|
|
||||||
|
|
||||||
module.exports = ClassList
|
|
||||||
|
|
||||||
function ClassList(elem) {
|
|
||||||
var cl = elem.classList
|
|
||||||
|
|
||||||
if (cl) {
|
|
||||||
return cl
|
|
||||||
}
|
|
||||||
|
|
||||||
var classList = {
|
|
||||||
add: add
|
|
||||||
, remove: remove
|
|
||||||
, contains: contains
|
|
||||||
, toggle: toggle
|
|
||||||
, toString: $toString
|
|
||||||
, length: 0
|
|
||||||
, item: item
|
|
||||||
}
|
|
||||||
|
|
||||||
return classList
|
|
||||||
|
|
||||||
function add(token) {
|
|
||||||
var list = getTokens()
|
|
||||||
if (indexof(list, token) > -1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
list.push(token)
|
|
||||||
setTokens(list)
|
|
||||||
}
|
|
||||||
|
|
||||||
function remove(token) {
|
|
||||||
var list = getTokens()
|
|
||||||
, index = indexof(list, token)
|
|
||||||
|
|
||||||
if (index === -1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
list.splice(index, 1)
|
|
||||||
setTokens(list)
|
|
||||||
}
|
|
||||||
|
|
||||||
function contains(token) {
|
|
||||||
return indexof(getTokens(), token) > -1
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggle(token) {
|
|
||||||
if (contains(token)) {
|
|
||||||
remove(token)
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
add(token)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function $toString() {
|
|
||||||
return elem.className
|
|
||||||
}
|
|
||||||
|
|
||||||
function item(index) {
|
|
||||||
var tokens = getTokens()
|
|
||||||
return tokens[index] || null
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTokens() {
|
|
||||||
var className = elem.className
|
|
||||||
|
|
||||||
return filter(className.split(" "), isTruthy)
|
|
||||||
}
|
|
||||||
|
|
||||||
function setTokens(list) {
|
|
||||||
var length = list.length
|
|
||||||
|
|
||||||
elem.className = list.join(" ")
|
|
||||||
classList.length = length
|
|
||||||
|
|
||||||
for (var i = 0; i < list.length; i++) {
|
|
||||||
classList[i] = list[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
delete list[length]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function filter (arr, fn) {
|
|
||||||
var ret = []
|
|
||||||
for (var i = 0; i < arr.length; i++) {
|
|
||||||
if (fn(arr[i])) ret.push(arr[i])
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
function isTruthy(value) {
|
|
||||||
return !!value
|
|
||||||
}
|
|
||||||
|
|
||||||
},{"indexof":4}],4:[function(require,module,exports){
|
|
||||||
|
|
||||||
var indexOf = [].indexOf;
|
|
||||||
|
|
||||||
module.exports = function(arr, obj){
|
|
||||||
if (indexOf) return arr.indexOf(obj);
|
|
||||||
for (var i = 0; i < arr.length; ++i) {
|
|
||||||
if (arr[i] === obj) return i;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
};
|
|
||||||
},{}],5:[function(require,module,exports){
|
|
||||||
var h = require("./index.js");
|
|
||||||
|
|
||||||
module.exports = h;
|
|
||||||
|
|
||||||
/*
|
|
||||||
$(function () {
|
|
||||||
|
|
||||||
var newDoc = h('p',
|
|
||||||
|
|
||||||
h('ul', 'bang bang bang'.split(/\s/).map(function (word) {
|
|
||||||
return h('li', word);
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
$('body').html(newDoc.outerHTML);
|
|
||||||
});
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
},{"./index.js":1}],6:[function(require,module,exports){
|
|
||||||
|
|
||||||
},{}]},{},[5]);
|
|
||||||
|
|
||||||
return Hyperscript;
|
|
||||||
});
|
|
||||||
@ -4,6 +4,24 @@
|
|||||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
<script data-main="main" src="/bower_components/requirejs/require.js"></script>
|
<script data-main="main" src="/bower_components/requirejs/require.js"></script>
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.report {
|
||||||
|
font-size: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success {
|
||||||
|
border: 3px solid green;
|
||||||
|
}
|
||||||
|
.failure {
|
||||||
|
border: 3px solid red;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
border: 1px solid red;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@ -12,7 +30,12 @@
|
|||||||
<h2>Test 1</h2>
|
<h2>Test 1</h2>
|
||||||
<h3>class strings</h3>
|
<h3>class strings</h3>
|
||||||
<!-- put in weird HTML that might cause problems -->
|
<!-- put in weird HTML that might cause problems -->
|
||||||
<div id="target"><p class=" alice bob charlie has.dot" id="bang">pewpewpew</p></div>
|
<div id="target"><p class=" alice bob charlie has.dot">pewpewpew</p></div>
|
||||||
|
|
||||||
|
<h2>Test 1</h2>
|
||||||
|
<h3>paragraph text</h3>
|
||||||
|
<!-- -->
|
||||||
|
<div id="quot"><p>"pewpewpew"</p></div>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
@ -20,6 +43,6 @@
|
|||||||
<h3>XWiki Macros</h3>
|
<h3>XWiki Macros</h3>
|
||||||
|
|
||||||
<!-- Can we serialize XWiki Macros? -->
|
<!-- Can we serialize XWiki Macros? -->
|
||||||
<div id="widget"><div data-cke-widget-id="0" tabindex="-1" data-cke-widget-wrapper="1" data-cke-filter="off" class="cke_widget_wrapper cke_widget_block" data-cke-display-name="macro:velocity" contenteditable="false"><div class="macro cke_widget_element" data-macro="startmacro:velocity|-||-|Here is a macro" data-cke-widget-data="%7B%22classes%22%3A%7B%22macro%22%3A1%7D%7D" data-cke-widget-upcasted="1" data-cke-widget-keep-attr="0" data-widget="xwiki-macro"><p>Here is a macro</p></div><span style='background: rgba(220, 220, 220, 0.5) url("/common/cryptofist.png") repeat scroll 0% 0%; top: -15px; left: 0px; display: block;' class="cke_reset cke_widget_drag_handler_container"><img title="Click and drag to move" src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" data-cke-widget-drag-handler="1" class="cke_reset cke_widget_drag_handler" height="15" width="15"></span></div></div>
|
<div id="widget"><div data-cke-widget-id="0" tabindex="-1" data-cke-widget-wrapper="1" data-cke-filter="off" class="cke_widget_wrapper cke_widget_block" data-cke-display-name="macro:velocity" contenteditable="false"><div class="macro cke_widget_element" data-macro="startmacro:velocity|-||-|Here is a macro" data-cke-widget-data="%7B%22classes%22%3A%7B%22macro%22%3A1%7D%7D" data-cke-widget-upcasted="1" data-cke-widget-keep-attr="0" data-widget="xwiki-macro"><p>Here is a macro</p></div><span style='background: rgba(220, 220, 220, 0.5) url("/customize/cryptofist_small.png") repeat scroll 0% 0%; top: -15px; left: 0px; display: block;' class="cke_reset cke_widget_drag_handler_container"><img title="Click and drag to move" src="data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==" data-cke-widget-drag-handler="1" class="cke_reset cke_widget_drag_handler" height="15" width="15"></span></div></div>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|||||||
@ -1,27 +1,111 @@
|
|||||||
|
require.config({ paths: { 'json.sortify': '/bower_components/json.sortify/dist/JSON.sortify' } });
|
||||||
define([
|
define([
|
||||||
'/bower_components/jquery/dist/jquery.min.js',
|
'/bower_components/jquery/dist/jquery.min.js',
|
||||||
'/assert/hyperjson.js', // serializing classes as an attribute
|
'/bower_components/hyperjson/hyperjson.amd.js', // serializing classes as an attribute
|
||||||
'/assert/hyperscript.js', // using setAttribute
|
'/common/hyperscript.js', // using setAttribute
|
||||||
'/common/TextPatcher.js'
|
'/bower_components/textpatcher/TextPatcher.amd.js',
|
||||||
], function (jQuery, Hyperjson, Hyperscript, TextPatcher) {
|
'json.sortify',
|
||||||
|
], function (jQuery, Hyperjson, Hyperscript, TextPatcher, Sortify) {
|
||||||
var $ = window.jQuery;
|
var $ = window.jQuery;
|
||||||
window.Hyperjson = Hyperjson;
|
window.Hyperjson = Hyperjson;
|
||||||
window.Hyperscript = Hyperscript;
|
window.Hyperscript = Hyperscript;
|
||||||
window.TextPatcher = TextPatcher;
|
window.TextPatcher = TextPatcher;
|
||||||
|
window.Sortify = Sortify;
|
||||||
|
|
||||||
var assertions = 0;
|
var assertions = 0;
|
||||||
|
var failed = false;
|
||||||
|
var failedOn;
|
||||||
|
var failMessages = [];
|
||||||
|
|
||||||
|
var ASSERTS = [];
|
||||||
|
var runASSERTS = function () {
|
||||||
|
ASSERTS.forEach(function (f, index) {
|
||||||
|
f(index);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
var assert = function (test, msg) {
|
var assert = function (test, msg) {
|
||||||
if (test()) {
|
ASSERTS.push(function (i) {
|
||||||
assertions++;
|
var returned = test();
|
||||||
} else {
|
if (returned === true) {
|
||||||
throw new Error(msg || '');
|
assertions++;
|
||||||
}
|
} else {
|
||||||
|
failed = true;
|
||||||
|
failedOn = assertions;
|
||||||
|
failMessages.push({
|
||||||
|
test: i,
|
||||||
|
message: msg,
|
||||||
|
output: returned,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var $body = $('body');
|
var $body = $('body');
|
||||||
|
|
||||||
var roundTrip = function (target) {
|
var HJSON_list = [
|
||||||
|
'["DIV#target",{},[["P#bang",{"class":" alice bob charlie has.dot"},["pewpewpew"]]]]',
|
||||||
|
|
||||||
|
'["DIV#quot",{},[["P",{},["\\"pewpewpew\\""]]]]',
|
||||||
|
|
||||||
|
'["DIV#widget",{},[["DIV",{"class":"cke_widget_wrapper cke_widget_block","contenteditable":"false","data-cke-display-name":"macro:velocity","data-cke-filter":"off","data-cke-widget-id":"0","data-cke-widget-wrapper":"1","tabindex":"-1"},[["DIV",{"class":"macro cke_widget_element","data-cke-widget-data":"%7B%22classes%22%3A%7B%22macro%22%3A1%7D%7D","data-cke-widget-keep-attr":"0","data-cke-widget-upcasted":"1","data-macro":"startmacro:velocity|-||-|Here is a macro","data-widget":"xwiki-macro"},[["P",{},["Here is a macro"]]]],["SPAN",{"class":"cke_reset cke_widget_drag_handler_container","style":"background: rgba(220, 220, 220, 0.5) url(\\"/customize/cryptofist_small.png\\") repeat scroll 0% 0%; top: -15px; left: 0px; display: block;"},[["IMG",{"class":"cke_reset cke_widget_drag_handler","data-cke-widget-drag-handler":"1","height":"15","src":"data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==","title":"Click and drag to move","width":"15"},[]]]]]]]]',
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
var elementFilter = function () {
|
||||||
|
// pass everything
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
var attributeFilter = function (h) {
|
||||||
|
// don't filter anything
|
||||||
|
return h;
|
||||||
|
};
|
||||||
|
|
||||||
|
var HJSON_equal = function (shjson) {
|
||||||
|
assert(function () {
|
||||||
|
// parse your stringified Hyperjson
|
||||||
|
var hjson;
|
||||||
|
|
||||||
|
try {
|
||||||
|
hjson = JSON.parse(shjson);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// turn it into a DOM
|
||||||
|
var DOM = Hyperjson.callOn(hjson, Hyperscript);
|
||||||
|
|
||||||
|
// turn it back into stringified Hyperjson, but apply filters
|
||||||
|
var shjson2 = Sortify(Hyperjson.fromDOM(DOM, elementFilter, attributeFilter));
|
||||||
|
|
||||||
|
var success = shjson === shjson2;
|
||||||
|
|
||||||
|
var op = TextPatcher.diff(shjson, shjson2);
|
||||||
|
//console.log(TextPatcher.format(shjson, op));
|
||||||
|
|
||||||
|
var diff = TextPatcher.format(shjson, op);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return '<br><br>insert: ' + diff.insert + '<br><br>' +
|
||||||
|
'remove: ' + diff.remove + '<br><br>';
|
||||||
|
}
|
||||||
|
}, "expected hyperjson equality");
|
||||||
|
};
|
||||||
|
|
||||||
|
HJSON_list.map(HJSON_equal);
|
||||||
|
|
||||||
|
/* FIXME
|
||||||
|
This test is not correct. It passes in Firefox, but fails in Chrome,
|
||||||
|
even though for our purposes the produced code is valid. Passing
|
||||||
|
`<p class="bob" id="alice"></p>` through the function yields
|
||||||
|
`<p id="alice" class="bob"></p>`. This is the same element, but string
|
||||||
|
equality is not a correct metric. */
|
||||||
|
var roundTrip = function (sel) {
|
||||||
|
var target = $(sel)[0];
|
||||||
assert(function () {
|
assert(function () {
|
||||||
var hjson = Hyperjson.fromDOM(target);
|
var hjson = Hyperjson.fromDOM(target);
|
||||||
var cloned = Hyperjson.callOn(hjson, Hyperscript);
|
var cloned = Hyperjson.callOn(hjson, Hyperscript);
|
||||||
@ -29,22 +113,117 @@ define([
|
|||||||
var success = cloned.outerHTML === target.outerHTML;
|
var success = cloned.outerHTML === target.outerHTML;
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
|
var op = TextPatcher.diff(target.outerHTML, cloned.outerHTML);
|
||||||
window.DEBUG = {
|
window.DEBUG = {
|
||||||
error: "Expected equality between A and B",
|
error: "Expected equality between A and B",
|
||||||
A: target.outerHTML,
|
A: target.outerHTML,
|
||||||
B: cloned.outerHTML,
|
B: cloned.outerHTML,
|
||||||
target: target,
|
diff: op
|
||||||
diff: TextPatcher.diff(target.outerHTML, cloned.outerHTML)
|
|
||||||
};
|
};
|
||||||
console.log(JSON.stringify(window.DEBUG, null, 2));
|
//console.log(JSON.stringify(window.DEBUG, null, 2));
|
||||||
|
console.log("DIFF:");
|
||||||
|
TextPatcher.log(target.outerHTML, op);
|
||||||
}
|
}
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}, "Round trip serialization introduced artifacts.");
|
}, "Round trip serialization introduced artifacts.");
|
||||||
};
|
};
|
||||||
|
|
||||||
roundTrip($('#target')[0]);
|
var HTML_list = [
|
||||||
roundTrip($('#widget')[0]);
|
'#target',
|
||||||
|
'#widget', // fails in Firefox 19?
|
||||||
|
'#quot',
|
||||||
|
];
|
||||||
|
|
||||||
|
HTML_list.forEach(roundTrip);
|
||||||
|
|
||||||
|
var strungJSON = function (orig) {
|
||||||
|
var result;
|
||||||
|
assert(function () {
|
||||||
|
result = JSON.stringify(JSON.parse(orig));
|
||||||
|
return result === orig;
|
||||||
|
}, "expected result (" + result + ") to equal original (" + orig + ")");
|
||||||
|
};
|
||||||
|
|
||||||
|
[ '{"border":"1","style":{"width":"500px"}}',
|
||||||
|
'{"style":"width: 500px;","border":"1"}',
|
||||||
|
].forEach(function (orig) {
|
||||||
|
strungJSON(orig);
|
||||||
|
});
|
||||||
|
|
||||||
|
//assert(function () { }, "this is expected to fail");
|
||||||
|
|
||||||
|
/* TODO Test how browsers handle weird elements
|
||||||
|
like "_moz-resizing":"true"
|
||||||
|
|
||||||
|
and anything else you can think of.
|
||||||
|
|
||||||
|
Start with Hyperjson, turn it into a DOM and come back
|
||||||
|
*/
|
||||||
|
|
||||||
|
var swap = function (str, dict) {
|
||||||
|
return str.replace(/\{\{(.*?)\}\}/g, function (all, key) {
|
||||||
|
return typeof dict[key] !== 'undefined'? dict[key] : all;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var multiline = function (f) {
|
||||||
|
var str;
|
||||||
|
f.toString().replace(/\/\*([\s\S]*)\*\//g, function (all, out) {
|
||||||
|
str = out;
|
||||||
|
});
|
||||||
|
return str || '';
|
||||||
|
};
|
||||||
|
|
||||||
|
var formatFailures = function () {
|
||||||
|
var template = multiline(function () { /*
|
||||||
|
<p class="error">
|
||||||
|
Failed on test number {{test}} with error message:
|
||||||
|
"{{message}}"
|
||||||
|
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The test returned:
|
||||||
|
{{output}}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
*/});
|
||||||
|
return failMessages.map(function (obj) {
|
||||||
|
console.log(obj);
|
||||||
|
return swap(template, obj);
|
||||||
|
}).join("\n");
|
||||||
|
};
|
||||||
|
|
||||||
|
runASSERTS();
|
||||||
|
|
||||||
|
$("body").html(function (i, val) {
|
||||||
|
var dict = {
|
||||||
|
previous: val,
|
||||||
|
totalAssertions: ASSERTS.length,
|
||||||
|
passedAssertions: assertions,
|
||||||
|
plural: (assertions === 1? '' : 's'),
|
||||||
|
failMessages: formatFailures()
|
||||||
|
};
|
||||||
|
|
||||||
|
var SUCCESS = swap(multiline(function(){/*
|
||||||
|
<div class="report">{{passedAssertions}} / {{totalAssertions}} test{{plural}} passed.
|
||||||
|
|
||||||
|
{{failMessages}}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{{previous}}
|
||||||
|
*/}), dict);
|
||||||
|
|
||||||
|
var report = SUCCESS;
|
||||||
|
|
||||||
|
return report;
|
||||||
|
});
|
||||||
|
|
||||||
|
var $report = $('.report');
|
||||||
|
$report.addClass(failed?'failure':'success');
|
||||||
|
|
||||||
console.log("%s test%s passed", assertions, assertions === 1? '':'s');
|
|
||||||
});
|
});
|
||||||
|
|||||||
78
www/canvas/index.html
Normal file
78
www/canvas/index.html
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
<script data-main="main" src="/bower_components/requirejs/require.js"></script>
|
||||||
|
<style>
|
||||||
|
html, body{
|
||||||
|
padding: 0px;
|
||||||
|
margin: 0px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
textarea{
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100vh;
|
||||||
|
|
||||||
|
font-size: 18px;
|
||||||
|
background-color: #073642;
|
||||||
|
color: #839496;
|
||||||
|
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
/* disallow textarea resizes */
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
canvas {
|
||||||
|
border: 5px solid black;
|
||||||
|
}
|
||||||
|
#clear {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
#colors {
|
||||||
|
z-index: 100;
|
||||||
|
border: 3px solid black;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
#copy {
|
||||||
|
padding-left: 75px;
|
||||||
|
}
|
||||||
|
span.palette {
|
||||||
|
height: 4vw;
|
||||||
|
width: 4vw;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 5px;
|
||||||
|
border: 2px solid black;
|
||||||
|
}
|
||||||
|
#controls {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
border: 3px solid black;
|
||||||
|
}
|
||||||
|
#width, #colors {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<canvas id="canvas" width="600" height="600" ></canvas>
|
||||||
|
|
||||||
|
<div id="copy">
|
||||||
|
<h2>Welcome to CryptCanvas!</h2>
|
||||||
|
<h3>Zero Knowledge Realtime Collaborative Canvas Editing</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="controls">
|
||||||
|
<button id="clear">Clear</button>
|
||||||
|
<input id="width" type="number" value="5"></input>
|
||||||
|
<div id="colors"> </div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
120
www/canvas/main.js
Normal file
120
www/canvas/main.js
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
require.config({ paths: {
|
||||||
|
'json.sortify': '/bower_components/json.sortify/dist/JSON.sortify'
|
||||||
|
}});
|
||||||
|
|
||||||
|
define([
|
||||||
|
'/api/config?cb=' + Math.random().toString(16).substring(2),
|
||||||
|
'/bower_components/chainpad-netflux/chainpad-netflux.js',
|
||||||
|
'/common/messages.js',
|
||||||
|
'/bower_components/chainpad-crypto/crypto.js',
|
||||||
|
'/bower_components/textpatcher/TextPatcher.amd.js',
|
||||||
|
'json.sortify',
|
||||||
|
'/bower_components/chainpad-json-validator/json-ot.js',
|
||||||
|
'/bower_components/fabric.js/dist/fabric.min.js',
|
||||||
|
'/bower_components/jquery/dist/jquery.min.js',
|
||||||
|
'/customize/pad.js'
|
||||||
|
], function (Config, Realtime, Messages, Crypto, TextPatcher, JSONSortify, JsonOT) {
|
||||||
|
var module = window.APP = { };
|
||||||
|
var $ = module.$ = window.jQuery;
|
||||||
|
var Fabric = module.Fabric = window.fabric;
|
||||||
|
|
||||||
|
|
||||||
|
var key;
|
||||||
|
var channel = '';
|
||||||
|
if (!/#/.test(window.location.href)) {
|
||||||
|
key = Crypto.genKey();
|
||||||
|
} else {
|
||||||
|
var hash = window.location.hash.slice(1);
|
||||||
|
channel = hash.slice(0, 32);
|
||||||
|
key = hash.slice(32);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Initialize Fabric */
|
||||||
|
var canvas = module.canvas = new Fabric.Canvas('canvas');
|
||||||
|
var $canvas = $('canvas');
|
||||||
|
|
||||||
|
var $width = $('#width');
|
||||||
|
var updateBrushWidth = function () {
|
||||||
|
canvas.freeDrawingBrush.width = Number($width.val());
|
||||||
|
};
|
||||||
|
updateBrushWidth();
|
||||||
|
|
||||||
|
$width.on('change', updateBrushWidth);
|
||||||
|
|
||||||
|
var palette = ['red', 'blue', 'green', 'white', 'black', 'purple',
|
||||||
|
'gray', 'beige', 'brown', 'cyan', 'darkcyan', 'gold', 'yellow', 'pink'];
|
||||||
|
var $colors = $('#colors');
|
||||||
|
$colors.html(function (i, val) {
|
||||||
|
return palette.map(function (c) {
|
||||||
|
return "<span class='palette' style='background-color:"+c+"'></span>";
|
||||||
|
}).join("");
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.palette').on('click', function () {
|
||||||
|
var color = $(this).css('background-color');
|
||||||
|
canvas.freeDrawingBrush.color = color;
|
||||||
|
});
|
||||||
|
|
||||||
|
var setEditable = function (bool) {
|
||||||
|
canvas.isDrawingMode = bool;
|
||||||
|
$canvas.css('border-color', bool? 'black': 'red');
|
||||||
|
};
|
||||||
|
|
||||||
|
var initializing = true;
|
||||||
|
|
||||||
|
var config = module.config = {
|
||||||
|
// TODO initialState ?
|
||||||
|
websocketURL: Config.websocketURL,
|
||||||
|
userName: Crypto.rand64(8),
|
||||||
|
channel: channel,
|
||||||
|
cryptKey: key,
|
||||||
|
crypto: Crypto,
|
||||||
|
transformFunction: JsonOT.validate,
|
||||||
|
};
|
||||||
|
|
||||||
|
var onInit = config.onInit = function (info) {
|
||||||
|
window.location.hash = info.channel + key;
|
||||||
|
$(window).on('hashchange', function() {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var onRemote = config.onRemote = function () {
|
||||||
|
if (initializing) { return; }
|
||||||
|
var userDoc = module.realtime.getUserDoc();
|
||||||
|
canvas.loadFromJSON(userDoc);
|
||||||
|
canvas.renderAll();
|
||||||
|
};
|
||||||
|
|
||||||
|
var onLocal = config.onLocal = function () {
|
||||||
|
if (initializing) { return; }
|
||||||
|
var content = JSONSortify(canvas.toDatalessJSON());
|
||||||
|
module.patchText(content);
|
||||||
|
};
|
||||||
|
|
||||||
|
var onReady = config.onReady = function (info) {
|
||||||
|
var realtime = module.realtime = info.realtime;
|
||||||
|
module.patchText = TextPatcher.create({
|
||||||
|
realtime: realtime
|
||||||
|
});
|
||||||
|
|
||||||
|
setEditable(true);
|
||||||
|
initializing = false;
|
||||||
|
onRemote();
|
||||||
|
};
|
||||||
|
|
||||||
|
var onAbort = config.onAbort = function (info) {
|
||||||
|
setEditable(false);
|
||||||
|
window.alert("Server Connection Lost");
|
||||||
|
};
|
||||||
|
|
||||||
|
var rt = Realtime.start(config);
|
||||||
|
|
||||||
|
canvas.on('mouse:up', onLocal);
|
||||||
|
|
||||||
|
$('#clear').on('click', function () {
|
||||||
|
canvas.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
@ -27,6 +27,8 @@ Alex Piggott
|
|||||||
Aliaksei Chapyzhenka
|
Aliaksei Chapyzhenka
|
||||||
Allen Sarkisyan
|
Allen Sarkisyan
|
||||||
Amin Shali
|
Amin Shali
|
||||||
|
Amin Ullah Khan
|
||||||
|
amshali@google.com
|
||||||
Amsul
|
Amsul
|
||||||
amuntean
|
amuntean
|
||||||
Amy
|
Amy
|
||||||
@ -59,10 +61,13 @@ Anthony Grimes
|
|||||||
Anton Kovalyov
|
Anton Kovalyov
|
||||||
AQNOUCH Mohammed
|
AQNOUCH Mohammed
|
||||||
areos
|
areos
|
||||||
|
Arnab Bose
|
||||||
as3boyan
|
as3boyan
|
||||||
AtomicPages LLC
|
AtomicPages LLC
|
||||||
Atul Bhouraskar
|
Atul Bhouraskar
|
||||||
Aurelian Oancea
|
Aurelian Oancea
|
||||||
|
Barret Rennie
|
||||||
|
Basarat Ali Syed
|
||||||
Bastian Müller
|
Bastian Müller
|
||||||
belhaj
|
belhaj
|
||||||
Bem Jones-Bey
|
Bem Jones-Bey
|
||||||
@ -70,6 +75,7 @@ benbro
|
|||||||
Beni Cherniavsky-Paskin
|
Beni Cherniavsky-Paskin
|
||||||
Benjamin DeCoste
|
Benjamin DeCoste
|
||||||
Ben Keen
|
Ben Keen
|
||||||
|
Ben Mosher
|
||||||
Bernhard Sirlinger
|
Bernhard Sirlinger
|
||||||
Bert Chang
|
Bert Chang
|
||||||
Billy Moon
|
Billy Moon
|
||||||
@ -86,11 +92,14 @@ Brett Zamir
|
|||||||
Brian Grinstead
|
Brian Grinstead
|
||||||
Brian Sletten
|
Brian Sletten
|
||||||
Bruce Mitchener
|
Bruce Mitchener
|
||||||
|
Caitlin Potter
|
||||||
Calin Barbat
|
Calin Barbat
|
||||||
|
Chad Jolly
|
||||||
Chandra Sekhar Pydi
|
Chandra Sekhar Pydi
|
||||||
Charles Skelton
|
Charles Skelton
|
||||||
Cheah Chu Yeow
|
Cheah Chu Yeow
|
||||||
Chris Coyier
|
Chris Coyier
|
||||||
|
Chris Ford
|
||||||
Chris Granger
|
Chris Granger
|
||||||
Chris Houseknecht
|
Chris Houseknecht
|
||||||
Chris Lohfink
|
Chris Lohfink
|
||||||
@ -100,9 +109,11 @@ Christian Petrov
|
|||||||
Christopher Brown
|
Christopher Brown
|
||||||
Christopher Mitchell
|
Christopher Mitchell
|
||||||
Christopher Pfohl
|
Christopher Pfohl
|
||||||
|
Chunliang Lyu
|
||||||
ciaranj
|
ciaranj
|
||||||
CodeAnimal
|
CodeAnimal
|
||||||
coderaiser
|
coderaiser
|
||||||
|
Cole R Lawrence
|
||||||
ComFreek
|
ComFreek
|
||||||
Curtis Gagliardi
|
Curtis Gagliardi
|
||||||
dagsta
|
dagsta
|
||||||
@ -114,6 +125,7 @@ Daniel, Dao Quang Minh
|
|||||||
Daniele Di Sarli
|
Daniele Di Sarli
|
||||||
Daniel Faust
|
Daniel Faust
|
||||||
Daniel Huigens
|
Daniel Huigens
|
||||||
|
Daniel Kesler
|
||||||
Daniel KJ
|
Daniel KJ
|
||||||
Daniel Neel
|
Daniel Neel
|
||||||
Daniel Parnell
|
Daniel Parnell
|
||||||
@ -126,8 +138,10 @@ David Barnett
|
|||||||
David Mignot
|
David Mignot
|
||||||
David Pathakjee
|
David Pathakjee
|
||||||
David Vázquez
|
David Vázquez
|
||||||
|
David Whittington
|
||||||
deebugger
|
deebugger
|
||||||
Deep Thought
|
Deep Thought
|
||||||
|
Devin Abbott
|
||||||
Devon Carew
|
Devon Carew
|
||||||
dignifiedquire
|
dignifiedquire
|
||||||
Dimage Sapelkin
|
Dimage Sapelkin
|
||||||
@ -139,13 +153,16 @@ Doug Wikle
|
|||||||
Drew Bratcher
|
Drew Bratcher
|
||||||
Drew Hintz
|
Drew Hintz
|
||||||
Drew Khoury
|
Drew Khoury
|
||||||
|
Drini Cami
|
||||||
Dror BG
|
Dror BG
|
||||||
duralog
|
duralog
|
||||||
eborden
|
eborden
|
||||||
edsharp
|
edsharp
|
||||||
ekhaled
|
ekhaled
|
||||||
|
Elisée
|
||||||
Enam Mijbah Noor
|
Enam Mijbah Noor
|
||||||
Eric Allam
|
Eric Allam
|
||||||
|
Erik Welander
|
||||||
eustas
|
eustas
|
||||||
Fabien O'Carroll
|
Fabien O'Carroll
|
||||||
Fabio Zendhi Nagao
|
Fabio Zendhi Nagao
|
||||||
@ -167,19 +184,24 @@ Gabriel Horner
|
|||||||
Gabriel Nahmias
|
Gabriel Nahmias
|
||||||
galambalazs
|
galambalazs
|
||||||
Gautam Mehta
|
Gautam Mehta
|
||||||
|
Gavin Douglas
|
||||||
gekkoe
|
gekkoe
|
||||||
|
geowarin
|
||||||
Gerard Braad
|
Gerard Braad
|
||||||
Gergely Hegykozi
|
Gergely Hegykozi
|
||||||
Giovanni Calò
|
Giovanni Calò
|
||||||
|
Glebov Boris
|
||||||
Glenn Jorde
|
Glenn Jorde
|
||||||
Glenn Ruehle
|
Glenn Ruehle
|
||||||
Golevka
|
Golevka
|
||||||
|
Google Inc.
|
||||||
Gordon Smith
|
Gordon Smith
|
||||||
Grant Skinner
|
Grant Skinner
|
||||||
greengiant
|
greengiant
|
||||||
Gregory Koberger
|
Gregory Koberger
|
||||||
Guillaume Massé
|
Guillaume Massé
|
||||||
Guillaume Massé
|
Guillaume Massé
|
||||||
|
guraga
|
||||||
Gustavo Rodrigues
|
Gustavo Rodrigues
|
||||||
Hakan Tunc
|
Hakan Tunc
|
||||||
Hans Engel
|
Hans Engel
|
||||||
@ -190,12 +212,14 @@ Herculano Campos
|
|||||||
Hiroyuki Makino
|
Hiroyuki Makino
|
||||||
hitsthings
|
hitsthings
|
||||||
Hocdoc
|
Hocdoc
|
||||||
|
Hugues Malphettes
|
||||||
Ian Beck
|
Ian Beck
|
||||||
Ian Dickinson
|
Ian Dickinson
|
||||||
Ian Wehrman
|
Ian Wehrman
|
||||||
Ian Wetherbee
|
Ian Wetherbee
|
||||||
Ice White
|
Ice White
|
||||||
ICHIKAWA, Yuji
|
ICHIKAWA, Yuji
|
||||||
|
idleberg
|
||||||
ilvalle
|
ilvalle
|
||||||
Ingo Richter
|
Ingo Richter
|
||||||
Irakli Gozalishvili
|
Irakli Gozalishvili
|
||||||
@ -212,6 +236,7 @@ Jan Jongboom
|
|||||||
jankeromnes
|
jankeromnes
|
||||||
Jan Keromnes
|
Jan Keromnes
|
||||||
Jan Odvarko
|
Jan Odvarko
|
||||||
|
Jan Schär
|
||||||
Jan T. Sott
|
Jan T. Sott
|
||||||
Jared Forsyth
|
Jared Forsyth
|
||||||
Jason
|
Jason
|
||||||
@ -227,12 +252,17 @@ jeffkenton
|
|||||||
Jeff Pickhardt
|
Jeff Pickhardt
|
||||||
jem (graphite)
|
jem (graphite)
|
||||||
Jeremy Parmenter
|
Jeremy Parmenter
|
||||||
|
Jim
|
||||||
|
JobJob
|
||||||
|
jochenberger
|
||||||
Jochen Berger
|
Jochen Berger
|
||||||
Johan Ask
|
Johan Ask
|
||||||
John Connor
|
John Connor
|
||||||
|
John Engler
|
||||||
John Lees-Miller
|
John Lees-Miller
|
||||||
John Snelson
|
John Snelson
|
||||||
John Van Der Loo
|
John Van Der Loo
|
||||||
|
Jon Ander Peñalba
|
||||||
Jonas Döbertin
|
Jonas Döbertin
|
||||||
Jonathan Malmaud
|
Jonathan Malmaud
|
||||||
jongalloway
|
jongalloway
|
||||||
@ -240,6 +270,7 @@ Jon Malmaud
|
|||||||
Jon Sangster
|
Jon Sangster
|
||||||
Joost-Wim Boekesteijn
|
Joost-Wim Boekesteijn
|
||||||
Joseph Pecoraro
|
Joseph Pecoraro
|
||||||
|
Josh Cohen
|
||||||
Joshua Newman
|
Joshua Newman
|
||||||
Josh Watzman
|
Josh Watzman
|
||||||
jots
|
jots
|
||||||
@ -248,11 +279,15 @@ ju1ius
|
|||||||
Juan Benavides Romero
|
Juan Benavides Romero
|
||||||
Jucovschi Constantin
|
Jucovschi Constantin
|
||||||
Juho Vuori
|
Juho Vuori
|
||||||
|
Julien Rebetez
|
||||||
|
Justin Andresen
|
||||||
Justin Hileman
|
Justin Hileman
|
||||||
jwallers@gmail.com
|
jwallers@gmail.com
|
||||||
kaniga
|
kaniga
|
||||||
karevn
|
karevn
|
||||||
|
Kayur Patel
|
||||||
Ken Newman
|
Ken Newman
|
||||||
|
ken restivo
|
||||||
Ken Rockot
|
Ken Rockot
|
||||||
Kevin Earls
|
Kevin Earls
|
||||||
Kevin Sawicki
|
Kevin Sawicki
|
||||||
@ -296,12 +331,15 @@ Marek Rudnicki
|
|||||||
Marijn Haverbeke
|
Marijn Haverbeke
|
||||||
Mário Gonçalves
|
Mário Gonçalves
|
||||||
Mario Pietsch
|
Mario Pietsch
|
||||||
|
Mark Anderson
|
||||||
Mark Lentczner
|
Mark Lentczner
|
||||||
Marko Bonaci
|
Marko Bonaci
|
||||||
|
Markus Bordihn
|
||||||
Martin Balek
|
Martin Balek
|
||||||
Martín Gaitán
|
Martín Gaitán
|
||||||
Martin Hasoň
|
Martin Hasoň
|
||||||
Martin Hunt
|
Martin Hunt
|
||||||
|
Martin Laine
|
||||||
Martin Zagora
|
Martin Zagora
|
||||||
Mason Malone
|
Mason Malone
|
||||||
Mateusz Paprocki
|
Mateusz Paprocki
|
||||||
@ -323,10 +361,12 @@ Max Kirsch
|
|||||||
Max Schaefer
|
Max Schaefer
|
||||||
Max Xiantu
|
Max Xiantu
|
||||||
mbarkhau
|
mbarkhau
|
||||||
|
McBrainy
|
||||||
melpon
|
melpon
|
||||||
Metatheos
|
Metatheos
|
||||||
Micah Dubinko
|
Micah Dubinko
|
||||||
Michael
|
Michael
|
||||||
|
Michael Goderbauer
|
||||||
Michael Grey
|
Michael Grey
|
||||||
Michael Kaminsky
|
Michael Kaminsky
|
||||||
Michael Lehenbauer
|
Michael Lehenbauer
|
||||||
@ -361,6 +401,7 @@ Nicholas Bollweg
|
|||||||
Nicholas Bollweg (Nick)
|
Nicholas Bollweg (Nick)
|
||||||
Nick Kreeger
|
Nick Kreeger
|
||||||
Nick Small
|
Nick Small
|
||||||
|
Nicolò Ribaudo
|
||||||
Niels van Groningen
|
Niels van Groningen
|
||||||
nightwing
|
nightwing
|
||||||
Nikita Beloglazov
|
Nikita Beloglazov
|
||||||
@ -373,6 +414,7 @@ noragrossman
|
|||||||
Norman Rzepka
|
Norman Rzepka
|
||||||
Oreoluwa Onatemowo
|
Oreoluwa Onatemowo
|
||||||
pablo
|
pablo
|
||||||
|
pabloferz
|
||||||
Page
|
Page
|
||||||
Panupong Pasupat
|
Panupong Pasupat
|
||||||
paris
|
paris
|
||||||
@ -382,18 +424,25 @@ Patrick Stoica
|
|||||||
Patrick Strawderman
|
Patrick Strawderman
|
||||||
Paul Garvin
|
Paul Garvin
|
||||||
Paul Ivanov
|
Paul Ivanov
|
||||||
|
Pavel
|
||||||
Pavel Feldman
|
Pavel Feldman
|
||||||
Pavel Strashkin
|
Pavel Strashkin
|
||||||
Paweł Bartkiewicz
|
Paweł Bartkiewicz
|
||||||
peteguhl
|
peteguhl
|
||||||
|
peter
|
||||||
Peter Flynn
|
Peter Flynn
|
||||||
peterkroon
|
peterkroon
|
||||||
Peter Kroon
|
Peter Kroon
|
||||||
|
Philipp A
|
||||||
Philip Stadermann
|
Philip Stadermann
|
||||||
|
Pierre Gerold
|
||||||
|
Piët Delport
|
||||||
prasanthj
|
prasanthj
|
||||||
Prasanth J
|
Prasanth J
|
||||||
|
Prayag Verma
|
||||||
Radek Piórkowski
|
Radek Piórkowski
|
||||||
Rahul
|
Rahul
|
||||||
|
Rahul Anand
|
||||||
ramwin1
|
ramwin1
|
||||||
Randall Mason
|
Randall Mason
|
||||||
Randy Burden
|
Randy Burden
|
||||||
@ -420,10 +469,12 @@ Sascha Peilicke
|
|||||||
satamas
|
satamas
|
||||||
satchmorun
|
satchmorun
|
||||||
sathyamoorthi
|
sathyamoorthi
|
||||||
|
S. Chris Colbert
|
||||||
SCLINIC\jdecker
|
SCLINIC\jdecker
|
||||||
Scott Aikin
|
Scott Aikin
|
||||||
Scott Goodhew
|
Scott Goodhew
|
||||||
Sebastian Zaha
|
Sebastian Zaha
|
||||||
|
Sergey Goder
|
||||||
Se-Won Kim
|
Se-Won Kim
|
||||||
shaund
|
shaund
|
||||||
shaun gilchrist
|
shaun gilchrist
|
||||||
@ -434,6 +485,7 @@ Shiv Deepak
|
|||||||
Shmuel Englard
|
Shmuel Englard
|
||||||
Shubham Jain
|
Shubham Jain
|
||||||
silverwind
|
silverwind
|
||||||
|
sinkuu
|
||||||
snasa
|
snasa
|
||||||
soliton4
|
soliton4
|
||||||
sonson
|
sonson
|
||||||
@ -443,14 +495,19 @@ Stanislav Oaserele
|
|||||||
Stas Kobzar
|
Stas Kobzar
|
||||||
Stefan Borsje
|
Stefan Borsje
|
||||||
Steffen Beyer
|
Steffen Beyer
|
||||||
|
Steffen Bruchmann
|
||||||
Stephen Lavelle
|
Stephen Lavelle
|
||||||
|
Steve Champagne
|
||||||
Steve O'Hara
|
Steve O'Hara
|
||||||
stoskov
|
stoskov
|
||||||
|
Stu Kennedy
|
||||||
Sungho Kim
|
Sungho Kim
|
||||||
sverweij
|
sverweij
|
||||||
Taha Jahangir
|
Taha Jahangir
|
||||||
|
Tako Schotanus
|
||||||
Takuji Shimokawa
|
Takuji Shimokawa
|
||||||
Tarmil
|
Tarmil
|
||||||
|
TDaglis
|
||||||
tel
|
tel
|
||||||
tfjgeorge
|
tfjgeorge
|
||||||
Thaddee Tyl
|
Thaddee Tyl
|
||||||
@ -471,6 +528,8 @@ Tom MacWright
|
|||||||
Tony Jian
|
Tony Jian
|
||||||
Travis Heppe
|
Travis Heppe
|
||||||
Triangle717
|
Triangle717
|
||||||
|
Tristan Tarrant
|
||||||
|
TSUYUSATO Kitsune
|
||||||
twifkak
|
twifkak
|
||||||
Vestimir Markov
|
Vestimir Markov
|
||||||
vf
|
vf
|
||||||
@ -481,15 +540,18 @@ wenli
|
|||||||
Wes Cossick
|
Wes Cossick
|
||||||
Wesley Wiser
|
Wesley Wiser
|
||||||
Will Binns-Smith
|
Will Binns-Smith
|
||||||
|
Will Dean
|
||||||
William Jamieson
|
William Jamieson
|
||||||
William Stein
|
William Stein
|
||||||
Willy
|
Willy
|
||||||
Wojtek Ptak
|
Wojtek Ptak
|
||||||
|
Wu Cheng-Han
|
||||||
Xavier Mendez
|
Xavier Mendez
|
||||||
Yassin N. Hassan
|
Yassin N. Hassan
|
||||||
YNH Webdev
|
YNH Webdev
|
||||||
Yunchi Luo
|
Yunchi Luo
|
||||||
Yuvi Panda
|
Yuvi Panda
|
||||||
|
Zac Anger
|
||||||
Zachary Dremann
|
Zachary Dremann
|
||||||
Zhang Hao
|
Zhang Hao
|
||||||
zziuni
|
zziuni
|
||||||
718
www/code/codemirror-5.13.2/CHANGELOG.md
Normal file
718
www/code/codemirror-5.13.2/CHANGELOG.md
Normal file
@ -0,0 +1,718 @@
|
|||||||
|
## 5.13.2 (2016-03-23)
|
||||||
|
|
||||||
|
### Bugfixes
|
||||||
|
|
||||||
|
Solves a problem where the gutter would sometimes not extend all the way to the end of the document.
|
||||||
|
|
||||||
|
## 5.13.0 (2016-03-21)
|
||||||
|
|
||||||
|
### New features
|
||||||
|
|
||||||
|
New DOM event forwarded: [`"dragleave"`](http://codemirror.net/doc/manual.html#event_dom).
|
||||||
|
|
||||||
|
[protobuf mode](http://codemirror.net/mode/protobuf/index.html): Newly added.
|
||||||
|
|
||||||
|
### Bugfixes
|
||||||
|
|
||||||
|
Fix problem where [`findMarks`](http://codemirror.net/doc/manual.html#findMarks) sometimes failed to find multi-line marks.
|
||||||
|
|
||||||
|
Fix crash that showed up when atomic ranges and bidi text were combined.
|
||||||
|
|
||||||
|
[show-hint addon](http://codemirror.net/demo/complete.html): Completion widgets no longer close when the line indented or dedented.
|
||||||
|
|
||||||
|
[merge addon](http://codemirror.net/demo/merge.html): Fix bug when merging chunks at the end of the file.
|
||||||
|
|
||||||
|
[placeholder addon](http://codemirror.net/doc/manual.html#addon_placeholder): No longer gets confused by [`swapDoc`](http://codemirror.net/doc/manual.html#swapDoc).
|
||||||
|
|
||||||
|
[simplescrollbars addon](http://codemirror.net/doc/manual.html#addon_simplescrollbars): Fix invalid state when deleting at end of document.
|
||||||
|
|
||||||
|
[clike mode](http://codemirror.net/mode/clike/index.html): No longer gets confused when a comment starts after an operator.
|
||||||
|
|
||||||
|
[markdown mode](http://codemirror.net/mode/markdown/index.html): Now supports CommonMark-style flexible list indentation.
|
||||||
|
|
||||||
|
[dylan mode](http://codemirror.net/mode/dylan/index.html): Several improvements and fixes.
|
||||||
|
|
||||||
|
## 5.12.0 (2016-02-19)
|
||||||
|
|
||||||
|
### New features
|
||||||
|
|
||||||
|
[Vim bindings](http://codemirror.net/demo/vim.html): Ctrl-Q is now an alias for Ctrl-V.
|
||||||
|
|
||||||
|
[Vim bindings](http://codemirror.net/demo/vim.html): The Vim API now exposes an `unmap` method to unmap bindings.
|
||||||
|
|
||||||
|
[active-line addon](http://codemirror.net/demo/activeline.html): This addon can now style the active line's gutter.
|
||||||
|
|
||||||
|
[FCL mode](http://codemirror.net/mode/fcl/): Newly added.
|
||||||
|
|
||||||
|
[SQL mode](http://codemirror.net/mode/sql/): Now has a Postgresql dialect.
|
||||||
|
|
||||||
|
### Bugfixes
|
||||||
|
|
||||||
|
Fix [issue](https://github.com/codemirror/CodeMirror/issues/3781) where trying to scroll to a horizontal position outside of the document's width could cause the gutter to be positioned incorrectly.
|
||||||
|
|
||||||
|
Use absolute, rather than fixed positioning in the context-menu intercept hack, to work around a [problem](https://github.com/codemirror/CodeMirror/issues/3238) when the editor is inside a transformed parent container.
|
||||||
|
|
||||||
|
Solve a [problem](https://github.com/codemirror/CodeMirror/issues/3821) where the horizontal scrollbar could hide text in Firefox.
|
||||||
|
|
||||||
|
Fix a [bug](https://github.com/codemirror/CodeMirror/issues/3834) that caused phantom scroll space under the text in some situations.
|
||||||
|
|
||||||
|
[Sublime Text bindings](http://codemirror.net/demo/sublime.html): Bind delete-line to Shift-Ctrl-K on OS X.
|
||||||
|
|
||||||
|
[Markdown mode](http://codemirror.net/mode/markdown/): Fix [issue](https://github.com/codemirror/CodeMirror/issues/3787) where the mode would keep state related to fenced code blocks in an unsafe way, leading to occasional corrupted parses.
|
||||||
|
|
||||||
|
[Markdown mode](http://codemirror.net/mode/markdown/): Ignore backslashes in code fragments.
|
||||||
|
|
||||||
|
[Markdown mode](http://codemirror.net/mode/markdown/): Use whichever mode is registered as `text/html` to parse HTML.
|
||||||
|
|
||||||
|
[Clike mode](http://codemirror.net/mode/clike/): Improve indentation of Scala `=>` functions.
|
||||||
|
|
||||||
|
[Python mode](http://codemirror.net/mode/python/): Improve indentation of bracketed code.
|
||||||
|
|
||||||
|
[HTMLMixed mode](http://codemirror.net/mode/htmlmixed/): Support multi-line opening tags for sub-languages (`<script>`, `<style>`, etc).
|
||||||
|
|
||||||
|
[Spreadsheet mode](http://codemirror.net/mode/spreadsheet/): Fix bug where the mode did not advance the stream when finding a backslash.
|
||||||
|
|
||||||
|
[XML mode](http://codemirror.net/mode/xml/): The mode now takes a `matchClosing` option to configure whether mismatched closing tags should be highlighted as errors.
|
||||||
|
|
||||||
|
## 5.11.0 (2016-01-20)
|
||||||
|
|
||||||
|
* New modes: [JSX](http://codemirror.net/mode/jsx/index.html), [literate Haskell](http://codemirror.net/mode/haskell-literate/index.html)
|
||||||
|
* The editor now forwards more [DOM events](http://codemirror.net/doc/manual.html#event_dom): `cut`, `copy`, `paste`, and `touchstart`. It will also forward `mousedown` for drag events
|
||||||
|
* Fixes a bug where bookmarks next to collapsed spans were not rendered
|
||||||
|
* The [Swift](http://codemirror.net/mode/swift/index.html) mode now supports auto-indentation
|
||||||
|
* Frontmatters in the [YAML frontmatter](http://codemirror.net/mode/yaml-frontmatter/index.html) mode are now optional as intended
|
||||||
|
|
||||||
|
## 5.10.0 (2015-12-21)
|
||||||
|
|
||||||
|
* Modify the way [atomic ranges](http://codemirror.net/doc/manual.html#mark_atomic) are skipped by selection to try and make it less surprising.
|
||||||
|
* The [Swift mode](http://codemirror.net/mode/swift/index.html) was rewritten.
|
||||||
|
* New addon: [jump-to-line](http://codemirror.net/doc/manual.html#addon_jump-to-line).
|
||||||
|
* New method: [`isReadOnly`](http://codemirror.net/doc/manual.html#isReadOnly).
|
||||||
|
* The [show-hint addon](http://codemirror.net/doc/manual.html#addon_show-hint) now defaults to picking completions on single click.
|
||||||
|
* The object passed to [`"beforeSelectionChange"`](http://codemirror.net/doc/manual.html#event_beforeSelectionChange) events now has an `origin` property.
|
||||||
|
* New mode: [Crystal](http://codemirror.net/mode/crystal/index.html).
|
||||||
|
|
||||||
|
## 5.9.0 (2015-11-23)
|
||||||
|
|
||||||
|
* Improve the way overlay (OS X-style) scrollbars are handled
|
||||||
|
* Make [annotatescrollbar](http://codemirror.net/doc/manual.html#addon_annotatescrollbar) and scrollpastend addons work properly together
|
||||||
|
* Make [show-hint](http://codemirror.net/doc/manual.html#addon_show-hint) addon select options on single click by default, move selection to hovered item
|
||||||
|
* Properly fold comments that include block-comment-start markers
|
||||||
|
* Many small language mode fixes
|
||||||
|
|
||||||
|
## 5.8.0 (2015-10-20)
|
||||||
|
|
||||||
|
* Fixes an infinite loop in the [hardwrap addon](http://codemirror.net/doc/manual.html#addon_hardwrap)
|
||||||
|
* New modes: [NSIS](http://codemirror.net/mode/nsis/index.html), [Ceylon](http://codemirror.net/mode/clike/index.html)
|
||||||
|
* The Kotlin mode is now a [clike](http://codemirror.net/mode/clike/index.html) dialect, rather than a stand-alone mode
|
||||||
|
* New option: [`allowDropFileTypes`](http://codemirror.net/doc/manual.html#option_allowDropFileTypes). Binary files can no longer be dropped into CodeMirror
|
||||||
|
* New themes: [bespin](http://codemirror.net/demo/theme.html#bespin), [hopscotch](http://codemirror.net/demo/theme.html#hopscotch), [isotope](http://codemirror.net/demo/theme.html#isotope), [railscasts](http://codemirror.net/demo/theme.html#railscasts)
|
||||||
|
|
||||||
|
## 5.7.0 (2015-09-21)
|
||||||
|
|
||||||
|
* New modes: [Vue](http://codemirror.net/mode/vue/index.html), [Oz](http://codemirror.net/mode/oz/index.html), [MscGen](http://codemirror.net/mode/mscgen/index.html) (and dialects), [Closure Stylesheets](http://codemirror.net/mode/css/gss.html)
|
||||||
|
* Implement [CommonMark](http://commonmark.org)-style flexible list indent and cross-line code spans in [Markdown](http://codemirror.net/mode/markdown/index.html) mode
|
||||||
|
* Add a replace-all button to the [search addon](http://codemirror.net/doc/manual.html#addon_search), and make the persistent search dialog transparent when it obscures the match
|
||||||
|
* Handle `acync`/`await` and ocal and binary numbers in [JavaScript mode](http://codemirror.net/mode/javascript/index.html)
|
||||||
|
* Fix various issues with the [Haxe mode](http://codemirror.net/mode/haxe/index.html)
|
||||||
|
* Make the [closebrackets addon](http://codemirror.net/doc/manual.html#addon_closebrackets) select only the wrapped text when wrapping selection in brackets
|
||||||
|
* Tokenize properties as properties in the [CoffeeScript mode](http://codemirror.net/mode/coffeescript/index.html)
|
||||||
|
* The [placeholder addon](http://codemirror.net/doc/manual.html#addon_placeholder) now accepts a DOM node as well as a string placeholder
|
||||||
|
|
||||||
|
## 5.6.0 (2015-08-20)
|
||||||
|
|
||||||
|
* Fix bug where you could paste into a `readOnly` editor
|
||||||
|
* Show a cursor at the drop location when dragging over the editor
|
||||||
|
* The [Rust mode](http://codemirror.net/mode/rust/index.html) was rewritten to handle modern Rust
|
||||||
|
* The editor and theme CSS was cleaned up. Some selectors are now less specific than before
|
||||||
|
* New theme: [abcdef](http://codemirror.net/demo/theme.html#abcdef)
|
||||||
|
* Lines longer than [`maxHighlightLength`](http://codemirror.net/doc/manual.html#option_maxHighlightLength) are now less likely to mess up indentation
|
||||||
|
* New addons: [`autorefresh`](http://codemirror.net/doc/manual.html#addon_autorefresh) for refreshing an editor the first time it becomes visible, and `html-lint` for using [HTMLHint](http://htmlhint.com/)
|
||||||
|
* The [`search`](http://codemirror.net/doc/manual.html#addon_search) addon now recognizes `\r` and `\n` in pattern and replacement input
|
||||||
|
|
||||||
|
## 5.5.0 (2015-07-20)
|
||||||
|
|
||||||
|
* New option: [`lineSeparator`](http://codemirror.net/doc/manual.html#option_lineSeparator) (with corresponding [method](http://codemirror.net/doc/manual.html#lineSeparator))
|
||||||
|
* New themes: [dracula](http://codemirror.net/demo/theme.html#dracula), [seti](http://codemirror.net/demo/theme.html#seti), [yeti](http://codemirror.net/demo/theme.html#yeti), [material](http://codemirror.net/demo/theme.html#material), and [icecoder](http://codemirror.net/demo/theme.html#icecoder)
|
||||||
|
* New modes: [Brainfuck](http://codemirror.net/mode/brainfuck/index.html), [VHDL](http://codemirror.net/mode/vhdl/index.html), Squirrel ([clike](http://codemirror.net/mode/clike/index.html) dialect)
|
||||||
|
* Define a `findPersistent` command in the [search](http://codemirror.net/demo/search.html) addon, for a dialog that stays open as you cycle through matches
|
||||||
|
* From this release on, the NPM module doesn't include documentation and demos
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/5.4.0...5.5.0)
|
||||||
|
|
||||||
|
## 5.4.0 (2015-06-25)
|
||||||
|
|
||||||
|
* New modes: [Twig](http://codemirror.net/mode/twig/index.html), [Elm](http://codemirror.net/mode/elm/index.html), [Factor](http://codemirror.net/mode/factor/index.html), [Swift](http://codemirror.net/mode/swift/index.html)
|
||||||
|
* Prefer clipboard API (if available) when pasting
|
||||||
|
* Refined definition highlighting in [clike](http://codemirror.net/mode/clike/index.html) mode
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/5.3.0...5.4.0)
|
||||||
|
|
||||||
|
## 5.3.0 (2015-05-20)
|
||||||
|
|
||||||
|
* Fix several regressions in the [`show-hint`](http://codemirror.net/doc/manual.html#addon_show-hint) addon (`completeSingle` option, `"shown"` and `"close"` events)
|
||||||
|
* The [vim mode](http://codemirror.net/demo/vim.html) API was [documented](http://codemirror.net/doc/manual.html#vimapi)
|
||||||
|
* New modes: [ASN.1](http://codemirror.net/mode/asn.1/index.html), [TTCN](http://codemirror.net/mode/ttcn/index.html), and [TTCN-CFG](http://codemirror.net/mode/ttcn-cfg/index.html)
|
||||||
|
* The [clike](http://codemirror.net/mode/clike/index.html) mode can now deep-indent `switch` statements, and roughly recognizes types and defined identifiers
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/5.2.0...5.3.0)
|
||||||
|
|
||||||
|
## 5.2.0 (2015-04-20)
|
||||||
|
|
||||||
|
* Fix several race conditions in [`show-hint`](http://codemirror.net/doc/manual.html#addon_show-hint)'s asynchronous mode
|
||||||
|
* Fix backspace binding in [Sublime bindings](http://codemirror.net/demo/sublime.html)
|
||||||
|
* Change the way IME is handled in the `"textarea"` [input style](http://codemirror.net/doc/manual.html#option_inputStyle)
|
||||||
|
* New modes: [MUMPS](http://codemirror.net/mode/mumps/index.html), [Handlebars](http://codemirror.net/mode/handlebars/index.html)
|
||||||
|
* Rewritten modes: [Django](http://codemirror.net/mode/django/index.html), [Z80](http://codemirror.net/mode/z80/index.html)
|
||||||
|
* New theme: [Liquibyte](http://codemirror.net/demo/theme.html#liquibyte)
|
||||||
|
* New option: [`lineWiseCopyCut`](http://codemirror.net/doc/manual.html#option_lineWiseCopyCut)
|
||||||
|
* The [Vim mode](http://codemirror.net/demo/vim.html) now supports buffer-local options and the `filetype` setting
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/5.1.0...5.2.0)
|
||||||
|
|
||||||
|
## 5.1.0 (2015-03-23)
|
||||||
|
|
||||||
|
* New modes: [ASCII armor](http://codemirror.net/mode/asciiarmor/index.html) (PGP data), [Troff](http://codemirror.net/mode/troff/index.html), and [CMake](http://codemirror.net/mode/cmake/index.html).
|
||||||
|
* Remove SmartyMixed mode, rewrite [Smarty](http://codemirror.net/mode/smarty/index.html) mode to supersede it.
|
||||||
|
* New commands in the [merge addon](http://codemirror.net/doc/manual.html#addon_merge): `goNextDiff` and `goPrevDiff`.
|
||||||
|
* The [closebrackets addon](http://codemirror.net/doc/manual.html#addon_closebrackets) can now be configured per mode.
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/5.0.0...5.1.0).
|
||||||
|
|
||||||
|
## 5.0.0 (2015-02-20)
|
||||||
|
|
||||||
|
* Experimental mobile support (tested on iOS, Android Chrome, stock Android browser)
|
||||||
|
* New option [`inputStyle`](http://codemirror.net/doc/manual.html#option_inputStyle) to switch between hidden textarea and contenteditable input.
|
||||||
|
* The [`getInputField`](http://codemirror.net/doc/manual.html#getInputField) method is no longer guaranteed to return a textarea.
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.13.0...5.0.0).
|
||||||
|
|
||||||
|
## 4.13.0 (2015-02-20)
|
||||||
|
|
||||||
|
* Fix the way the [`closetag`](http://codemirror.net/demo/closetag.html) demo handles the slash character.
|
||||||
|
* New modes: [Forth](http://codemirror.net/mode/forth/index.html), [Stylus](http://codemirror.net/mode/stylus/index.html).
|
||||||
|
* Make the [CSS mode](http://codemirror.net/mode/css/index.html) understand some modern CSS extensions.
|
||||||
|
* Have the [Scala mode](http://codemirror.net/mode/clike/index.html) handle symbols and triple-quoted strings.
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.12.0...4.13.0).
|
||||||
|
|
||||||
|
## 4.12.0 (2015-01-22)
|
||||||
|
|
||||||
|
* The [`closetag`](http://codemirror.net/doc/manual.html#addon_closetag) addon now defines a `"closeTag"` command.
|
||||||
|
* Adds a `findModeByFileName` to the [mode metadata](http://codemirror.net/doc/manual.html#addon_meta) addon.
|
||||||
|
* [Simple mode](http://codemirror.net/demo/simplemode.html) rules can now contain a `sol` property to only match at the start of a line.
|
||||||
|
* New addon: [`selection-pointer`](http://codemirror.net/doc/manual.html#addon_selection-pointer) to style the mouse cursor over the selection.
|
||||||
|
* Improvements to the [Sass mode](http://codemirror.net/mode/sass/index.html)'s indentation.
|
||||||
|
* The [Vim keymap](http://codemirror.net/demo/vim.html)'s search functionality now supports [scrollbar annotation](http://codemirror.net/doc/manual.html#addon_matchesonscrollbar).
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.11.0...4.12.0).
|
||||||
|
|
||||||
|
## 4.11.0 (2015-01-09)
|
||||||
|
|
||||||
|
Unfortunately, 4.10 did not take care of the Firefox scrolling issue entirely. This release adds two more patches to address that.
|
||||||
|
|
||||||
|
## 4.10.0 (2014-12-29)
|
||||||
|
|
||||||
|
Emergency single-patch update to 4.9\. Fixes Firefox-specific problem where the cursor could end up behind the horizontal scrollbar.
|
||||||
|
|
||||||
|
## 4.9.0 (2014-12-23)
|
||||||
|
|
||||||
|
* Overhauled scroll bar handling. Add pluggable [scrollbar implementations](http://codemirror.net/demo/simplescrollbars.html).
|
||||||
|
* Tweaked behavior for the [completion addons](http://codemirror.net/doc/manual.html#addon_show-hint) to not take text after cursor into account.
|
||||||
|
* Two new optional features in the [merge addon](http://codemirror.net/doc/manual.html#addon_merge): aligning editors, and folding unchanged text.
|
||||||
|
* New modes: [Dart](http://codemirror.net/mode/dart/index.html), [EBNF](http://codemirror.net/mode/ebnf/index.html), [spreadsheet](http://codemirror.net/mode/spreadsheet/index.html), and [Soy](http://codemirror.net/mode/soy/index.html).
|
||||||
|
* New [addon](http://codemirror.net/demo/panel.html) to show persistent panels below/above an editor.
|
||||||
|
* New themes: [zenburn](http://codemirror.net/demo/theme.html#zenburn) and [tomorrow night bright](http://codemirror.net/demo/theme.html#tomorrow-night-bright).
|
||||||
|
* Allow ctrl-click to clear existing cursors.
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.8.0...4.9.0).
|
||||||
|
|
||||||
|
## 4.8.0 (2014-11-22)
|
||||||
|
|
||||||
|
* Built-in support for [multi-stroke key bindings](http://codemirror.net/doc/manual.html#normalizeKeyMap).
|
||||||
|
* New method: [`getLineTokens`](http://codemirror.net/doc/manual.html#getLineTokens).
|
||||||
|
* New modes: [dockerfile](http://codemirror.net/mode/dockerfile/index.html), [IDL](http://codemirror.net/mode/idl/index.html), [Objective C](http://codemirror.net/mode/clike/index.html) (crude).
|
||||||
|
* Support styling of gutter backgrounds, allow `"gutter"` styles in [`addLineClass`](http://codemirror.net/doc/manual.html#addLineClass).
|
||||||
|
* Many improvements to the [Vim mode](http://codemirror.net/demo/vim.html), rewritten visual mode.
|
||||||
|
* Improvements to modes: [gfm](http://codemirror.net/mode/gfm/index.html) (strikethrough), [SPARQL](http://codemirror.net/mode/sparql/index.html) (version 1.1 support), and [sTeX](http://codemirror.net/mode/stex/index.html) (no more runaway math mode).
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.7.0...4.8.0).
|
||||||
|
|
||||||
|
## 4.7.0 (2014-10-20)
|
||||||
|
|
||||||
|
* **Incompatible**: The [lint addon](http://codemirror.net/demo/lint.html) now passes the editor's value as first argument to asynchronous lint functions, for consistency. The editor is still passed, as fourth argument.
|
||||||
|
* Improved handling of unicode identifiers in modes for languages that support them.
|
||||||
|
* More mode improvements: [CoffeeScript](http://codemirror.net/mode/coffeescript/index.html) (indentation), [Verilog](http://codemirror.net/mode/verilog/index.html) (indentation), [Scala](http://codemirror.net/mode/clike/index.html) (indentation, triple-quoted strings), and [PHP](http://codemirror.net/mode/php/index.html) (interpolated variables in heredoc strings).
|
||||||
|
* New modes: [Textile](http://codemirror.net/mode/textile/index.html) and [Tornado templates](http://codemirror.net/mode/tornado/index.html).
|
||||||
|
* Experimental new [way to define modes](http://codemirror.net/demo/simplemode.html).
|
||||||
|
* Improvements to the [Vim bindings](http://codemirror.net/demo/vim.html): Arbitrary insert mode key mappings are now possible, and text objects are supported in visual mode.
|
||||||
|
* The mode [meta-information file](http://codemirror.net/mode/meta.js) now includes information about file extensions, and [helper functions](http://codemirror.net/doc/manual.html#addon_meta) `findModeByMIME` and `findModeByExtension`.
|
||||||
|
* New logo!
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.6.0...4.7.0).
|
||||||
|
|
||||||
|
## 4.6.0 (2014-09-19)
|
||||||
|
|
||||||
|
* New mode: [Modelica](http://codemirror.net/mode/modelica/index.html)
|
||||||
|
* New method: [`findWordAt`](http://codemirror.net/doc/manual.html#findWordAt)
|
||||||
|
* Make it easier to [use text background styling](http://codemirror.net/demo/markselection.html)
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.5.0...4.6.0).
|
||||||
|
|
||||||
|
## 4.5.0 (2014-08-21)
|
||||||
|
|
||||||
|
* Fix several serious bugs with horizontal scrolling
|
||||||
|
* New mode: [Slim](http://codemirror.net/mode/slim/index.html)
|
||||||
|
* New command: [`goLineLeftSmart`](http://codemirror.net/doc/manual.html#command_goLineLeftSmart)
|
||||||
|
* More fixes and extensions for the [Vim](http://codemirror.net/demo/vim.html) visual block mode
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.4.0...4.5.0).
|
||||||
|
|
||||||
|
## 4.4.0 (2014-07-21)
|
||||||
|
|
||||||
|
* **Note:** Some events might now fire in slightly different order (`"change"` is still guaranteed to fire before `"cursorActivity"`)
|
||||||
|
* Nested operations in multiple editors are now synced (complete at same time, reducing DOM reflows)
|
||||||
|
* Visual block mode for [vim](http://codemirror.net/demo/vim.html) (<C-v>) is nearly complete
|
||||||
|
* New mode: [Kotlin](http://codemirror.net/mode/kotlin/index.html)
|
||||||
|
* Better multi-selection paste for text copied from multiple CodeMirror selections
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.3.0...4.4.0).
|
||||||
|
|
||||||
|
## 4.3.0 (2014-06-23)
|
||||||
|
|
||||||
|
* Several [vim bindings](http://codemirror.net/demo/vim.html) improvements: search and exCommand history, global flag for `:substitute`, `:global` command.
|
||||||
|
* Allow hiding the cursor by setting [`cursorBlinkRate`](http://codemirror.net/doc/manual.html#option_cursorBlinkRate) to a negative value.
|
||||||
|
* Make gutter markers themeable, use this in foldgutter.
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.2.0...4.3.0).
|
||||||
|
|
||||||
|
## 4.2.0 (2014-05-19)
|
||||||
|
|
||||||
|
* Fix problem where some modes were broken by the fact that empty tokens were forbidden.
|
||||||
|
* Several fixes to context menu handling.
|
||||||
|
* On undo, scroll _change_, not cursor, into view.
|
||||||
|
* Rewritten [Jade](http://codemirror.net/mode/jade/index.html) mode.
|
||||||
|
* Various improvements to [Shell](http://codemirror.net/mode/shell/index.html) (support for more syntax) and [Python](http://codemirror.net/mode/python/index.html) (better indentation) modes.
|
||||||
|
* New mode: [Cypher](http://codemirror.net/mode/cypher/index.html).
|
||||||
|
* New theme: [Neo](http://codemirror.net/demo/theme.html#neo).
|
||||||
|
* Support direct styling options (color, line style, width) in the [rulers](http://codemirror.net/doc/manual.html#addon_rulers) addon.
|
||||||
|
* Recognize per-editor configuration for the [show-hint](http://codemirror.net/doc/manual.html#addon_show-hint) and [foldcode](http://codemirror.net/doc/manual.html#addon_foldcode) addons.
|
||||||
|
* More intelligent scanning for existing close tags in [closetag](http://codemirror.net/doc/manual.html#addon_closetag) addon.
|
||||||
|
* In the [Vim bindings](http://codemirror.net/demo/vim.html): Fix bracket matching, support case conversion in visual mode, visual paste, append action.
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.1.0...4.2.0).
|
||||||
|
|
||||||
|
## 4.1.0 (2014-04-22)
|
||||||
|
|
||||||
|
* _Slightly incompatible_: The [`"cursorActivity"`](http://codemirror.net/doc/manual.html#event_cursorActivity) event now fires after all other events for the operation (and only for handlers that were actually registered at the time the activity happened).
|
||||||
|
* New command: [`insertSoftTab`](http://codemirror.net/doc/manual.html#command_insertSoftTab).
|
||||||
|
* New mode: [Django](http://codemirror.net/mode/django/index.html).
|
||||||
|
* Improved modes: [Verilog](http://codemirror.net/mode/verilog/index.html) (rewritten), [Jinja2](http://codemirror.net/mode/jinja2/index.html), [Haxe](http://codemirror.net/mode/haxe/index.html), [PHP](http://codemirror.net/mode/php/index.html) (string interpolation highlighted), [JavaScript](http://codemirror.net/mode/javascript/index.html) (indentation of trailing else, template strings), [LiveScript](http://codemirror.net/mode/livescript/index.html) (multi-line strings).
|
||||||
|
* Many small issues from the 3.x→4.x transition were found and fixed.
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/4.0.3...4.1.0).
|
||||||
|
|
||||||
|
## 3.24.0 (2014-04-22)
|
||||||
|
|
||||||
|
Merges the improvements from 4.1 that could easily be applied to the 3.x code. Also improves the way the editor size is updated when line widgets change.
|
||||||
|
|
||||||
|
## 3.23.0 (2014-03-20)
|
||||||
|
|
||||||
|
* In the [XML mode](http://codemirror.net/mode/xml/index.html), add `brackets` style to angle brackets, fix case-sensitivity of tags for HTML.
|
||||||
|
* New mode: [Dylan](http://codemirror.net/mode/dylan/index.html).
|
||||||
|
* Many improvements to the [Vim bindings](http://codemirror.net/demo/vim.html).
|
||||||
|
|
||||||
|
## 3.22.0 (2014-02-21)
|
||||||
|
|
||||||
|
* Adds the [`findMarks`](http://codemirror.net/doc/manual.html#findMarks) method.
|
||||||
|
* New addons: [rulers](http://codemirror.net/doc/manual.html#addon_rulers), markdown-fold, yaml-lint.
|
||||||
|
* New theme: [mdn-like](http://codemirror.net/demo/theme.html#mdn-like).
|
||||||
|
* New mode: [Solr](http://codemirror.net/mode/solr/index.html).
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/3.21.0...3.22.0).
|
||||||
|
|
||||||
|
## 3.21.0 (2014-01-16)
|
||||||
|
|
||||||
|
* Auto-indenting a block will no longer add trailing whitespace to blank lines.
|
||||||
|
* Marking text has a new option [`clearWhenEmpty`](http://codemirror.net/doc/manual.html#markText) to control auto-removal.
|
||||||
|
* Several bugfixes in the handling of bidirectional text.
|
||||||
|
* The [XML](http://codemirror.net/mode/xml/index.html) and [CSS](http://codemirror.net/mode/css/index.html) modes were largely rewritten. [LESS](http://codemirror.net/mode/css/less.html) support was added to the CSS mode.
|
||||||
|
* The OCaml mode was moved to an [mllike](http://codemirror.net/mode/mllike/index.html) mode, F# support added.
|
||||||
|
* Make it possible to fetch multiple applicable helper values with [`getHelpers`](http://codemirror.net/doc/manual.html#getHelpers), and to register helpers matched on predicates with [`registerGlobalHelper`](http://codemirror.net/doc/manual.html#registerGlobalHelper).
|
||||||
|
* New theme [pastel-on-dark](http://codemirror.net/demo/theme.html#pastel-on-dark).
|
||||||
|
* Better ECMAScript 6 support in [JavaScript](http://codemirror.net/mode/javascript/index.html) mode.
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/3.20.0...3.21.0).
|
||||||
|
|
||||||
|
## 3.20.0 (2013-11-21)
|
||||||
|
|
||||||
|
* New modes: [Julia](http://codemirror.net/mode/julia/index.html) and [PEG.js](http://codemirror.net/mode/pegjs/index.html).
|
||||||
|
* Support ECMAScript 6 in the [JavaScript mode](http://codemirror.net/mode/javascript/index.html).
|
||||||
|
* Improved indentation for the [CoffeeScript mode](http://codemirror.net/mode/coffeescript/index.html).
|
||||||
|
* Make non-printable-character representation [configurable](http://codemirror.net/doc/manual.html#option_specialChars).
|
||||||
|
* Add ‘notification’ functionality to [dialog](http://codemirror.net/doc/manual.html#addon_dialog) addon.
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/3.19.0...3.20.0).
|
||||||
|
|
||||||
|
## 3.19.0 (2013-10-21)
|
||||||
|
|
||||||
|
* New modes: [Eiffel](http://codemirror.net/mode/eiffel/index.html), [Gherkin](http://codemirror.net/mode/gherkin/index.html), [MSSQL dialect](http://codemirror.net/mode/sql/?mime=text/x-mssql).
|
||||||
|
* New addons: [hardwrap](http://codemirror.net/doc/manual.html#addon_hardwrap), [sql-hint](http://codemirror.net/doc/manual.html#addon_sql-hint).
|
||||||
|
* New theme: [MBO](http://codemirror.net/demo/theme.html#mbo).
|
||||||
|
* Add [support](http://codemirror.net/doc/manual.html#token_style_line) for line-level styling from mode tokenizers.
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/3.18.0...3.19.0).
|
||||||
|
|
||||||
|
## 3.18.0 (2013-09-23)
|
||||||
|
|
||||||
|
Emergency release to fix a problem in 3.17 where `.setOption("lineNumbers", false)` would raise an error.
|
||||||
|
|
||||||
|
## 3.17.0 (2013-09-23)
|
||||||
|
|
||||||
|
* New modes: [Fortran](http://codemirror.net/mode/fortran/index.html), [Octave](http://codemirror.net/mode/octave/index.html) (Matlab), [TOML](http://codemirror.net/mode/toml/index.html), and [DTD](http://codemirror.net/mode/dtd/index.html).
|
||||||
|
* New addons: [`css-lint`](http://codemirror.net/addon/lint/css-lint.js), [`css-hint`](http://codemirror.net/doc/manual.html#addon_css-hint).
|
||||||
|
* Improve resilience to CSS 'frameworks' that globally mess up `box-sizing`.
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/3.16.0...3.17.0).
|
||||||
|
|
||||||
|
## 3.16.0 (2013-08-21)
|
||||||
|
|
||||||
|
* The whole codebase is now under a single [license](http://codemirror.net/LICENSE) file.
|
||||||
|
* The project page was overhauled and redesigned.
|
||||||
|
* New themes: [Paraiso](http://codemirror.net/demo/theme.html#paraiso-dark) ([light](http://codemirror.net/demo/theme.html#paraiso-light)), [The Matrix](http://codemirror.net/demo/theme.html#the-matrix).
|
||||||
|
* Improved interaction between themes and [active-line](http://codemirror.net/doc/manual.html#addon_active-line)/[matchbrackets](http://codemirror.net/doc/manual.html#addon_matchbrackets) addons.
|
||||||
|
* New [folding](http://codemirror.net/doc/manual.html#addon_foldcode) function `CodeMirror.fold.comment`.
|
||||||
|
* Added [fullscreen](http://codemirror.net/doc/manual.html#addon_fullscreen) addon.
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/3.15.0...3.16.0).
|
||||||
|
|
||||||
|
## 3.15.0 (2013-07-29)
|
||||||
|
|
||||||
|
* New modes: [Jade](http://codemirror.net/mode/jade/index.html), [Nginx](http://codemirror.net/mode/nginx/index.html).
|
||||||
|
* New addons: [Tern](http://codemirror.net/demo/tern.html), [matchtags](http://codemirror.net/doc/manual.html#addon_matchtags), and [foldgutter](http://codemirror.net/doc/manual.html#addon_foldgutter).
|
||||||
|
* Introduced [_helper_](http://codemirror.net/doc/manual.html#getHelper) concept ([context](https://groups.google.com/forum/#!msg/codemirror/cOc0xvUUEUU/nLrX1-qnidgJ)).
|
||||||
|
* New method: [`getModeAt`](http://codemirror.net/doc/manual.html#getModeAt).
|
||||||
|
* New themes: base16 [dark](http://codemirror.net/demo/theme.html#base16-dark)/[light](http://codemirror.net/demo/theme.html#base16-light), 3024 [dark](http://codemirror.net/demo/theme.html#3024-night)/[light](http://codemirror.net/demo/theme.html#3024-day), [tomorrow-night](http://codemirror.net/demo/theme.html#tomorrow-night-eighties).
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/3.14.0...3.15.0).
|
||||||
|
|
||||||
|
## 3.14.0 (2013-06-20)
|
||||||
|
|
||||||
|
* New addons: [trailing space highlight](http://codemirror.net/doc/manual.html#addon_trailingspace), [XML completion](http://codemirror.net/doc/manual.html#addon_xml-hint) (rewritten), and [diff merging](http://codemirror.net/doc/manual.html#addon_merge).
|
||||||
|
* [`markText`](http://codemirror.net/doc/manual.html#markText) and [`addLineWidget`](http://codemirror.net/doc/manual.html#addLineWidget) now take a `handleMouseEvents` option.
|
||||||
|
* New methods: [`lineAtHeight`](http://codemirror.net/doc/manual.html#lineAtHeight), [`getTokenTypeAt`](http://codemirror.net/doc/manual.html#getTokenTypeAt).
|
||||||
|
* More precise cleanness-tracking using [`changeGeneration`](http://codemirror.net/doc/manual.html#changeGeneration) and [`isClean`](http://codemirror.net/doc/manual.html#isClean).
|
||||||
|
* Many extensions to [Emacs](http://codemirror.net/demo/emacs.html) mode (prefixes, more navigation units, and more).
|
||||||
|
* New events [`"keyHandled"`](http://codemirror.net/doc/manual.html#event_keyHandled) and [`"inputRead"`](http://codemirror.net/doc/manual.html#event_inputRead).
|
||||||
|
* Various improvements to [Ruby](http://codemirror.net/mode/ruby/index.html), [Smarty](http://codemirror.net/mode/smarty/index.html), [SQL](http://codemirror.net/mode/sql/index.html), and [Vim](http://codemirror.net/demo/vim.html) modes.
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/3.13.0...3.14.0).
|
||||||
|
|
||||||
|
## 3.13.0 (2013-05-20)
|
||||||
|
|
||||||
|
* New modes: [COBOL](http://codemirror.net/mode/cobol/index.html) and [HAML](http://codemirror.net/mode/haml/index.html).
|
||||||
|
* New options: [`cursorScrollMargin`](http://codemirror.net/doc/manual.html#option_cursorScrollMargin) and [`coverGutterNextToScrollbar`](http://codemirror.net/doc/manual.html#option_coverGutterNextToScrollbar).
|
||||||
|
* New addon: [commenting](http://codemirror.net/doc/manual.html#addon_comment).
|
||||||
|
* More features added to the [Vim keymap](http://codemirror.net/demo/vim.html).
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/v3.12...3.13.0).
|
||||||
|
|
||||||
|
## 3.12.0 (2013-04-19)
|
||||||
|
|
||||||
|
* New mode: [GNU assembler](http://codemirror.net/mode/gas/index.html).
|
||||||
|
* New options: [`maxHighlightLength`](http://codemirror.net/doc/manual.html#option_maxHighlightLength) and [`historyEventDelay`](http://codemirror.net/doc/manual.html#option_historyEventDelay).
|
||||||
|
* Added [`addToHistory`](http://codemirror.net/doc/manual.html#mark_addToHistory) option for `markText`.
|
||||||
|
* Various fixes to JavaScript tokenization and indentation corner cases.
|
||||||
|
* Further improvements to the vim mode.
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/v3.11...v3.12).
|
||||||
|
|
||||||
|
## 3.11.0 (2013-03-20)
|
||||||
|
|
||||||
|
* **Removed code:** `collapserange`, `formatting`, and `simple-hint` addons. `plsql` and `mysql` modes (use [`sql`](http://codemirror.net/mode/sql/index.html) mode).
|
||||||
|
* **Moved code:** the range-finding functions for folding now have [their own files](http://codemirror.net/addon/fold/).
|
||||||
|
* **Changed interface:** the [`continuecomment`](http://codemirror.net/doc/manual.html#addon_continuecomment) addon now exposes an option, rather than a command.
|
||||||
|
* New modes: [SCSS](http://codemirror.net/mode/css/scss.html), [Tcl](http://codemirror.net/mode/tcl/index.html), [LiveScript](http://codemirror.net/mode/livescript/index.html), and [mIRC](http://codemirror.net/mode/mirc/index.html).
|
||||||
|
* New addons: [`placeholder`](http://codemirror.net/demo/placeholder.html), [HTML completion](http://codemirror.net/demo/html5complete.html).
|
||||||
|
* New methods: [`hasFocus`](http://codemirror.net/doc/manual.html#hasFocus), [`defaultCharWidth`](http://codemirror.net/doc/manual.html#defaultCharWidth).
|
||||||
|
* New events: [`beforeCursorEnter`](http://codemirror.net/doc/manual.html#event_beforeCursorEnter), [`renderLine`](http://codemirror.net/doc/manual.html#event_renderLine).
|
||||||
|
* Many improvements to the [`show-hint`](http://codemirror.net/doc/manual.html#addon_show-hint) completion dialog addon.
|
||||||
|
* Tweak behavior of by-word cursor motion.
|
||||||
|
* Further improvements to the [vim mode](http://codemirror.net/demo/vim.html).
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/v3.1...v3.11).
|
||||||
|
|
||||||
|
## 3.02.0 (2013-01-25)
|
||||||
|
|
||||||
|
Single-bugfix release. Fixes a problem that prevents CodeMirror instances from being garbage-collected after they become unused.
|
||||||
|
|
||||||
|
## 3.01.0 (2013-01-21)
|
||||||
|
|
||||||
|
* Move all add-ons into an organized directory structure under [`/addon`](http://codemirror.net/addon/). **You might have to adjust your paths.**
|
||||||
|
* New modes: [D](http://codemirror.net/mode/d/index.html), [Sass](http://codemirror.net/mode/sass/index.html), [APL](http://codemirror.net/mode/apl/index.html), [SQL](http://codemirror.net/mode/sql/index.html) (configurable), and [Asterisk](http://codemirror.net/mode/asterisk/index.html).
|
||||||
|
* Several bugfixes in right-to-left text support.
|
||||||
|
* Add [`rtlMoveVisually`](http://codemirror.net/doc/manual.html#option_rtlMoveVisually) option.
|
||||||
|
* Improvements to vim keymap.
|
||||||
|
* Add built-in (lightweight) [overlay mode](http://codemirror.net/doc/manual.html#addOverlay) support.
|
||||||
|
* Support `showIfHidden` option for [line widgets](http://codemirror.net/doc/manual.html#addLineWidget).
|
||||||
|
* Add simple [Python hinter](http://codemirror.net/doc/manual.html#addon_python-hint).
|
||||||
|
* Bring back the [`fixedGutter`](http://codemirror.net/doc/manual.html#option_fixedGutter) option.
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/v3.0...v3.01).
|
||||||
|
|
||||||
|
## 3.1.0 (2013-02-21)
|
||||||
|
|
||||||
|
* **Incompatible:** key handlers may now _return_, rather than _throw_ `CodeMirror.Pass` to signal they didn't handle the key.
|
||||||
|
* Make documents a [first-class construct](http://codemirror.net/doc/manual.html#api_doc), support split views and subviews.
|
||||||
|
* Add a [new module](http://codemirror.net/doc/manual.html#addon_show-hint) for showing completion hints. Deprecate `simple-hint.js`.
|
||||||
|
* Extend [htmlmixed mode](http://codemirror.net/mode/htmlmixed/index.html) to allow custom handling of script types.
|
||||||
|
* Support an `insertLeft` option to [`setBookmark`](http://codemirror.net/doc/manual.html#setBookmark).
|
||||||
|
* Add an [`eachLine`](http://codemirror.net/doc/manual.html#eachLine) method to iterate over a document.
|
||||||
|
* New addon modules: [selection marking](http://codemirror.net/demo/markselection.html), [linting](http://codemirror.net/demo/lint.html), and [automatic bracket closing](http://codemirror.net/demo/closebrackets.html).
|
||||||
|
* Add [`"beforeChange"`](http://codemirror.net/doc/manual.html#event_beforeChange) and [`"beforeSelectionChange"`](http://codemirror.net/doc/manual.html#event_beforeSelectionChange) events.
|
||||||
|
* Add [`"hide"`](http://codemirror.net/doc/manual.html#event_hide) and [`"unhide"`](http://codemirror.net/doc/manual.html#event_unhide) events to marked ranges.
|
||||||
|
* Fix [`coordsChar`](http://codemirror.net/doc/manual.html#coordsChar)'s interpretation of its argument to match the documentation.
|
||||||
|
* New modes: [Turtle](http://codemirror.net/mode/turtle/index.html) and [Q](http://codemirror.net/mode/q/index.html).
|
||||||
|
* Further improvements to the [vim mode](http://codemirror.net/demo/vim.html).
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/v3.01...v3.1).
|
||||||
|
|
||||||
|
## 3.0.0 (2012-12-10)
|
||||||
|
|
||||||
|
**New major version**. Only partially backwards-compatible. See the [upgrading guide](http://codemirror.net/doc/upgrade_v3.html) for more information. Changes since release candidate 2:
|
||||||
|
|
||||||
|
* Rewritten VIM mode.
|
||||||
|
* Fix a few minor scrolling and sizing issues.
|
||||||
|
* Work around Safari segfault when dragging.
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/v3.0rc2...v3.0).
|
||||||
|
|
||||||
|
## 2.38.0 (2013-01-21)
|
||||||
|
|
||||||
|
Integrate some bugfixes, enhancements to the vim keymap, and new modes ([D](http://codemirror.net/mode/d/index.html), [Sass](http://codemirror.net/mode/sass/index.html), [APL](http://codemirror.net/mode/apl/index.html)) from the v3 branch.
|
||||||
|
|
||||||
|
## 2.37.0 (2012-12-20)
|
||||||
|
|
||||||
|
* New mode: [SQL](http://codemirror.net/mode/sql/index.html) (will replace [plsql](http://codemirror.net/mode/plsql/index.html) and [mysql](http://codemirror.net/mode/mysql/index.html) modes).
|
||||||
|
* Further work on the new VIM mode.
|
||||||
|
* Fix Cmd/Ctrl keys on recent Operas on OS X.
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/v2.36...v2.37).
|
||||||
|
|
||||||
|
## 2.36.0 (2012-11-20)
|
||||||
|
|
||||||
|
* New mode: [Z80 assembly](http://codemirror.net/mode/z80/index.html).
|
||||||
|
* New theme: [Twilight](http://codemirror.net/demo/theme.html#twilight).
|
||||||
|
* Add command-line compression helper.
|
||||||
|
* Make [`scrollIntoView`](http://codemirror.net/doc/manual.html#scrollIntoView) public.
|
||||||
|
* Add [`defaultTextHeight`](http://codemirror.net/doc/manual.html#defaultTextHeight) method.
|
||||||
|
* Various extensions to the vim keymap.
|
||||||
|
* Make [PHP mode](http://codemirror.net/mode/php/index.html) build on [mixed HTML mode](http://codemirror.net/mode/htmlmixed/index.html).
|
||||||
|
* Add [comment-continuing](http://codemirror.net/doc/manual.html#addon_continuecomment) add-on.
|
||||||
|
* Full [list of patches](http://codemirror.net/https://github.com/codemirror/CodeMirror/compare/v2.35...v2.36).
|
||||||
|
|
||||||
|
## 2.35.0 (2012-10-22)
|
||||||
|
|
||||||
|
* New (sub) mode: [TypeScript](http://codemirror.net/mode/javascript/typescript.html).
|
||||||
|
* Don't overwrite (insert key) when pasting.
|
||||||
|
* Fix several bugs in [`markText`](http://codemirror.net/doc/manual.html#markText)/undo interaction.
|
||||||
|
* Better indentation of JavaScript code without semicolons.
|
||||||
|
* Add [`defineInitHook`](http://codemirror.net/doc/manual.html#defineInitHook) function.
|
||||||
|
* Full [list of patches](https://github.com/codemirror/CodeMirror/compare/v2.34...v2.35).
|
||||||
|
|
||||||
|
## 2.34.0 (2012-09-19)
|
||||||
|
|
||||||
|
* New mode: [Common Lisp](http://codemirror.net/mode/commonlisp/index.html).
|
||||||
|
* Fix right-click select-all on most browsers.
|
||||||
|
* Change the way highlighting happens:
|
||||||
|
Saves memory and CPU cycles.
|
||||||
|
`compareStates` is no longer needed.
|
||||||
|
`onHighlightComplete` no longer works.
|
||||||
|
* Integrate mode (Markdown, XQuery, CSS, sTex) tests in central testsuite.
|
||||||
|
* Add a [`CodeMirror.version`](http://codemirror.net/doc/manual.html#version) property.
|
||||||
|
* More robust handling of nested modes in [formatting](http://codemirror.net/demo/formatting.html) and [closetag](http://codemirror.net/demo/closetag.html) plug-ins.
|
||||||
|
* Un/redo now preserves [marked text](http://codemirror.net/doc/manual.html#markText) and bookmarks.
|
||||||
|
* [Full list](https://github.com/codemirror/CodeMirror/compare/v2.33...v2.34) of patches.
|
||||||
|
|
||||||
|
## 2.33.0 (2012-08-23)
|
||||||
|
|
||||||
|
* New mode: [Sieve](http://codemirror.net/mode/sieve/index.html).
|
||||||
|
* New [`getViewPort`](http://codemirror.net/doc/manual.html#getViewport) and [`onViewportChange`](http://codemirror.net/doc/manual.html#option_onViewportChange) API.
|
||||||
|
* [Configurable](http://codemirror.net/doc/manual.html#option_cursorBlinkRate) cursor blink rate.
|
||||||
|
* Make binding a key to `false` disabling handling (again).
|
||||||
|
* Show non-printing characters as red dots.
|
||||||
|
* More tweaks to the scrolling model.
|
||||||
|
* Expanded testsuite. Basic linter added.
|
||||||
|
* Remove most uses of `innerHTML`. Remove `CodeMirror.htmlEscape`.
|
||||||
|
* [Full list](https://github.com/codemirror/CodeMirror/compare/v2.32...v2.33) of patches.
|
||||||
|
|
||||||
|
## 2.32.0 (2012-07-23)
|
||||||
|
|
||||||
|
Emergency fix for a bug where an editor with line wrapping on IE will break when there is _no_ scrollbar.
|
||||||
|
|
||||||
|
## 2.31.0 (2012-07-20)
|
||||||
|
|
||||||
|
* New modes: [OCaml](http://codemirror.net/mode/ocaml/index.html), [Haxe](http://codemirror.net/mode/haxe/index.html), and [VB.NET](http://codemirror.net/mode/vb/index.html).
|
||||||
|
* Several fixes to the new scrolling model.
|
||||||
|
* Add a [`setSize`](http://codemirror.net/doc/manual.html#setSize) method for programmatic resizing.
|
||||||
|
* Add [`getHistory`](http://codemirror.net/doc/manual.html#getHistory) and [`setHistory`](http://codemirror.net/doc/manual.html#setHistory) methods.
|
||||||
|
* Allow custom line separator string in [`getValue`](http://codemirror.net/doc/manual.html#getValue) and [`getRange`](http://codemirror.net/doc/manual.html#getRange).
|
||||||
|
* Support double- and triple-click drag, double-clicking whitespace.
|
||||||
|
* And more... [(all patches)](https://github.com/codemirror/CodeMirror/compare/v2.3...v2.31)
|
||||||
|
|
||||||
|
## 2.30.0 (2012-06-22)
|
||||||
|
|
||||||
|
* **New scrollbar implementation**. Should flicker less. Changes DOM structure of the editor.
|
||||||
|
* New theme: [vibrant-ink](http://codemirror.net/demo/theme.html#vibrant-ink).
|
||||||
|
* Many extensions to the VIM keymap (including text objects).
|
||||||
|
* Add [mode-multiplexing](http://codemirror.net/demo/multiplex.html) utility script.
|
||||||
|
* Fix bug where right-click paste works in read-only mode.
|
||||||
|
* Add a [`getScrollInfo`](http://codemirror.net/doc/manual.html#getScrollInfo) method.
|
||||||
|
* Lots of other [fixes](https://github.com/codemirror/CodeMirror/compare/v2.25...v2.3).
|
||||||
|
|
||||||
|
## 2.25.0 (2012-05-23)
|
||||||
|
|
||||||
|
* New mode: [Erlang](http://codemirror.net/mode/erlang/index.html).
|
||||||
|
* **Remove xmlpure mode** (use [xml.js](http://codemirror.net/mode/xml/index.html)).
|
||||||
|
* Fix line-wrapping in Opera.
|
||||||
|
* Fix X Windows middle-click paste in Chrome.
|
||||||
|
* Fix bug that broke pasting of huge documents.
|
||||||
|
* Fix backspace and tab key repeat in Opera.
|
||||||
|
|
||||||
|
## 2.24.0 (2012-04-23)
|
||||||
|
|
||||||
|
* **Drop support for Internet Explorer 6**.
|
||||||
|
* New modes: [Shell](http://codemirror.net/mode/shell/index.html), [Tiki wiki](http://codemirror.net/mode/tiki/index.html), [Pig Latin](http://codemirror.net/mode/pig/index.html).
|
||||||
|
* New themes: [Ambiance](http://codemirror.net/demo/theme.html#ambiance), [Blackboard](http://codemirror.net/demo/theme.html#blackboard).
|
||||||
|
* More control over drag/drop with [`dragDrop`](http://codemirror.net/doc/manual.html#option_dragDrop) and [`onDragEvent`](http://codemirror.net/doc/manual.html#option_onDragEvent) options.
|
||||||
|
* Make HTML mode a bit less pedantic.
|
||||||
|
* Add [`compoundChange`](http://codemirror.net/doc/manual.html#compoundChange) API method.
|
||||||
|
* Several fixes in undo history and line hiding.
|
||||||
|
* Remove (broken) support for `catchall` in key maps, add `nofallthrough` boolean field instead.
|
||||||
|
|
||||||
|
## 2.23.0 (2012-03-26)
|
||||||
|
|
||||||
|
* Change **default binding for tab**. Starting in 2.23, these bindings are default:
|
||||||
|
* Tab: Insert tab character
|
||||||
|
* Shift-tab: Reset line indentation to default
|
||||||
|
* Ctrl/Cmd-[: Reduce line indentation (old tab behaviour)
|
||||||
|
* Ctrl/Cmd-]: Increase line indentation (old shift-tab behaviour)
|
||||||
|
* New modes: [XQuery](http://codemirror.net/mode/xquery/index.html) and [VBScript](http://codemirror.net/mode/vbscript/index.html).
|
||||||
|
* Two new themes: [lesser-dark](http://codemirror.net/mode/less/index.html) and [xq-dark](http://codemirror.net/mode/xquery/index.html).
|
||||||
|
* Differentiate between background and text styles in [`setLineClass`](http://codemirror.net/doc/manual.html#setLineClass).
|
||||||
|
* Fix drag-and-drop in IE9+.
|
||||||
|
* Extend [`charCoords`](http://codemirror.net/doc/manual.html#charCoords) and [`cursorCoords`](http://codemirror.net/doc/manual.html#cursorCoords) with a `mode` argument.
|
||||||
|
* Add [`autofocus`](http://codemirror.net/doc/manual.html#option_autofocus) option.
|
||||||
|
* Add [`findMarksAt`](http://codemirror.net/doc/manual.html#findMarksAt) method.
|
||||||
|
|
||||||
|
## 2.22.0 (2012-02-27)
|
||||||
|
|
||||||
|
* Allow [key handlers](http://codemirror.net/doc/manual.html#keymaps) to pass up events, allow binding characters.
|
||||||
|
* Add [`autoClearEmptyLines`](http://codemirror.net/doc/manual.html#option_autoClearEmptyLines) option.
|
||||||
|
* Properly use tab stops when rendering tabs.
|
||||||
|
* Make PHP mode more robust.
|
||||||
|
* Support indentation blocks in [code folder](http://codemirror.net/doc/manual.html#addon_foldcode).
|
||||||
|
* Add a script for [highlighting instances of the selection](http://codemirror.net/doc/manual.html#addon_match-highlighter).
|
||||||
|
* New [.properties](http://codemirror.net/mode/properties/index.html) mode.
|
||||||
|
* Fix many bugs.
|
||||||
|
|
||||||
|
## 2.21.0 (2012-01-27)
|
||||||
|
|
||||||
|
* Added [LESS](http://codemirror.net/mode/less/index.html), [MySQL](http://codemirror.net/mode/mysql/index.html), [Go](http://codemirror.net/mode/go/index.html), and [Verilog](http://codemirror.net/mode/verilog/index.html) modes.
|
||||||
|
* Add [`smartIndent`](http://codemirror.net/doc/manual.html#option_smartIndent) option.
|
||||||
|
* Support a cursor in [`readOnly`](http://codemirror.net/doc/manual.html#option_readOnly)-mode.
|
||||||
|
* Support assigning multiple styles to a token.
|
||||||
|
* Use a new approach to drawing the selection.
|
||||||
|
* Add [`scrollTo`](http://codemirror.net/doc/manual.html#scrollTo) method.
|
||||||
|
* Allow undo/redo events to span non-adjacent lines.
|
||||||
|
* Lots and lots of bugfixes.
|
||||||
|
|
||||||
|
## 2.20.0 (2011-12-20)
|
||||||
|
|
||||||
|
* Slightly incompatible API changes. Read [this](http://codemirror.net/doc/upgrade_v2.2.html).
|
||||||
|
* New approach to [binding](http://codemirror.net/doc/manual.html#option_extraKeys) keys, support for [custom bindings](http://codemirror.net/doc/manual.html#option_keyMap).
|
||||||
|
* Support for overwrite (insert).
|
||||||
|
* [Custom-width](http://codemirror.net/doc/manual.html#option_tabSize) and [stylable](http://codemirror.net/demo/visibletabs.html) tabs.
|
||||||
|
* Moved more code into [add-on scripts](http://codemirror.net/doc/manual.html#addons).
|
||||||
|
* Support for sane vertical cursor movement in wrapped lines.
|
||||||
|
* More reliable handling of editing [marked text](http://codemirror.net/doc/manual.html#markText).
|
||||||
|
* Add minimal [emacs](http://codemirror.net/demo/emacs.html) and [vim](http://codemirror.net/demo/vim.html) bindings.
|
||||||
|
* Rename `coordsFromIndex` to [`posFromIndex`](http://codemirror.net/doc/manual.html#posFromIndex), add [`indexFromPos`](http://codemirror.net/doc/manual.html#indexFromPos) method.
|
||||||
|
|
||||||
|
## 2.18.0 (2011-11-21)
|
||||||
|
|
||||||
|
Fixes `TextMarker.clear`, which is broken in 2.17.
|
||||||
|
|
||||||
|
## 2.17.0 (2011-11-21)
|
||||||
|
|
||||||
|
* Add support for [line wrapping](http://codemirror.net/doc/manual.html#option_lineWrapping) and [code folding](http://codemirror.net/doc/manual.html#hideLine).
|
||||||
|
* Add [Github-style Markdown](http://codemirror.net/mode/gfm/index.html) mode.
|
||||||
|
* Add [Monokai](http://codemirror.net/theme/monokai.css) and [Rubyblue](http://codemirror.net/theme/rubyblue.css) themes.
|
||||||
|
* Add [`setBookmark`](http://codemirror.net/doc/manual.html#setBookmark) method.
|
||||||
|
* Move some of the demo code into reusable components under [`lib/util`](http://codemirror.net/addon/).
|
||||||
|
* Make screen-coord-finding code faster and more reliable.
|
||||||
|
* Fix drag-and-drop in Firefox.
|
||||||
|
* Improve support for IME.
|
||||||
|
* Speed up content rendering.
|
||||||
|
* Fix browser's built-in search in Webkit.
|
||||||
|
* Make double- and triple-click work in IE.
|
||||||
|
* Various fixes to modes.
|
||||||
|
|
||||||
|
## 2.16.0 (2011-10-27)
|
||||||
|
|
||||||
|
* Add [Perl](http://codemirror.net/mode/perl/index.html), [Rust](http://codemirror.net/mode/rust/index.html), [TiddlyWiki](http://codemirror.net/mode/tiddlywiki/index.html), and [Groovy](http://codemirror.net/mode/groovy/index.html) modes.
|
||||||
|
* Dragging text inside the editor now moves, rather than copies.
|
||||||
|
* Add a [`coordsFromIndex`](http://codemirror.net/doc/manual.html#coordsFromIndex) method.
|
||||||
|
* **API change**: `setValue` now no longer clears history. Use [`clearHistory`](http://codemirror.net/doc/manual.html#clearHistory) for that.
|
||||||
|
* **API change**: [`markText`](http://codemirror.net/doc/manual.html#markText) now returns an object with `clear` and `find` methods. Marked text is now more robust when edited.
|
||||||
|
* Fix editing code with tabs in Internet Explorer.
|
||||||
|
|
||||||
|
## 2.15.0 (2011-09-26)
|
||||||
|
|
||||||
|
Fix bug that snuck into 2.14: Clicking the character that currently has the cursor didn't re-focus the editor.
|
||||||
|
|
||||||
|
## 2.14.0 (2011-09-26)
|
||||||
|
|
||||||
|
* Add [Clojure](http://codemirror.net/mode/clojure/index.html), [Pascal](http://codemirror.net/mode/pascal/index.html), [NTriples](http://codemirror.net/mode/ntriples/index.html), [Jinja2](http://codemirror.net/mode/jinja2/index.html), and [Markdown](http://codemirror.net/mode/markdown/index.html) modes.
|
||||||
|
* Add [Cobalt](http://codemirror.net/theme/cobalt.css) and [Eclipse](http://codemirror.net/theme/eclipse.css) themes.
|
||||||
|
* Add a [`fixedGutter`](http://codemirror.net/doc/manual.html#option_fixedGutter) option.
|
||||||
|
* Fix bug with `setValue` breaking cursor movement.
|
||||||
|
* Make gutter updates much more efficient.
|
||||||
|
* Allow dragging of text out of the editor (on modern browsers).
|
||||||
|
|
||||||
|
## 2.13.0 (2011-08-23)
|
||||||
|
|
||||||
|
* Add [Ruby](http://codemirror.net/mode/ruby/index.html), [R](http://codemirror.net/mode/r/index.html), [CoffeeScript](http://codemirror.net/mode/coffeescript/index.html), and [Velocity](http://codemirror.net/mode/velocity/index.html) modes.
|
||||||
|
* Add [`getGutterElement`](http://codemirror.net/doc/manual.html#getGutterElement) to API.
|
||||||
|
* Several fixes to scrolling and positioning.
|
||||||
|
* Add [`smartHome`](http://codemirror.net/doc/manual.html#option_smartHome) option.
|
||||||
|
* Add an experimental [pure XML](http://codemirror.net/mode/xmlpure/index.html) mode.
|
||||||
|
|
||||||
|
## 2.12.0 (2011-07-25)
|
||||||
|
|
||||||
|
* Add a [SPARQL](http://codemirror.net/mode/sparql/index.html) mode.
|
||||||
|
* Fix bug with cursor jumping around in an unfocused editor in IE.
|
||||||
|
* Allow key and mouse events to bubble out of the editor. Ignore widget clicks.
|
||||||
|
* Solve cursor flakiness after undo/redo.
|
||||||
|
* Fix block-reindent ignoring the last few lines.
|
||||||
|
* Fix parsing of multi-line attrs in XML mode.
|
||||||
|
* Use `innerHTML` for HTML-escaping.
|
||||||
|
* Some fixes to indentation in C-like mode.
|
||||||
|
* Shrink horiz scrollbars when long lines removed.
|
||||||
|
* Fix width feedback loop bug that caused the width of an inner DIV to shrink.
|
||||||
|
|
||||||
|
## 2.11.0 (2011-07-04)
|
||||||
|
|
||||||
|
* Add a [Scheme mode](http://codemirror.net/mode/scheme/index.html).
|
||||||
|
* Add a `replace` method to search cursors, for cursor-preserving replacements.
|
||||||
|
* Make the [C-like mode](http://codemirror.net/mode/clike/index.html) mode more customizable.
|
||||||
|
* Update XML mode to spot mismatched tags.
|
||||||
|
* Add `getStateAfter` API and `compareState` mode API methods for finer-grained mode magic.
|
||||||
|
* Add a `getScrollerElement` API method to manipulate the scrolling DIV.
|
||||||
|
* Fix drag-and-drop for Firefox.
|
||||||
|
* Add a C# configuration for the [C-like mode](http://codemirror.net/mode/clike/index.html).
|
||||||
|
* Add [full-screen editing](http://codemirror.net/demo/fullscreen.html) and [mode-changing](http://codemirror.net/demo/changemode.html) demos.
|
||||||
|
|
||||||
|
## 2.10.0 (2011-06-07)
|
||||||
|
|
||||||
|
Add a [theme](http://codemirror.net/doc/manual.html#option_theme) system ([demo](http://codemirror.net/demo/theme.html)). Note that this is not backwards-compatible—you'll have to update your styles and modes!
|
||||||
|
|
||||||
|
## 2.2.0 (2011-06-07)
|
||||||
|
|
||||||
|
* Add a [Lua mode](http://codemirror.net/mode/lua/index.html).
|
||||||
|
* Fix reverse-searching for a regexp.
|
||||||
|
* Empty lines can no longer break highlighting.
|
||||||
|
* Rework scrolling model (the outer wrapper no longer does the scrolling).
|
||||||
|
* Solve horizontal jittering on long lines.
|
||||||
|
* Add [runmode.js](http://codemirror.net/demo/runmode.html).
|
||||||
|
* Immediately re-highlight text when typing.
|
||||||
|
* Fix problem with 'sticking' horizontal scrollbar.
|
||||||
|
|
||||||
|
## 2.1.0 (2011-05-26)
|
||||||
|
|
||||||
|
* Add a [Smalltalk mode](http://codemirror.net/mode/smalltalk/index.html).
|
||||||
|
* Add a [reStructuredText mode](http://codemirror.net/mode/rst/index.html).
|
||||||
|
* Add a [Python mode](http://codemirror.net/mode/python/index.html).
|
||||||
|
* Add a [PL/SQL mode](http://codemirror.net/mode/plsql/index.html).
|
||||||
|
* `coordsChar` now works
|
||||||
|
* Fix a problem where `onCursorActivity` interfered with `onChange`.
|
||||||
|
* Fix a number of scrolling and mouse-click-position glitches.
|
||||||
|
* Pass information about the changed lines to `onChange`.
|
||||||
|
* Support cmd-up/down on OS X.
|
||||||
|
* Add triple-click line selection.
|
||||||
|
* Don't handle shift when changing the selection through the API.
|
||||||
|
* Support `"nocursor"` mode for `readOnly` option.
|
||||||
|
* Add an `onHighlightComplete` option.
|
||||||
|
* Fix the context menu for Firefox.
|
||||||
|
|
||||||
|
## 2.0.0 (2011-03-28)
|
||||||
|
|
||||||
|
CodeMirror 2 is a complete rewrite that's faster, smaller, simpler to use, and less dependent on browser quirks. See [this](http://codemirror.net/doc/internals.html) and [this](http://groups.google.com/group/codemirror/browse_thread/thread/5a8e894024a9f580) for more information.
|
||||||
@ -1,8 +1,8 @@
|
|||||||
# How to contribute
|
# How to contribute
|
||||||
|
|
||||||
- [Getting help](#getting-help-)
|
- [Getting help](#getting-help)
|
||||||
- [Submitting bug reports](#submitting-bug-reports-)
|
- [Submitting bug reports](#submitting-bug-reports)
|
||||||
- [Contributing code](#contributing-code-)
|
- [Contributing code](#contributing-code)
|
||||||
|
|
||||||
## Getting help
|
## Getting help
|
||||||
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
Copyright (C) 2015 by Marijn Haverbeke <marijnh@gmail.com> and others
|
Copyright (C) 2016 by Marijn Haverbeke <marijnh@gmail.com> and others
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@ -16,7 +16,7 @@ new functionality.
|
|||||||
You can find more information (and the
|
You can find more information (and the
|
||||||
[manual](http://codemirror.net/doc/manual.html)) on the [project
|
[manual](http://codemirror.net/doc/manual.html)) on the [project
|
||||||
page](http://codemirror.net). For questions and discussion, use the
|
page](http://codemirror.net). For questions and discussion, use the
|
||||||
[discussion forum](http://discuss.codemirror.net/).
|
[discussion forum](https://discuss.codemirror.net/).
|
||||||
|
|
||||||
See
|
See
|
||||||
[CONTRIBUTING.md](https://github.com/codemirror/CodeMirror/blob/master/CONTRIBUTING.md)
|
[CONTRIBUTING.md](https://github.com/codemirror/CodeMirror/blob/master/CONTRIBUTING.md)
|
||||||
@ -21,22 +21,28 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
CodeMirror.commands.toggleComment = function(cm) {
|
CodeMirror.commands.toggleComment = function(cm) {
|
||||||
var minLine = Infinity, ranges = cm.listSelections(), mode = null;
|
cm.toggleComment();
|
||||||
|
};
|
||||||
|
|
||||||
|
CodeMirror.defineExtension("toggleComment", function(options) {
|
||||||
|
if (!options) options = noOptions;
|
||||||
|
var cm = this;
|
||||||
|
var minLine = Infinity, ranges = this.listSelections(), mode = null;
|
||||||
for (var i = ranges.length - 1; i >= 0; i--) {
|
for (var i = ranges.length - 1; i >= 0; i--) {
|
||||||
var from = ranges[i].from(), to = ranges[i].to();
|
var from = ranges[i].from(), to = ranges[i].to();
|
||||||
if (from.line >= minLine) continue;
|
if (from.line >= minLine) continue;
|
||||||
if (to.line >= minLine) to = Pos(minLine, 0);
|
if (to.line >= minLine) to = Pos(minLine, 0);
|
||||||
minLine = from.line;
|
minLine = from.line;
|
||||||
if (mode == null) {
|
if (mode == null) {
|
||||||
if (cm.uncomment(from, to)) mode = "un";
|
if (cm.uncomment(from, to, options)) mode = "un";
|
||||||
else { cm.lineComment(from, to); mode = "line"; }
|
else { cm.lineComment(from, to, options); mode = "line"; }
|
||||||
} else if (mode == "un") {
|
} else if (mode == "un") {
|
||||||
cm.uncomment(from, to);
|
cm.uncomment(from, to, options);
|
||||||
} else {
|
} else {
|
||||||
cm.lineComment(from, to);
|
cm.lineComment(from, to, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
CodeMirror.defineExtension("lineComment", function(from, to, options) {
|
CodeMirror.defineExtension("lineComment", function(from, to, options) {
|
||||||
if (!options) options = noOptions;
|
if (!options) options = noOptions;
|
||||||
@ -57,7 +63,14 @@
|
|||||||
|
|
||||||
self.operation(function() {
|
self.operation(function() {
|
||||||
if (options.indent) {
|
if (options.indent) {
|
||||||
var baseString = firstLine.slice(0, firstNonWS(firstLine));
|
var baseString = null;
|
||||||
|
for (var i = from.line; i < end; ++i) {
|
||||||
|
var line = self.getLine(i);
|
||||||
|
var whitespace = line.slice(0, firstNonWS(line));
|
||||||
|
if (baseString == null || baseString.length > whitespace.length) {
|
||||||
|
baseString = whitespace;
|
||||||
|
}
|
||||||
|
}
|
||||||
for (var i = from.line; i < end; ++i) {
|
for (var i = from.line; i < end; ++i) {
|
||||||
var line = self.getLine(i), cut = baseString.length;
|
var line = self.getLine(i), cut = baseString.length;
|
||||||
if (!blankLines && !nonWS.test(line)) continue;
|
if (!blankLines && !nonWS.test(line)) continue;
|
||||||
@ -56,6 +56,8 @@
|
|||||||
|
|
||||||
var inp = dialog.getElementsByTagName("input")[0], button;
|
var inp = dialog.getElementsByTagName("input")[0], button;
|
||||||
if (inp) {
|
if (inp) {
|
||||||
|
inp.focus();
|
||||||
|
|
||||||
if (options.value) {
|
if (options.value) {
|
||||||
inp.value = options.value;
|
inp.value = options.value;
|
||||||
if (options.selectValueOnOpen !== false) {
|
if (options.selectValueOnOpen !== false) {
|
||||||
@ -79,8 +81,6 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (options.closeOnBlur !== false) CodeMirror.on(inp, "blur", close);
|
if (options.closeOnBlur !== false) CodeMirror.on(inp, "blur", close);
|
||||||
|
|
||||||
inp.focus();
|
|
||||||
} else if (button = dialog.getElementsByTagName("button")[0]) {
|
} else if (button = dialog.getElementsByTagName("button")[0]) {
|
||||||
CodeMirror.on(button, "click", function() {
|
CodeMirror.on(button, "click", function() {
|
||||||
close();
|
close();
|
||||||
@ -14,10 +14,12 @@
|
|||||||
if (val && !prev) {
|
if (val && !prev) {
|
||||||
cm.on("blur", onBlur);
|
cm.on("blur", onBlur);
|
||||||
cm.on("change", onChange);
|
cm.on("change", onChange);
|
||||||
|
cm.on("swapDoc", onChange);
|
||||||
onChange(cm);
|
onChange(cm);
|
||||||
} else if (!val && prev) {
|
} else if (!val && prev) {
|
||||||
cm.off("blur", onBlur);
|
cm.off("blur", onBlur);
|
||||||
cm.off("change", onChange);
|
cm.off("change", onChange);
|
||||||
|
cm.off("swapDoc", onChange);
|
||||||
clearPlaceholder(cm);
|
clearPlaceholder(cm);
|
||||||
var wrapper = cm.getWrapperElement();
|
var wrapper = cm.getWrapperElement();
|
||||||
wrapper.className = wrapper.className.replace(" CodeMirror-empty", "");
|
wrapper.className = wrapper.className.replace(" CodeMirror-empty", "");
|
||||||
@ -63,7 +63,7 @@
|
|||||||
}
|
}
|
||||||
for (var i = ranges.length - 1; i >= 0; i--) {
|
for (var i = ranges.length - 1; i >= 0; i--) {
|
||||||
var cur = ranges[i].head;
|
var cur = ranges[i].head;
|
||||||
cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1));
|
cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1), "+delete");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,7 +28,9 @@ CodeMirror.registerGlobalHelper("fold", "comment", function(mode) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (pass == 1 && found < start.ch) return;
|
if (pass == 1 && found < start.ch) return;
|
||||||
if (/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1)))) {
|
if (/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1))) &&
|
||||||
|
(lineText.slice(found - endToken.length, found) == endToken ||
|
||||||
|
!/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found))))) {
|
||||||
startCh = found + startToken.length;
|
startCh = found + startToken.length;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -20,7 +20,7 @@
|
|||||||
cm.off("viewportChange", onViewportChange);
|
cm.off("viewportChange", onViewportChange);
|
||||||
cm.off("fold", onFold);
|
cm.off("fold", onFold);
|
||||||
cm.off("unfold", onFold);
|
cm.off("unfold", onFold);
|
||||||
cm.off("swapDoc", updateInViewport);
|
cm.off("swapDoc", onChange);
|
||||||
}
|
}
|
||||||
if (val) {
|
if (val) {
|
||||||
cm.state.foldGutter = new State(parseOptions(val));
|
cm.state.foldGutter = new State(parseOptions(val));
|
||||||
@ -30,7 +30,7 @@
|
|||||||
cm.on("viewportChange", onViewportChange);
|
cm.on("viewportChange", onViewportChange);
|
||||||
cm.on("fold", onFold);
|
cm.on("fold", onFold);
|
||||||
cm.on("unfold", onFold);
|
cm.on("unfold", onFold);
|
||||||
cm.on("swapDoc", updateInViewport);
|
cm.on("swapDoc", onChange);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -25,8 +25,18 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
CodeMirror.defineExtension("showHint", function(options) {
|
CodeMirror.defineExtension("showHint", function(options) {
|
||||||
// We want a single cursor position.
|
options = parseOptions(this, this.getCursor("start"), options);
|
||||||
if (this.listSelections().length > 1 || this.somethingSelected()) return;
|
var selections = this.listSelections()
|
||||||
|
if (selections.length > 1) return;
|
||||||
|
// By default, don't allow completion when something is selected.
|
||||||
|
// A hint function can have a `supportsSelection` property to
|
||||||
|
// indicate that it can handle selections.
|
||||||
|
if (this.somethingSelected()) {
|
||||||
|
if (!options.hint.supportsSelection) return;
|
||||||
|
// Don't try with cross-line selections
|
||||||
|
for (var i = 0; i < selections.length; i++)
|
||||||
|
if (selections[i].head.line != selections[i].anchor.line) return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.state.completionActive) this.state.completionActive.close();
|
if (this.state.completionActive) this.state.completionActive.close();
|
||||||
var completion = this.state.completionActive = new Completion(this, options);
|
var completion = this.state.completionActive = new Completion(this, options);
|
||||||
@ -38,12 +48,12 @@
|
|||||||
|
|
||||||
function Completion(cm, options) {
|
function Completion(cm, options) {
|
||||||
this.cm = cm;
|
this.cm = cm;
|
||||||
this.options = this.buildOptions(options);
|
this.options = options;
|
||||||
this.widget = null;
|
this.widget = null;
|
||||||
this.debounce = 0;
|
this.debounce = 0;
|
||||||
this.tick = 0;
|
this.tick = 0;
|
||||||
this.startPos = this.cm.getCursor();
|
this.startPos = this.cm.getCursor("start");
|
||||||
this.startLen = this.cm.getLine(this.startPos.line).length;
|
this.startLen = this.cm.getLine(this.startPos.line).length - this.cm.getSelection().length;
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
cm.on("cursorActivity", this.activityFunc = function() { self.cursorActivity(); });
|
cm.on("cursorActivity", this.activityFunc = function() { self.cursorActivity(); });
|
||||||
@ -111,11 +121,13 @@
|
|||||||
|
|
||||||
finishUpdate: function(data, first) {
|
finishUpdate: function(data, first) {
|
||||||
if (this.data) CodeMirror.signal(this.data, "update");
|
if (this.data) CodeMirror.signal(this.data, "update");
|
||||||
if (data && this.data && CodeMirror.cmpPos(data.from, this.data.from)) data = null;
|
|
||||||
this.data = data;
|
|
||||||
|
|
||||||
var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle);
|
var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle);
|
||||||
if (this.widget) this.widget.close();
|
if (this.widget) this.widget.close();
|
||||||
|
|
||||||
|
if (data && this.data && isNewCompletion(this.data, data)) return;
|
||||||
|
this.data = data;
|
||||||
|
|
||||||
if (data && data.list.length) {
|
if (data && data.list.length) {
|
||||||
if (picked && data.list.length == 1) {
|
if (picked && data.list.length == 1) {
|
||||||
this.pick(data, 0);
|
this.pick(data, 0);
|
||||||
@ -124,20 +136,26 @@
|
|||||||
CodeMirror.signal(data, "shown");
|
CodeMirror.signal(data, "shown");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
buildOptions: function(options) {
|
|
||||||
var editor = this.cm.options.hintOptions;
|
|
||||||
var out = {};
|
|
||||||
for (var prop in defaultOptions) out[prop] = defaultOptions[prop];
|
|
||||||
if (editor) for (var prop in editor)
|
|
||||||
if (editor[prop] !== undefined) out[prop] = editor[prop];
|
|
||||||
if (options) for (var prop in options)
|
|
||||||
if (options[prop] !== undefined) out[prop] = options[prop];
|
|
||||||
return out;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function isNewCompletion(old, nw) {
|
||||||
|
var moved = CodeMirror.cmpPos(nw.from, old.from)
|
||||||
|
return moved > 0 && old.to.ch - old.from.ch != nw.to.ch - nw.from.ch
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseOptions(cm, pos, options) {
|
||||||
|
var editor = cm.options.hintOptions;
|
||||||
|
var out = {};
|
||||||
|
for (var prop in defaultOptions) out[prop] = defaultOptions[prop];
|
||||||
|
if (editor) for (var prop in editor)
|
||||||
|
if (editor[prop] !== undefined) out[prop] = editor[prop];
|
||||||
|
if (options) for (var prop in options)
|
||||||
|
if (options[prop] !== undefined) out[prop] = options[prop];
|
||||||
|
if (out.hint.resolve) out.hint = out.hint.resolve(cm, pos)
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
function getText(completion) {
|
function getText(completion) {
|
||||||
if (typeof completion == "string") return completion;
|
if (typeof completion == "string") return completion;
|
||||||
else return completion.text;
|
else return completion.text;
|
||||||
@ -336,18 +354,61 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
CodeMirror.registerHelper("hint", "auto", function(cm, options) {
|
function applicableHelpers(cm, helpers) {
|
||||||
var helpers = cm.getHelpers(cm.getCursor(), "hint"), words;
|
if (!cm.somethingSelected()) return helpers
|
||||||
|
var result = []
|
||||||
|
for (var i = 0; i < helpers.length; i++)
|
||||||
|
if (helpers[i].supportsSelection) result.push(helpers[i])
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveAutoHints(cm, pos) {
|
||||||
|
var helpers = cm.getHelpers(pos, "hint"), words
|
||||||
if (helpers.length) {
|
if (helpers.length) {
|
||||||
for (var i = 0; i < helpers.length; i++) {
|
var async = false, resolved
|
||||||
var cur = helpers[i](cm, options);
|
for (var i = 0; i < helpers.length; i++) if (helpers[i].async) async = true
|
||||||
if (cur && cur.list.length) return cur;
|
if (async) {
|
||||||
|
resolved = function(cm, callback, options) {
|
||||||
|
var app = applicableHelpers(cm, helpers)
|
||||||
|
function run(i, result) {
|
||||||
|
if (i == app.length) return callback(null)
|
||||||
|
var helper = app[i]
|
||||||
|
if (helper.async) {
|
||||||
|
helper(cm, function(result) {
|
||||||
|
if (result) callback(result)
|
||||||
|
else run(i + 1)
|
||||||
|
}, options)
|
||||||
|
} else {
|
||||||
|
var result = helper(cm, options)
|
||||||
|
if (result) callback(result)
|
||||||
|
else run(i + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run(0)
|
||||||
|
}
|
||||||
|
resolved.async = true
|
||||||
|
} else {
|
||||||
|
resolved = function(cm, options) {
|
||||||
|
var app = applicableHelpers(cm, helpers)
|
||||||
|
for (var i = 0; i < app.length; i++) {
|
||||||
|
var cur = app[i](cm, options)
|
||||||
|
if (cur && cur.list.length) return cur
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
resolved.supportsSelection = true
|
||||||
|
return resolved
|
||||||
} else if (words = cm.getHelper(cm.getCursor(), "hintWords")) {
|
} else if (words = cm.getHelper(cm.getCursor(), "hintWords")) {
|
||||||
if (words) return CodeMirror.hint.fromList(cm, {words: words});
|
return function(cm) { return CodeMirror.hint.fromList(cm, {words: words}) }
|
||||||
} else if (CodeMirror.hint.anyword) {
|
} else if (CodeMirror.hint.anyword) {
|
||||||
return CodeMirror.hint.anyword(cm, options);
|
return function(cm, options) { return CodeMirror.hint.anyword(cm, options) }
|
||||||
|
} else {
|
||||||
|
return function() {}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeMirror.registerHelper("hint", "auto", {
|
||||||
|
resolve: resolveAutoHints
|
||||||
});
|
});
|
||||||
|
|
||||||
CodeMirror.registerHelper("hint", "fromList", function(cm, options) {
|
CodeMirror.registerHelper("hint", "fromList", function(cm, options) {
|
||||||
@ -376,7 +437,7 @@
|
|||||||
alignWithWord: true,
|
alignWithWord: true,
|
||||||
closeCharacters: /[\s()\[\]{};:>,]/,
|
closeCharacters: /[\s()\[\]{};:>,]/,
|
||||||
closeOnUnfocus: true,
|
closeOnUnfocus: true,
|
||||||
completeOnSingleClick: false,
|
completeOnSingleClick: true,
|
||||||
container: null,
|
container: null,
|
||||||
customKeys: null,
|
customKeys: null,
|
||||||
extraKeys: null
|
extraKeys: null
|
||||||
@ -20,6 +20,8 @@
|
|||||||
};
|
};
|
||||||
var Pos = CodeMirror.Pos;
|
var Pos = CodeMirror.Pos;
|
||||||
|
|
||||||
|
function isArray(val) { return Object.prototype.toString.call(val) == "[object Array]" }
|
||||||
|
|
||||||
function getKeywords(editor) {
|
function getKeywords(editor) {
|
||||||
var mode = editor.doc.modeOption;
|
var mode = editor.doc.modeOption;
|
||||||
if (mode === "sql") mode = "text/x-sql";
|
if (mode === "sql") mode = "text/x-sql";
|
||||||
@ -30,10 +32,28 @@
|
|||||||
return typeof item == "string" ? item : item.text;
|
return typeof item == "string" ? item : item.text;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getItem(list, item) {
|
function wrapTable(name, value) {
|
||||||
if (!list.slice) return list[item];
|
if (isArray(value)) value = {columns: value}
|
||||||
for (var i = list.length - 1; i >= 0; i--) if (getText(list[i]) == item)
|
if (!value.text) value.text = name
|
||||||
return list[i];
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseTables(input) {
|
||||||
|
var result = {}
|
||||||
|
if (isArray(input)) {
|
||||||
|
for (var i = input.length - 1; i >= 0; i--) {
|
||||||
|
var item = input[i]
|
||||||
|
result[getText(item).toUpperCase()] = wrapTable(getText(item), item)
|
||||||
|
}
|
||||||
|
} else if (input) {
|
||||||
|
for (var name in input)
|
||||||
|
result[name.toUpperCase()] = wrapTable(name, input[name])
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTable(name) {
|
||||||
|
return tables[name.toUpperCase()]
|
||||||
}
|
}
|
||||||
|
|
||||||
function shallowClone(object) {
|
function shallowClone(object) {
|
||||||
@ -50,11 +70,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function addMatches(result, search, wordlist, formatter) {
|
function addMatches(result, search, wordlist, formatter) {
|
||||||
for (var word in wordlist) {
|
if (isArray(wordlist)) {
|
||||||
if (!wordlist.hasOwnProperty(word)) continue;
|
for (var i = 0; i < wordlist.length; i++)
|
||||||
if (wordlist.slice) word = wordlist[word];
|
if (match(search, wordlist[i])) result.push(formatter(wordlist[i]))
|
||||||
|
} else {
|
||||||
if (match(search, word)) result.push(formatter(word));
|
for (var word in wordlist) if (wordlist.hasOwnProperty(word)) {
|
||||||
|
var val = wordlist[word]
|
||||||
|
if (!val || val === true)
|
||||||
|
val = word
|
||||||
|
else
|
||||||
|
val = val.displayText ? {text: val.text, displayText: val.displayText} : val.text
|
||||||
|
if (match(search, val)) result.push(formatter(val))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,13 +142,13 @@
|
|||||||
var alias = false;
|
var alias = false;
|
||||||
var aliasTable = table;
|
var aliasTable = table;
|
||||||
// Check if table is available. If not, find table by Alias
|
// Check if table is available. If not, find table by Alias
|
||||||
if (!getItem(tables, table)) {
|
if (!getTable(table)) {
|
||||||
var oldTable = table;
|
var oldTable = table;
|
||||||
table = findTableByAlias(table, editor);
|
table = findTableByAlias(table, editor);
|
||||||
if (table !== oldTable) alias = true;
|
if (table !== oldTable) alias = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var columns = getItem(tables, table);
|
var columns = getTable(table);
|
||||||
if (columns && columns.columns)
|
if (columns && columns.columns)
|
||||||
columns = columns.columns;
|
columns = columns.columns;
|
||||||
|
|
||||||
@ -184,7 +211,7 @@
|
|||||||
//find valid range
|
//find valid range
|
||||||
var prevItem = 0;
|
var prevItem = 0;
|
||||||
var current = convertCurToNumber(editor.getCursor());
|
var current = convertCurToNumber(editor.getCursor());
|
||||||
for (var i=0; i< separator.length; i++) {
|
for (var i = 0; i < separator.length; i++) {
|
||||||
var _v = convertCurToNumber(separator[i]);
|
var _v = convertCurToNumber(separator[i]);
|
||||||
if (current > prevItem && current <= _v) {
|
if (current > prevItem && current <= _v) {
|
||||||
validRange = { start: convertNumberToCur(prevItem), end: convertNumberToCur(_v) };
|
validRange = { start: convertNumberToCur(prevItem), end: convertNumberToCur(_v) };
|
||||||
@ -199,7 +226,7 @@
|
|||||||
var lineText = query[i];
|
var lineText = query[i];
|
||||||
eachWord(lineText, function(word) {
|
eachWord(lineText, function(word) {
|
||||||
var wordUpperCase = word.toUpperCase();
|
var wordUpperCase = word.toUpperCase();
|
||||||
if (wordUpperCase === aliasUpperCase && getItem(tables, previousWord))
|
if (wordUpperCase === aliasUpperCase && getTable(previousWord))
|
||||||
table = previousWord;
|
table = previousWord;
|
||||||
if (wordUpperCase !== CONS.ALIAS_KEYWORD)
|
if (wordUpperCase !== CONS.ALIAS_KEYWORD)
|
||||||
previousWord = word;
|
previousWord = word;
|
||||||
@ -210,10 +237,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
CodeMirror.registerHelper("hint", "sql", function(editor, options) {
|
CodeMirror.registerHelper("hint", "sql", function(editor, options) {
|
||||||
tables = (options && options.tables) || {};
|
tables = parseTables(options && options.tables)
|
||||||
var defaultTableName = options && options.defaultTable;
|
var defaultTableName = options && options.defaultTable;
|
||||||
var disableKeywords = options && options.disableKeywords;
|
var disableKeywords = options && options.disableKeywords;
|
||||||
defaultTable = defaultTableName && getItem(tables, defaultTableName);
|
defaultTable = defaultTableName && getTable(defaultTableName);
|
||||||
keywords = keywords || getKeywords(editor);
|
keywords = keywords || getKeywords(editor);
|
||||||
|
|
||||||
if (defaultTableName && !defaultTable)
|
if (defaultTableName && !defaultTable)
|
||||||
@ -186,9 +186,14 @@
|
|||||||
state.timeout = setTimeout(function(){startLinting(cm);}, state.options.delay || 500);
|
state.timeout = setTimeout(function(){startLinting(cm);}, state.options.delay || 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
function popupSpanTooltip(ann, e) {
|
function popupTooltips(annotations, e) {
|
||||||
var target = e.target || e.srcElement;
|
var target = e.target || e.srcElement;
|
||||||
showTooltipFor(e, annotationTooltip(ann), target);
|
var tooltip = document.createDocumentFragment();
|
||||||
|
for (var i = 0; i < annotations.length; i++) {
|
||||||
|
var ann = annotations[i];
|
||||||
|
tooltip.appendChild(annotationTooltip(ann));
|
||||||
|
}
|
||||||
|
showTooltipFor(e, tooltip, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMouseOver(cm, e) {
|
function onMouseOver(cm, e) {
|
||||||
@ -196,10 +201,12 @@
|
|||||||
if (!/\bCodeMirror-lint-mark-/.test(target.className)) return;
|
if (!/\bCodeMirror-lint-mark-/.test(target.className)) return;
|
||||||
var box = target.getBoundingClientRect(), x = (box.left + box.right) / 2, y = (box.top + box.bottom) / 2;
|
var box = target.getBoundingClientRect(), x = (box.left + box.right) / 2, y = (box.top + box.bottom) / 2;
|
||||||
var spans = cm.findMarksAt(cm.coordsChar({left: x, top: y}, "client"));
|
var spans = cm.findMarksAt(cm.coordsChar({left: x, top: y}, "client"));
|
||||||
|
|
||||||
|
var annotations = [];
|
||||||
for (var i = 0; i < spans.length; ++i) {
|
for (var i = 0; i < spans.length; ++i) {
|
||||||
var ann = spans[i].__annotation;
|
annotations.push(spans[i].__annotation);
|
||||||
if (ann) return popupSpanTooltip(ann, e);
|
|
||||||
}
|
}
|
||||||
|
if (annotations.length) popupTooltips(annotations, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
CodeMirror.defineOption("lint", false, function(cm, val, old) {
|
CodeMirror.defineOption("lint", false, function(cm, val, old) {
|
||||||
@ -60,6 +60,7 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: #44c;
|
color: #44c;
|
||||||
|
z-index: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.CodeMirror-merge-copy-reverse {
|
.CodeMirror-merge-copy-reverse {
|
||||||
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
(function(mod) {
|
(function(mod) {
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
mod(require("../../lib/codemirror"), require("diff_match_patch"));
|
mod(require("../../lib/codemirror")); // Note non-packaged dependency diff_match_patch
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
define(["../../lib/codemirror", "diff_match_patch"], mod);
|
define(["../../lib/codemirror", "diff_match_patch"], mod);
|
||||||
else // Plain browser env
|
else // Plain browser env
|
||||||
@ -427,8 +427,9 @@
|
|||||||
|
|
||||||
function copyChunk(dv, to, from, chunk) {
|
function copyChunk(dv, to, from, chunk) {
|
||||||
if (dv.diffOutOfDate) return;
|
if (dv.diffOutOfDate) return;
|
||||||
to.replaceRange(from.getRange(Pos(chunk.origFrom, 0), Pos(chunk.origTo, 0)),
|
var editStart = chunk.editTo > to.lastLine() ? Pos(chunk.editFrom - 1) : Pos(chunk.editFrom, 0)
|
||||||
Pos(chunk.editFrom, 0), Pos(chunk.editTo, 0));
|
var origStart = chunk.origTo > from.lastLine() ? Pos(chunk.origFrom - 1) : Pos(chunk.origFrom, 0)
|
||||||
|
to.replaceRange(from.getRange(origStart, Pos(chunk.origTo, 0)), editStart, Pos(chunk.editTo, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge view, containing 0, 1, or 2 diff views.
|
// Merge view, containing 0, 1, or 2 diff views.
|
||||||
@ -471,13 +472,10 @@
|
|||||||
if (left) left.init(leftPane, origLeft, options);
|
if (left) left.init(leftPane, origLeft, options);
|
||||||
if (right) right.init(rightPane, origRight, options);
|
if (right) right.init(rightPane, origRight, options);
|
||||||
|
|
||||||
if (options.collapseIdentical) {
|
if (options.collapseIdentical)
|
||||||
updating = true;
|
|
||||||
this.editor().operation(function() {
|
this.editor().operation(function() {
|
||||||
collapseIdenticalStretches(self, options.collapseIdentical);
|
collapseIdenticalStretches(self, options.collapseIdentical);
|
||||||
});
|
});
|
||||||
updating = false;
|
|
||||||
}
|
|
||||||
if (options.connect == "align") {
|
if (options.connect == "align") {
|
||||||
this.aligners = [];
|
this.aligners = [];
|
||||||
alignChunks(this.left || this.right, true);
|
alignChunks(this.left || this.right, true);
|
||||||
@ -640,7 +638,7 @@
|
|||||||
mark.clear();
|
mark.clear();
|
||||||
cm.removeLineClass(from, "wrap", "CodeMirror-merge-collapsed-line");
|
cm.removeLineClass(from, "wrap", "CodeMirror-merge-collapsed-line");
|
||||||
}
|
}
|
||||||
widget.addEventListener("click", clear);
|
CodeMirror.on(widget, "click", clear);
|
||||||
return {mark: mark, clear: clear};
|
return {mark: mark, clear: clear};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ CodeMirror.multiplexingMode = function(outer /*, others */) {
|
|||||||
if (!other.parseDelimiters) stream.match(other.open);
|
if (!other.parseDelimiters) stream.match(other.open);
|
||||||
state.innerActive = other;
|
state.innerActive = other;
|
||||||
state.inner = CodeMirror.startState(other.mode, outer.indent ? outer.indent(state.outer, "") : 0);
|
state.inner = CodeMirror.startState(other.mode, outer.indent ? outer.indent(state.outer, "") : 0);
|
||||||
return other.delimStyle;
|
return other.delimStyle && (other.delimStyle + " " + other.delimStyle + "-open");
|
||||||
} else if (found != -1 && found < cutOff) {
|
} else if (found != -1 && found < cutOff) {
|
||||||
cutOff = found;
|
cutOff = found;
|
||||||
}
|
}
|
||||||
@ -70,7 +70,7 @@ CodeMirror.multiplexingMode = function(outer /*, others */) {
|
|||||||
if (found == stream.pos && !curInner.parseDelimiters) {
|
if (found == stream.pos && !curInner.parseDelimiters) {
|
||||||
stream.match(curInner.close);
|
stream.match(curInner.close);
|
||||||
state.innerActive = state.inner = null;
|
state.innerActive = state.inner = null;
|
||||||
return curInner.delimStyle;
|
return curInner.delimStyle && (curInner.delimStyle + " " + curInner.delimStyle + "-close");
|
||||||
}
|
}
|
||||||
if (found > -1) stream.string = oldContent.slice(0, found);
|
if (found > -1) stream.string = oldContent.slice(0, found);
|
||||||
var innerToken = curInner.mode.token(stream, state.inner);
|
var innerToken = curInner.mode.token(stream, state.inner);
|
||||||
@ -80,7 +80,7 @@ CodeMirror.multiplexingMode = function(outer /*, others */) {
|
|||||||
state.innerActive = state.inner = null;
|
state.innerActive = state.inner = null;
|
||||||
|
|
||||||
if (curInner.innerStyle) {
|
if (curInner.innerStyle) {
|
||||||
if (innerToken) innerToken = innerToken + ' ' + curInner.innerStyle;
|
if (innerToken) innerToken = innerToken + " " + curInner.innerStyle;
|
||||||
else innerToken = curInner.innerStyle;
|
else innerToken = curInner.innerStyle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,5 +29,5 @@
|
|||||||
|
|
||||||
MT(
|
MT(
|
||||||
"stexInsideMarkdown",
|
"stexInsideMarkdown",
|
||||||
"[strong **Equation:**] [delim $][inner&tag \\pi][delim $]");
|
"[strong **Equation:**] [delim&delim-open $][inner&tag \\pi][delim&delim-close $]");
|
||||||
})();
|
})();
|
||||||
@ -60,7 +60,7 @@
|
|||||||
|
|
||||||
function ensureState(states, name) {
|
function ensureState(states, name) {
|
||||||
if (!states.hasOwnProperty(name))
|
if (!states.hasOwnProperty(name))
|
||||||
throw new Error("Undefined state " + name + "in simple mode");
|
throw new Error("Undefined state " + name + " in simple mode");
|
||||||
}
|
}
|
||||||
|
|
||||||
function toRegex(val, caret) {
|
function toRegex(val, caret) {
|
||||||
@ -16,7 +16,7 @@ CodeMirror.runMode = function(string, modespec, callback, options) {
|
|||||||
var ie = /MSIE \d/.test(navigator.userAgent);
|
var ie = /MSIE \d/.test(navigator.userAgent);
|
||||||
var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9);
|
var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9);
|
||||||
|
|
||||||
if (callback.nodeType == 1) {
|
if (callback.appendChild) {
|
||||||
var tabSize = (options && options.tabSize) || CodeMirror.defaults.tabSize;
|
var tabSize = (options && options.tabSize) || CodeMirror.defaults.tabSize;
|
||||||
var node = callback, col = 0;
|
var node = callback, col = 0;
|
||||||
node.innerHTML = "";
|
node.innerHTML = "";
|
||||||
@ -176,3 +176,4 @@ exports.runMode = function(string, modespec, callback, options) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
require.cache[require.resolve("../../lib/codemirror")] = require.cache[require.resolve("./runmode.node")];
|
require.cache[require.resolve("../../lib/codemirror")] = require.cache[require.resolve("./runmode.node")];
|
||||||
|
require.cache[require.resolve("../../addon/runmode/runmode")] = require.cache[require.resolve("./runmode.node")];
|
||||||
@ -51,7 +51,7 @@
|
|||||||
Annotation.prototype.computeScale = function() {
|
Annotation.prototype.computeScale = function() {
|
||||||
var cm = this.cm;
|
var cm = this.cm;
|
||||||
var hScale = (cm.getWrapperElement().clientHeight - cm.display.barHeight - this.buttonHeight * 2) /
|
var hScale = (cm.getWrapperElement().clientHeight - cm.display.barHeight - this.buttonHeight * 2) /
|
||||||
cm.heightAtLine(cm.lastLine() + 1, "local");
|
cm.getScrollerElement().scrollHeight
|
||||||
if (hScale != this.hScale) {
|
if (hScale != this.hScale) {
|
||||||
this.hScale = hScale;
|
this.hScale = hScale;
|
||||||
return true;
|
return true;
|
||||||
@ -100,6 +100,9 @@
|
|||||||
elt.style.cssText = "position: absolute; right: 0px; width: " + Math.max(cm.display.barWidth - 1, 2) + "px; top: "
|
elt.style.cssText = "position: absolute; right: 0px; width: " + Math.max(cm.display.barWidth - 1, 2) + "px; top: "
|
||||||
+ (top + this.buttonHeight) + "px; height: " + height + "px";
|
+ (top + this.buttonHeight) + "px; height: " + height + "px";
|
||||||
elt.className = this.options.className;
|
elt.className = this.options.className;
|
||||||
|
if (ann.id) {
|
||||||
|
elt.setAttribute("annotation-id", ann.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.div.textContent = "";
|
this.div.textContent = "";
|
||||||
this.div.appendChild(frag);
|
this.div.appendChild(frag);
|
||||||
@ -59,16 +59,20 @@
|
|||||||
CodeMirror.on(this.node, "DOMMouseScroll", onWheel);
|
CodeMirror.on(this.node, "DOMMouseScroll", onWheel);
|
||||||
}
|
}
|
||||||
|
|
||||||
Bar.prototype.moveTo = function(pos, update) {
|
Bar.prototype.setPos = function(pos) {
|
||||||
if (pos < 0) pos = 0;
|
if (pos < 0) pos = 0;
|
||||||
if (pos > this.total - this.screen) pos = this.total - this.screen;
|
if (pos > this.total - this.screen) pos = this.total - this.screen;
|
||||||
if (pos == this.pos) return;
|
if (pos == this.pos) return false;
|
||||||
this.pos = pos;
|
this.pos = pos;
|
||||||
this.inner.style[this.orientation == "horizontal" ? "left" : "top"] =
|
this.inner.style[this.orientation == "horizontal" ? "left" : "top"] =
|
||||||
(pos * (this.size / this.total)) + "px";
|
(pos * (this.size / this.total)) + "px";
|
||||||
if (update !== false) this.scroll(pos, this.orientation);
|
return true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Bar.prototype.moveTo = function(pos) {
|
||||||
|
if (this.setPos(pos)) this.scroll(pos, this.orientation);
|
||||||
|
}
|
||||||
|
|
||||||
var minButtonSize = 10;
|
var minButtonSize = 10;
|
||||||
|
|
||||||
Bar.prototype.update = function(scrollSize, clientSize, barSize) {
|
Bar.prototype.update = function(scrollSize, clientSize, barSize) {
|
||||||
@ -83,8 +87,7 @@
|
|||||||
}
|
}
|
||||||
this.inner.style[this.orientation == "horizontal" ? "width" : "height"] =
|
this.inner.style[this.orientation == "horizontal" ? "width" : "height"] =
|
||||||
buttonSize + "px";
|
buttonSize + "px";
|
||||||
this.inner.style[this.orientation == "horizontal" ? "left" : "top"] =
|
this.setPos(this.pos);
|
||||||
this.pos * (this.size / this.total) + "px";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function SimpleScrollbars(cls, place, scroll) {
|
function SimpleScrollbars(cls, place, scroll) {
|
||||||
@ -111,7 +114,6 @@
|
|||||||
if (needsV) {
|
if (needsV) {
|
||||||
this.vert.update(measure.scrollHeight, measure.clientHeight,
|
this.vert.update(measure.scrollHeight, measure.clientHeight,
|
||||||
measure.viewHeight - (needsH ? width : 0));
|
measure.viewHeight - (needsH ? width : 0));
|
||||||
this.vert.node.style.display = "block";
|
|
||||||
this.vert.node.style.bottom = needsH ? width + "px" : "0";
|
this.vert.node.style.bottom = needsH ? width + "px" : "0";
|
||||||
}
|
}
|
||||||
if (needsH) {
|
if (needsH) {
|
||||||
@ -125,11 +127,11 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
SimpleScrollbars.prototype.setScrollTop = function(pos) {
|
SimpleScrollbars.prototype.setScrollTop = function(pos) {
|
||||||
this.vert.moveTo(pos, false);
|
this.vert.setPos(pos);
|
||||||
};
|
};
|
||||||
|
|
||||||
SimpleScrollbars.prototype.setScrollLeft = function(pos) {
|
SimpleScrollbars.prototype.setScrollLeft = function(pos) {
|
||||||
this.horiz.moveTo(pos, false);
|
this.horiz.setPos(pos);
|
||||||
};
|
};
|
||||||
|
|
||||||
SimpleScrollbars.prototype.clear = function() {
|
SimpleScrollbars.prototype.clear = function() {
|
||||||
49
www/code/codemirror-5.13.2/addon/search/jump-to-line.js
Normal file
49
www/code/codemirror-5.13.2/addon/search/jump-to-line.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
||||||
|
|
||||||
|
// Defines jumpToLine command. Uses dialog.js if present.
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"), require("../dialog/dialog"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror", "../dialog/dialog"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function dialog(cm, text, shortText, deflt, f) {
|
||||||
|
if (cm.openDialog) cm.openDialog(text, f, {value: deflt, selectValueOnOpen: true});
|
||||||
|
else f(prompt(shortText, deflt));
|
||||||
|
}
|
||||||
|
|
||||||
|
var jumpDialog =
|
||||||
|
'Jump to line: <input type="text" style="width: 10em" class="CodeMirror-search-field"/> <span style="color: #888" class="CodeMirror-search-hint">(Use line:column or scroll% syntax)</span>';
|
||||||
|
|
||||||
|
function interpretLine(cm, string) {
|
||||||
|
var num = Number(string)
|
||||||
|
if (/^[-+]/.test(string)) return cm.getCursor().line + num
|
||||||
|
else return num - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeMirror.commands.jumpToLine = function(cm) {
|
||||||
|
var cur = cm.getCursor();
|
||||||
|
dialog(cm, jumpDialog, "Jump to line:", (cur.line + 1) + ":" + cur.ch, function(posStr) {
|
||||||
|
if (!posStr) return;
|
||||||
|
|
||||||
|
var match;
|
||||||
|
if (match = /^\s*([\+\-]?\d+)\s*\:\s*(\d+)\s*$/.exec(posStr)) {
|
||||||
|
cm.setCursor(interpretLine(cm, match[1]), Number(match[2]))
|
||||||
|
} else if (match = /^\s*([\+\-]?\d+(\.\d+)?)\%\s*/.exec(posStr)) {
|
||||||
|
var line = Math.round(cm.lineCount() * Number(match[1]) / 100);
|
||||||
|
if (/^[-+]/.test(match[1])) line = cur.line + line + 1;
|
||||||
|
cm.setCursor(line - 1, cur.ch);
|
||||||
|
} else if (match = /^\s*\:?\s*([\+\-]?\d+)\s*/.exec(posStr)) {
|
||||||
|
cm.setCursor(interpretLine(cm, match[1]), cur.ch);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
CodeMirror.keyMap["default"]["Alt-G"] = "jumpToLine";
|
||||||
|
});
|
||||||
@ -16,13 +16,14 @@
|
|||||||
// highlighted only if the selected text is a word. showToken, when enabled,
|
// highlighted only if the selected text is a word. showToken, when enabled,
|
||||||
// will cause the current token to be highlighted when nothing is selected.
|
// will cause the current token to be highlighted when nothing is selected.
|
||||||
// delay is used to specify how much time to wait, in milliseconds, before
|
// delay is used to specify how much time to wait, in milliseconds, before
|
||||||
// highlighting the matches.
|
// highlighting the matches. If annotateScrollbar is enabled, the occurances
|
||||||
|
// will be highlighted on the scrollbar via the matchesonscrollbar addon.
|
||||||
|
|
||||||
(function(mod) {
|
(function(mod) {
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
mod(require("../../lib/codemirror"));
|
mod(require("../../lib/codemirror"), require("./matchesonscrollbar"));
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
define(["../../lib/codemirror"], mod);
|
define(["../../lib/codemirror", "./matchesonscrollbar"], mod);
|
||||||
else // Plain browser env
|
else // Plain browser env
|
||||||
mod(CodeMirror);
|
mod(CodeMirror);
|
||||||
})(function(CodeMirror) {
|
})(function(CodeMirror) {
|
||||||
@ -40,18 +41,19 @@
|
|||||||
this.showToken = options.showToken;
|
this.showToken = options.showToken;
|
||||||
this.delay = options.delay;
|
this.delay = options.delay;
|
||||||
this.wordsOnly = options.wordsOnly;
|
this.wordsOnly = options.wordsOnly;
|
||||||
|
this.annotateScrollbar = options.annotateScrollbar;
|
||||||
}
|
}
|
||||||
if (this.style == null) this.style = DEFAULT_TOKEN_STYLE;
|
if (this.style == null) this.style = DEFAULT_TOKEN_STYLE;
|
||||||
if (this.minChars == null) this.minChars = DEFAULT_MIN_CHARS;
|
if (this.minChars == null) this.minChars = DEFAULT_MIN_CHARS;
|
||||||
if (this.delay == null) this.delay = DEFAULT_DELAY;
|
if (this.delay == null) this.delay = DEFAULT_DELAY;
|
||||||
if (this.wordsOnly == null) this.wordsOnly = DEFAULT_WORDS_ONLY;
|
if (this.wordsOnly == null) this.wordsOnly = DEFAULT_WORDS_ONLY;
|
||||||
this.overlay = this.timeout = null;
|
this.overlay = this.timeout = null;
|
||||||
|
this.matchesonscroll = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
CodeMirror.defineOption("highlightSelectionMatches", false, function(cm, val, old) {
|
CodeMirror.defineOption("highlightSelectionMatches", false, function(cm, val, old) {
|
||||||
if (old && old != CodeMirror.Init) {
|
if (old && old != CodeMirror.Init) {
|
||||||
var over = cm.state.matchHighlighter.overlay;
|
removeOverlay(cm);
|
||||||
if (over) cm.removeOverlay(over);
|
|
||||||
clearTimeout(cm.state.matchHighlighter.timeout);
|
clearTimeout(cm.state.matchHighlighter.timeout);
|
||||||
cm.state.matchHighlighter = null;
|
cm.state.matchHighlighter = null;
|
||||||
cm.off("cursorActivity", cursorActivity);
|
cm.off("cursorActivity", cursorActivity);
|
||||||
@ -69,20 +71,39 @@
|
|||||||
state.timeout = setTimeout(function() {highlightMatches(cm);}, state.delay);
|
state.timeout = setTimeout(function() {highlightMatches(cm);}, state.delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addOverlay(cm, query, hasBoundary, style) {
|
||||||
|
var state = cm.state.matchHighlighter;
|
||||||
|
cm.addOverlay(state.overlay = makeOverlay(query, hasBoundary, style));
|
||||||
|
if (state.annotateScrollbar) {
|
||||||
|
var searchFor = hasBoundary ? new RegExp("\\b" + query + "\\b") : query;
|
||||||
|
state.matchesonscroll = cm.showMatchesOnScrollbar(searchFor, true,
|
||||||
|
{className: "CodeMirror-selection-highlight-scrollbar"});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeOverlay(cm) {
|
||||||
|
var state = cm.state.matchHighlighter;
|
||||||
|
if (state.overlay) {
|
||||||
|
cm.removeOverlay(state.overlay);
|
||||||
|
state.overlay = null;
|
||||||
|
if (state.annotateScrollbar) {
|
||||||
|
state.matchesonscroll.clear();
|
||||||
|
state.matchesonscroll = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function highlightMatches(cm) {
|
function highlightMatches(cm) {
|
||||||
cm.operation(function() {
|
cm.operation(function() {
|
||||||
var state = cm.state.matchHighlighter;
|
var state = cm.state.matchHighlighter;
|
||||||
if (state.overlay) {
|
removeOverlay(cm);
|
||||||
cm.removeOverlay(state.overlay);
|
|
||||||
state.overlay = null;
|
|
||||||
}
|
|
||||||
if (!cm.somethingSelected() && state.showToken) {
|
if (!cm.somethingSelected() && state.showToken) {
|
||||||
var re = state.showToken === true ? /[\w$]/ : state.showToken;
|
var re = state.showToken === true ? /[\w$]/ : state.showToken;
|
||||||
var cur = cm.getCursor(), line = cm.getLine(cur.line), start = cur.ch, end = start;
|
var cur = cm.getCursor(), line = cm.getLine(cur.line), start = cur.ch, end = start;
|
||||||
while (start && re.test(line.charAt(start - 1))) --start;
|
while (start && re.test(line.charAt(start - 1))) --start;
|
||||||
while (end < line.length && re.test(line.charAt(end))) ++end;
|
while (end < line.length && re.test(line.charAt(end))) ++end;
|
||||||
if (start < end)
|
if (start < end)
|
||||||
cm.addOverlay(state.overlay = makeOverlay(line.slice(start, end), re, state.style));
|
addOverlay(cm, line.slice(start, end), re, state.style);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var from = cm.getCursor("from"), to = cm.getCursor("to");
|
var from = cm.getCursor("from"), to = cm.getCursor("to");
|
||||||
@ -90,7 +111,7 @@
|
|||||||
if (state.wordsOnly && !isWord(cm, from, to)) return;
|
if (state.wordsOnly && !isWord(cm, from, to)) return;
|
||||||
var selection = cm.getRange(from, to).replace(/^\s+|\s+$/g, "");
|
var selection = cm.getRange(from, to).replace(/^\s+|\s+$/g, "");
|
||||||
if (selection.length >= state.minChars)
|
if (selection.length >= state.minChars)
|
||||||
cm.addOverlay(state.overlay = makeOverlay(selection, false, state.style));
|
addOverlay(cm, selection, false, state.style);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,7 +29,7 @@
|
|||||||
query.lastIndex = stream.pos;
|
query.lastIndex = stream.pos;
|
||||||
var match = query.exec(stream.string);
|
var match = query.exec(stream.string);
|
||||||
if (match && match.index == stream.pos) {
|
if (match && match.index == stream.pos) {
|
||||||
stream.pos += match[0].length;
|
stream.pos += match[0].length || 1;
|
||||||
return "searching";
|
return "searching";
|
||||||
} else if (match) {
|
} else if (match) {
|
||||||
stream.pos = match.index;
|
stream.pos = match.index;
|
||||||
@ -18,6 +18,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
var WRAP_CLASS = "CodeMirror-activeline";
|
var WRAP_CLASS = "CodeMirror-activeline";
|
||||||
var BACK_CLASS = "CodeMirror-activeline-background";
|
var BACK_CLASS = "CodeMirror-activeline-background";
|
||||||
|
var GUTT_CLASS = "CodeMirror-activeline-gutter";
|
||||||
|
|
||||||
CodeMirror.defineOption("styleActiveLine", false, function(cm, val, old) {
|
CodeMirror.defineOption("styleActiveLine", false, function(cm, val, old) {
|
||||||
var prev = old && old != CodeMirror.Init;
|
var prev = old && old != CodeMirror.Init;
|
||||||
@ -36,6 +37,7 @@
|
|||||||
for (var i = 0; i < cm.state.activeLines.length; i++) {
|
for (var i = 0; i < cm.state.activeLines.length; i++) {
|
||||||
cm.removeLineClass(cm.state.activeLines[i], "wrap", WRAP_CLASS);
|
cm.removeLineClass(cm.state.activeLines[i], "wrap", WRAP_CLASS);
|
||||||
cm.removeLineClass(cm.state.activeLines[i], "background", BACK_CLASS);
|
cm.removeLineClass(cm.state.activeLines[i], "background", BACK_CLASS);
|
||||||
|
cm.removeLineClass(cm.state.activeLines[i], "gutter", GUTT_CLASS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,6 +62,7 @@
|
|||||||
for (var i = 0; i < active.length; i++) {
|
for (var i = 0; i < active.length; i++) {
|
||||||
cm.addLineClass(active[i], "wrap", WRAP_CLASS);
|
cm.addLineClass(active[i], "wrap", WRAP_CLASS);
|
||||||
cm.addLineClass(active[i], "background", BACK_CLASS);
|
cm.addLineClass(active[i], "background", BACK_CLASS);
|
||||||
|
cm.addLineClass(active[i], "gutter", GUTT_CLASS);
|
||||||
}
|
}
|
||||||
cm.state.activeLines = active;
|
cm.state.activeLines = active;
|
||||||
});
|
});
|
||||||
@ -135,6 +135,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
destroy: function () {
|
destroy: function () {
|
||||||
|
closeArgHints(this)
|
||||||
if (this.worker) {
|
if (this.worker) {
|
||||||
this.worker.terminate();
|
this.worker.terminate();
|
||||||
this.worker = null;
|
this.worker = null;
|
||||||
@ -32,11 +32,13 @@
|
|||||||
function findBreakPoint(text, column, wrapOn, killTrailingSpace) {
|
function findBreakPoint(text, column, wrapOn, killTrailingSpace) {
|
||||||
for (var at = column; at > 0; --at)
|
for (var at = column; at > 0; --at)
|
||||||
if (wrapOn.test(text.slice(at - 1, at + 1))) break;
|
if (wrapOn.test(text.slice(at - 1, at + 1))) break;
|
||||||
if (at == 0) at = column;
|
for (var first = true;; first = false) {
|
||||||
var endOfText = at;
|
var endOfText = at;
|
||||||
if (killTrailingSpace)
|
if (killTrailingSpace)
|
||||||
while (text.charAt(endOfText - 1) == " ") --endOfText;
|
while (text.charAt(endOfText - 1) == " ") --endOfText;
|
||||||
return {from: endOfText, to: at};
|
if (endOfText == 0 && first) at = column;
|
||||||
|
else return {from: endOfText, to: at};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function wrapRange(cm, from, to, options) {
|
function wrapRange(cm, from, to, options) {
|
||||||
@ -86,7 +88,8 @@
|
|||||||
if (changes.length) cm.operation(function() {
|
if (changes.length) cm.operation(function() {
|
||||||
for (var i = 0; i < changes.length; ++i) {
|
for (var i = 0; i < changes.length; ++i) {
|
||||||
var change = changes[i];
|
var change = changes[i];
|
||||||
cm.replaceRange(change.text, change.from, change.to);
|
if (change.text || CodeMirror.cmpPos(change.from, change.to))
|
||||||
|
cm.replaceRange(change.text, change.from, change.to);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return changes.length ? {from: changes[0].from, to: CodeMirror.changeEnd(changes[changes.length - 1])} : null;
|
return changes.length ? {from: changes[0].from, to: CodeMirror.changeEnd(changes[changes.length - 1])} : null;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user