Merge branch 'master' into webpack-frontend

This commit is contained in:
Yukai Huang 2016-10-11 18:39:15 +08:00
commit 6e651c8108
527 changed files with 2584 additions and 601 deletions

2
.gitignore vendored
View file

@ -19,7 +19,7 @@ backups/
# ignore config files # ignore config files
config.json config.json
public/js/common.js public/js/config.js
.sequelizerc .sequelizerc
# ignore webpack build # ignore webpack build

16
AUTHORS
View file

@ -1,10 +1,26 @@
List of HackMD contributors. List of HackMD contributors.
Bartlomiej Szala
Dmytro Kytsmen
Fabien Meghazi Fabien Meghazi
Ikumi Shimizu Ikumi Shimizu
ivanorsolic
Jason Croft Jason Croft
Jannik Lorenz Jannik Lorenz
Jordan Matelsky
Lapinot
Laura Kyle
Marcelo Alencar
Martijnpold
Massimo Ghinassi
Max Wu Max Wu
Ömer Erdinç Yağmurlu
p0v1n0m
Pablo Guerrero
Peter Dave Hello Peter Dave Hello
Qubo
Sergio Valverde
Yukai Huang
Zacharias Traianos
Zankio Zankio
葉家郡 葉家郡

View file

@ -35,7 +35,7 @@ Browsers Requirement
Prerequisite Prerequisite
--- ---
- Node.js 4.x or up (test up to 6.2.2) - Node.js 4.x or up (test up to 6.7.0)
- Database (PostgreSQL, MySQL, MariaDB, SQLite, MSSQL) - Database (PostgreSQL, MySQL, MariaDB, SQLite, MSSQL)
- npm and bower - npm and bower
@ -83,10 +83,10 @@ There are some configs you need to change in the files below
``` ```
./config.json --- for server settings ./config.json --- for server settings
./public/js/common.js --- for client settings ./public/js/config.js --- for client settings
``` ```
Client settings `common.js` Client settings `config.js`
--- ---
| variables | example values | description | | variables | example values | description |
@ -101,10 +101,27 @@ Environment variables (will overwrite other server configs)
| variables | example values | description | | variables | example values | description |
| --------- | ------ | ----------- | | --------- | ------ | ----------- |
| NODE_ENV | `production` or `development` | set current environment (will apply corresponding settings in the `config.json`) | | NODE_ENV | `production` or `development` | set current environment (will apply corresponding settings in the `config.json`) |
| DOMAIN | `hackmd.io` | domain name |
| URL_PATH | `hackmd` | sub url path, like `www.example.com/<URL_PATH>` |
| PORT | `80` | web app port |
| DEBUG | `true` or `false` | set debug mode, show more logs | | DEBUG | `true` or `false` | set debug mode, show more logs |
| HMD_DOMAIN | `hackmd.io` | domain name |
| HMD_URL_PATH | `hackmd` | sub url path, like `www.example.com/<URL_PATH>` |
| HMD_PORT | `80` | web app port |
| HMD_ALLOW_ORIGIN | `localhost, hackmd.io` | domain name whitelist (use comma to separate) |
| HMD_PROTOCOL_USESSL | `true` or `false` | set to use ssl protocol for resources path (only applied when domain is set) |
| HMD_URL_ADDPORT | `true` or `false` | set to add port on callback url (port 80 or 443 won't applied) (only applied when domain is set) |
| HMD_FACEBOOK_CLIENTID | no example | Facebook API client id |
| HMD_FACEBOOK_CLIENTSECRET | no example | Facebook API client secret |
| HMD_TWITTER_CONSUMERKEY | no example | Twitter API consumer key |
| HMD_TWITTER_CONSUMERSECRET | no example | Twitter API consumer secret |
| HMD_GITHUB_CLIENTID | no example | GitHub API client id |
| HMD_GITHUB_CLIENTSECRET | no example | GitHub API client secret |
| HMD_GITLAB_BASEURL | no example | GitLab authentication endpoint, set to use other endpoint than GitLab.com (optional) |
| HMD_GITLAB_CLIENTID | no example | GitLab API client id |
| HMD_GITLAB_CLIENTSECRET | no example | GitLab API client secret |
| HMD_DROPBOX_CLIENTID | no example | Dropbox API client id |
| HMD_DROPBOX_CLIENTSECRET | no example | Dropbox API client secret |
| HMD_GOOGLE_CLIENTID | no example | Google API client id |
| HMD_GOOGLE_CLIENTSECRET | no example | Google API client secret |
| HMD_IMGUR_CLIENTID | no example | Imgur API client id |
Server settings `config.json` Server settings `config.json`
--- ---
@ -117,8 +134,8 @@ Server settings `config.json`
| port | `80` | web app port | | port | `80` | web app port |
| alloworigin | `['localhost']` | domain name whitelist | | alloworigin | `['localhost']` | domain name whitelist |
| usessl | `true` or `false` | set to use ssl server (if true will auto turn on `protocolusessl`) | | usessl | `true` or `false` | set to use ssl server (if true will auto turn on `protocolusessl`) |
| protocolusessl | `true` or `false` | set to use ssl protocol for resources path | | protocolusessl | `true` or `false` | set to use ssl protocol for resources path (only applied when domain is set) |
| urladdport | `true` or `false` | set to add port on callback url (port 80 or 443 won't applied) | | urladdport | `true` or `false` | set to add port on callback url (port 80 or 443 won't applied) (only applied when domain is set) |
| usecdn | `true` or `false` | set to use CDN resources or not | | usecdn | `true` or `false` | set to use CDN resources or not |
| db | `{ "dialect": "sqlite", "storage": "./db.hackmd.sqlite" }` | set the db configs, [see more here](http://sequelize.readthedocs.org/en/latest/api/sequelize/) | | db | `{ "dialect": "sqlite", "storage": "./db.hackmd.sqlite" }` | set the db configs, [see more here](http://sequelize.readthedocs.org/en/latest/api/sequelize/) |
| sslkeypath | `./cert/client.key` | ssl key path (only need when you set usessl) | | sslkeypath | `./cert/client.key` | ssl key path (only need when you set usessl) |
@ -144,11 +161,11 @@ Server settings `config.json`
Third-party integration api key settings Third-party integration api key settings
--- ---
| service | file path | description | | service | settings location | description |
| ------- | --------- | ----------- | | ------- | --------- | ----------- |
| facebook, twitter, github, gitlab, dropbox, google | `config.json` | for signin | | facebook, twitter, github, gitlab, dropbox, google | environment variables or `config.json` | for signin |
| imgur | `config.json` | for image upload | | imgur | environment variables or `config.json` | for image upload |
| google drive, dropbox | `public/js/common.js` | for export and import | | google drive, dropbox | `public/js/config.js` | for export and import |
Third-party integration oauth callback urls Third-party integration oauth callback urls
--- ---

68
app.js
View file

@ -22,6 +22,7 @@ var i18n = require('i18n');
var config = require("./lib/config.js"); var config = require("./lib/config.js");
var logger = require("./lib/logger.js"); var logger = require("./lib/logger.js");
var auth = require("./lib/auth.js"); var auth = require("./lib/auth.js");
var history = require("./lib/history.js");
var response = require("./lib/response.js"); var response = require("./lib/response.js");
var models = require("./lib/models"); var models = require("./lib/models");
@ -94,7 +95,7 @@ app.use(helmet.hsts({
})); }));
i18n.configure({ i18n.configure({
locales: ['en', 'zh', 'fr', 'de', 'ja', 'es', 'el', 'pt'], locales: ['en', 'zh', 'fr', 'de', 'ja', 'es', 'el', 'pt', 'it', 'tr', 'ru', 'nl', 'hr', 'pl', 'uk'],
cookie: 'locale', cookie: 'locale',
directory: __dirname + '/locales' directory: __dirname + '/locales'
}); });
@ -365,56 +366,15 @@ app.get('/logout', function (req, res) {
res.redirect(config.serverurl + '/'); res.redirect(config.serverurl + '/');
}); });
//get history //get history
app.get('/history', function (req, res) { app.get('/history', history.historyGet);
if (req.isAuthenticated()) {
models.User.findOne({
where: {
id: req.user.id
}
}).then(function (user) {
if (!user)
return response.errorNotFound(res);
var history = [];
if (user.history)
history = JSON.parse(user.history);
res.send({
history: history
});
if (config.debug)
logger.info('read history success: ' + user.id);
}).catch(function (err) {
logger.error('read history failed: ' + err);
return response.errorInternalError(res);
});
} else {
return response.errorForbidden(res);
}
});
//post history //post history
app.post('/history', urlencodedParser, function (req, res) { app.post('/history', urlencodedParser, history.historyPost);
if (req.isAuthenticated()) { //post history by note id
if (config.debug) app.post('/history/:noteId', urlencodedParser, history.historyPost);
logger.info('SERVER received history from [' + req.user.id + ']: ' + req.body.history); //delete history
models.User.update({ app.delete('/history', history.historyDelete);
history: req.body.history //delete history by note id
}, { app.delete('/history/:noteId', history.historyDelete);
where: {
id: req.user.id
}
}).then(function (count) {
if (!count)
return response.errorNotFound(res);
if (config.debug)
logger.info("write user history success: " + req.user.id);
}).catch(function (err) {
logger.error('write history failed: ' + err);
return response.errorInternalError(res);
});
res.end();
} else {
return response.errorForbidden(res);
}
});
//get me info //get me info
app.get('/me', function (req, res) { app.get('/me', function (req, res) {
if (req.isAuthenticated()) { if (req.isAuthenticated()) {
@ -522,9 +482,9 @@ function startListen() {
// sync db then start listen // sync db then start listen
models.sequelize.sync().then(function () { models.sequelize.sync().then(function () {
// check if realtime is ready // check if realtime is ready
if (realtime.isReady()) { if (history.isReady() && realtime.isReady()) {
models.Revision.checkAllNotesRevision(function (err, notes) { models.Revision.checkAllNotesRevision(function (err, notes) {
if (err) return new Error(err); if (err) throw new Error(err);
if (!notes || notes.length <= 0) return startListen(); if (!notes || notes.length <= 0) return startListen();
}); });
} }
@ -549,9 +509,9 @@ process.on('SIGINT', function () {
socket.disconnect(true); socket.disconnect(true);
}); });
var checkCleanTimer = setInterval(function () { var checkCleanTimer = setInterval(function () {
if (realtime.isReady()) { if (history.isReady() && realtime.isReady()) {
models.Revision.checkAllNotesRevision(function (err, notes) { models.Revision.checkAllNotesRevision(function (err, notes) {
if (err) return new Error(err); if (err) throw new Error(err);
if (notes.length <= 0) { if (notes.length <= 0) {
clearInterval(checkCleanTimer); clearInterval(checkCleanTimer);
return process.exit(0); return process.exit(0);

View file

@ -25,8 +25,8 @@ if [ ! -f config.json ]; then
cp config.json.example config.json cp config.json.example config.json
fi fi
if [ ! -f publis/js/common.js ]; then if [ ! -f publis/js/config.js ]; then
cp public/js/common.js.example public/js/common.js cp public/js/config.js.example public/js/config.js
fi fi
if [ ! -f .sequelizerc ]; then if [ ! -f .sequelizerc ]; then
@ -43,7 +43,7 @@ Edit the following config file to setup hackmd server and client.
Read more info at https://github.com/hackmdio/hackmd#configuration-files Read more info at https://github.com/hackmdio/hackmd#configuration-files
* config.json -- server config * config.json -- server config
* public/js/common.js -- client config * public/js/config.js -- client config
* .sequelizerc -- db config * .sequelizerc -- db config
EOF EOF

View file

@ -20,20 +20,19 @@
"Ionicons": "ionicons#~2.0.1", "Ionicons": "ionicons#~2.0.1",
"reveal.js": "~3.3.0", "reveal.js": "~3.3.0",
"spin.js": "~2.3.2", "spin.js": "~2.3.2",
"moment": "~2.14.1", "moment": "~2.15.1",
"handlebars": "~4.0.5", "handlebars": "~4.0.5",
"js-yaml": "~3.6.1", "js-yaml": "~3.6.1",
"raphael": "~2.2.1", "raphael": "~2.2.6",
"xss": "~0.2.13",
"mermaid": "^6.0.0", "mermaid": "^6.0.0",
"MathJax": "^2.6.1", "MathJax": "^2.6.1",
"octicons": "~3.5.0", "octicons": "~3.5.0",
"velocity": "^1.2.3", "velocity": "^1.3.1",
"randomcolor": "randomColor#^0.4.4", "randomcolor": "randomColor#^0.4.4",
"Idle.Js": "idle.js#^1.0.0", "Idle.Js": "idle.js#^1.0.0",
"gist-embed": "*" "gist-embed": "*"
}, },
"resolutions": { "resolutions": {
"jquery": "~3.1.0" "jquery": "~3.1.1"
} }
} }

View file

@ -7,16 +7,17 @@ var config = require(path.join(__dirname, '..', 'config.json'))[env];
var debug = process.env.DEBUG ? (process.env.DEBUG === 'true') : ((typeof config.debug === 'boolean') ? config.debug : (env === 'development')); var debug = process.env.DEBUG ? (process.env.DEBUG === 'true') : ((typeof config.debug === 'boolean') ? config.debug : (env === 'development'));
// url // url
var domain = process.env.DOMAIN || config.domain || ''; var domain = process.env.DOMAIN || process.env.HMD_DOMAIN || config.domain || '';
var urlpath = process.env.URL_PATH || config.urlpath || ''; var urlpath = process.env.URL_PATH || process.env.HMD_URL_PATH || config.urlpath || '';
var port = process.env.PORT || config.port || 3000; var port = process.env.PORT || process.env.HMD_PORT || config.port || 3000;
var alloworigin = config.alloworigin || ['localhost']; var alloworigin = process.env.HMD_ALLOW_ORIGIN ? process.env.HMD_ALLOW_ORIGIN.split(',') : (config.alloworigin || ['localhost']);
var usessl = !!config.usessl; var usessl = !!config.usessl;
var protocolusessl = (config.usessl === true && typeof config.protocolusessl === 'undefined') ? true : !!config.protocolusessl; var protocolusessl = (usessl === true && typeof process.env.HMD_PROTOCOL_USESSL === 'undefined' && typeof config.protocolusessl === 'undefined')
var urladdport = !!config.urladdport; ? true : (process.env.HMD_PROTOCOL_USESSL ? (process.env.HMD_PROTOCOL_USESSL === 'true') : !!config.protocolusessl);
var urladdport = process.env.HMD_URL_ADDPORT ? (process.env.HMD_URL_ADDPORT === 'true') : !!config.urladdport;
var usecdn = !!config.usecdn; var usecdn = process.env.HMD_USECDN ? (process.env.HMD_USECDN === 'true') : !!config.usecdn;
// db // db
var db = config.db || { var db = config.db || {
@ -56,13 +57,32 @@ var heartbeattimeout = config.heartbeattimeout || 5000;
var documentmaxlength = config.documentmaxlength || 100000; var documentmaxlength = config.documentmaxlength || 100000;
// auth // auth
var facebook = config.facebook || false; var facebook = (process.env.HMD_FACEBOOK_CLIENTID && process.env.HMD_FACEBOOK_CLIENTSECRET) ? {
var twitter = config.twitter || false; clientID: process.env.HMD_FACEBOOK_CLIENTID,
var github = config.github || false; clientSecret: process.env.HMD_FACEBOOK_CLIENTSECRET
var gitlab = config.gitlab || false; } : config.facebook || false;
var dropbox = config.dropbox || false; var twitter = (process.env.HMD_TWITTER_CONSUMERKEY && process.env.HMD_TWITTER_CONSUMERSECRET) ? {
var google = config.google || false; consumerKey: process.env.HMD_TWITTER_CONSUMERKEY,
var imgur = config.imgur || false; consumerSecret: process.env.HMD_TWITTER_CONSUMERSECRET
} : config.twitter || false;
var github = (process.env.HMD_GITHUB_CLIENTID && process.env.HMD_GITHUB_CLIENTSECRET) ? {
clientID: process.env.HMD_GITHUB_CLIENTID,
clientSecret: process.env.HMD_GITHUB_CLIENTSECRET
} : config.github || false;
var gitlab = (process.env.HMD_GITLAB_CLIENTID && process.env.HMD_GITLAB_CLIENTSECRET) ? {
baseURL: process.env.HMD_GITLAB_BASEURL,
clientID: process.env.HMD_GITLAB_CLIENTID,
clientSecret: process.env.HMD_GITLAB_CLIENTSECRET
} : config.gitlab || false;
var dropbox = (process.env.HMD_DROPBOX_CLIENTID && process.env.HMD_DROPBOX_CLIENTSECRET) ? {
clientID: process.env.HMD_DROPBOX_CLIENTID,
clientSecret: process.env.HMD_DROPBOX_CLIENTSECRET
} : config.dropbox || false;
var google = (process.env.HMD_GOOGLE_CLIENTID && process.env.HMD_GOOGLE_CLIENTSECRET) ? {
clientID: process.env.HMD_GOOGLE_CLIENTID,
clientSecret: process.env.HMD_GOOGLE_CLIENTSECRET
} : config.google || false;
var imgur = process.env.HMD_IMGUR_CLIENTID || config.imgur || false;
function getserverurl() { function getserverurl() {
var url = ''; var url = '';
@ -77,8 +97,8 @@ function getserverurl() {
return url; return url;
} }
var version = '0.4.4'; var version = '0.4.5';
var minimumCompatibleVersion = '0.4.4'; var minimumCompatibleVersion = '0.4.5';
var maintenance = true; var maintenance = true;
var cwd = path.join(__dirname, '..'); var cwd = path.join(__dirname, '..');

225
lib/history.js Normal file
View file

@ -0,0 +1,225 @@
//history
//external modules
var async = require('async');
var moment = require('moment');
//core
var config = require("./config.js");
var logger = require("./logger.js");
var response = require("./response.js");
var models = require("./models");
//public
var History = {
historyGet: historyGet,
historyPost: historyPost,
historyDelete: historyDelete,
isReady: isReady,
updateHistory: updateHistory
};
var caches = {};
//update when the history is dirty
var updater = setInterval(function () {
var deleted = [];
async.each(Object.keys(caches), function (key, callback) {
var cache = caches[key];
if (cache.isDirty) {
if (config.debug) logger.info("history updater found dirty history: " + key);
var history = parseHistoryToArray(cache.history);
finishUpdateHistory(key, history, function (err, count) {
if (err) return callback(err, null);
if (!count) return callback(null, null);
cache.isDirty = false;
cache.updateAt = Date.now();
return callback(null, null);
});
} else {
if (moment().isAfter(moment(cache.updateAt).add(5, 'minutes'))) {
deleted.push(key);
}
return callback(null, null);
}
}, function (err) {
if (err) return logger.error('history updater error', err);
});
// delete specified caches
for (var i = 0, l = deleted.length; i < l; i++) {
caches[deleted[i]].history = {};
delete caches[deleted[i]];
}
}, 1000);
function finishUpdateHistory(userid, history, callback) {
models.User.update({
history: JSON.stringify(history)
}, {
where: {
id: userid
}
}).then(function (count) {
return callback(null, count);
}).catch(function (err) {
return callback(err, null);
});
}
function isReady() {
var dirtyCount = 0;
async.each(Object.keys(caches), function (key, callback) {
if (caches[key].isDirty) dirtyCount++;
return callback(null, null);
}, function (err) {
if (err) return logger.error('history ready check error', err);
});
return dirtyCount > 0 ? false : true;
}
function getHistory(userid, callback) {
if (caches[userid]) {
return callback(null, caches[userid].history);
} else {
models.User.findOne({
where: {
id: userid
}
}).then(function (user) {
if (!user)
return callback(null, null);
var history = [];
if (user.history)
history = JSON.parse(user.history);
if (config.debug)
logger.info('read history success: ' + user.id);
setHistory(userid, history);
return callback(null, history);
}).catch(function (err) {
logger.error('read history failed: ' + err);
return callback(err, null);
});
}
}
function setHistory(userid, history) {
if (Array.isArray(history)) history = parseHistoryToObject(history);
if (!caches[userid]) {
caches[userid] = {
history: {},
isDirty: false,
updateAt: Date.now()
};
}
caches[userid].history = history;
}
function updateHistory(userid, noteId, document) {
if (userid && noteId && typeof document !== 'undefined') {
getHistory(userid, function (err, history) {
if (err || !history) return;
if (!caches[userid].history[noteId]) {
caches[userid].history[noteId] = {};
}
var noteHistory = caches[userid].history[noteId];
var noteInfo = models.Note.parseNoteInfo(document);
noteHistory.id = noteId;
noteHistory.text = noteInfo.title;
noteHistory.time = moment().valueOf();
noteHistory.tags = noteInfo.tags;
caches[userid].isDirty = true;
});
}
}
function parseHistoryToArray(history) {
var _history = [];
Object.keys(history).forEach(function (key) {
var item = history[key];
_history.push(item);
});
return _history;
}
function parseHistoryToObject(history) {
var _history = {};
for (var i = 0, l = history.length; i < l; i++) {
var item = history[i];
_history[item.id] = item;
}
return _history;
}
function historyGet(req, res) {
if (req.isAuthenticated()) {
getHistory(req.user.id, function (err, history) {
if (err) return response.errorInternalError(res);
if (!history) return response.errorNotFound(res);
res.send({
history: parseHistoryToArray(history)
});
});
} else {
return response.errorForbidden(res);
}
}
function historyPost(req, res) {
if (req.isAuthenticated()) {
var noteId = req.params.noteId;
if (!noteId) {
if (typeof req.body['history'] === 'undefined') return response.errorBadRequest(res);
if (config.debug)
logger.info('SERVER received history from [' + req.user.id + ']: ' + req.body.history);
try {
var history = JSON.parse(req.body.history);
} catch (err) {
return response.errorBadRequest(res);
}
if (Array.isArray(history)) {
setHistory(req.user.id, history);
caches[req.user.id].isDirty = true;
res.end();
} else {
return response.errorBadRequest(res);
}
} else {
if (typeof req.body['pinned'] === 'undefined') return response.errorBadRequest(res);
getHistory(req.user.id, function (err, history) {
if (err) return response.errorInternalError(res);
if (!history) return response.errorNotFound(res);
if (!caches[req.user.id].history[noteId]) return response.errorNotFound(res);
if (req.body.pinned === 'true' || req.body.pinned === 'false') {
caches[req.user.id].history[noteId].pinned = (req.body.pinned === 'true');
caches[req.user.id].isDirty = true;
res.end();
} else {
return response.errorBadRequest(res);
}
});
}
} else {
return response.errorForbidden(res);
}
}
function historyDelete(req, res) {
if (req.isAuthenticated()) {
var noteId = req.params.noteId;
if (!noteId) {
setHistory(req.user.id, []);
caches[req.user.id].isDirty = true;
res.end();
} else {
getHistory(req.user.id, function (err, history) {
if (err) return response.errorInternalError(res);
if (!history) return response.errorNotFound(res);
delete caches[req.user.id].history[noteId];
caches[req.user.id].isDirty = true;
res.end();
});
}
} else {
return response.errorForbidden(res);
}
}
module.exports = History;

View file

@ -0,0 +1,11 @@
'use strict';
module.exports = {
up: function (queryInterface, Sequelize) {
queryInterface.addColumn('Notes', 'deletedAt', Sequelize.DATE);
},
down: function (queryInterface, Sequelize) {
queryInterface.removeColumn('Notes', 'deletedAt', Sequelize.DATE);
}
};

View file

@ -11,11 +11,17 @@ var shortId = require('shortid');
var Sequelize = require("sequelize"); var Sequelize = require("sequelize");
var async = require('async'); var async = require('async');
var moment = require('moment'); var moment = require('moment');
var DiffMatchPatch = require('diff-match-patch');
var dmp = new DiffMatchPatch();
var S = require('string');
// core // core
var config = require("../config.js"); var config = require("../config.js");
var logger = require("../logger.js"); var logger = require("../logger.js");
//ot
var ot = require("../ot/index.js");
// permission types // permission types
var permissionTypes = ["freely", "editable", "locked", "private"]; var permissionTypes = ["freely", "editable", "locked", "private"];
@ -61,6 +67,7 @@ module.exports = function (sequelize, DataTypes) {
type: DataTypes.DATE type: DataTypes.DATE
} }
}, { }, {
paranoid: true,
classMethods: { classMethods: {
associate: function (models) { associate: function (models) {
Note.belongsTo(models.User, { Note.belongsTo(models.User, {
@ -115,6 +122,7 @@ module.exports = function (sequelize, DataTypes) {
var fsModifiedTime = moment(fs.statSync(filePath).mtime); var fsModifiedTime = moment(fs.statSync(filePath).mtime);
var dbModifiedTime = moment(note.lastchangeAt || note.createdAt); var dbModifiedTime = moment(note.lastchangeAt || note.createdAt);
var body = fs.readFileSync(filePath, 'utf8'); var body = fs.readFileSync(filePath, 'utf8');
var contentLength = body.length;
var title = Note.parseNoteTitle(body); var title = Note.parseNoteTitle(body);
body = LZString.compressToBase64(body); body = LZString.compressToBase64(body);
title = LZString.compressToBase64(title); title = LZString.compressToBase64(title);
@ -126,7 +134,20 @@ module.exports = function (sequelize, DataTypes) {
}).then(function (note) { }).then(function (note) {
sequelize.models.Revision.saveNoteRevision(note, function (err, revision) { sequelize.models.Revision.saveNoteRevision(note, function (err, revision) {
if (err) return _callback(err, null); if (err) return _callback(err, null);
return callback(null, note.id); // update authorship on after making revision of docs
var patch = dmp.patch_fromText(LZString.decompressFromBase64(revision.patch));
var operations = Note.transformPatchToOperations(patch, contentLength);
var authorship = note.authorship ? JSON.parse(LZString.decompressFromBase64(note.authorship)) : [];
for (var i = 0; i < operations.length; i++) {
authorship = Note.updateAuthorshipByOperation(operations[i], null, authorship);
}
note.update({
authorship: LZString.compressToBase64(JSON.stringify(authorship))
}).then(function (note) {
return callback(null, note.id);
}).catch(function (err) {
return _callback(err, null);
});
}); });
}).catch(function (err) { }).catch(function (err) {
return _callback(err, null); return _callback(err, null);
@ -198,8 +219,7 @@ module.exports = function (sequelize, DataTypes) {
return callback(null, null); return callback(null, null);
}); });
}, },
parseNoteTitle: function (body) { parseNoteInfo: function (body) {
var title = "";
var meta = null; var meta = null;
try { try {
var obj = metaMarked(body); var obj = metaMarked(body);
@ -209,13 +229,33 @@ module.exports = function (sequelize, DataTypes) {
//na //na
} }
if (!meta) meta = {}; if (!meta) meta = {};
var $ = cheerio.load(md.render(body));
return {
title: Note.extractNoteTitle(meta, $),
tags: Note.extractNoteTags(meta, $)
};
},
parseNoteTitle: function (body) {
var meta = null;
try {
var obj = metaMarked(body);
body = obj.markdown;
meta = obj.meta;
} catch (err) {
//na
}
if (!meta) meta = {};
var $ = cheerio.load(md.render(body));
return Note.extractNoteTitle(meta, $);
},
extractNoteTitle: function (meta, $) {
var title = "";
if (meta.title && (typeof meta.title == "string" || typeof meta.title == "number")) { if (meta.title && (typeof meta.title == "string" || typeof meta.title == "number")) {
title = meta.title; title = meta.title;
} else { } else {
var $ = cheerio.load(md.render(body));
var h1s = $("h1"); var h1s = $("h1");
if (h1s.length > 0 && h1s.first().text().split('\n').length == 1) if (h1s.length > 0 && h1s.first().text().split('\n').length == 1)
title = h1s.first().text(); title = S(h1s.first().text()).stripTags().s;
} }
if (!title) title = "Untitled"; if (!title) title = "Untitled";
return title; return title;
@ -230,6 +270,40 @@ module.exports = function (sequelize, DataTypes) {
title = !title || title == "Untitled" ? "HackMD - Collaborative markdown notes" : title + " - HackMD"; title = !title || title == "Untitled" ? "HackMD - Collaborative markdown notes" : title + " - HackMD";
return title; return title;
}, },
extractNoteTags: function (meta, $) {
var tags = [];
var rawtags = [];
if (meta.tags && (typeof meta.tags == "string" || typeof meta.tags == "number")) {
var metaTags = ('' + meta.tags).split(',');
for (var i = 0; i < metaTags.length; i++) {
var text = metaTags[i].trim();
if (text) rawtags.push(text);
}
} else {
var h6s = $("h6");
h6s.each(function (key, value) {
if (/^tags/gmi.test($(value).text())) {
var codes = $(value).find("code");
for (var i = 0; i < codes.length; i++) {
var text = $(codes[i]).html().trim();
if (text) rawtags.push(text);
}
}
});
}
for (var i = 0; i < rawtags.length; i++) {
var found = false;
for (var j = 0; j < tags.length; j++) {
if (tags[j] == rawtags[i]) {
found = true;
break;
}
}
if (!found)
tags.push(rawtags[i]);
}
return tags;
},
parseMeta: function (meta) { parseMeta: function (meta) {
var _meta = {}; var _meta = {};
if (meta) { if (meta) {
@ -247,6 +321,162 @@ module.exports = function (sequelize, DataTypes) {
_meta.slideOptions = meta.slideOptions; _meta.slideOptions = meta.slideOptions;
} }
return _meta; return _meta;
},
updateAuthorshipByOperation: function (operation, userId, authorships) {
var index = 0;
var timestamp = Date.now();
for (var i = 0; i < operation.length; i++) {
var op = operation[i];
if (ot.TextOperation.isRetain(op)) {
index += op;
} else if (ot.TextOperation.isInsert(op)) {
var opStart = index;
var opEnd = index + op.length;
var inserted = false;
// authorship format: [userId, startPos, endPos, createdAt, updatedAt]
if (authorships.length <= 0) authorships.push([userId, opStart, opEnd, timestamp, timestamp]);
else {
for (var j = 0; j < authorships.length; j++) {
var authorship = authorships[j];
if (!inserted) {
var nextAuthorship = authorships[j + 1] || -1;
if (nextAuthorship != -1 && nextAuthorship[1] >= opEnd || j >= authorships.length - 1) {
if (authorship[1] < opStart && authorship[2] > opStart) {
// divide
var postLength = authorship[2] - opStart;
authorship[2] = opStart;
authorship[4] = timestamp;
authorships.splice(j + 1, 0, [userId, opStart, opEnd, timestamp, timestamp]);
authorships.splice(j + 2, 0, [authorship[0], opEnd, opEnd + postLength, authorship[3], timestamp]);
j += 2;
inserted = true;
} else if (authorship[1] >= opStart) {
authorships.splice(j, 0, [userId, opStart, opEnd, timestamp, timestamp]);
j += 1;
inserted = true;
} else if (authorship[2] <= opStart) {
authorships.splice(j + 1, 0, [userId, opStart, opEnd, timestamp, timestamp]);
j += 1;
inserted = true;
}
}
}
if (authorship[1] >= opStart) {
authorship[1] += op.length;
authorship[2] += op.length;
}
}
}
index += op.length;
} else if (ot.TextOperation.isDelete(op)) {
var opStart = index;
var opEnd = index - op;
if (operation.length == 1) {
authorships = [];
} else if (authorships.length > 0) {
for (var j = 0; j < authorships.length; j++) {
var authorship = authorships[j];
if (authorship[1] >= opStart && authorship[1] <= opEnd && authorship[2] >= opStart && authorship[2] <= opEnd) {
authorships.splice(j, 1);
j -= 1;
} else if (authorship[1] < opStart && authorship[1] < opEnd && authorship[2] > opStart && authorship[2] > opEnd) {
authorship[2] += op;
authorship[4] = timestamp;
} else if (authorship[2] >= opStart && authorship[2] <= opEnd) {
authorship[2] = opStart;
authorship[4] = timestamp;
} else if (authorship[1] >= opStart && authorship[1] <= opEnd) {
authorship[1] = opEnd;
authorship[4] = timestamp;
}
if (authorship[1] >= opEnd) {
authorship[1] += op;
authorship[2] += op;
}
}
}
index += op;
}
}
// merge
for (var j = 0; j < authorships.length; j++) {
var authorship = authorships[j];
for (var k = j + 1; k < authorships.length; k++) {
var nextAuthorship = authorships[k];
if (nextAuthorship && authorship[0] === nextAuthorship[0] && authorship[2] === nextAuthorship[1]) {
var minTimestamp = Math.min(authorship[3], nextAuthorship[3]);
var maxTimestamp = Math.max(authorship[3], nextAuthorship[3]);
authorships.splice(j, 1, [authorship[0], authorship[1], nextAuthorship[2], minTimestamp, maxTimestamp]);
authorships.splice(k, 1);
j -= 1;
break;
}
}
}
// clear
for (var j = 0; j < authorships.length; j++) {
var authorship = authorships[j];
if (!authorship[0]) {
authorships.splice(j, 1);
j -= 1;
}
}
return authorships;
},
transformPatchToOperations: function (patch, contentLength) {
var operations = [];
if (patch.length > 0) {
// calculate original content length
for (var j = patch.length - 1; j >= 0; j--) {
var p = patch[j];
for (var i = 0; i < p.diffs.length; i++) {
var diff = p.diffs[i];
switch(diff[0]) {
case 1: // insert
contentLength -= diff[1].length;
break;
case -1: // delete
contentLength += diff[1].length;
break;
}
}
}
// generate operations
var bias = 0;
var lengthBias = 0;
for (var j = 0; j < patch.length; j++) {
var operation = [];
var p = patch[j];
var currIndex = p.start1;
var currLength = contentLength - bias;
for (var i = 0; i < p.diffs.length; i++) {
var diff = p.diffs[i];
switch(diff[0]) {
case 0: // retain
if (i == 0) // first
operation.push(currIndex + diff[1].length);
else if (i != p.diffs.length - 1) // mid
operation.push(diff[1].length);
else // last
operation.push(currLength + lengthBias - currIndex);
currIndex += diff[1].length;
break;
case 1: // insert
operation.push(diff[1]);
lengthBias += diff[1].length;
currIndex += diff[1].length;
break;
case -1: // delete
operation.push(-diff[1].length);
bias += diff[1].length;
currIndex += diff[1].length;
break;
}
}
operations.push(operation);
}
}
return operations;
} }
}, },
hooks: { hooks: {

View file

@ -55,7 +55,15 @@ EditorSocketIOServer.prototype.addClient = function (socket) {
if (typeof self.operationCallback === 'function') if (typeof self.operationCallback === 'function')
self.operationCallback(socket, operation); self.operationCallback(socket, operation);
} catch (err) { } catch (err) {
socket.disconnect(true); setTimeout(function() {
var docOut = {
str: self.document,
revision: self.operations.length,
clients: self.users,
force: true
};
socket.emit('doc', LZString.compressToUTF16(JSON.stringify(docOut)));
}, 100);
} }
}); });
}); });

View file

@ -13,6 +13,7 @@ var moment = require('moment');
//core //core
var config = require("./config.js"); var config = require("./config.js");
var logger = require("./logger.js"); var logger = require("./logger.js");
var history = require("./history.js");
var models = require("./models"); var models = require("./models");
//ot //ot
@ -63,6 +64,7 @@ function secure(socket, next) {
function emitCheck(note) { function emitCheck(note) {
var out = { var out = {
title: note.title,
updatetime: note.updatetime, updatetime: note.updatetime,
lastchangeuser: note.lastchangeuser, lastchangeuser: note.lastchangeuser,
lastchangeuserprofile: note.lastchangeuserprofile, lastchangeuserprofile: note.lastchangeuserprofile,
@ -148,7 +150,7 @@ function updateNote(note, callback) {
function finishUpdateNote(note, _note, callback) { function finishUpdateNote(note, _note, callback) {
if (!note || !note.server) return callback(null, null); if (!note || !note.server) return callback(null, null);
var body = note.server.document; var body = note.server.document;
var title = models.Note.parseNoteTitle(body); var title = note.title = models.Note.parseNoteTitle(body);
title = LZString.compressToBase64(title); title = LZString.compressToBase64(title);
body = LZString.compressToBase64(body); body = LZString.compressToBase64(body);
var values = { var values = {
@ -312,6 +314,7 @@ function emitRefresh(socket) {
if (!noteId || !notes[noteId]) return; if (!noteId || !notes[noteId]) return;
var note = notes[noteId]; var note = notes[noteId];
var out = { var out = {
title: note.title,
docmaxlength: config.documentmaxlength, docmaxlength: config.documentmaxlength,
owner: note.owner, owner: note.owner,
ownerprofile: note.ownerprofile, ownerprofile: note.ownerprofile,
@ -327,6 +330,15 @@ function emitRefresh(socket) {
socket.emit('refresh', out); socket.emit('refresh', out);
} }
function isDuplicatedInSocketQueue(queue, socket) {
for (var i = 0; i < queue.length; i++) {
if (queue[i] && queue[i].id == socket.id) {
return true;
}
}
return false;
}
function clearSocketQueue(queue, socket) { function clearSocketQueue(queue, socket) {
for (var i = 0; i < queue.length; i++) { for (var i = 0; i < queue.length; i++) {
if (!queue[i] || queue[i].id == socket.id) { if (!queue[i] || queue[i].id == socket.id) {
@ -381,6 +393,12 @@ function finishConnection(socket, note, user) {
note.server.setName(socket, user.name); note.server.setName(socket, user.name);
note.server.setColor(socket, user.color); note.server.setColor(socket, user.color);
// update user note history
setTimeout(function () {
var noteId = note.alias ? note.alias : LZString.compressToBase64(note.id);
history.updateHistory(user.userid, noteId, note.server.document);
}, 0);
emitOnlineUsers(socket); emitOnlineUsers(socket);
emitRefresh(socket); emitRefresh(socket);
@ -459,6 +477,8 @@ function startConnection(socket) {
notes[noteId] = { notes[noteId] = {
id: noteId, id: noteId,
alias: note.alias,
title: LZString.decompressFromBase64(note.title),
owner: owner, owner: owner,
ownerprofile: ownerprofile, ownerprofile: ownerprofile,
permission: note.permission, permission: note.permission,
@ -509,17 +529,23 @@ function disconnect(socket) {
var noteId = socket.noteId; var noteId = socket.noteId;
var note = notes[noteId]; var note = notes[noteId];
if (note) { if (note) {
// delete user in users
delete note.users[socket.id]; delete note.users[socket.id];
// remove sockets in the note socks
do { do {
var index = note.socks.indexOf(socket); var index = note.socks.indexOf(socket);
if (index != -1) { if (index != -1) {
note.socks.splice(index, 1); note.socks.splice(index, 1);
} }
} while (index != -1); } while (index != -1);
// remove note in notes if no user inside
if (Object.keys(note.users).length <= 0) { if (Object.keys(note.users).length <= 0) {
if (note.server.isDirty) { if (note.server.isDirty) {
updateNote(note, function (err, _note) { updateNote(note, function (err, _note) {
if (err) return logger.error('disconnect note failed: ' + err); if (err) return logger.error('disconnect note failed: ' + err);
// clear server before delete to avoid memory leaks
note.server.document = "";
note.server.operations = [];
delete note.server; delete note.server;
delete notes[noteId]; delete notes[noteId];
if (config.debug) { if (config.debug) {
@ -640,108 +666,15 @@ function operationCallback(socket, operation) {
return logger.error('operation callback failed: ' + err); return logger.error('operation callback failed: ' + err);
}); });
} }
// update user note history
setTimeout(function() {
var noteId = note.alias ? note.alias : LZString.compressToBase64(note.id);
history.updateHistory(userId, noteId, note.server.document);
}, 0);
} }
// save authorship // save authorship
var index = 0; note.authorship = models.Note.updateAuthorshipByOperation(operation, userId, note.authorship);
var authorships = note.authorship;
var timestamp = Date.now();
for (var i = 0; i < operation.length; i++) {
var op = operation[i];
if (ot.TextOperation.isRetain(op)) {
index += op;
} else if (ot.TextOperation.isInsert(op)) {
var opStart = index;
var opEnd = index + op.length;
var inserted = false;
// authorship format: [userId, startPos, endPos, createdAt, updatedAt]
if (authorships.length <= 0) authorships.push([userId, opStart, opEnd, timestamp, timestamp]);
else {
for (var j = 0; j < authorships.length; j++) {
var authorship = authorships[j];
if (!inserted) {
var nextAuthorship = authorships[j + 1] || -1;
if (nextAuthorship != -1 && nextAuthorship[1] >= opEnd || j >= authorships.length - 1) {
if (authorship[1] < opStart && authorship[2] > opStart) {
// divide
var postLength = authorship[2] - opStart;
authorship[2] = opStart;
authorship[4] = timestamp;
authorships.splice(j + 1, 0, [userId, opStart, opEnd, timestamp, timestamp]);
authorships.splice(j + 2, 0, [authorship[0], opEnd, opEnd + postLength, authorship[3], timestamp]);
j += 2;
inserted = true;
} else if (authorship[1] >= opStart) {
authorships.splice(j, 0, [userId, opStart, opEnd, timestamp, timestamp]);
j += 1;
inserted = true;
} else if (authorship[2] <= opStart) {
authorships.splice(j + 1, 0, [userId, opStart, opEnd, timestamp, timestamp]);
j += 1;
inserted = true;
}
}
}
if (authorship[1] >= opStart) {
authorship[1] += op.length;
authorship[2] += op.length;
}
}
}
index += op.length;
} else if (ot.TextOperation.isDelete(op)) {
var opStart = index;
var opEnd = index - op;
if (operation.length == 1) {
authorships = [];
} else if (authorships.length > 0) {
for (var j = 0; j < authorships.length; j++) {
var authorship = authorships[j];
if (authorship[1] >= opStart && authorship[1] <= opEnd && authorship[2] >= opStart && authorship[2] <= opEnd) {
authorships.splice(j, 1);
j -= 1;
} else if (authorship[1] < opStart && authorship[1] < opEnd && authorship[2] > opStart && authorship[2] > opEnd) {
authorship[2] += op;
authorship[4] = timestamp;
} else if (authorship[2] >= opStart && authorship[2] <= opEnd) {
authorship[2] = opStart;
authorship[4] = timestamp;
} else if (authorship[1] >= opStart && authorship[1] <= opEnd) {
authorship[1] = opEnd;
authorship[4] = timestamp;
}
if (authorship[1] >= opEnd) {
authorship[1] += op;
authorship[2] += op;
}
}
}
index += op;
}
}
// merge
for (var j = 0; j < authorships.length; j++) {
var authorship = authorships[j];
for (var k = j + 1; k < authorships.length; k++) {
var nextAuthorship = authorships[k];
if (nextAuthorship && authorship[0] === nextAuthorship[0] && authorship[2] === nextAuthorship[1]) {
var minTimestamp = Math.min(authorship[3], nextAuthorship[3]);
var maxTimestamp = Math.max(authorship[3], nextAuthorship[3]);
authorships.splice(j, 1, [authorship[0], authorship[1], nextAuthorship[2], minTimestamp, maxTimestamp]);
authorships.splice(k, 1);
j -= 1;
break;
}
}
}
// clear
for (var j = 0; j < authorships.length; j++) {
var authorship = authorships[j];
if (!authorship[0]) {
authorships.splice(j, 1);
j -= 1;
}
}
note.authorship = authorships;
} }
function connection(socket) { function connection(socket) {
@ -753,6 +686,8 @@ function connection(socket) {
if (!noteId) { if (!noteId) {
return failConnection(404, 'note id not found', socket); return failConnection(404, 'note id not found', socket);
} }
if (isDuplicatedInSocketQueue(socket, connectionSocketQueue)) return;
// store noteId in this socket session // store noteId in this socket session
socket.noteId = noteId; socket.noteId = noteId;
@ -864,6 +799,35 @@ function connection(socket) {
} }
}); });
// delete a note
socket.on('delete', function () {
//need login to do more actions
if (socket.request.user && socket.request.user.logged_in) {
var noteId = socket.noteId;
if (!noteId || !notes[noteId]) return;
var note = notes[noteId];
//Only owner can delete note
if (note.owner && note.owner == socket.request.user.id) {
models.Note.destroy({
where: {
id: noteId
}
}).then(function (count) {
if (!count) return;
for (var i = 0, l = note.socks.length; i < l; i++) {
var sock = note.socks[i];
if (typeof sock !== 'undefined' && sock) {
sock.emit('delete');
return sock.disconnect(true);
}
}
}).catch(function (err) {
return logger.error('delete note failed: ' + err);
});
}
}
});
//reveiced when user logout or changed //reveiced when user logout or changed
socket.on('user changed', function () { socket.on('user changed', function () {
logger.info('user changed'); logger.info('user changed');
@ -929,6 +893,7 @@ function connection(socket) {
//when a new client disconnect //when a new client disconnect
socket.on('disconnect', function () { socket.on('disconnect', function () {
if (isDuplicatedInSocketQueue(socket, disconnectSocketQueue)) return;
disconnectSocketQueue.push(socket); disconnectSocketQueue.push(socket);
disconnect(socket); disconnect(socket);
}); });

View file

@ -33,6 +33,9 @@ var response = {
errorNotFound: function (res) { errorNotFound: function (res) {
responseError(res, "404", "Not Found", "oops."); responseError(res, "404", "Not Found", "oops.");
}, },
errorBadRequest: function (res) {
responseError(res, "400", "Bad Request", "something not right.");
},
errorInternalError: function (res) { errorInternalError: function (res) {
responseError(res, "500", "Internal Error", "wtf."); responseError(res, "500", "Internal Error", "wtf.");
}, },
@ -205,6 +208,9 @@ function showPublishNote(req, res, next) {
url: origin, url: origin,
body: text, body: text,
useCDN: config.usecdn, useCDN: config.usecdn,
owner: note.owner ? note.owner.id : null,
ownerprofile: note.owner ? models.User.parseProfile(note.owner.profile) : null,
lastchangeuser: note.lastchangeuser ? note.lastchangeuser.id : null,
lastchangeuserprofile: note.lastchangeuser ? models.User.parseProfile(note.lastchangeuser.profile) : null, lastchangeuserprofile: note.lastchangeuser ? models.User.parseProfile(note.lastchangeuser.profile) : null,
robots: meta.robots || false, //default allow robots robots: meta.robots || false, //default allow robots
GA: meta.GA, GA: meta.GA,
@ -332,6 +338,13 @@ function actionRevision(req, res, note) {
if (!content) { if (!content) {
return response.errorNotFound(res); return response.errorNotFound(res);
} }
res.set({
'Access-Control-Allow-Origin': '*', //allow CORS as API
'Access-Control-Allow-Headers': 'Range',
'Access-Control-Expose-Headers': 'Cache-Control, Content-Encoding, Content-Range',
'Cache-Control': 'private', // only cache by client
'X-Robots-Tag': 'noindex, nofollow' // prevent crawling
});
res.send(content); res.send(content);
}); });
} else { } else {
@ -346,6 +359,13 @@ function actionRevision(req, res, note) {
var out = { var out = {
revision: data revision: data
}; };
res.set({
'Access-Control-Allow-Origin': '*', //allow CORS as API
'Access-Control-Allow-Headers': 'Range',
'Access-Control-Expose-Headers': 'Cache-Control, Content-Encoding, Content-Range',
'Cache-Control': 'private', // only cache by client
'X-Robots-Tag': 'noindex, nofollow' // prevent crawling
});
res.send(out); res.send(out);
}); });
} }
@ -576,6 +596,9 @@ function showPublishSlide(req, res, next) {
slides: slides, slides: slides,
meta: JSON.stringify(obj.meta || {}), meta: JSON.stringify(obj.meta || {}),
useCDN: config.usecdn, useCDN: config.usecdn,
owner: note.owner ? note.owner.id : null,
ownerprofile: note.owner ? models.User.parseProfile(note.owner.profile) : null,
lastchangeuser: note.lastchangeuser ? note.lastchangeuser.id : null,
lastchangeuserprofile: note.lastchangeuser ? models.User.parseProfile(note.lastchangeuser.profile) : null, lastchangeuserprofile: note.lastchangeuser ? models.User.parseProfile(note.lastchangeuser.profile) : null,
robots: meta.robots || false, //default allow robots robots: meta.robots || false, //default allow robots
GA: meta.GA, GA: meta.GA,

104
locales/hr.json Normal file
View file

@ -0,0 +1,104 @@
{
"Collaborative markdown notes": "Kolaborativne markdown bilješke",
"Realtime collaborative markdown notes on all platforms.": "Kolaborativne markdown bilješke na svim platformama u realnom vremenu.",
"Best way to write and share your knowledge in markdown.": "Najbolji način za pisanje i dijeljenje svog znanja u markdown-u.",
"Intro": "Uvod",
"History": "Povijest",
"New guest note": "Nova bilješka gosta",
"Collaborate with URL": "Kolaboracija sa URL-om",
"Support charts and MathJax": "Support charts and MathJax",
"Support slide mode": "Način podrške slajda",
"Sign In": "Prijavu se",
"Below is the history from browser": "Ispod je povijest preglednika",
"Welcome!": "Dobrodošli!",
"New note": "Nova bilješka",
"or": "ili",
"Sign Out": "Odjavi se",
"Explore all features": "Istraži sve značajke",
"Select tags...": "Odaberi oznake...",
"Search keyword...": "Pretraži ključnu riječ...",
"Sort by title": "Sortiraj po naslovu",
"Title": "Naslov",
"Sort by time": "Sortiraj po vremenu",
"Time": "Vrijeme",
"Export history": "Izvezi povijest",
"Import history": "Uvezi povijest",
"Clear history": "Očisti povijest",
"Refresh history": "Osvježi povijest",
"No history": "Nema povijesti",
"Import from browser": "Uvezi iz preglednika",
"Releases": "Izdanja",
"Are you sure?": "Jeste li sigurni?",
"Cancel": "Odustani",
"Yes, do it!": "Da, učini to!",
"Choose method": "Izaberi metodu",
"Sign in via %s": "Prijavi se pomoću %s",
"New": "Novo",
"Publish": "Objavi",
"Extra": "Dodatno",
"Revision": "Revizija",
"Slide Mode": "Način slajda",
"Export": "Izvoz",
"Import": "Uvoz",
"Clipboard": "Međuspremnik",
"Download": "Preuzimanje",
"Raw HTML": "Raw HTML",
"Edit": "Uredi",
"View": "Pregledaj",
"Both": "Oboje",
"Help": "Pomoć",
"Upload Image": "Prenesi sliku",
"Menu": "Meni",
"This page need refresh": "Ovu stranicu je potrebno osvježiti",
"You have an incompatible client version.": "Imate nekompatibilnu verziju klijenta.",
"Refresh to update.": "Osvježite za ažuriranje.",
"New version available!": "Nova verzija dostupna!",
"See releases notes here": "Pogledajte bilješke izdanja ovdje",
"Refresh to enjoy new features.": "Osvježi za nove značajke.",
"Your user state has changed.": "Stanje Vašeg korisnika se promijenilo.",
"Refresh to load new user state.": "Osvježi za učitavanje novog stanja korisnika.",
"Refresh": "Osvježi",
"Contacts": "Kontakti",
"Report an issue": "Prijavi problem",
"Send us email": "Pošalji nam email",
"Documents": "Dokumenti",
"Features": "Značajke",
"YAML Metadata": "YAML Metadata",
"Slide Example": "Primjer slajda",
"Cheatsheet": "Cheatsheet",
"Example": "Primjer",
"Syntax": "Sintaksa",
"Header": "Zaglavlje",
"Unordered List": "Neuređeni popis",
"Ordered List": "Uređeni popis",
"Todo List": "Popis obaveza",
"Blockquote": "Blockquote",
"Bold font": "Bold font",
"Italics font": "Kurzivan font",
"Strikethrough": "Precrtano",
"Inserted text": "Umetnuti tekst",
"Marked text": "Označeni tekst",
"Link": "Link",
"Image": "Slika",
"Code": "Kod",
"Externals": "Vanjski izgled",
"This is a alert area.": "Ovo je područje upozorenja.",
"Revert": "Vrati",
"Import from clipboard": "Uvezi iz međuspremnika",
"Paste your markdown or webpage here...": "Zalijepi svoj markdown ili web stranicu ovdje...",
"Clear": "Očisti",
"This note is locked": "Ova bilješka je zaključana",
"Sorry, only owner can edit this note.": "Žao nam je, samo vlasnik ove bilješke ju može uređivati.",
"OK": "OK",
"Reach the limit": "Dosegni granicu",
"Sorry, you've reached the max length this note can be.": "Žao nam je, dosegli ste maksimalnu moguću duljinu ove bilješke.",
"Please reduce the content or divide it to more notes, thank you!": "Molimo Vas smanjite sardžaj ili ga podijelite na više bilješki, hvala!",
"Import from Gist": "Uvezi iz Gist-a",
"Paste your gist url here...": "Zalijepi svoj gist url ovdje...",
"Import from Snippet": "Uvezi iz isječka",
"Select From Available Projects": "Odaberi iz raspoloživih projekta",
"Select From Available Snippets": "Odaberi iz raspoloživih isječaka",
"OR": "ILI",
"Export to Snippet": "Izvoz u isječak",
"Select Visibility Level": "Odaberi razinu vidljivosti"
}

104
locales/pl.json Normal file
View file

@ -0,0 +1,104 @@
{
"Collaborative markdown notes": "Wspólne markdown notatki",
"Realtime collaborative markdown notes on all platforms.": "Rzeczywiste wspólne markdown notatki dla wszystkich platform",
"Best way to write and share your knowledge in markdown.": "Najlepszy sposób na pisanie i dzielenie się swoją wiedzą w markdown.",
"Intro": "Intro",
"History": "Historia",
"New guest note": "Nowa notatka gościa",
"Collaborate with URL": "Wspólnie z URL",
"Support charts and MathJax": "Support charts and MathJax",
"Support slide mode": "Support slide mode",
"Sign In": "Zaloguj się",
"Below is the history from browser": "Historia z przeglądarki poniżej",
"Welcome!": "Witam!",
"New note": "Nowa notatka",
"or": "lub",
"Sign Out": "Wyloguj się",
"Explore all features": "Przeglądaj wszystkie funkcje",
"Select tags...": "Wybierz tagi...",
"Search keyword...": "Znajdź kluczowe słowo...",
"Sort by title": "Sortuj według tytułu",
"Title": "Tytuł",
"Sort by time": "Sortuj według czasu",
"Time": "Czas",
"Export history": "Eksportuj historię",
"Import history": "Importuj historię",
"Clear history": "Wyczyść historię",
"Refresh history": "Odśwież historię",
"No history": "Brak histori",
"Import from browser": "Importuj z przeglądarki",
"Releases": "Wydania",
"Are you sure?": "Jesteś pewny?",
"Cancel": "Anuluj",
"Yes, do it!": "Tak, zrób to!",
"Choose method": "Wybierz metodę",
"Sign in via %s": "Zaloguj się poprzez %s",
"New": "Nowy",
"Publish": "Publikuj",
"Extra": "Ekstra",
"Revision": "Korekta",
"Slide Mode": "Tryb slajdów",
"Export": "Eksport",
"Import": "Import",
"Clipboard": "Schowek",
"Download": "Pobierz",
"Raw HTML": "Raw HTML",
"Edit": "Edytuj",
"View": "Pogląd",
"Both": "Both",
"Help": "Pomoc",
"Upload Image": "Prześlij zdjęcie",
"Menu": "Menu",
"This page need refresh": "Strona wymaga odświeżenia",
"You have an incompatible client version.": "Posiadasz niezgodną wersję kliencką.",
"Refresh to update.": "Odświerz aby zaktualizować.",
"New version available!": "Nowa wersja dostępna!",
"See releases notes here": "Zobacz informacje o wydaniach tutaj",
"Refresh to enjoy new features.": "Odśwież, aby korzystać z nowych funkcji.",
"Your user state has changed.": "Stan twojego użytkownika się zmienił.",
"Refresh to load new user state.": "Odśwież aby załadować nowy stan użytkownika.",
"Refresh": "Odśwież",
"Contacts": "Kontakty",
"Report an issue": "Zgłoś błąd",
"Send us email": "Wyślij nam email",
"Documents": "Dokumenty",
"Features": "Funkcje",
"YAML Metadata": "YAML Meta dane",
"Slide Example": "Przykład slajdu",
"Cheatsheet": "Ściągawka",
"Example": "Przykład",
"Syntax": "Składnia",
"Header": "Nagłówek",
"Unordered List": "Nie posortowana lista",
"Ordered List": "Posortowana lista",
"Todo List": "Todo lista",
"Blockquote": "Cytat blokowy",
"Bold font": "Czcionka pogrubiona",
"Italics font": "Czcionka pochylona",
"Strikethrough": "Przekreślenie",
"Inserted text": "Wstawiony tekst",
"Marked text": "Zaznaczony tekst",
"Link": "Odnośnik",
"Image": "Zdjęcie",
"Code": "Kod",
"Externals": "Zewnętrzne",
"This is a alert area.": "This is a alert area.",
"Revert": "Cofnij",
"Import from clipboard": "Importuj ze schowka",
"Paste your markdown or webpage here...": "Wklej markdown lub stronę tutaj...",
"Clear": "Wyczyść",
"This note is locked": "Notatka jest zablokowana",
"Sorry, only owner can edit this note.": "Tylko właściciel może edytować tą notatkę.",
"OK": "OK",
"Reach the limit": "Osiągnięto limit",
"Sorry, you've reached the max length this note can be.": "Niestety, osiągnięto maksymalną długość notatki.",
"Please reduce the content or divide it to more notes, thank you!": "Proszę zmniejszyć zawartość notatki lub podzielić ją na kilka notatek, dziękuję!",
"Import from Gist": "Importuj z Gist",
"Paste your gist url here...": "Wklej gist url tutaj...",
"Import from Snippet": "Importuj z Snippet",
"Select From Available Projects": "Wybierz z dostępnych projektów",
"Select From Available Snippets": "Wybierz z dostępnych Snippets",
"OR": "LUB",
"Export to Snippet": "Eksportuj do Snippet",
"Select Visibility Level": "Wybierz poziom widoczności"
}

104
locales/uk.json Normal file
View file

@ -0,0 +1,104 @@
{
"Collaborative markdown notes": "Спільні примітки щодо знижок",
"Realtime collaborative markdown notes on all platforms.": "Спільні примітки щодо знижок в реальному часі на всіх платформах.",
"Best way to write and share your knowledge in markdown.": "Кращий спосіб, щоб записувати і ділитись своїми знаннями щодо знижок в реальному часі.",
"Intro": "Вступ",
"History": "Історія",
"New guest note": "Примітка нового гостя",
"Collaborate with URL": "Спільна робота по URL",
"Support charts and MathJax": "Підтримка графіків і MathJax",
"Support slide mode": "Підтримка режиму слайдера",
"Sign In": "Ввійти",
"Below is the history from browser": "Нижче показана історія браузера",
"Welcome!": "Ласкаво просимо!",
"New note": "Нова примітка",
"or": "або",
"Sign Out": "Вийти",
"Explore all features": "Дослідити всі можливості",
"Select tags...": "Вибрати теги...",
"Search keyword...": "Пошук...",
"Sort by title": "Сортувати по заголовку",
"Title": "Заголовок",
"Sort by time": "Сортувати по часу",
"Time": "Час",
"Export history": "Еспортувати історію",
"Import history": "Імпортувати історію",
"Clear history": "Очистити історію",
"Refresh history": "Оновити історію",
"No history": "Історія відсутня",
"Import from browser": "Імпортувати з браузера",
"Releases": "Релізи",
"Are you sure?": "Ви впевнені?",
"Cancel": "Відмінити",
"Yes, do it!": "Так, зробити це!",
"Choose method": "Вибрати метод",
"Sign in via %s": "Увійти за допомогою %s",
"New": "Нова",
"Publish": "Опублікувати",
"Extra": "Дотатково",
"Revision": "Ревізія",
"Slide Mode": "Режим слайдера",
"Export": "Експорт",
"Import": "Імпорт",
"Clipboard": "Буфер обміну",
"Download": "Завантажити",
"Raw HTML": "Raw HTML",
"Edit": "Редагувати",
"View": "Вигляд",
"Both": "Обоє",
"Help": "Допомога",
"Upload Image": "Завантажити зображення",
"Menu": "Меню",
"This page need refresh": "Цю сторінку необхідно обновити",
"You have an incompatible client version.": "Ви використовуєте несумісну версію клієнта.",
"Refresh to update.": "Оновіть сторінку для оновлення.",
"New version available!": "Нова версія доступна!",
"See releases notes here": "Огляньте деталі оновлень тут",
"Refresh to enjoy new features.": "Оновіть, щоб насолоджуватись новими можливостями.",
"Your user state has changed.": "Ваш акаунт змінено.",
"Refresh to load new user state.": "Оновіть, щоб завантажити зміни акаунта.",
"Refresh": "Оновити",
"Contacts": "Контакти",
"Report an issue": "Повідомити про проблему",
"Send us email": "Відправити нам лист",
"Documents": "Документи",
"Features": "Можливості",
"YAML Metadata": "Метадані YAML",
"Slide Example": "Приклад слайдера",
"Cheatsheet": "Шпаргалка",
"Example": "Приклад",
"Syntax": "Синтаксис",
"Header": "Заголовок",
"Unordered List": "Маркований список",
"Ordered List": "Нумерований список",
"Todo List": "Список завдань",
"Blockquote": "Цитата",
"Bold font": "Жирний шрифт",
"Italics font": "Курсив",
"Strikethrough": "Перекреслений",
"Inserted text": "Підкреслений текст",
"Marked text": "Виділений текст",
"Link": "Посилання",
"Image": "Зображення",
"Code": "Код",
"Externals": "Зовнішнє",
"This is a alert area.": "Це область повідомлення.",
"Revert": "Відмінити",
"Import from clipboard": "Імпорт з буферу обміну",
"Paste your markdown or webpage here...": "Вставте ваш markdown або веб-сторінку тут...",
"Clear": "Очистити",
"This note is locked": "Ця замітка заблокована",
"Sorry, only owner can edit this note.": "Вибачте, лише власник може редагувати цю замітку.",
"OK": "OK",
"Reach the limit": "Досягнено ліміту",
"Sorry, you've reached the max length this note can be.": "Нажаль, ви досягли максимальної довжини замітки.",
"Please reduce the content or divide it to more notes, thank you!": "Будь-ласка, зменшіть розмір вмісту або розділіть його на декілька заміток!",
"Import from Gist": "Імпортувати з Gist",
"Paste your gist url here...": "Вставте посилання на ваш gist тут...",
"Import from Snippet": "Імпортувати фрагмент коду",
"Select From Available Projects": "Виберіть з доступних проектів",
"Select From Available Snippets": "Виберіть з доступних фрагментів коду",
"OR": "АБО",
"Export to Snippet": "Експорт фрагменту коду",
"Select Visibility Level": "Вибрати рівень видимості"
}

View file

@ -1,6 +1,6 @@
{ {
"name": "hackmd", "name": "hackmd",
"version": "0.4.4", "version": "0.4.5",
"description": "Realtime collaborative markdown notes on all platforms.", "description": "Realtime collaborative markdown notes on all platforms.",
"main": "app.js", "main": "app.js",
"license": "MIT", "license": "MIT",
@ -10,26 +10,26 @@
}, },
"dependencies": { "dependencies": {
"async": "^2.0.1", "async": "^2.0.1",
"blueimp-md5": "^2.3.0", "blueimp-md5": "^2.4.0",
"body-parser": "^1.15.2", "body-parser": "^1.15.2",
"bootstrap": "^3.3.7", "bootstrap": "^3.3.7",
"chance": "^1.0.4", "chance": "^1.0.4",
"cheerio": "^0.20.0", "cheerio": "^0.22.0",
"compression": "^1.6.2", "compression": "^1.6.2",
"connect-session-sequelize": "^3.1.0", "connect-session-sequelize": "^3.2.0",
"cookie": "0.3.1", "cookie": "0.3.1",
"cookie-parser": "1.4.3", "cookie-parser": "1.4.3",
"diff-match-patch": "git+https://github.com/hackmdio/diff-match-patch.git", "diff-match-patch": "git+https://github.com/hackmdio/diff-match-patch.git",
"ejs": "^2.5.1", "ejs": "^2.5.2",
"emojify.js": "^1.1.0", "emojify.js": "^1.1.0",
"express": ">=4.14", "express": ">=4.14",
"express-session": "^1.14.0", "express-session": "^1.14.1",
"file-saver": "^1.3.3", "file-saver": "^1.3.3",
"flowchart.js": "^1.6.3", "flowchart.js": "^1.6.3",
"formidable": "^1.0.17", "formidable": "^1.0.17",
"gist-embed": "github:yukaii/gist-embed", "gist-embed": "github:yukaii/gist-embed",
"handlebars": "^4.0.5", "handlebars": "^4.0.5",
"helmet": "^2.1.2", "helmet": "^2.3.0",
"highlight.js": "^9.7.0", "highlight.js": "^9.7.0",
"i18n": "^0.8.3", "i18n": "^0.8.3",
"imgur": "git+https://github.com/hackmdio/node-imgur.git", "imgur": "git+https://github.com/hackmdio/node-imgur.git",
@ -44,9 +44,10 @@
"jsdom-nogyp": "^0.8.3", "jsdom-nogyp": "^0.8.3",
"keymaster": "^1.6.2", "keymaster": "^1.6.2",
"list.js": "^1.2.0", "list.js": "^1.2.0",
"list.pagination.js": "^0.1.1",
"lodash": "^4.16.4", "lodash": "^4.16.4",
"lz-string": "1.4.4", "lz-string": "1.4.4",
"markdown-it": "^7.0.1", "markdown-it": "^8.0.0",
"markdown-it-abbr": "^1.0.4", "markdown-it-abbr": "^1.0.4",
"markdown-it-container": "^2.0.0", "markdown-it-container": "^2.0.0",
"markdown-it-deflist": "^2.0.1", "markdown-it-deflist": "^2.0.1",
@ -59,9 +60,9 @@
"markdown-it-sub": "^1.0.0", "markdown-it-sub": "^1.0.0",
"markdown-it-sup": "^1.0.0", "markdown-it-sup": "^1.0.0",
"markdown-pdf": "^7.0.0", "markdown-pdf": "^7.0.0",
"meta-marked": "^0.4.1", "meta-marked": "^0.4.2",
"method-override": "^2.3.6", "method-override": "^2.3.6",
"moment": "^2.14.1", "moment": "^2.15.1",
"morgan": "^1.7.0", "morgan": "^1.7.0",
"mysql": "^2.11.1", "mysql": "^2.11.1",
"node-uuid": "^1.4.7", "node-uuid": "^1.4.7",
@ -74,19 +75,19 @@
"passport-twitter": "^1.0.4", "passport-twitter": "^1.0.4",
"passport.socketio": "^3.6.2", "passport.socketio": "^3.6.2",
"pdfobject": "^2.0.201604172", "pdfobject": "^2.0.201604172",
"pg": "^6.0.3", "pg": "^6.1.0",
"pg-hstore": "^2.3.2", "pg-hstore": "^2.3.2",
"prismjs": "^1.5.1", "prismjs": "^1.5.1",
"randomcolor": "^0.4.4", "randomcolor": "^0.4.4",
"raphael": "github:dmitrybaranovskiy/raphael", "raphael": "github:dmitrybaranovskiy/raphael",
"request": "^2.74.0", "request": "^2.75.0",
"reveal.js": "3.3.0", "reveal.js": "3.3.0",
"sequelize": "^3.23.6", "sequelize": "^3.24.3",
"sequelize-cli": "^2.4.0", "sequelize-cli": "^2.4.0",
"shortid": "2.2.6", "shortid": "2.2.6",
"socket.io": "1.4.8", "socket.io": "1.5.0",
"socket.io-client": "^1.4.8", "socket.io-client": "^1.4.8",
"sqlite3": "^3.1.4", "sqlite3": "^3.1.6",
"store": "^1.3.20", "store": "^1.3.20",
"string": "^3.3.1", "string": "^3.3.1",
"tedious": "^1.14.0", "tedious": "^1.14.0",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 684 B

View file

@ -1,6 +1,66 @@
Release Notes Release Notes
=== ===
<i class="fa fa-tag"></i> 0.4.5 `latte` <i class="fa fa-clock-o"></i> 2016-10-11 01:22
---
### Features
+ Add more environment variables for server configuration
+ Add setup script for getting started
+ Add support of deleting note
+ Add support of shortcut keys which can add and remove symbol surround text
+ Add support of shortcut keys for changing mode
+ Add support of i18n (English, Chinese, French, German, Japanese, Spanish, Portuguese, Greek, Italian, Turkish, Russian, Dutch, Croatian, Polish, Ukrainian)
+ Add support of note info API
+ Add support of disqus via yaml-metadata
### Enhancements
* Optimize png images by using zopflipng
* Update CodeMirror to 5.19.0 and rename jade to pug
* Update to add cache to history and improve its performance
* Update default indent to use spaces instead of tabs
* Improve syntax highlighting performance
* Update to make client handle syncing error better, use delay to avoid wrong document revision
* Update to allow CORS as API on revision actions
* Update to support showing owner on the infobar
* Update to prevent duplicate client push in queue to lower down server loading
* Reduce update view debounce time to make preview refresh quicker
* Update help modal cheatsheet font styles to make it more clear on spaces
* Update to add revision saving policy
* Update to support tiddlywiki and mediawiki syntax highlighting in editor
* Update to support save mode to url and vise versa
* Update edit and publish icon and change toggle icon for UX
* Improve authorship markers update performance
* Update slide mode to show extra info and support url actions
* Change the last change user saving strategy
* Update to support data uri in src attribute of image tag
* Improve index layout and UX with UI adjustments
* Update XSS policy to allow iframe and link with custom protocol
* Update markdown styles to follow github latest layout styles
* Update slide mode, now respect all meta settings and update default styles
* Update to make ToC menu always accessible without scrolling
* Update to make doc only update while filesystem content not match db content
### Fixes
* Fix README and features document format and grammar issues
* Fix some potential memory leaks bugs
* Fix history storage might not fallback correctly
* Fix to make mathjax expression display in editor correctly (not italic)
* Fix note title might have unstriped html tags
* Fix client reconnect should resend last operation
* Fix a bug when setting both maxAge and expires may cause user can't signin
* Fix text complete extra tags for blockquote and referrals
* Fix bug that when window close will make ajax fail and cause cookies set to wrong state
* Fix markdown render might fall into regex infinite loop
* Fix syntax error caused by element contain special characters
* Fix reference error caused by some scripts loading order
* Fix ToC id naming to avoid possible overlap with user ToC
* Fix header nav bar rwd detect element should use div tag or it might glitch the layout
* Fix textcomplete of extra tags for blockquote not match space character in the between
* Fix text-shadow for text antialiased might cause IE or Edge text cutoff
### Removes
- Cancel updating history on page unload
<i class="fa fa-tag"></i> 0.4.4 `mocha` <i class="fa fa-clock-o"></i> 2016-08-02 17:10 <i class="fa fa-tag"></i> 0.4.4 `mocha` <i class="fa fa-clock-o"></i> 2016-08-02 17:10
--- ---
### Features ### Features

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 725 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

View file

@ -1,20 +1,18 @@
var config = require('./config');
var domain = config.domain; // domain name
var urlpath = config.urlpath; // sub url path, like: www.example.com/<urlpath>
var debug = config.debug;
var GOOGLE_API_KEY = config.GOOGLE_API_KEY;
var GOOGLE_CLIENT_ID = config.GOOGLE_CLIENT_ID;
var DROPBOX_APP_KEY = config.DROPBOX_APP_KEY;
//common //common
var domain = ''; // domain name
var urlpath = ''; // sub url path, like: www.example.com/<urlpath>
//settings
var debug = false;
var GOOGLE_API_KEY = '';
var GOOGLE_CLIENT_ID = '';
var DROPBOX_APP_KEY = '';
var port = window.location.port; var port = window.location.port;
var serverurl = window.location.protocol + '//' + (domain ? domain : window.location.hostname) + (port ? ':' + port : '') + (urlpath ? '/' + urlpath : ''); var serverurl = window.location.protocol + '//' + (domain ? domain : window.location.hostname) + (port ? ':' + port : '') + (urlpath ? '/' + urlpath : '');
var noteid = urlpath ? window.location.pathname.slice(urlpath.length + 1, window.location.pathname.length).split('/')[1] : window.location.pathname.split('/')[1]; var noteid = urlpath ? window.location.pathname.slice(urlpath.length + 1, window.location.pathname.length).split('/')[1] : window.location.pathname.split('/')[1];
var noteurl = serverurl + '/' + noteid; var noteurl = serverurl + '/' + noteid;
var version = '0.4.4'; var version = '0.4.5';
var checkAuth = false; var checkAuth = false;
var profile = null; var profile = null;

View file

@ -0,0 +1,19 @@
//config
var domain = ''; // domain name
var urlpath = ''; // sub url path, like: www.example.com/<urlpath>
//settings
var debug = false;
var GOOGLE_API_KEY = '';
var GOOGLE_CLIENT_ID = '';
var DROPBOX_APP_KEY = '';
module.exports = {
domain: domain,
urlpath: urlpath,
debug: debug,
GOOGLE_API_KEY: GOOGLE_API_KEY,
GOOGLE_CLIENT_ID: GOOGLE_CLIENT_ID,
DROPBOX_APP_KEY: DROPBOX_APP_KEY
};

View file

@ -35,7 +35,11 @@ var options = {
</div>\ </div>\
</div>\ </div>\
</a>\ </a>\
</li>' </li>',
page: 18,
plugins: [
ListPagination({})
]
}; };
var historyList = new List('history', options); var historyList = new List('history', options);
@ -183,19 +187,32 @@ function parseHistoryCallback(list, notehistory) {
pinned = false; pinned = false;
item._values.pinned = false; item._values.pinned = false;
} }
getHistory(function (notehistory) { checkIfAuth(function () {
for(var i = 0; i < notehistory.length; i++) { postHistoryToServer(id, {
if (notehistory[i].id == id) { pinned: pinned
notehistory[i].pinned = pinned; }, function (err, result) {
break; if (!err) {
} if (pinned)
} $this.addClass('active');
saveHistory(notehistory); else
if (pinned) $this.removeClass('active');
$this.addClass('active'); }
else });
$this.removeClass('active'); }, function () {
}); getHistory(function (notehistory) {
for(var i = 0; i < notehistory.length; i++) {
if (notehistory[i].id == id) {
notehistory[i].pinned = pinned;
break;
}
}
saveHistory(notehistory);
if (pinned)
$this.addClass('active');
else
$this.removeClass('active');
});
})
}); });
buildTagsFilter(filtertags); buildTagsFilter(filtertags);
} }
@ -216,23 +233,40 @@ var clearHistory = false;
var deleteId = null; var deleteId = null;
function deleteHistory() { function deleteHistory() {
if (clearHistory) { checkIfAuth(function () {
saveHistory([]); deleteServerHistory(deleteId, function (err, result) {
historyList.clear(); if (!err) {
checkHistoryList(); if (clearHistory) {
deleteId = null; historyList.clear();
} else { checkHistoryList();
if (!deleteId) return; } else {
getHistory(function (notehistory) { historyList.remove('id', deleteId);
var newnotehistory = removeHistory(deleteId, notehistory); checkHistoryList();
saveHistory(newnotehistory); }
historyList.remove('id', deleteId); }
$('.delete-modal').modal('hide');
deleteId = null;
clearHistory = false;
});
}, function () {
if (clearHistory) {
saveHistory([]);
historyList.clear();
checkHistoryList(); checkHistoryList();
deleteId = null; deleteId = null;
}); } else {
} if (!deleteId) return;
$('.delete-modal').modal('hide'); getHistory(function (notehistory) {
clearHistory = false; var newnotehistory = removeHistory(deleteId, notehistory);
saveHistory(newnotehistory);
historyList.remove('id', deleteId);
checkHistoryList();
deleteId = null;
});
}
$('.delete-modal').modal('hide');
clearHistory = false;
});
} }
$(".ui-delete-modal-confirm").click(function () { $(".ui-delete-modal-confirm").click(function () {

View file

@ -12,6 +12,7 @@ var lastchangeui = {
user: $(".ui-lastchangeuser"), user: $(".ui-lastchangeuser"),
nouser: $(".ui-no-lastchangeuser") nouser: $(".ui-no-lastchangeuser")
} }
var ownerui = $(".ui-owner");
function updateLastChange() { function updateLastChange() {
if (!lastchangeui) return; if (!lastchangeui) return;
@ -46,6 +47,23 @@ function updateLastChangeUser() {
} }
} }
var owner = null;
var ownerprofile = null;
function updateOwner() {
if (ownerui) {
if (owner && ownerprofile && owner !== lastchangeuser) {
var icon = ownerui.children('i');
icon.attr('title', ownerprofile.name).tooltip('fixTitle');
var styleString = 'background-image:url(' + ownerprofile.photo + ')';
if (ownerprofile.photo && icon.attr('style') !== styleString)
icon.attr('style', styleString);
ownerui.show();
} else {
ownerui.hide();
}
}
}
//get title //get title
function getTitle(view) { function getTitle(view) {
var title = ""; var title = "";
@ -426,6 +444,33 @@ function finishView(view) {
height: '400px' height: '400px'
}); });
}); });
//syntax highlighting
view.find("pre.raw").removeClass("raw")
.each(function (key, value) {
var langDiv = $(value).find('code.hljs');
if (langDiv.length > 0) {
var reallang = langDiv[0].className.replace('hljs', '').trim();
var codeDiv = $(value).find('.code');
var code = "";
if (codeDiv.length > 0) code = codeDiv.html();
else code = langDiv.html();
code = md.utils.unescapeAll(code);
if (reallang == "tiddlywiki" || reallang == "mediawiki") {
var result = {
value: Prism.highlight(code, Prism.languages.wiki)
};
} else {
var languages = hljs.listLanguages();
if (languages.indexOf(reallang) == -1) {
var result = hljs.highlightAuto(code);
} else {
var result = hljs.highlight(reallang, code);
}
}
if (codeDiv.length > 0) codeDiv.html(result.value);
else langDiv.html(result.value);
}
});
//render title //render title
document.title = renderTitle(view); document.title = renderTitle(view);
} }
@ -766,19 +811,9 @@ function highlightRender(code, lang) {
} else if (lang == 'mermaid') { } else if (lang == 'mermaid') {
return '<div class="mermaid raw">' + code + '</div>'; return '<div class="mermaid raw">' + code + '</div>';
} }
var reallang = lang.replace(/\=$|\=\d+$|\=\+$/, ''); var result = {
if (reallang == "tiddlywiki" || reallang == "mediawiki") { value: code
var result = { };
value: Prism.highlight(code, Prism.languages.wiki)
};
} else {
var languages = hljs.listLanguages();
if (languages.indexOf(reallang) == -1) {
var result = hljs.highlightAuto(code);
} else {
var result = hljs.highlight(reallang, code);
}
}
var showlinenumbers = /\=$|\=\d+$|\=\+$/.test(lang); var showlinenumbers = /\=$|\=\d+$|\=\+$/.test(lang);
if (showlinenumbers) { if (showlinenumbers) {
var startnumber = 1; var startnumber = 1;
@ -878,7 +913,7 @@ md.renderer.rules.fence = function (tokens, idx, options, env, self) {
return highlighted + '\n'; return highlighted + '\n';
} }
return '<pre><code' + self.renderAttrs(token) + '>' return '<pre class="raw"><code' + self.renderAttrs(token) + '>'
+ highlighted + highlighted
+ '</code></pre>\n'; + '</code></pre>\n';
}; };
@ -1050,5 +1085,6 @@ module.exports = {
renderFilename: renderFilename, renderFilename: renderFilename,
generateToc: generateToc, generateToc: generateToc,
smoothHashScroll: smoothHashScroll, smoothHashScroll: smoothHashScroll,
scrollToHash: scrollToHash scrollToHash: scrollToHash,
owner: owner
}; };

View file

@ -58,7 +58,7 @@ function saveHistoryToStorage(notehistory) {
if (store.enabled) if (store.enabled)
store.set('notehistory', JSON.stringify(notehistory)); store.set('notehistory', JSON.stringify(notehistory));
else else
saveHistoryToStorage(notehistory); saveHistoryToCookie(notehistory);
} }
function saveHistoryToCookie(notehistory) { function saveHistoryToCookie(notehistory) {
@ -107,8 +107,8 @@ function clearDuplicatedHistory(notehistory) {
var id = notehistory[i].id.replace(/\=+$/, ''); var id = notehistory[i].id.replace(/\=+$/, '');
var newId = newnotehistory[j].id.replace(/\=+$/, ''); var newId = newnotehistory[j].id.replace(/\=+$/, '');
if (id == newId || notehistory[i].id == newnotehistory[j].id || !notehistory[i].id || !newnotehistory[j].id) { if (id == newId || notehistory[i].id == newnotehistory[j].id || !notehistory[i].id || !newnotehistory[j].id) {
var time = moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a'); var time = (typeof notehistory[i].time === 'number' ? moment(notehistory[i].time) : moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a'));
var newTime = moment(newnotehistory[j].time, 'MMMM Do YYYY, h:mm:ss a'); var newTime = (typeof newnotehistory[i].time === 'number' ? moment(newnotehistory[i].time) : moment(newnotehistory[i].time, 'MMMM Do YYYY, h:mm:ss a'));
if(time >= newTime) { if(time >= newTime) {
newnotehistory[j] = notehistory[i]; newnotehistory[j] = notehistory[i];
} }
@ -150,7 +150,8 @@ function removeHistory(id, notehistory) {
function writeHistory(view) { function writeHistory(view) {
checkIfAuth( checkIfAuth(
function () { function () {
writeHistoryToServer(view); // no need to do this anymore, this will count from server-side
// writeHistoryToServer(view);
}, },
function () { function () {
writeHistoryToStorage(view); writeHistoryToStorage(view);
@ -176,8 +177,8 @@ function writeHistoryToServer(view) {
var newnotehistory = generateHistory(view, notehistory); var newnotehistory = generateHistory(view, notehistory);
saveHistoryToServer(newnotehistory); saveHistoryToServer(newnotehistory);
}) })
.fail(function () { .fail(function (xhr, status, error) {
writeHistoryToStorage(view); console.error(xhr.responseText);
}); });
} }
@ -257,7 +258,7 @@ function renderHistory(view) {
return { return {
id: id, id: id,
text: title, text: title,
time: moment().format('MMMM Do YYYY, h:mm:ss a'), time: moment().valueOf(),
tags: tags tags: tags
}; };
} }
@ -297,8 +298,8 @@ function getServerHistory(callback) {
callback(data.history); callback(data.history);
} }
}) })
.fail(function () { .fail(function (xhr, status, error) {
getStorageHistory(callback); console.error(xhr.responseText);
}); });
} }
@ -338,8 +339,8 @@ function parseServerToHistory(list, callback) {
parseToHistory(list, data.history, callback); parseToHistory(list, data.history, callback);
} }
}) })
.fail(function () { .fail(function (xhr, status, error) {
parseStorageToHistory(list, callback); console.error(xhr.responseText);
}); });
} }
@ -368,9 +369,10 @@ function parseToHistory(list, notehistory, callback) {
else if (notehistory && notehistory.length > 0) { else if (notehistory && notehistory.length > 0) {
for (var i = 0; i < notehistory.length; i++) { for (var i = 0; i < notehistory.length; i++) {
//parse time to timestamp and fromNow //parse time to timestamp and fromNow
notehistory[i].timestamp = moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a').valueOf(); var timestamp = (typeof notehistory[i].time === 'number' ? moment(notehistory[i].time) : moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a'));
notehistory[i].fromNow = moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a').fromNow(); notehistory[i].timestamp = timestamp.valueOf();
notehistory[i].time = moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a').format('llll'); notehistory[i].fromNow = timestamp.fromNow();
notehistory[i].time = timestamp.format('llll');
if (notehistory[i].id && list.get('id', notehistory[i].id).length == 0) if (notehistory[i].id && list.get('id', notehistory[i].id).length == 0)
list.add(notehistory[i]); list.add(notehistory[i]);
} }
@ -378,6 +380,31 @@ function parseToHistory(list, notehistory, callback) {
callback(list, notehistory); callback(list, notehistory);
} }
function postHistoryToServer(noteId, data, callback) {
$.post(serverurl + '/history/' + noteId, data)
.done(function (result) {
return callback(null, result);
})
.fail(function (xhr, status, error) {
console.error(xhr.responseText);
return callback(error, null);
});
}
function deleteServerHistory(noteId, callback) {
$.ajax({
url: serverurl + '/history' + (noteId ? '/' + noteId : ""),
type: 'DELETE'
})
.done(function (result) {
return callback(null, result);
})
.fail(function (xhr, status, error) {
console.error(xhr.responseText);
return callback(error, null);
});
}
module.exports = { module.exports = {
writeHistory: writeHistory, writeHistory: writeHistory,
parseHistory: parseHistory, parseHistory: parseHistory,
@ -385,5 +412,7 @@ module.exports = {
getHistory: getHistory, getHistory: getHistory,
saveHistory: saveHistory, saveHistory: saveHistory,
removeHistory: removeHistory, removeHistory: removeHistory,
parseStorageToHistory: parseStorageToHistory parseStorageToHistory: parseStorageToHistory,
postHistoryToServer: postHistoryToServer,
deleteServerHistory: deleteServerHistory
} }

View file

@ -62,6 +62,7 @@ var renderTOC = extra.renderTOC;
var renderTitle = extra.renderTitle; var renderTitle = extra.renderTitle;
var renderFilename = extra.renderFilename; var renderFilename = extra.renderFilename;
var scrollToHash = extra.scrollToHash; var scrollToHash = extra.scrollToHash;
var owner = extra.owner;
var historyModule = require('./history'); var historyModule = require('./history');
var writeHistory = historyModule.writeHistory; var writeHistory = historyModule.writeHistory;
@ -72,6 +73,7 @@ var preventXSS = renderer.preventXSS;
var defaultTextHeight = 20; var defaultTextHeight = 20;
var viewportMargin = 20; var viewportMargin = 20;
var mac = CodeMirror.keyMap["default"] == CodeMirror.keyMap.macDefault; var mac = CodeMirror.keyMap["default"] == CodeMirror.keyMap.macDefault;
var defaultEditorMode = 'gfm';
var defaultExtraKeys = { var defaultExtraKeys = {
"F10": function (cm) { "F10": function (cm) {
cm.setOption("fullScreen", !cm.getOption("fullScreen")); cm.setOption("fullScreen", !cm.getOption("fullScreen"));
@ -214,7 +216,7 @@ var cursorMenuThrottle = 50;
var cursorActivityDebounce = 50; var cursorActivityDebounce = 50;
var cursorAnimatePeriod = 100; var cursorAnimatePeriod = 100;
var supportContainers = ['success', 'info', 'warning', 'danger']; var supportContainers = ['success', 'info', 'warning', 'danger'];
var supportCodeModes = ['javascript', 'htmlmixed', 'htmlembedded', 'css', 'xml', 'clike', 'clojure', 'ruby', 'python', 'shell', 'php', 'sql', 'coffeescript', 'yaml', 'jade', 'lua', 'cmake', 'nginx', 'perl', 'sass', 'r', 'dockerfile', 'tiddlywiki', 'mediawiki']; var supportCodeModes = ['javascript', 'htmlmixed', 'htmlembedded', 'css', 'xml', 'clike', 'clojure', 'ruby', 'python', 'shell', 'php', 'sql', 'coffeescript', 'yaml', 'pug', 'lua', 'cmake', 'nginx', 'perl', 'sass', 'r', 'dockerfile', 'tiddlywiki', 'mediawiki'];
var supportCharts = ['sequence', 'flow', 'graphviz', 'mermaid']; var supportCharts = ['sequence', 'flow', 'graphviz', 'mermaid'];
var supportHeaders = [ var supportHeaders = [
{ {
@ -430,8 +432,8 @@ window.fileTypes = {
var textit = document.getElementById("textit"); var textit = document.getElementById("textit");
if (!textit) throw new Error("There was no textit area!"); if (!textit) throw new Error("There was no textit area!");
window.editor = CodeMirror.fromTextArea(textit, { window.editor = CodeMirror.fromTextArea(textit, {
mode: 'gfm', mode: defaultEditorMode,
backdrop: 'gfm', backdrop: defaultEditorMode,
keyMap: "sublime", keyMap: "sublime",
viewportMargin: viewportMargin, viewportMargin: viewportMargin,
styleActiveLine: true, styleActiveLine: true,
@ -440,7 +442,6 @@ window.editor = CodeMirror.fromTextArea(textit, {
showCursorWhenSelecting: true, showCursorWhenSelecting: true,
highlightSelectionMatches: true, highlightSelectionMatches: true,
indentUnit: 4, indentUnit: 4,
indentWithTabs: true,
continueComments: "Enter", continueComments: "Enter",
theme: "one-dark", theme: "one-dark",
inputStyle: "textarea", inputStyle: "textarea",
@ -675,7 +676,7 @@ function setSpellcheck() {
if (cookieSpellcheck === 'true' || cookieSpellcheck === true) { if (cookieSpellcheck === 'true' || cookieSpellcheck === true) {
mode = 'spell-checker'; mode = 'spell-checker';
} else { } else {
mode = 'gfm'; mode = defaultEditorMode;
} }
if (mode && mode !== editor.getOption('mode')) { if (mode && mode !== editor.getOption('mode')) {
editor.setOption('mode', mode); editor.setOption('mode', mode);
@ -685,10 +686,10 @@ function setSpellcheck() {
var spellcheckToggle = statusSpellcheck.find('.ui-spellcheck-toggle'); var spellcheckToggle = statusSpellcheck.find('.ui-spellcheck-toggle');
spellcheckToggle.click(function () { spellcheckToggle.click(function () {
var mode = editor.getOption('mode'); var mode = editor.getOption('mode');
if (mode == "gfm") { if (mode == defaultEditorMode) {
mode = "spell-checker"; mode = "spell-checker";
} else { } else {
mode = "gfm"; mode = defaultEditorMode;
} }
if (mode && mode !== editor.getOption('mode')) { if (mode && mode !== editor.getOption('mode')) {
editor.setOption('mode', mode); editor.setOption('mode', mode);
@ -700,7 +701,7 @@ function setSpellcheck() {
}); });
function checkSpellcheck() { function checkSpellcheck() {
var mode = editor.getOption('mode'); var mode = editor.getOption('mode');
if (mode == "gfm") { if (mode == defaultEditorMode) {
spellcheckToggle.removeClass('active'); spellcheckToggle.removeClass('active');
} else { } else {
spellcheckToggle.addClass('active'); spellcheckToggle.addClass('active');
@ -748,7 +749,18 @@ function updateStatusBar() {
statusCursor.text(cursorText); statusCursor.text(cursorText);
var fileText = ' — ' + editor.lineCount() + ' Lines'; var fileText = ' — ' + editor.lineCount() + ' Lines';
statusFile.text(fileText); statusFile.text(fileText);
statusLength.text('Length ' + editor.getValue().length); var docLength = editor.getValue().length;
statusLength.text('Length ' + docLength);
if (docLength > (docmaxlength * 0.95)) {
statusLength.css('color', 'red');
statusLength.attr('title', 'Your almost reach note max length limit.');
} else if (docLength > (docmaxlength * 0.8)) {
statusLength.css('color', 'orange');
statusLength.attr('title', 'You nearly fill the note, consider to make more pieces.');
} else {
statusLength.css('color', 'white');
statusLength.attr('title', 'You could write up to ' + docmaxlength + ' characters in this note.');
}
} }
//ui vars //ui vars
@ -800,7 +812,8 @@ var ui = {
editable: $(".ui-permission-editable"), editable: $(".ui-permission-editable"),
locked: $(".ui-permission-locked"), locked: $(".ui-permission-locked"),
private: $(".ui-permission-private") private: $(".ui-permission-private")
} },
delete: $(".ui-delete-note")
}, },
toc: { toc: {
toc: $('.ui-toc'), toc: $('.ui-toc'),
@ -989,7 +1002,7 @@ $(window).resize(function () {
}); });
//when page unload //when page unload
$(window).on('unload', function () { $(window).on('unload', function () {
updateHistoryInner(); //updateHistoryInner();
}); });
$(window).on('error', function () { $(window).on('error', function () {
//setNeedRefresh(); //setNeedRefresh();
@ -1817,7 +1830,7 @@ function initRevisionViewer() {
if (revisionViewer) return; if (revisionViewer) return;
var revisionViewerTextArea = document.getElementById("revisionViewer"); var revisionViewerTextArea = document.getElementById("revisionViewer");
revisionViewer = CodeMirror.fromTextArea(revisionViewerTextArea, { revisionViewer = CodeMirror.fromTextArea(revisionViewerTextArea, {
mode: 'gfm', mode: defaultEditorMode,
viewportMargin: viewportMargin, viewportMargin: viewportMargin,
lineNumbers: true, lineNumbers: true,
lineWrapping: true, lineWrapping: true,
@ -2175,6 +2188,13 @@ ui.infobar.permission.locked.click(function () {
ui.infobar.permission.private.click(function () { ui.infobar.permission.private.click(function () {
emitPermission("private"); emitPermission("private");
}); });
// delete note
ui.infobar.delete.click(function () {
$('.delete-modal').modal('show');
});
$('.ui-delete-modal-confirm').click(function () {
socket.emit('delete');
});
function emitPermission(_permission) { function emitPermission(_permission) {
if (_permission != permission) { if (_permission != permission) {
@ -2263,24 +2283,30 @@ socket.on('info', function (data) {
console.error(data); console.error(data);
switch (data.code) { switch (data.code) {
case 403: case 403:
location.href = "./403"; location.href = serverurl + "/403";
break; break;
case 404: case 404:
location.href = "./404"; location.href = serverurl + "/404";
break; break;
case 500: case 500:
location.href = "./500"; location.href = serverurl + "/500";
break; break;
} }
}); });
socket.on('error', function (data) { socket.on('error', function (data) {
console.error(data); console.error(data);
if (data.message && data.message.indexOf('AUTH failed') === 0) if (data.message && data.message.indexOf('AUTH failed') === 0)
location.href = "./403"; location.href = serverurl + "/403";
});
socket.on('delete', function () {
deleteServerHistory(noteid, function (err, data) {
if (!err) location.href = serverurl;
});
}); });
var retryOnDisconnect = false; var retryOnDisconnect = false;
var retryTimer = null; var retryTimer = null;
socket.on('maintenance', function () { socket.on('maintenance', function () {
cmClient.revision = -1;
retryOnDisconnect = true; retryOnDisconnect = true;
}); });
socket.on('disconnect', function (data) { socket.on('disconnect', function (data) {
@ -2310,8 +2336,6 @@ socket.on('connect', function (data) {
personalInfo['id'] = socket.id; personalInfo['id'] = socket.id;
showStatus(statusType.connected); showStatus(statusType.connected);
socket.emit('version'); socket.emit('version');
if (socket.id.indexOf('/') == -1)
socket.id = socket.nsp + '#' + socket.id;
}); });
socket.on('version', function (data) { socket.on('version', function (data) {
if (version != data.version) { if (version != data.version) {
@ -2328,7 +2352,7 @@ var authorship = [];
var authorshipMarks = {}; var authorshipMarks = {};
var authorMarks = {}; // temp variable var authorMarks = {}; // temp variable
var addTextMarkers = []; // temp variable var addTextMarkers = []; // temp variable
function updateLastInfo(data) { function updateInfo(data) {
//console.log(data); //console.log(data);
if (data.hasOwnProperty('createtime') && createtime !== data.createtime) { if (data.hasOwnProperty('createtime') && createtime !== data.createtime) {
createtime = data.createtime; createtime = data.createtime;
@ -2338,10 +2362,16 @@ function updateLastInfo(data) {
lastchangetime = data.updatetime; lastchangetime = data.updatetime;
updateLastChange(); updateLastChange();
} }
if (data.hasOwnProperty('owner') && owner !== data.owner) {
owner = data.owner;
ownerprofile = data.ownerprofile;
updateOwner();
}
if (data.hasOwnProperty('lastchangeuser') && lastchangeuser !== data.lastchangeuser) { if (data.hasOwnProperty('lastchangeuser') && lastchangeuser !== data.lastchangeuser) {
lastchangeuser = data.lastchangeuser; lastchangeuser = data.lastchangeuser;
lastchangeuserprofile = data.lastchangeuserprofile; lastchangeuserprofile = data.lastchangeuserprofile;
updateLastChangeUser(); updateLastChangeUser();
updateOwner();
} }
if (data.hasOwnProperty('authors') && authors !== data.authors) { if (data.hasOwnProperty('authors') && authors !== data.authors) {
authors = data.authors; authors = data.authors;
@ -2391,7 +2421,7 @@ var addStyleRule = (function () {
}()); }());
function updateAuthorshipInner() { function updateAuthorshipInner() {
// ignore when ot not synced yet // ignore when ot not synced yet
if (Object.keys(cmClient.state).length > 0) return; if (cmClient && Object.keys(cmClient.state).length > 0) return;
authorMarks = {}; authorMarks = {};
for (var i = 0; i < authorship.length; i++) { for (var i = 0; i < authorship.length; i++) {
var atom = authorship[i]; var atom = authorship[i];
@ -2556,14 +2586,12 @@ socket.on('check', function (data) {
data = LZString.decompressFromUTF16(data); data = LZString.decompressFromUTF16(data);
data = JSON.parse(data); data = JSON.parse(data);
//console.log(data); //console.log(data);
updateLastInfo(data); updateInfo(data);
}); });
socket.on('permission', function (data) { socket.on('permission', function (data) {
updatePermission(data.permission); updatePermission(data.permission);
}); });
var docmaxlength = null; var docmaxlength = null;
var otk = null;
var owner = null;
var permission = null; var permission = null;
socket.on('refresh', function (data) { socket.on('refresh', function (data) {
data = LZString.decompressFromUTF16(data); data = LZString.decompressFromUTF16(data);
@ -2571,10 +2599,8 @@ socket.on('refresh', function (data) {
//console.log(data); //console.log(data);
docmaxlength = data.docmaxlength; docmaxlength = data.docmaxlength;
editor.setOption("maxLength", docmaxlength); editor.setOption("maxLength", docmaxlength);
otk = data.otk; updateInfo(data);
owner = data.owner;
updatePermission(data.permission); updatePermission(data.permission);
updateLastInfo(data);
if (!loaded) { if (!loaded) {
// auto change mode if no content detected // auto change mode if no content detected
var nocontent = editor.getValue().length <= 0; var nocontent = editor.getValue().length <= 0;
@ -2617,16 +2643,14 @@ socket.on('doc', function (obj) {
obj = LZString.decompressFromUTF16(obj); obj = LZString.decompressFromUTF16(obj);
obj = JSON.parse(obj); obj = JSON.parse(obj);
var body = obj.str; var body = obj.str;
var bodyMismatch = (editor.getValue() != body); var bodyMismatch = editor.getValue() !== body;
var setDoc = !cmClient || (cmClient && cmClient.revision === -1) || obj.force;
saveInfo(); saveInfo();
if (bodyMismatch) { if (setDoc && bodyMismatch) {
if (cmClient) if (cmClient) cmClient.editorAdapter.ignoreNextChange = true;
cmClient.editorAdapter.ignoreNextChange = true; if (body) editor.setValue(body);
if (body) else editor.setValue("");
editor.setValue(body);
else
editor.setValue("");
} }
if (!loaded) { if (!loaded) {
@ -2635,12 +2659,8 @@ socket.on('doc', function (obj) {
ui.content.fadeIn(); ui.content.fadeIn();
} else { } else {
//if current doc is equal to the doc before disconnect //if current doc is equal to the doc before disconnect
if (bodyMismatch) if (setDoc && bodyMismatch) editor.clearHistory();
editor.clearHistory(); else if (lastInfo.history) editor.setHistory(lastInfo.history);
else {
if (lastInfo.history)
editor.setHistory(lastInfo.history);
}
lastInfo.history = null; lastInfo.history = null;
} }
@ -2649,7 +2669,7 @@ socket.on('doc', function (obj) {
obj.revision, obj.clients, obj.revision, obj.clients,
new SocketIOAdapter(socket), new CodeMirrorAdapter(editor) new SocketIOAdapter(socket), new CodeMirrorAdapter(editor)
); );
} else { } else if (setDoc) {
if (bodyMismatch) { if (bodyMismatch) {
cmClient.undoManager.undoStack.length = 0; cmClient.undoManager.undoStack.length = 0;
cmClient.undoManager.redoStack.length = 0; cmClient.undoManager.redoStack.length = 0;
@ -2660,7 +2680,7 @@ socket.on('doc', function (obj) {
cmClient.initializeClients(obj.clients); cmClient.initializeClients(obj.clients);
} }
if (bodyMismatch) { if (setDoc && bodyMismatch) {
isDirty = true; isDirty = true;
updateView(); updateView();
} }

View file

@ -78,7 +78,7 @@ md.renderer.rules.fence = function (tokens, idx, options, env, self) {
if (tokens[idx].map && tokens[idx].level === 0) { if (tokens[idx].map && tokens[idx].level === 0) {
var startline = tokens[idx].map[0] + 1; var startline = tokens[idx].map[0] + 1;
var endline = tokens[idx].map[1]; var endline = tokens[idx].map[1];
return '<pre class="part" data-startline="' + startline + '" data-endline="' + endline + '"><code' + self.renderAttrs(token) + '>' return '<pre class="part raw" data-startline="' + startline + '" data-endline="' + endline + '"><code' + self.renderAttrs(token) + '>'
+ highlighted + highlighted
+ '</code></pre>\n'; + '</code></pre>\n';
} }

View file

@ -1,45 +0,0 @@
//parse Youtube
result.find(".youtube").each(function (key, value) {
if (!$(value).attr('videoid')) return;
setSizebyAttr(this, this);
var icon = '<i class="icon fa fa-youtube-play fa-5x"></i>';
$(this).append(icon);
var videoid = $(value).attr('videoid');
var thumbnail_src = '//img.youtube.com/vi/' + videoid + '/hqdefault.jpg';
$(value).css('background-image', 'url(' + thumbnail_src + ')');
$(this).click(function () {
imgPlayiframe(this, '//www.youtube.com/embed/');
});
});
//parse vimeo
result.find(".vimeo").each(function (key, value) {
if (!$(value).attr('videoid')) return;
setSizebyAttr(this, this);
var icon = '<i class="icon fa fa-vimeo-square fa-5x"></i>';
$(this).append(icon);
var videoid = $(value).attr('videoid');
$.ajax({
type: 'GET',
url: 'http://vimeo.com/api/v2/video/' + videoid + '.json',
jsonp: 'callback',
dataType: 'jsonp',
success: function (data) {
var thumbnail_src = data[0].thumbnail_large;
$(value).css('background-image', 'url(' + thumbnail_src + ')');
}
});
$(this).click(function () {
imgPlayiframe(this, '//player.vimeo.com/video/');
});
});
//todo list
var lis = result[0].getElementsByTagName('li');
for (var i = 0; i < lis.length; i++) {
var html = lis[i].innerHTML;
if (/^\s*\[[x ]\]\s*/.test(html)) {
lis[i].innerHTML = html.replace(/^\s*\[ \]\s*/, '<input type="checkbox" class="task-list-item-checkbox" disabled>')
.replace(/^\s*\[x\]\s*/, '<input type="checkbox" class="task-list-item-checkbox" checked disabled>');
lis[i].setAttribute('class', 'task-list-item');
}
}

View file

@ -103,6 +103,7 @@
self.lineComment(from, to, options); self.lineComment(from, to, options);
return; return;
} }
if (/\bcomment\b/.test(self.getTokenTypeAt(Pos(from.line, 0)))) return
var end = Math.min(to.line, self.lastLine()); var end = Math.min(to.line, self.lastLine());
if (end != from.line && to.ch == 0 && nonWS.test(self.getLine(end))) --end; if (end != from.line && to.ch == 0 && nonWS.test(self.getLine(end))) --end;
@ -140,7 +141,7 @@
var line = self.getLine(i); var line = self.getLine(i);
var found = line.indexOf(lineString); var found = line.indexOf(lineString);
if (found > -1 && !/comment/.test(self.getTokenTypeAt(Pos(i, found + 1)))) found = -1; if (found > -1 && !/comment/.test(self.getTokenTypeAt(Pos(i, found + 1)))) found = -1;
if (found == -1 && (i != end || i == start) && nonWS.test(line)) break lineComment; if (found == -1 && nonWS.test(line)) break lineComment;
if (found > -1 && nonWS.test(line.slice(0, found))) break lineComment; if (found > -1 && nonWS.test(line.slice(0, found))) break lineComment;
lines.push(line); lines.push(line);
} }
@ -162,13 +163,15 @@
var endString = options.blockCommentEnd || mode.blockCommentEnd; var endString = options.blockCommentEnd || mode.blockCommentEnd;
if (!startString || !endString) return false; if (!startString || !endString) return false;
var lead = options.blockCommentLead || mode.blockCommentLead; var lead = options.blockCommentLead || mode.blockCommentLead;
var startLine = self.getLine(start), endLine = end == start ? startLine : self.getLine(end); var startLine = self.getLine(start), open = startLine.indexOf(startString)
var open = startLine.indexOf(startString), close = endLine.lastIndexOf(endString); if (open == -1) return false
var endLine = end == start ? startLine : self.getLine(end)
var close = endLine.indexOf(endString, end == start ? open + startString.length : 0);
if (close == -1 && start != end) { if (close == -1 && start != end) {
endLine = self.getLine(--end); endLine = self.getLine(--end);
close = endLine.lastIndexOf(endString); close = endLine.indexOf(endString);
} }
if (open == -1 || close == -1 || if (close == -1 ||
!/comment/.test(self.getTokenTypeAt(Pos(start, open + 1))) || !/comment/.test(self.getTokenTypeAt(Pos(start, open + 1))) ||
!/comment/.test(self.getTokenTypeAt(Pos(end, close + 1)))) !/comment/.test(self.getTokenTypeAt(Pos(end, close + 1))))
return false; return false;

View file

@ -21,8 +21,8 @@
function Iter(cm, line, ch, range) { function Iter(cm, line, ch, range) {
this.line = line; this.ch = ch; this.line = line; this.ch = ch;
this.cm = cm; this.text = cm.getLine(line); this.cm = cm; this.text = cm.getLine(line);
this.min = range ? range.from : cm.firstLine(); this.min = range ? Math.max(range.from, cm.firstLine()) : cm.firstLine();
this.max = range ? range.to - 1 : cm.lastLine(); this.max = range ? Math.min(range.to - 1, cm.lastLine()) : cm.lastLine();
} }
function tagAt(iter, ch) { function tagAt(iter, ch) {

View file

@ -97,6 +97,15 @@
var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " + var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " +
"if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" "); "if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" ");
function forAllProps(obj, callback) {
if (!Object.getOwnPropertyNames || !Object.getPrototypeOf) {
for (var name in obj) callback(name)
} else {
for (var o = obj; o; o = Object.getPrototypeOf(o))
Object.getOwnPropertyNames(o).forEach(callback)
}
}
function getCompletions(token, context, keywords, options) { function getCompletions(token, context, keywords, options) {
var found = [], start = token.string, global = options && options.globalScope || window; var found = [], start = token.string, global = options && options.globalScope || window;
function maybeAdd(str) { function maybeAdd(str) {
@ -106,7 +115,7 @@
if (typeof obj == "string") forEach(stringProps, maybeAdd); if (typeof obj == "string") forEach(stringProps, maybeAdd);
else if (obj instanceof Array) forEach(arrayProps, maybeAdd); else if (obj instanceof Array) forEach(arrayProps, maybeAdd);
else if (obj instanceof Function) forEach(funcProps, maybeAdd); else if (obj instanceof Function) forEach(funcProps, maybeAdd);
for (var name in obj) maybeAdd(name); forAllProps(obj, maybeAdd)
} }
if (context && context.length) { if (context && context.length) {

View file

@ -45,6 +45,7 @@
this.options[name] = (options && options.hasOwnProperty(name) ? options : defaults)[name] this.options[name] = (options && options.hasOwnProperty(name) ? options : defaults)[name]
this.overlay = this.timeout = null; this.overlay = this.timeout = null;
this.matchesonscroll = null; this.matchesonscroll = null;
this.active = false;
} }
CodeMirror.defineOption("highlightSelectionMatches", false, function(cm, val, old) { CodeMirror.defineOption("highlightSelectionMatches", false, function(cm, val, old) {
@ -53,16 +54,34 @@
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);
cm.off("focus", onFocus)
} }
if (val) { if (val) {
cm.state.matchHighlighter = new State(val); var state = cm.state.matchHighlighter = new State(val);
highlightMatches(cm); if (cm.hasFocus()) {
state.active = true
highlightMatches(cm)
} else {
cm.on("focus", onFocus)
}
cm.on("cursorActivity", cursorActivity); cm.on("cursorActivity", cursorActivity);
} }
}); });
function cursorActivity(cm) { function cursorActivity(cm) {
var state = cm.state.matchHighlighter; var state = cm.state.matchHighlighter;
if (state.active || cm.hasFocus()) scheduleHighlight(cm, state)
}
function onFocus(cm) {
var state = cm.state.matchHighlighter
if (!state.active) {
state.active = true
scheduleHighlight(cm, state)
}
}
function scheduleHighlight(cm, state) {
clearTimeout(state.timeout); clearTimeout(state.timeout);
state.timeout = setTimeout(function() {highlightMatches(cm);}, state.options.delay); state.timeout = setTimeout(function() {highlightMatches(cm);}, state.options.delay);
} }

View file

@ -136,8 +136,11 @@
}) })
}; };
persistentDialog(cm, queryDialog, q, searchNext, function(event, query) { persistentDialog(cm, queryDialog, q, searchNext, function(event, query) {
var cmd = CodeMirror.keyMap[cm.getOption("keyMap")][CodeMirror.keyName(event)]; var keyName = CodeMirror.keyName(event)
if (cmd == "findNext" || cmd == "findPrev") { var cmd = CodeMirror.keyMap[cm.getOption("keyMap")][keyName]
if (!cmd) cmd = cm.getOption('extraKeys')[keyName]
if (cmd == "findNext" || cmd == "findPrev" ||
cmd == "findPersistentNext" || cmd == "findPersistentPrev") {
CodeMirror.e_stop(event); CodeMirror.e_stop(event);
startSearch(cm, getSearchState(cm), query); startSearch(cm, getSearchState(cm), query);
cm.execCommand(cmd); cm.execCommand(cmd);
@ -146,7 +149,7 @@
searchNext(query, event); searchNext(query, event);
} }
}); });
if (immediate) { if (immediate && q) {
startSearch(cm, state, q); startSearch(cm, state, q);
findNext(cm, rev); findNext(cm, rev);
} }

File diff suppressed because one or more lines are too long

View file

@ -30,7 +30,7 @@ addon/fold/foldgutter.js \
addon/fold/markdown-fold.js \ addon/fold/markdown-fold.js \
addon/fold/xml-fold.js \ addon/fold/xml-fold.js \
mode/xml/xml.js \ mode/xml/xml.js \
mode/markdown/markdown.js \ mode/markdown/markdown_math.js \
mode/gfm/gfm.js \ mode/gfm/gfm.js \
mode/javascript/javascript.js \ mode/javascript/javascript.js \
mode/css/css.js \ mode/css/css.js \
@ -45,7 +45,7 @@ mode/php/php.js \
mode/sql/sql.js \ mode/sql/sql.js \
mode/coffeescript/coffeescript.js \ mode/coffeescript/coffeescript.js \
mode/yaml/yaml.js \ mode/yaml/yaml.js \
mode/jade/jade.js \ mode/pug/pug.js \
mode/lua/lua.js \ mode/lua/lua.js \
mode/cmake/cmake.js \ mode/cmake/cmake.js \
mode/nginx/nginx.js \ mode/nginx/nginx.js \

View file

@ -780,8 +780,12 @@
if (lastInsertModeKeyTimer) { window.clearTimeout(lastInsertModeKeyTimer); } if (lastInsertModeKeyTimer) { window.clearTimeout(lastInsertModeKeyTimer); }
if (keysAreChars) { if (keysAreChars) {
var here = cm.getCursor(); var selections = cm.listSelections();
cm.replaceRange('', offsetCursor(here, 0, -(keys.length - 1)), here, '+input'); for (var i = 0; i < selections.length; i++) {
var here = selections[i].head;
cm.replaceRange('', offsetCursor(here, 0, -(keys.length - 1)), here, '+input');
}
vimGlobalState.macroModeState.lastInsertModeChanges.changes.pop();
} }
clearInputState(cm); clearInputState(cm);
return match.command; return match.command;
@ -818,7 +822,7 @@
// TODO: Look into using CodeMirror's multi-key handling. // TODO: Look into using CodeMirror's multi-key handling.
// Return no-op since we are caching the key. Counts as handled, but // Return no-op since we are caching the key. Counts as handled, but
// don't want act on it just yet. // don't want act on it just yet.
return function() {}; return function() { return true; };
} else { } else {
return function() { return function() {
return cm.operation(function() { return cm.operation(function() {
@ -4874,6 +4878,10 @@
if (changeObj.origin == '+input' || changeObj.origin == 'paste' if (changeObj.origin == '+input' || changeObj.origin == 'paste'
|| changeObj.origin === undefined /* only in testing */) { || changeObj.origin === undefined /* only in testing */) {
var text = changeObj.text.join('\n'); var text = changeObj.text.join('\n');
if (lastChange.maybeReset) {
lastChange.changes = [];
lastChange.maybeReset = false;
}
lastChange.changes.push(text); lastChange.changes.push(text);
} }
// Change objects may be chained with next. // Change objects may be chained with next.
@ -4896,7 +4904,7 @@
lastChange.expectCursorActivityForChange = false; lastChange.expectCursorActivityForChange = false;
} else { } else {
// Cursor moved outside the context of an edit. Reset the change. // Cursor moved outside the context of an edit. Reset the change.
lastChange.changes = []; lastChange.maybeReset = true;
} }
} else if (!cm.curOp.isVimOp) { } else if (!cm.curOp.isVimOp) {
handleExternalSelection(cm, vim); handleExternalSelection(cm, vim);
@ -4960,6 +4968,10 @@
var keyName = CodeMirror.keyName(e); var keyName = CodeMirror.keyName(e);
if (!keyName) { return; } if (!keyName) { return; }
function onKeyFound() { function onKeyFound() {
if (lastChange.maybeReset) {
lastChange.changes = [];
lastChange.maybeReset = false;
}
lastChange.changes.push(new InsertModeKey(keyName)); lastChange.changes.push(new InsertModeKey(keyName));
return true; return true;
} }

View file

@ -592,8 +592,12 @@
var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft; var comp = compensateForHScroll(display) - display.scroller.scrollLeft + cm.doc.scrollLeft;
var gutterW = display.gutters.offsetWidth, left = comp + "px"; var gutterW = display.gutters.offsetWidth, left = comp + "px";
for (var i = 0; i < view.length; i++) if (!view[i].hidden) { for (var i = 0; i < view.length; i++) if (!view[i].hidden) {
if (cm.options.fixedGutter && view[i].gutter) if (cm.options.fixedGutter) {
view[i].gutter.style.left = left; if (view[i].gutter)
view[i].gutter.style.left = left;
if (view[i].gutterBackground)
view[i].gutterBackground.style.left = left;
}
var align = view[i].alignable; var align = view[i].alignable;
if (align) for (var j = 0; j < align.length; j++) if (align) for (var j = 0; j < align.length; j++)
align[j].style.left = left; align[j].style.left = left;
@ -1149,7 +1153,7 @@
} }
function handlePaste(e, cm) { function handlePaste(e, cm) {
var pasted = e.clipboardData && e.clipboardData.getData("text/plain"); var pasted = e.clipboardData && e.clipboardData.getData("Text");
if (pasted) { if (pasted) {
e.preventDefault(); e.preventDefault();
if (!cm.isReadOnly() && !cm.options.disableInput) if (!cm.isReadOnly() && !cm.options.disableInput)
@ -1193,10 +1197,10 @@
return {text: text, ranges: ranges}; return {text: text, ranges: ranges};
} }
function disableBrowserMagic(field) { function disableBrowserMagic(field, spellcheck) {
field.setAttribute("autocorrect", "off"); field.setAttribute("autocorrect", "off");
field.setAttribute("autocapitalize", "off"); field.setAttribute("autocapitalize", "off");
field.setAttribute("spellcheck", "false"); field.setAttribute("spellcheck", !!spellcheck);
} }
// TEXTAREA INPUT STYLE // TEXTAREA INPUT STYLE
@ -1576,10 +1580,14 @@
init: function(display) { init: function(display) {
var input = this, cm = input.cm; var input = this, cm = input.cm;
var div = input.div = display.lineDiv; var div = input.div = display.lineDiv;
disableBrowserMagic(div); disableBrowserMagic(div, cm.options.spellcheck);
on(div, "paste", function(e) { on(div, "paste", function(e) {
if (!signalDOMEvent(cm, e)) handlePaste(e, cm); if (signalDOMEvent(cm, e) || handlePaste(e, cm)) return
// IE doesn't fire input events, so we schedule a read for the pasted content in this way
if (ie_version <= 11) setTimeout(operation(cm, function() {
if (!input.pollContent()) regChange(cm);
}), 20)
}) })
on(div, "compositionstart", function(e) { on(div, "compositionstart", function(e) {
@ -1639,23 +1647,27 @@
}); });
} }
} }
// iOS exposes the clipboard API, but seems to discard content inserted into it if (e.clipboardData) {
if (e.clipboardData && !ios) {
e.preventDefault();
e.clipboardData.clearData(); e.clipboardData.clearData();
e.clipboardData.setData("text/plain", lastCopied.text.join("\n")); var content = lastCopied.text.join("\n")
} else { // iOS exposes the clipboard API, but seems to discard content inserted into it
// Old-fashioned briefly-focus-a-textarea hack e.clipboardData.setData("Text", content);
var kludge = hiddenTextarea(), te = kludge.firstChild; if (e.clipboardData.getData("Text") == content) {
cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild); e.preventDefault();
te.value = lastCopied.text.join("\n"); return
var hadFocus = document.activeElement; }
selectInput(te);
setTimeout(function() {
cm.display.lineSpace.removeChild(kludge);
hadFocus.focus();
}, 50);
} }
// Old-fashioned briefly-focus-a-textarea hack
var kludge = hiddenTextarea(), te = kludge.firstChild;
cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild);
te.value = lastCopied.text.join("\n");
var hadFocus = document.activeElement;
selectInput(te);
setTimeout(function() {
cm.display.lineSpace.removeChild(kludge);
hadFocus.focus();
if (hadFocus == div) input.showPrimarySelection()
}, 50);
} }
on(div, "copy", onCopyCut); on(div, "copy", onCopyCut);
on(div, "cut", onCopyCut); on(div, "cut", onCopyCut);
@ -1963,7 +1975,7 @@
if (found) if (found)
return badPos(Pos(found.line, found.ch + dist), bad); return badPos(Pos(found.line, found.ch + dist), bad);
else else
dist += after.textContent.length; dist += before.textContent.length;
} }
} }
@ -3533,8 +3545,8 @@
on(inp, "keyup", function(e) { onKeyUp.call(cm, e); }); on(inp, "keyup", function(e) { onKeyUp.call(cm, e); });
on(inp, "keydown", operation(cm, onKeyDown)); on(inp, "keydown", operation(cm, onKeyDown));
on(inp, "keypress", operation(cm, onKeyPress)); on(inp, "keypress", operation(cm, onKeyPress));
on(inp, "focus", bind(onFocus, cm)); on(inp, "focus", function (e) { onFocus(cm, e); });
on(inp, "blur", bind(onBlur, cm)); on(inp, "blur", function (e) { onBlur(cm, e); });
} }
function dragDropChanged(cm, value, old) { function dragDropChanged(cm, value, old) {
@ -4262,12 +4274,12 @@
}, 100); }, 100);
} }
function onFocus(cm) { function onFocus(cm, e) {
if (cm.state.delayingBlurEvent) cm.state.delayingBlurEvent = false; if (cm.state.delayingBlurEvent) cm.state.delayingBlurEvent = false;
if (cm.options.readOnly == "nocursor") return; if (cm.options.readOnly == "nocursor") return;
if (!cm.state.focused) { if (!cm.state.focused) {
signal(cm, "focus", cm); signal(cm, "focus", cm, e);
cm.state.focused = true; cm.state.focused = true;
addClass(cm.display.wrapper, "CodeMirror-focused"); addClass(cm.display.wrapper, "CodeMirror-focused");
// This test prevents this from firing when a context // This test prevents this from firing when a context
@ -4281,11 +4293,11 @@
} }
restartBlink(cm); restartBlink(cm);
} }
function onBlur(cm) { function onBlur(cm, e) {
if (cm.state.delayingBlurEvent) return; if (cm.state.delayingBlurEvent) return;
if (cm.state.focused) { if (cm.state.focused) {
signal(cm, "blur", cm); signal(cm, "blur", cm, e);
cm.state.focused = false; cm.state.focused = false;
rmClass(cm.display.wrapper, "CodeMirror-focused"); rmClass(cm.display.wrapper, "CodeMirror-focused");
} }
@ -4907,7 +4919,8 @@
var doc = cm.doc, x = pos.left, y; var doc = cm.doc, x = pos.left, y;
if (unit == "page") { if (unit == "page") {
var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight); var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight);
y = pos.top + dir * (pageSize - (dir < 0 ? 1.5 : .5) * textHeight(cm.display)); var moveAmount = Math.max(pageSize - .5 * textHeight(cm.display), 3);
y = (dir > 0 ? pos.bottom : pos.top) + dir * moveAmount;
} else if (unit == "line") { } else if (unit == "line") {
y = dir > 0 ? pos.bottom + 3 : pos.top - 3; y = dir > 0 ? pos.bottom + 3 : pos.top - 3;
} }
@ -4960,7 +4973,10 @@
addOverlay: methodOp(function(spec, options) { addOverlay: methodOp(function(spec, options) {
var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec); var mode = spec.token ? spec : CodeMirror.getMode(this.options, spec);
if (mode.startState) throw new Error("Overlays may not be stateful."); if (mode.startState) throw new Error("Overlays may not be stateful.");
this.state.overlays.push({mode: mode, modeSpec: spec, opaque: options && options.opaque}); insertSorted(this.state.overlays,
{mode: mode, modeSpec: spec, opaque: options && options.opaque,
priority: (options && options.priority) || 0},
function(overlay) { return overlay.priority })
this.state.modeGen++; this.state.modeGen++;
regChange(this); regChange(this);
}), }),
@ -5432,6 +5448,9 @@
option("inputStyle", mobile ? "contenteditable" : "textarea", function() { option("inputStyle", mobile ? "contenteditable" : "textarea", function() {
throw new Error("inputStyle can not (yet) be changed in a running editor"); // FIXME throw new Error("inputStyle can not (yet) be changed in a running editor"); // FIXME
}, true); }, true);
option("spellcheck", false, function(cm, val) {
cm.getInputField().spellcheck = val
}, true);
option("rtlMoveVisually", !windows); option("rtlMoveVisually", !windows);
option("wholeLineUpdateBefore", true); option("wholeLineUpdateBefore", true);
@ -5541,6 +5560,8 @@
spec.name = found.name; spec.name = found.name;
} else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) { } else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) {
return CodeMirror.resolveMode("application/xml"); return CodeMirror.resolveMode("application/xml");
} else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+json$/.test(spec)) {
return CodeMirror.resolveMode("application/json");
} }
if (typeof spec == "string") return {name: spec}; if (typeof spec == "string") return {name: spec};
else return spec || {name: "null"}; else return spec || {name: "null"};
@ -6852,7 +6873,7 @@
} }
if (!flattenSpans || curStyle != style) { if (!flattenSpans || curStyle != style) {
while (curStart < stream.start) { while (curStart < stream.start) {
curStart = Math.min(stream.start, curStart + 50000); curStart = Math.min(stream.start, curStart + 5000);
f(curStart, curStyle); f(curStart, curStyle);
} }
curStyle = style; curStyle = style;
@ -6860,8 +6881,10 @@
stream.start = stream.pos; stream.start = stream.pos;
} }
while (curStart < stream.pos) { while (curStart < stream.pos) {
// Webkit seems to refuse to render text nodes longer than 57444 characters // Webkit seems to refuse to render text nodes longer than 57444
var pos = Math.min(stream.pos, curStart + 50000); // characters, and returns inaccurate measurements in nodes
// starting around 5000 chars.
var pos = Math.min(stream.pos, curStart + 5000);
f(pos, curStyle); f(pos, curStyle);
curStart = pos; curStart = pos;
} }
@ -7970,7 +7993,7 @@
} }
// Register a change in the history. Merges changes that are within // Register a change in the history. Merges changes that are within
// a single operation, ore are close together with an origin that // a single operation, or are close together with an origin that
// allows merging (starting with "+") into a single event. // allows merging (starting with "+") into a single event.
function addChangeToHistory(doc, change, selAfter, opId) { function addChangeToHistory(doc, change, selAfter, opId) {
if(change.origin == "ignoreHistory") return; if(change.origin == "ignoreHistory") return;
@ -8374,6 +8397,12 @@
return out; return out;
} }
function insertSorted(array, value, score) {
var pos = 0, priority = score(value)
while (pos < array.length && score(array[pos]) <= priority) pos++
array.splice(pos, 0, value)
}
function nothing() {} function nothing() {}
function createObj(base, props) { function createObj(base, props) {
@ -8942,7 +8971,7 @@
// THE END // THE END
CodeMirror.version = "5.17.1"; CodeMirror.version = "5.19.0";
return CodeMirror; return CodeMirror;
}); });

View file

@ -62,7 +62,7 @@
var curPunc; var curPunc;
var funcs = wordRegexp(["abs", "acos", "allShortestPaths", "asin", "atan", "atan2", "avg", "ceil", "coalesce", "collect", "cos", "cot", "count", "degrees", "e", "endnode", "exp", "extract", "filter", "floor", "haversin", "head", "id", "keys", "labels", "last", "left", "length", "log", "log10", "lower", "ltrim", "max", "min", "node", "nodes", "percentileCont", "percentileDisc", "pi", "radians", "rand", "range", "reduce", "rel", "relationship", "relationships", "replace", "reverse", "right", "round", "rtrim", "shortestPath", "sign", "sin", "size", "split", "sqrt", "startnode", "stdev", "stdevp", "str", "substring", "sum", "tail", "tan", "timestamp", "toFloat", "toInt", "toString", "trim", "type", "upper"]); var funcs = wordRegexp(["abs", "acos", "allShortestPaths", "asin", "atan", "atan2", "avg", "ceil", "coalesce", "collect", "cos", "cot", "count", "degrees", "e", "endnode", "exp", "extract", "filter", "floor", "haversin", "head", "id", "keys", "labels", "last", "left", "length", "log", "log10", "lower", "ltrim", "max", "min", "node", "nodes", "percentileCont", "percentileDisc", "pi", "radians", "rand", "range", "reduce", "rel", "relationship", "relationships", "replace", "reverse", "right", "round", "rtrim", "shortestPath", "sign", "sin", "size", "split", "sqrt", "startnode", "stdev", "stdevp", "str", "substring", "sum", "tail", "tan", "timestamp", "toFloat", "toInt", "toString", "trim", "type", "upper"]);
var preds = wordRegexp(["all", "and", "any", "contains", "exists", "has", "in", "none", "not", "or", "single", "xor"]); var preds = wordRegexp(["all", "and", "any", "contains", "exists", "has", "in", "none", "not", "or", "single", "xor"]);
var keywords = wordRegexp(["as", "asc", "ascending", "assert", "by", "case", "commit", "constraint", "create", "csv", "cypher", "delete", "desc", "descending", "detach", "distinct", "drop", "else", "end", "ends", "explain", "false", "fieldterminator", "foreach", "from", "headers", "in", "index", "is", "join", "limit", "load", "match", "merge", "null", "on", "optional", "order", "periodic", "profile", "remove", "return", "scan", "set", "skip", "start", "starts", "then", "true", "union", "unique", "unwind", "using", "when", "where", "with"]); var keywords = wordRegexp(["as", "asc", "ascending", "assert", "by", "case", "commit", "constraint", "create", "csv", "cypher", "delete", "desc", "descending", "detach", "distinct", "drop", "else", "end", "ends", "explain", "false", "fieldterminator", "foreach", "from", "headers", "in", "index", "is", "join", "limit", "load", "match", "merge", "null", "on", "optional", "order", "periodic", "profile", "remove", "return", "scan", "set", "skip", "start", "starts", "then", "true", "union", "unique", "unwind", "using", "when", "where", "with", "call", "yield"]);
var operatorChars = /[*+\-<>=&|~%^]/; var operatorChars = /[*+\-<>=&|~%^]/;
return { return {

View file

@ -433,15 +433,16 @@ CodeMirror.defineMode("erlang", function(cmCfg) {
} }
function maybe_drop_post(s) { function maybe_drop_post(s) {
if (!s.length) return s
var last = s.length-1; var last = s.length-1;
if (s[last].type === "dot") { if (s[last].type === "dot") {
return []; return [];
} }
if (s[last].type === "fun" && s[last-1].token === "fun") { if (last > 1 && s[last].type === "fun" && s[last-1].token === "fun") {
return s.slice(0,last-1); return s.slice(0,last-1);
} }
switch (s[s.length-1].token) { switch (s[last].token) {
case "}": return d(s,{g:["{"]}); case "}": return d(s,{g:["{"]});
case "]": return d(s,{i:["["]}); case "]": return d(s,{i:["["]});
case ")": return d(s,{i:["("]}); case ")": return d(s,{i:["("]});

View file

@ -46,7 +46,7 @@
function getAttrValue(text, attr) { function getAttrValue(text, attr) {
var match = text.match(getAttrRegexp(attr)) var match = text.match(getAttrRegexp(attr))
return match ? match[2] : "" return match ? /^\s*(.*?)\s*$/.exec(match[2])[1] : ""
} }
function getTagRegexp(tagName, anchored) { function getTagRegexp(tagName, anchored) {

View file

@ -76,7 +76,6 @@ option.</p>
<li><a href="http/index.html">HTTP</a></li> <li><a href="http/index.html">HTTP</a></li>
<li><a href="idl/index.html">IDL</a></li> <li><a href="idl/index.html">IDL</a></li>
<li><a href="clike/index.html">Java</a></li> <li><a href="clike/index.html">Java</a></li>
<li><a href="jade/index.html">Jade</a></li>
<li><a href="javascript/index.html">JavaScript</a> (<a href="jsx/index.html">JSX</a>)</li> <li><a href="javascript/index.html">JavaScript</a> (<a href="jsx/index.html">JSX</a>)</li>
<li><a href="jinja2/index.html">Jinja2</a></li> <li><a href="jinja2/index.html">Jinja2</a></li>
<li><a href="julia/index.html">Julia</a></li> <li><a href="julia/index.html">Julia</a></li>
@ -107,6 +106,7 @@ option.</p>
<li><a href="powershell/index.html">PowerShell</a></li> <li><a href="powershell/index.html">PowerShell</a></li>
<li><a href="properties/index.html">Properties files</a></li> <li><a href="properties/index.html">Properties files</a></li>
<li><a href="protobuf/index.html">ProtoBuf</a></li> <li><a href="protobuf/index.html">ProtoBuf</a></li>
<li><a href="pug/index.html">Pug</a></li>
<li><a href="puppet/index.html">Puppet</a></li> <li><a href="puppet/index.html">Puppet</a></li>
<li><a href="python/index.html">Python</a></li> <li><a href="python/index.html">Python</a></li>
<li><a href="q/index.html">Q</a></li> <li><a href="q/index.html">Q</a></li>

View file

@ -1,8 +1,6 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others // CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE // Distributed under an MIT license: http://codemirror.net/LICENSE
// TODO actually recognize syntax of TypeScript constructs
(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"));
@ -56,6 +54,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
"namespace": C, "namespace": C,
"module": kw("module"), "module": kw("module"),
"enum": kw("module"), "enum": kw("module"),
"type": kw("type"),
// scope modifiers // scope modifiers
"public": kw("modifier"), "public": kw("modifier"),
@ -345,19 +344,19 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
function statement(type, value) { function statement(type, value) {
if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex); if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex);
if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex); if (type == "keyword a") return cont(pushlex("form"), parenExpr, statement, poplex);
if (type == "keyword b") return cont(pushlex("form"), statement, poplex); if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
if (type == "{") return cont(pushlex("}"), block, poplex); if (type == "{") return cont(pushlex("}"), block, poplex);
if (type == ";") return cont(); if (type == ";") return cont();
if (type == "if") { if (type == "if") {
if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex) if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
cx.state.cc.pop()(); cx.state.cc.pop()();
return cont(pushlex("form"), expression, statement, poplex, maybeelse); return cont(pushlex("form"), parenExpr, statement, poplex, maybeelse);
} }
if (type == "function") return cont(functiondef); if (type == "function") return cont(functiondef);
if (type == "for") return cont(pushlex("form"), forspec, statement, poplex); if (type == "for") return cont(pushlex("form"), forspec, statement, poplex);
if (type == "variable") return cont(pushlex("stat"), maybelabel); if (type == "variable") return cont(pushlex("stat"), maybelabel);
if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), if (type == "switch") return cont(pushlex("form"), parenExpr, pushlex("}", "switch"), expect("{"),
block, poplex, poplex); block, poplex, poplex);
if (type == "case") return cont(expression, expect(":")); if (type == "case") return cont(expression, expect(":"));
if (type == "default") return cont(expect(":")); if (type == "default") return cont(expect(":"));
@ -367,6 +366,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == "export") return cont(pushlex("stat"), afterExport, poplex); if (type == "export") return cont(pushlex("stat"), afterExport, poplex);
if (type == "import") return cont(pushlex("stat"), afterImport, poplex); if (type == "import") return cont(pushlex("stat"), afterImport, poplex);
if (type == "module") return cont(pushlex("form"), pattern, pushlex("}"), expect("{"), block, poplex, poplex) if (type == "module") return cont(pushlex("form"), pattern, pushlex("}"), expect("{"), block, poplex, poplex)
if (type == "type") return cont(typeexpr, expect("operator"), typeexpr, expect(";"));
if (type == "async") return cont(statement) if (type == "async") return cont(statement)
return pass(pushlex("stat"), expression, expect(";"), poplex); return pass(pushlex("stat"), expression, expect(";"), poplex);
} }
@ -376,6 +376,10 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
function expressionNoComma(type) { function expressionNoComma(type) {
return expressionInner(type, true); return expressionInner(type, true);
} }
function parenExpr(type) {
if (type != "(") return pass()
return cont(pushlex(")"), expression, expect(")"), poplex)
}
function expressionInner(type, noComma) { function expressionInner(type, noComma) {
if (cx.state.fatArrowAt == cx.stream.start) { if (cx.state.fatArrowAt == cx.stream.start) {
var body = noComma ? arrowBodyNoComma : arrowBody; var body = noComma ? arrowBodyNoComma : arrowBody;
@ -463,8 +467,10 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == "variable") {cx.marked = "property"; return cont();} if (type == "variable") {cx.marked = "property"; return cont();}
} }
function objprop(type, value) { function objprop(type, value) {
if (type == "async") return cont(objprop); if (type == "async") {
if (type == "variable" || cx.style == "keyword") { cx.marked = "property";
return cont(objprop);
} else if (type == "variable" || cx.style == "keyword") {
cx.marked = "property"; cx.marked = "property";
if (value == "get" || value == "set") return cont(getterSetter); if (value == "get" || value == "set") return cont(getterSetter);
return cont(afterprop); return cont(afterprop);
@ -479,6 +485,8 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
return cont(expression, expect("]"), afterprop); return cont(expression, expect("]"), afterprop);
} else if (type == "spread") { } else if (type == "spread") {
return cont(expression); return cont(expression);
} else if (type == ":") {
return pass(afterprop)
} }
} }
function getterSetter(type) { function getterSetter(type) {
@ -517,14 +525,34 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == "}") return cont(); if (type == "}") return cont();
return pass(statement, block); return pass(statement, block);
} }
function maybetype(type) { function maybetype(type, value) {
if (isTS && type == ":") return cont(typeexpr); if (isTS) {
if (type == ":") return cont(typeexpr);
if (value == "?") return cont(maybetype);
}
} }
function maybedefault(_, value) { function maybedefault(_, value) {
if (value == "=") return cont(expressionNoComma); if (value == "=") return cont(expressionNoComma);
} }
function typeexpr(type) { function typeexpr(type) {
if (type == "variable") {cx.marked = "variable-3"; return cont(afterType);} if (type == "variable") {cx.marked = "variable-3"; return cont(afterType);}
if (type == "{") return cont(commasep(typeprop, "}"))
if (type == "(") return cont(commasep(typearg, ")"), maybeReturnType)
}
function maybeReturnType(type) {
if (type == "=>") return cont(typeexpr)
}
function typeprop(type) {
if (type == "variable" || cx.style == "keyword") {
cx.marked = "property"
return cont(typeprop)
} else if (type == ":") {
return cont(typeexpr)
}
}
function typearg(type) {
if (type == "variable") return cont(typearg)
else if (type == ":") return cont(typeexpr)
} }
function afterType(type, value) { function afterType(type, value) {
if (value == "<") return cont(commasep(typeexpr, ">"), afterType) if (value == "<") return cont(commasep(typeexpr, ">"), afterType)
@ -593,18 +621,19 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == "variable") {register(value); return cont(classNameAfter);} if (type == "variable") {register(value); return cont(classNameAfter);}
} }
function classNameAfter(type, value) { function classNameAfter(type, value) {
if (value == "extends") return cont(expression, classNameAfter); if (value == "extends") return cont(isTS ? typeexpr : expression, classNameAfter);
if (type == "{") return cont(pushlex("}"), classBody, poplex); if (type == "{") return cont(pushlex("}"), classBody, poplex);
} }
function classBody(type, value) { function classBody(type, value) {
if (type == "variable" || cx.style == "keyword") { if (type == "variable" || cx.style == "keyword") {
if (value == "static") { if ((value == "static" || value == "get" || value == "set" ||
(isTS && (value == "public" || value == "private" || value == "protected"))) &&
cx.stream.match(/^\s+[\w$\xa1-\uffff]/, false)) {
cx.marked = "keyword"; cx.marked = "keyword";
return cont(classBody); return cont(classBody);
} }
cx.marked = "property"; cx.marked = "property";
if (value == "get" || value == "set") return cont(classGetterSetter, functiondef, classBody); return cont(isTS ? classfield : functiondef, classBody);
return cont(functiondef, classBody);
} }
if (value == "*") { if (value == "*") {
cx.marked = "keyword"; cx.marked = "keyword";
@ -613,10 +642,9 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
if (type == ";") return cont(classBody); if (type == ";") return cont(classBody);
if (type == "}") return cont(); if (type == "}") return cont();
} }
function classGetterSetter(type) { function classfield(type) {
if (type != "variable") return pass(); if (type == ":") return cont(typeexpr)
cx.marked = "property"; return pass(functiondef)
return cont();
} }
function afterExport(_type, value) { function afterExport(_type, value) {
if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); } if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
@ -685,14 +713,18 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
indent: function(state, textAfter) { indent: function(state, textAfter) {
if (state.tokenize == tokenComment) return CodeMirror.Pass; if (state.tokenize == tokenComment) return CodeMirror.Pass;
if (state.tokenize != tokenBase) return 0; if (state.tokenize != tokenBase) return 0;
var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical; var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, top
// Kludge to prevent 'maybelse' from blocking lexical scope pops // Kludge to prevent 'maybelse' from blocking lexical scope pops
if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) { if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {
var c = state.cc[i]; var c = state.cc[i];
if (c == poplex) lexical = lexical.prev; if (c == poplex) lexical = lexical.prev;
else if (c != maybeelse) break; else if (c != maybeelse) break;
} }
if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev; while ((lexical.type == "stat" || lexical.type == "form") &&
(firstChar == "}" || ((top = state.cc[state.cc.length - 1]) &&
(top == maybeoperatorComma || top == maybeoperatorNoComma) &&
!/^[,\.=+\-*:?[\(]/.test(textAfter))))
lexical = lexical.prev;
if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat") if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
lexical = lexical.prev; lexical = lexical.prev;
var type = lexical.type, closing = firstChar == type; var type = lexical.type, closing = firstChar == type;

View file

@ -31,7 +31,7 @@
MT("class", MT("class",
"[keyword class] [def Point] [keyword extends] [variable SuperThing] {", "[keyword class] [def Point] [keyword extends] [variable SuperThing] {",
" [property get] [property prop]() { [keyword return] [number 24]; }", " [keyword get] [property prop]() { [keyword return] [number 24]; }",
" [property constructor]([def x], [def y]) {", " [property constructor]([def x], [def y]) {",
" [keyword super]([string 'something']);", " [keyword super]([string 'something']);",
" [keyword this].[property x] [operator =] [variable-2 x];", " [keyword this].[property x] [operator =] [variable-2 x];",
@ -140,6 +140,19 @@
" [number 1];", " [number 1];",
"[number 2];"); "[number 2];");
MT("indent_semicolonless_if",
"[keyword function] [def foo]() {",
" [keyword if] ([variable x])",
" [variable foo]()",
"}")
MT("indent_semicolonless_if_with_statement",
"[keyword function] [def foo]() {",
" [keyword if] ([variable x])",
" [variable foo]()",
" [variable bar]()",
"}")
MT("multilinestring", MT("multilinestring",
"[keyword var] [def x] [operator =] [string 'foo\\]", "[keyword var] [def x] [operator =] [string 'foo\\]",
"[string bar'];"); "[string bar'];");
@ -167,6 +180,23 @@
" }", " }",
"}"); "}");
var ts_mode = CodeMirror.getMode({indentUnit: 2}, "application/typescript")
function TS(name) {
test.mode(name, ts_mode, Array.prototype.slice.call(arguments, 1))
}
TS("extend_type",
"[keyword class] [def Foo] [keyword extends] [variable-3 Some][operator <][variable-3 Type][operator >] {}")
TS("arrow_type",
"[keyword let] [def x]: ([variable arg]: [variable-3 Type]) [operator =>] [variable-3 ReturnType]")
TS("typescript_class",
"[keyword class] [def Foo] {",
" [keyword public] [keyword static] [property main]() {}",
" [keyword private] [property _foo]: [variable-3 string];",
"}")
var jsonld_mode = CodeMirror.getMode( var jsonld_mode = CodeMirror.getMode(
{indentUnit: 2}, {indentUnit: 2},
{name: "javascript", jsonld: true} {name: "javascript", jsonld: true}

View file

@ -84,6 +84,6 @@ var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
<p>JSX Mode for <a href="http://facebook.github.io/react">React</a>'s <p>JSX Mode for <a href="http://facebook.github.io/react">React</a>'s
JavaScript syntax extension.</p> JavaScript syntax extension.</p>
<p><strong>MIME types defined:</strong> <code>text/jsx</code>.</p> <p><strong>MIME types defined:</strong> <code>text/jsx</code>, <code>text/typescript-jsx</code>.</p>
</article> </article>

View file

@ -144,4 +144,5 @@
}, "xml", "javascript") }, "xml", "javascript")
CodeMirror.defineMIME("text/jsx", "jsx") CodeMirror.defineMIME("text/jsx", "jsx")
CodeMirror.defineMIME("text/typescript-jsx", {name: "jsx", base: {name: "javascript", typescript: true}})
}); });

View file

@ -50,7 +50,7 @@
startState: function(){ startState: function(){
return { return {
next: 'start', next: 'start',
lastToken: null lastToken: {style: null, indent: 0, content: ""}
}; };
}, },
token: function(stream, state){ token: function(stream, state){

View file

@ -0,0 +1,864 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("../xml/xml"), require("../meta"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "../xml/xml", "../meta"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
var htmlMode = CodeMirror.getMode(cmCfg, "text/html");
var htmlModeMissing = htmlMode.name == "null"
function getMode(name) {
if (CodeMirror.findModeByName) {
var found = CodeMirror.findModeByName(name);
if (found) name = found.mime || found.mimes[0];
}
var mode = CodeMirror.getMode(cmCfg, name);
return mode.name == "null" ? null : mode;
}
// Should characters that affect highlighting be highlighted separate?
// Does not include characters that will be output (such as `1.` and `-` for lists)
if (modeCfg.highlightFormatting === undefined)
modeCfg.highlightFormatting = false;
// Maximum number of nested blockquotes. Set to 0 for infinite nesting.
// Excess `>` will emit `error` token.
if (modeCfg.maxBlockquoteDepth === undefined)
modeCfg.maxBlockquoteDepth = 0;
// Should underscores in words open/close em/strong?
if (modeCfg.underscoresBreakWords === undefined)
modeCfg.underscoresBreakWords = true;
// Use `fencedCodeBlocks` to configure fenced code blocks. false to
// disable, string to specify a precise regexp that the fence should
// match, and true to allow three or more backticks or tildes (as
// per CommonMark).
// Turn on task lists? ("- [ ] " and "- [x] ")
if (modeCfg.taskLists === undefined) modeCfg.taskLists = false;
// Turn on strikethrough syntax
if (modeCfg.strikethrough === undefined)
modeCfg.strikethrough = false;
// Allow token types to be overridden by user-provided token types.
if (modeCfg.tokenTypeOverrides === undefined)
modeCfg.tokenTypeOverrides = {};
var tokenTypes = {
header: "header",
code: "comment",
math: "math",
quote: "quote",
list1: "variable-2",
list2: "variable-3",
list3: "keyword",
hr: "hr",
image: "image",
imageAltText: "image-alt-text",
imageMarker: "image-marker",
formatting: "formatting",
linkInline: "link",
linkEmail: "link",
linkText: "link",
linkHref: "string",
em: "em",
strong: "strong",
strikethrough: "strikethrough"
};
for (var tokenType in tokenTypes) {
if (tokenTypes.hasOwnProperty(tokenType) && modeCfg.tokenTypeOverrides[tokenType]) {
tokenTypes[tokenType] = modeCfg.tokenTypeOverrides[tokenType];
}
}
var hrRE = /^([*\-_])(?:\s*\1){2,}\s*$/
, ulRE = /^[*\-+]\s+/
, olRE = /^[0-9]+([.)])\s+/
, taskListRE = /^\[(x| )\](?=\s)/ // Must follow ulRE or olRE
, atxHeaderRE = modeCfg.allowAtxHeaderWithoutSpace ? /^(#+)/ : /^(#+)(?: |$)/
, setextHeaderRE = /^ *(?:\={1,}|-{1,})\s*$/
, textRE = /^[^#!\[\]*_\\<>\$` "'(~]+/
, fencedCodeRE = new RegExp("^(" + (modeCfg.fencedCodeBlocks === true ? "~~~+|```+" : modeCfg.fencedCodeBlocks) +
")[ \\t]*([\\w+#\-]*)")
, fencedMathRE = new RegExp("^(\$\$)[ \\t]*([\\w+#\-]*)");
function switchInline(stream, state, f) {
state.f = state.inline = f;
return f(stream, state);
}
function switchBlock(stream, state, f) {
state.f = state.block = f;
return f(stream, state);
}
function lineIsEmpty(line) {
return !line || !/\S/.test(line.string)
}
// Blocks
function blankLine(state) {
// Reset linkTitle state
state.linkTitle = false;
// Reset EM state
state.em = false;
// Reset STRONG state
state.strong = false;
// Reset strikethrough state
state.strikethrough = false;
// Reset state.quote
state.quote = 0;
// Reset state.indentedCode
state.indentedCode = false;
if (htmlModeMissing && state.f == htmlBlock) {
state.f = inlineNormal;
state.block = blockNormal;
}
// Reset state.trailingSpace
state.trailingSpace = 0;
state.trailingSpaceNewLine = false;
// Mark this line as blank
state.prevLine = state.thisLine
state.thisLine = null
return null;
}
function blockNormal(stream, state) {
var sol = stream.sol();
var prevLineIsList = state.list !== false,
prevLineIsIndentedCode = state.indentedCode;
state.indentedCode = false;
if (prevLineIsList) {
if (state.indentationDiff >= 0) { // Continued list
if (state.indentationDiff < 4) { // Only adjust indentation if *not* a code block
state.indentation -= state.indentationDiff;
}
state.list = null;
} else if (state.indentation > 0) {
state.list = null;
} else { // No longer a list
state.list = false;
}
}
var match = null;
if (state.indentationDiff >= 4) {
stream.skipToEnd();
if (prevLineIsIndentedCode || lineIsEmpty(state.prevLine)) {
state.indentation -= 4;
state.indentedCode = true;
return tokenTypes.code;
} else {
return null;
}
} else if (stream.eatSpace()) {
return null;
} else if ((match = stream.match(atxHeaderRE)) && match[1].length <= 6) {
state.header = match[1].length;
if (modeCfg.highlightFormatting) state.formatting = "header";
state.f = state.inline;
return getType(state);
} else if (!lineIsEmpty(state.prevLine) && !state.quote && !prevLineIsList &&
!prevLineIsIndentedCode && (match = stream.match(setextHeaderRE))) {
state.header = match[0].charAt(0) == '=' ? 1 : 2;
if (modeCfg.highlightFormatting) state.formatting = "header";
state.f = state.inline;
return getType(state);
} else if (stream.eat('>')) {
state.quote = sol ? 1 : state.quote + 1;
if (modeCfg.highlightFormatting) state.formatting = "quote";
stream.eatSpace();
return getType(state);
} else if (stream.peek() === '[') {
return switchInline(stream, state, footnoteLink);
} else if (stream.match(hrRE, true)) {
state.hr = true;
return tokenTypes.hr;
} else if ((lineIsEmpty(state.prevLine) || prevLineIsList) && (stream.match(ulRE, false) || stream.match(olRE, false))) {
var listType = null;
if (stream.match(ulRE, true)) {
listType = 'ul';
} else {
stream.match(olRE, true);
listType = 'ol';
}
state.indentation = stream.column() + stream.current().length;
state.list = true;
// While this list item's marker's indentation
// is less than the deepest list item's content's indentation,
// pop the deepest list item indentation off the stack.
while (state.listStack && stream.column() < state.listStack[state.listStack.length - 1]) {
state.listStack.pop();
}
// Add this list item's content's indentation to the stack
state.listStack.push(state.indentation);
if (modeCfg.taskLists && stream.match(taskListRE, false)) {
state.taskList = true;
}
state.f = state.inline;
if (modeCfg.highlightFormatting) state.formatting = ["list", "list-" + listType];
return getType(state);
} else if (modeCfg.fencedCodeBlocks && (match = stream.match(fencedCodeRE, true))) {
state.fencedChars = match[1]
// try switching mode
state.localMode = getMode(match[2]);
if (state.localMode) state.localState = CodeMirror.startState(state.localMode);
state.f = state.block = local;
if (modeCfg.highlightFormatting) state.formatting = "code-block";
state.code = -1
return getType(state);
} else if (match = stream.match(fencedCodeRE, true)) {
state.fencedChars = match[1]
// try switching mode
state.localMode = getMode(match[2]);
if (state.localMode) state.localState = CodeMirror.startState(state.localMode);
state.f = state.block = local;
state.formatting = "math";
state.math = -1
return getType(state);
}
return switchInline(stream, state, state.inline);
}
function htmlBlock(stream, state) {
var style = htmlMode.token(stream, state.htmlState);
if (!htmlModeMissing) {
var inner = CodeMirror.innerMode(htmlMode, state.htmlState)
if ((inner.mode.name == "xml" && inner.state.tagStart === null &&
(!inner.state.context && inner.state.tokenize.isInText)) ||
(state.md_inside && stream.current().indexOf(">") > -1)) {
state.f = inlineNormal;
state.block = blockNormal;
state.htmlState = null;
}
}
return style;
}
function local(stream, state) {
if (state.fencedChars && stream.match(state.fencedChars, false)) {
state.localMode = state.localState = null;
state.f = state.block = leavingLocal;
return null;
} else if (state.localMode) {
return state.localMode.token(stream, state.localState);
} else {
stream.skipToEnd();
if (state.math === -1) {
return tokenTypes.math;
}
return tokenTypes.code;
}
}
function leavingLocal(stream, state) {
stream.match(state.fencedChars);
state.block = blockNormal;
state.f = inlineNormal;
state.fencedChars = null;
if (state.math === -1) {
state.formatting = "math";
state.math = 1
var returnType = getType(state);
state.math = 0
return returnType;
}
if (modeCfg.highlightFormatting) state.formatting = "code-block";
state.code = 1
var returnType = getType(state);
state.code = 0
return returnType;
}
// Inline
function getType(state) {
var styles = [];
if (state.formatting) {
styles.push(tokenTypes.formatting);
if (typeof state.formatting === "string") state.formatting = [state.formatting];
for (var i = 0; i < state.formatting.length; i++) {
styles.push(tokenTypes.formatting + "-" + state.formatting[i]);
if (state.formatting[i] === "header") {
styles.push(tokenTypes.formatting + "-" + state.formatting[i] + "-" + state.header);
}
// Add `formatting-quote` and `formatting-quote-#` for blockquotes
// Add `error` instead if the maximum blockquote nesting depth is passed
if (state.formatting[i] === "quote") {
if (!modeCfg.maxBlockquoteDepth || modeCfg.maxBlockquoteDepth >= state.quote) {
styles.push(tokenTypes.formatting + "-" + state.formatting[i] + "-" + state.quote);
} else {
styles.push("error");
}
}
}
}
if (state.taskOpen) {
styles.push("meta");
return styles.length ? styles.join(' ') : null;
}
if (state.taskClosed) {
styles.push("property");
return styles.length ? styles.join(' ') : null;
}
if (state.linkHref) {
styles.push(tokenTypes.linkHref, "url");
} else { // Only apply inline styles to non-url text
if (state.strong) { styles.push(tokenTypes.strong); }
if (state.em) { styles.push(tokenTypes.em); }
if (state.strikethrough) { styles.push(tokenTypes.strikethrough); }
if (state.linkText) { styles.push(tokenTypes.linkText); }
if (state.code) { styles.push(tokenTypes.code); }
if (state.math) { styles.push(tokenTypes.math); }
if (state.image) { styles.push(tokenTypes.image); }
if (state.imageAltText) { styles.push(tokenTypes.imageAltText, "link"); }
if (state.imageMarker) { styles.push(tokenTypes.imageMarker); }
}
if (state.header) { styles.push(tokenTypes.header, tokenTypes.header + "-" + state.header); }
if (state.quote) {
styles.push(tokenTypes.quote);
// Add `quote-#` where the maximum for `#` is modeCfg.maxBlockquoteDepth
if (!modeCfg.maxBlockquoteDepth || modeCfg.maxBlockquoteDepth >= state.quote) {
styles.push(tokenTypes.quote + "-" + state.quote);
} else {
styles.push(tokenTypes.quote + "-" + modeCfg.maxBlockquoteDepth);
}
}
if (state.list !== false) {
var listMod = (state.listStack.length - 1) % 3;
if (!listMod) {
styles.push(tokenTypes.list1);
} else if (listMod === 1) {
styles.push(tokenTypes.list2);
} else {
styles.push(tokenTypes.list3);
}
}
if (state.trailingSpaceNewLine) {
styles.push("trailing-space-new-line");
} else if (state.trailingSpace) {
styles.push("trailing-space-" + (state.trailingSpace % 2 ? "a" : "b"));
}
return styles.length ? styles.join(' ') : null;
}
function handleText(stream, state) {
if (stream.match(textRE, true)) {
return getType(state);
}
return undefined;
}
function inlineNormal(stream, state) {
var style = state.text(stream, state);
if (typeof style !== 'undefined')
return style;
if (state.list) { // List marker (*, +, -, 1., etc)
state.list = null;
return getType(state);
}
if (state.taskList) {
var taskOpen = stream.match(taskListRE, true)[1] !== "x";
if (taskOpen) state.taskOpen = true;
else state.taskClosed = true;
if (modeCfg.highlightFormatting) state.formatting = "task";
state.taskList = false;
return getType(state);
}
state.taskOpen = false;
state.taskClosed = false;
if (state.header && stream.match(/^#+$/, true)) {
if (modeCfg.highlightFormatting) state.formatting = "header";
return getType(state);
}
// Get sol() value now, before character is consumed
var sol = stream.sol();
var ch = stream.next();
// Matches link titles present on next line
if (state.linkTitle) {
state.linkTitle = false;
var matchCh = ch;
if (ch === '(') {
matchCh = ')';
}
matchCh = (matchCh+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
var regex = '^\\s*(?:[^' + matchCh + '\\\\]+|\\\\\\\\|\\\\.)' + matchCh;
if (stream.match(new RegExp(regex), true)) {
return tokenTypes.linkHref;
}
}
// If this block is changed, it may need to be updated in GFM mode
if (ch === '`') {
var previousFormatting = state.formatting;
if (modeCfg.highlightFormatting) state.formatting = "code";
stream.eatWhile('`');
var count = stream.current().length
if (state.code == 0) {
state.code = count
return getType(state)
} else if (count == state.code) { // Must be exact
var t = getType(state)
state.code = 0
return t
} else {
state.formatting = previousFormatting
return getType(state)
}
} else if (state.code) {
return getType(state);
}
// display math correctly
if (ch === '$') {
var previousFormatting = state.formatting;
state.formatting = "math";
stream.eatWhile('$');
var count = stream.current().length
if (state.math == 0) {
state.math = count
return getType(state)
} else if (count == state.math) { // Must be exact
var t = getType(state)
state.math = 0
return t
} else {
state.formatting = previousFormatting
return getType(state)
}
} else if (state.math) {
return getType(state);
}
if (ch === '\\') {
stream.next();
if (modeCfg.highlightFormatting) {
var type = getType(state);
var formattingEscape = tokenTypes.formatting + "-escape";
return type ? type + " " + formattingEscape : formattingEscape;
}
}
if (ch === '!' && stream.match(/\[[^\]]*\] ?(?:\(|\[)/, false)) {
state.imageMarker = true;
state.image = true;
if (modeCfg.highlightFormatting) state.formatting = "image";
return getType(state);
}
if (ch === '[' && state.imageMarker) {
state.imageMarker = false;
state.imageAltText = true
if (modeCfg.highlightFormatting) state.formatting = "image";
return getType(state);
}
if (ch === ']' && state.imageAltText) {
if (modeCfg.highlightFormatting) state.formatting = "image";
var type = getType(state);
state.imageAltText = false;
state.image = false;
state.inline = state.f = linkHref;
return type;
}
if (ch === '[' && stream.match(/[^\]]*\](\(.*\)| ?\[.*?\])/, false) && !state.image) {
state.linkText = true;
if (modeCfg.highlightFormatting) state.formatting = "link";
return getType(state);
}
if (ch === ']' && state.linkText && stream.match(/\(.*?\)| ?\[.*?\]/, false)) {
if (modeCfg.highlightFormatting) state.formatting = "link";
var type = getType(state);
state.linkText = false;
state.inline = state.f = linkHref;
return type;
}
if (ch === '<' && stream.match(/^(https?|ftps?):\/\/(?:[^\\>]|\\.)+>/, false)) {
state.f = state.inline = linkInline;
if (modeCfg.highlightFormatting) state.formatting = "link";
var type = getType(state);
if (type){
type += " ";
} else {
type = "";
}
return type + tokenTypes.linkInline;
}
if (ch === '<' && stream.match(/^[^> \\]+@(?:[^\\>]|\\.)+>/, false)) {
state.f = state.inline = linkInline;
if (modeCfg.highlightFormatting) state.formatting = "link";
var type = getType(state);
if (type){
type += " ";
} else {
type = "";
}
return type + tokenTypes.linkEmail;
}
if (ch === '<' && stream.match(/^(!--|\w)/, false)) {
var end = stream.string.indexOf(">", stream.pos);
if (end != -1) {
var atts = stream.string.substring(stream.start, end);
if (/markdown\s*=\s*('|"){0,1}1('|"){0,1}/.test(atts)) state.md_inside = true;
}
stream.backUp(1);
state.htmlState = CodeMirror.startState(htmlMode);
return switchBlock(stream, state, htmlBlock);
}
if (ch === '<' && stream.match(/^\/\w*?>/)) {
state.md_inside = false;
return "tag";
}
var ignoreUnderscore = false;
if (!modeCfg.underscoresBreakWords) {
if (ch === '_' && stream.peek() !== '_' && stream.match(/(\w)/, false)) {
var prevPos = stream.pos - 2;
if (prevPos >= 0) {
var prevCh = stream.string.charAt(prevPos);
if (prevCh !== '_' && prevCh.match(/(\w)/, false)) {
ignoreUnderscore = true;
}
}
}
}
if (ch === '*' || (ch === '_' && !ignoreUnderscore)) {
if (sol && stream.peek() === ' ') {
// Do nothing, surrounded by newline and space
} else if (state.strong === ch && stream.eat(ch)) { // Remove STRONG
if (modeCfg.highlightFormatting) state.formatting = "strong";
var t = getType(state);
state.strong = false;
return t;
} else if (!state.strong && stream.eat(ch)) { // Add STRONG
state.strong = ch;
if (modeCfg.highlightFormatting) state.formatting = "strong";
return getType(state);
} else if (state.em === ch) { // Remove EM
if (modeCfg.highlightFormatting) state.formatting = "em";
var t = getType(state);
state.em = false;
return t;
} else if (!state.em) { // Add EM
state.em = ch;
if (modeCfg.highlightFormatting) state.formatting = "em";
return getType(state);
}
} else if (ch === ' ') {
if (stream.eat('*') || stream.eat('_')) { // Probably surrounded by spaces
if (stream.peek() === ' ') { // Surrounded by spaces, ignore
return getType(state);
} else { // Not surrounded by spaces, back up pointer
stream.backUp(1);
}
}
}
if (modeCfg.strikethrough) {
if (ch === '~' && stream.eatWhile(ch)) {
if (state.strikethrough) {// Remove strikethrough
if (modeCfg.highlightFormatting) state.formatting = "strikethrough";
var t = getType(state);
state.strikethrough = false;
return t;
} else if (stream.match(/^[^\s]/, false)) {// Add strikethrough
state.strikethrough = true;
if (modeCfg.highlightFormatting) state.formatting = "strikethrough";
return getType(state);
}
} else if (ch === ' ') {
if (stream.match(/^~~/, true)) { // Probably surrounded by space
if (stream.peek() === ' ') { // Surrounded by spaces, ignore
return getType(state);
} else { // Not surrounded by spaces, back up pointer
stream.backUp(2);
}
}
}
}
if (ch === ' ') {
if (stream.match(/ +$/, false)) {
state.trailingSpace++;
} else if (state.trailingSpace) {
state.trailingSpaceNewLine = true;
}
}
return getType(state);
}
function linkInline(stream, state) {
var ch = stream.next();
if (ch === ">") {
state.f = state.inline = inlineNormal;
if (modeCfg.highlightFormatting) state.formatting = "link";
var type = getType(state);
if (type){
type += " ";
} else {
type = "";
}
return type + tokenTypes.linkInline;
}
stream.match(/^[^>]+/, true);
return tokenTypes.linkInline;
}
function linkHref(stream, state) {
// Check if space, and return NULL if so (to avoid marking the space)
if(stream.eatSpace()){
return null;
}
var ch = stream.next();
if (ch === '(' || ch === '[') {
state.f = state.inline = getLinkHrefInside(ch === "(" ? ")" : "]", 0);
if (modeCfg.highlightFormatting) state.formatting = "link-string";
state.linkHref = true;
return getType(state);
}
return 'error';
}
var linkRE = {
")": /^(?:[^\\\(\)]|\\.|\((?:[^\\\(\)]|\\.)*\))*?(?=\))/,
"]": /^(?:[^\\\[\]]|\\.|\[(?:[^\\\[\\]]|\\.)*\])*?(?=\])/
}
function getLinkHrefInside(endChar) {
return function(stream, state) {
var ch = stream.next();
if (ch === endChar) {
state.f = state.inline = inlineNormal;
if (modeCfg.highlightFormatting) state.formatting = "link-string";
var returnState = getType(state);
state.linkHref = false;
return returnState;
}
stream.match(linkRE[endChar])
state.linkHref = true;
return getType(state);
};
}
function footnoteLink(stream, state) {
if (stream.match(/^([^\]\\]|\\.)*\]:/, false)) {
state.f = footnoteLinkInside;
stream.next(); // Consume [
if (modeCfg.highlightFormatting) state.formatting = "link";
state.linkText = true;
return getType(state);
}
return switchInline(stream, state, inlineNormal);
}
function footnoteLinkInside(stream, state) {
if (stream.match(/^\]:/, true)) {
state.f = state.inline = footnoteUrl;
if (modeCfg.highlightFormatting) state.formatting = "link";
var returnType = getType(state);
state.linkText = false;
return returnType;
}
stream.match(/^([^\]\\]|\\.)+/, true);
return tokenTypes.linkText;
}
function footnoteUrl(stream, state) {
// Check if space, and return NULL if so (to avoid marking the space)
if(stream.eatSpace()){
return null;
}
// Match URL
stream.match(/^[^\s]+/, true);
// Check for link title
if (stream.peek() === undefined) { // End of line, set flag to check next line
state.linkTitle = true;
} else { // More content on line, check if link title
stream.match(/^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/, true);
}
state.f = state.inline = inlineNormal;
return tokenTypes.linkHref + " url";
}
var mode = {
startState: function() {
return {
f: blockNormal,
prevLine: null,
thisLine: null,
block: blockNormal,
htmlState: null,
indentation: 0,
inline: inlineNormal,
text: handleText,
formatting: false,
linkText: false,
linkHref: false,
linkTitle: false,
code: 0,
math: 0,
em: false,
strong: false,
header: 0,
hr: false,
taskList: false,
list: false,
listStack: [],
quote: 0,
trailingSpace: 0,
trailingSpaceNewLine: false,
strikethrough: false,
fencedChars: null
};
},
copyState: function(s) {
return {
f: s.f,
prevLine: s.prevLine,
thisLine: s.thisLine,
block: s.block,
htmlState: s.htmlState && CodeMirror.copyState(htmlMode, s.htmlState),
indentation: s.indentation,
localMode: s.localMode,
localState: s.localMode ? CodeMirror.copyState(s.localMode, s.localState) : null,
inline: s.inline,
text: s.text,
formatting: false,
linkTitle: s.linkTitle,
code: s.code,
math: s.math,
em: s.em,
strong: s.strong,
strikethrough: s.strikethrough,
header: s.header,
hr: s.hr,
taskList: s.taskList,
list: s.list,
listStack: s.listStack.slice(0),
quote: s.quote,
indentedCode: s.indentedCode,
trailingSpace: s.trailingSpace,
trailingSpaceNewLine: s.trailingSpaceNewLine,
md_inside: s.md_inside,
fencedChars: s.fencedChars
};
},
token: function(stream, state) {
// Reset state.formatting
state.formatting = false;
if (stream != state.thisLine) {
var forceBlankLine = state.header || state.hr;
// Reset state.header and state.hr
state.header = 0;
state.hr = false;
if (stream.match(/^\s*$/, true) || forceBlankLine) {
blankLine(state);
if (!forceBlankLine) return null
state.prevLine = null
}
state.prevLine = state.thisLine
state.thisLine = stream
// Reset state.taskList
state.taskList = false;
// Reset state.trailingSpace
state.trailingSpace = 0;
state.trailingSpaceNewLine = false;
state.f = state.block;
var indentation = stream.match(/^\s*/, true)[0].replace(/\t/g, ' ').length;
state.indentationDiff = Math.min(indentation - state.indentation, 4);
state.indentation = state.indentation + state.indentationDiff;
if (indentation > 0) return null;
}
return state.f(stream, state);
},
innerMode: function(state) {
if (state.block == htmlBlock) return {state: state.htmlState, mode: htmlMode};
if (state.localState) return {state: state.localState, mode: state.localMode};
return {state: state, mode: mode};
},
blankLine: blankLine,
getType: getType,
fold: "markdown"
};
return mode;
}, "xml");
CodeMirror.defineMIME("text/x-markdown", "markdown");
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 B

After

Width:  |  Height:  |  Size: 87 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 B

After

Width:  |  Height:  |  Size: 86 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 B

After

Width:  |  Height:  |  Size: 86 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 B

After

Width:  |  Height:  |  Size: 86 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 B

After

Width:  |  Height:  |  Size: 86 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 B

After

Width:  |  Height:  |  Size: 86 B

View file

@ -56,7 +56,7 @@
{name: "Gherkin", mime: "text/x-feature", mode: "gherkin", ext: ["feature"]}, {name: "Gherkin", mime: "text/x-feature", mode: "gherkin", ext: ["feature"]},
{name: "GitHub Flavored Markdown", mime: "text/x-gfm", mode: "gfm", file: /^(readme|contributing|history).md$/i}, {name: "GitHub Flavored Markdown", mime: "text/x-gfm", mode: "gfm", file: /^(readme|contributing|history).md$/i},
{name: "Go", mime: "text/x-go", mode: "go", ext: ["go"]}, {name: "Go", mime: "text/x-go", mode: "go", ext: ["go"]},
{name: "Groovy", mime: "text/x-groovy", mode: "groovy", ext: ["groovy", "gradle"]}, {name: "Groovy", mime: "text/x-groovy", mode: "groovy", ext: ["groovy", "gradle"], file: /^Jenkinsfile$/},
{name: "HAML", mime: "text/x-haml", mode: "haml", ext: ["haml"]}, {name: "HAML", mime: "text/x-haml", mode: "haml", ext: ["haml"]},
{name: "Haskell", mime: "text/x-haskell", mode: "haskell", ext: ["hs"]}, {name: "Haskell", mime: "text/x-haskell", mode: "haskell", ext: ["hs"]},
{name: "Haskell (Literate)", mime: "text/x-literate-haskell", mode: "haskell-literate", ext: ["lhs"]}, {name: "Haskell (Literate)", mime: "text/x-literate-haskell", mode: "haskell-literate", ext: ["lhs"]},
@ -66,7 +66,7 @@
{name: "HTML", mime: "text/html", mode: "htmlmixed", ext: ["html", "htm"], alias: ["xhtml"]}, {name: "HTML", mime: "text/html", mode: "htmlmixed", ext: ["html", "htm"], alias: ["xhtml"]},
{name: "HTTP", mime: "message/http", mode: "http"}, {name: "HTTP", mime: "message/http", mode: "http"},
{name: "IDL", mime: "text/x-idl", mode: "idl", ext: ["pro"]}, {name: "IDL", mime: "text/x-idl", mode: "idl", ext: ["pro"]},
{name: "Jade", mime: "text/x-jade", mode: "jade", ext: ["jade"]}, {name: "Pug", mime: "text/x-pug", mode: "pug", ext: ["jade", "pug"], alias: ["jade"]},
{name: "Java", mime: "text/x-java", mode: "clike", ext: ["java"]}, {name: "Java", mime: "text/x-java", mode: "clike", ext: ["java"]},
{name: "Java Server Pages", mime: "application/x-jsp", mode: "htmlembedded", ext: ["jsp"], alias: ["jsp"]}, {name: "Java Server Pages", mime: "application/x-jsp", mode: "htmlembedded", ext: ["jsp"], alias: ["jsp"]},
{name: "JavaScript", mimes: ["text/javascript", "text/ecmascript", "application/javascript", "application/x-javascript", "application/ecmascript"], {name: "JavaScript", mimes: ["text/javascript", "text/ecmascript", "application/javascript", "application/x-javascript", "application/ecmascript"],

View file

@ -1,6 +1,6 @@
<!doctype html> <!doctype html>
<title>CodeMirror: Jade Templating Mode</title> <title>CodeMirror: Pug Templating Mode</title>
<meta charset="utf-8"/> <meta charset="utf-8"/>
<link rel=stylesheet href="../../doc/docs.css"> <link rel=stylesheet href="../../doc/docs.css">
@ -10,7 +10,7 @@
<script src="../css/css.js"></script> <script src="../css/css.js"></script>
<script src="../xml/xml.js"></script> <script src="../xml/xml.js"></script>
<script src="../htmlmixed/htmlmixed.js"></script> <script src="../htmlmixed/htmlmixed.js"></script>
<script src="jade.js"></script> <script src="pug.js"></script>
<style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style> <style type="text/css">.CodeMirror {border-top: 1px solid black; border-bottom: 1px solid black;}</style>
<div id=nav> <div id=nav>
<a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../../doc/logo.png"></a> <a href="http://codemirror.net"><h1>CodeMirror</h1><img id=logo src="../../doc/logo.png"></a>
@ -22,17 +22,17 @@
</ul> </ul>
<ul> <ul>
<li><a href="../index.html">Language modes</a> <li><a href="../index.html">Language modes</a>
<li><a class=active href="#">Jade Templating Mode</a> <li><a class=active href="#">Pug Templating Mode</a>
</ul> </ul>
</div> </div>
<article> <article>
<h2>Jade Templating Mode</h2> <h2>Pug Templating Mode</h2>
<form><textarea id="code" name="code"> <form><textarea id="code" name="code">
doctype html doctype html
html html
head head
title= "Jade Templating CodeMirror Mode Example" title= "Pug Templating CodeMirror Mode Example"
link(rel='stylesheet', href='/css/bootstrap.min.css') link(rel='stylesheet', href='/css/bootstrap.min.css')
link(rel='stylesheet', href='/css/index.css') link(rel='stylesheet', href='/css/index.css')
script(type='text/javascript', src='/js/jquery-1.9.1.min.js') script(type='text/javascript', src='/js/jquery-1.9.1.min.js')
@ -60,11 +60,11 @@ doctype html
</textarea></form> </textarea></form>
<script> <script>
var editor = CodeMirror.fromTextArea(document.getElementById("code"), { var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
mode: {name: "jade", alignCDATA: true}, mode: {name: "pug", alignCDATA: true},
lineNumbers: true lineNumbers: true
}); });
</script> </script>
<h3>The Jade Templating Mode</h3> <h3>The Pug Templating Mode</h3>
<p> Created by Forbes Lindesay. Managed as part of a Brackets extension at <a href="https://github.com/ForbesLindesay/jade-brackets">https://github.com/ForbesLindesay/jade-brackets</a>.</p> <p> Created by Forbes Lindesay. Managed as part of a Brackets extension at <a href="https://github.com/ForbesLindesay/jade-brackets">https://github.com/ForbesLindesay/jade-brackets</a>.</p>
<p><strong>MIME type defined:</strong> <code>text/x-jade</code>.</p> <p><strong>MIME type defined:</strong> <code>text/x-pug</code>, <code>text/x-jade</code>.</p>
</article> </article>

View file

@ -11,7 +11,7 @@
})(function(CodeMirror) { })(function(CodeMirror) {
"use strict"; "use strict";
CodeMirror.defineMode('jade', function (config) { CodeMirror.defineMode("pug", function (config) {
// token types // token types
var KEYWORD = 'keyword'; var KEYWORD = 'keyword';
var DOCTYPE = 'meta'; var DOCTYPE = 'meta';
@ -585,6 +585,7 @@ CodeMirror.defineMode('jade', function (config) {
}; };
}, 'javascript', 'css', 'htmlmixed'); }, 'javascript', 'css', 'htmlmixed');
CodeMirror.defineMIME('text/x-jade', 'jade'); CodeMirror.defineMIME('text/x-pug', 'pug');
CodeMirror.defineMIME('text/x-jade', 'pug');
}); });

View file

@ -176,7 +176,7 @@ def pairwise_cython(double[:, ::1] X):
</script> </script>
<h2>Configuration Options for Python mode:</h2> <h2>Configuration Options for Python mode:</h2>
<ul> <ul>
<li>version - 2/3 - The version of Python to recognize. Default is 2.</li> <li>version - 2/3 - The version of Python to recognize. Default is 3.</li>
<li>singleLineStringErrors - true/false - If you have a single-line string that is not terminated at the end of the line, this will show subsequent lines as errors if true, otherwise it will consider the newline as the end of the string. Default is false.</li> <li>singleLineStringErrors - true/false - If you have a single-line string that is not terminated at the end of the line, this will show subsequent lines as errors if true, otherwise it will consider the newline as the end of the string. Default is false.</li>
<li>hangingIndent - int - If you want to write long arguments to a function starting on a new line, how much that line should be indented. Defaults to one normal indentation unit.</li> <li>hangingIndent - int - If you want to write long arguments to a function starting on a new line, how much that line should be indented. Defaults to one normal indentation unit.</li>
</ul> </ul>

View file

@ -55,7 +55,7 @@
if (parserConf.extra_builtins != undefined) if (parserConf.extra_builtins != undefined)
myBuiltins = myBuiltins.concat(parserConf.extra_builtins); myBuiltins = myBuiltins.concat(parserConf.extra_builtins);
var py3 = parserConf.version && parseInt(parserConf.version, 10) == 3 var py3 = !(parserConf.version && Number(parserConf.version) < 3)
if (py3) { if (py3) {
// since http://legacy.python.org/dev/peps/pep-0465/ @ is also an operator // since http://legacy.python.org/dev/peps/pep-0465/ @ is also an operator
var singleOperators = parserConf.singleOperators || /^[\+\-\*\/%&|\^~<>!@]/; var singleOperators = parserConf.singleOperators || /^[\+\-\*\/%&|\^~<>!@]/;
@ -185,7 +185,7 @@
} }
function tokenStringFactory(delimiter) { function tokenStringFactory(delimiter) {
while ("rub".indexOf(delimiter.charAt(0).toLowerCase()) >= 0) while ("rubf".indexOf(delimiter.charAt(0).toLowerCase()) >= 0)
delimiter = delimiter.substr(1); delimiter = delimiter.substr(1);
var singleline = delimiter.length == 1; var singleline = delimiter.length == 1;

71
public/vendor/codemirror/mode/sas/sas.js vendored Executable file → Normal file
View file

@ -137,12 +137,13 @@
stream.next(); stream.next();
return 'comment'; return 'comment';
} }
} else if (!state.continueString && (ch === '"' || ch === "'")) { } else if ((ch === '"' || ch === "'") && !state.continueString) {
// Have we found a string? state.continueString = ch
state.continueString = ch; //save the matching quote in the state return "string"
return "string"; } else if (state.continueString) {
} else if (state.continueString !== null) { if (state.continueString == ch) {
if (stream.skipTo(state.continueString)) { state.continueString = null;
} else if (stream.skipTo(state.continueString)) {
// quote found on this line // quote found on this line
stream.next(); stream.next();
state.continueString = null; state.continueString = null;
@ -187,12 +188,12 @@
if (stream.peek() === '.') stream.skipTo(' '); if (stream.peek() === '.') stream.skipTo(' ');
state.nextword = false; state.nextword = false;
return 'variable-2'; return 'variable-2';
} }
word = word.toLowerCase()
// Are we in a DATA Step? // Are we in a DATA Step?
if (state.inDataStep) { if (state.inDataStep) {
if (word.toLowerCase() === 'run;' || stream.match(/run\s;/)) { if (word === 'run;' || stream.match(/run\s;/)) {
state.inDataStep = false; state.inDataStep = false;
return 'builtin'; return 'builtin';
} }
@ -203,84 +204,84 @@
else return 'variable'; else return 'variable';
} }
// do we have a DATA Step keyword // do we have a DATA Step keyword
if (word && words.hasOwnProperty(word.toLowerCase()) && if (word && words.hasOwnProperty(word) &&
(words[word.toLowerCase()].state.indexOf("inDataStep") !== -1 || (words[word].state.indexOf("inDataStep") !== -1 ||
words[word.toLowerCase()].state.indexOf("ALL") !== -1)) { words[word].state.indexOf("ALL") !== -1)) {
//backup to the start of the word //backup to the start of the word
if (stream.start < stream.pos) if (stream.start < stream.pos)
stream.backUp(stream.pos - stream.start); stream.backUp(stream.pos - stream.start);
//advance the length of the word and return //advance the length of the word and return
for (var i = 0; i < word.length; ++i) stream.next(); for (var i = 0; i < word.length; ++i) stream.next();
return words[word.toLowerCase()].style; return words[word].style;
} }
} }
// Are we in an Proc statement? // Are we in an Proc statement?
if (state.inProc) { if (state.inProc) {
if (word.toLowerCase() === 'run;' || word.toLowerCase() === 'quit;') { if (word === 'run;' || word === 'quit;') {
state.inProc = false; state.inProc = false;
return 'builtin'; return 'builtin';
} }
// do we have a proc keyword // do we have a proc keyword
if (word && words.hasOwnProperty(word.toLowerCase()) && if (word && words.hasOwnProperty(word) &&
(words[word.toLowerCase()].state.indexOf("inProc") !== -1 || (words[word].state.indexOf("inProc") !== -1 ||
words[word.toLowerCase()].state.indexOf("ALL") !== -1)) { words[word].state.indexOf("ALL") !== -1)) {
stream.match(/[\w]+/); stream.match(/[\w]+/);
return words[word].style; return words[word].style;
} }
} }
// Are we in a Macro statement? // Are we in a Macro statement?
if (state.inMacro) { if (state.inMacro) {
if (word.toLowerCase() === '%mend') { if (word === '%mend') {
if (stream.peek() === ';') stream.next(); if (stream.peek() === ';') stream.next();
state.inMacro = false; state.inMacro = false;
return 'builtin'; return 'builtin';
} }
if (word && words.hasOwnProperty(word.toLowerCase()) && if (word && words.hasOwnProperty(word) &&
(words[word.toLowerCase()].state.indexOf("inMacro") !== -1 || (words[word].state.indexOf("inMacro") !== -1 ||
words[word.toLowerCase()].state.indexOf("ALL") !== -1)) { words[word].state.indexOf("ALL") !== -1)) {
stream.match(/[\w]+/); stream.match(/[\w]+/);
return words[word.toLowerCase()].style; return words[word].style;
} }
return 'atom'; return 'atom';
} }
// Do we have Keywords specific words? // Do we have Keywords specific words?
if (word && words.hasOwnProperty(word.toLowerCase())) { if (word && words.hasOwnProperty(word)) {
// Negates the initial next() // Negates the initial next()
stream.backUp(1); stream.backUp(1);
// Actually move the stream // Actually move the stream
stream.match(/[\w]+/); stream.match(/[\w]+/);
if (word.toLowerCase() === 'data' && /=/.test(stream.peek()) === false) { if (word === 'data' && /=/.test(stream.peek()) === false) {
state.inDataStep = true; state.inDataStep = true;
state.nextword = true; state.nextword = true;
return 'builtin'; return 'builtin';
} }
if (word.toLowerCase() === 'proc') { if (word === 'proc') {
state.inProc = true; state.inProc = true;
state.nextword = true; state.nextword = true;
return 'builtin'; return 'builtin';
} }
if (word.toLowerCase() === '%macro') { if (word === '%macro') {
state.inMacro = true; state.inMacro = true;
state.nextword = true; state.nextword = true;
return 'builtin'; return 'builtin';
} }
if (/title[1-9]/i.test(word)) return 'def'; if (/title[1-9]/.test(word)) return 'def';
if (word.toLowerCase() === 'footnote') { if (word === 'footnote') {
stream.eat(/[1-9]/); stream.eat(/[1-9]/);
return 'def'; return 'def';
} }
// Returns their value as state in the prior define methods // Returns their value as state in the prior define methods
if (state.inDataStep === true && words[word.toLowerCase()].state.indexOf("inDataStep") !== -1) if (state.inDataStep === true && words[word].state.indexOf("inDataStep") !== -1)
return words[word.toLowerCase()].style; return words[word].style;
if (state.inProc === true && words[word.toLowerCase()].state.indexOf("inProc") !== -1) if (state.inProc === true && words[word].state.indexOf("inProc") !== -1)
return words[word.toLowerCase()].style; return words[word].style;
if (state.inMacro === true && words[word.toLowerCase()].state.indexOf("inMacro") !== -1) if (state.inMacro === true && words[word].state.indexOf("inMacro") !== -1)
return words[word.toLowerCase()].style; return words[word].style;
if (words[word.toLowerCase()].state.indexOf("ALL") !== -1) if (words[word].state.indexOf("ALL") !== -1)
return words[word.toLowerCase()].style; return words[word].style;
return null; return null;
} }
// Unrecognized syntax // Unrecognized syntax

View file

@ -14,7 +14,7 @@
<script src="../css/css.js"></script> <script src="../css/css.js"></script>
<script src="../coffeescript/coffeescript.js"></script> <script src="../coffeescript/coffeescript.js"></script>
<script src="../sass/sass.js"></script> <script src="../sass/sass.js"></script>
<script src="../jade/jade.js"></script> <script src="../pug/pug.js"></script>
<script src="../handlebars/handlebars.js"></script> <script src="../handlebars/handlebars.js"></script>
<script src="../htmlmixed/htmlmixed.js"></script> <script src="../htmlmixed/htmlmixed.js"></script>

View file

@ -12,7 +12,7 @@
require("../css/css"), require("../css/css"),
require("../sass/sass"), require("../sass/sass"),
require("../stylus/stylus"), require("../stylus/stylus"),
require("../jade/jade"), require("../pug/pug"),
require("../handlebars/handlebars")); require("../handlebars/handlebars"));
} else if (typeof define === "function" && define.amd) { // AMD } else if (typeof define === "function" && define.amd) { // AMD
define(["../../lib/codemirror", define(["../../lib/codemirror",
@ -23,7 +23,7 @@
"../css/css", "../css/css",
"../sass/sass", "../sass/sass",
"../stylus/stylus", "../stylus/stylus",
"../jade/jade", "../pug/pug",
"../handlebars/handlebars"], mod); "../handlebars/handlebars"], mod);
} else { // Plain browser env } else { // Plain browser env
mod(CodeMirror); mod(CodeMirror);
@ -42,9 +42,9 @@
], ],
template: [ template: [
["lang", /^vue-template$/i, "vue"], ["lang", /^vue-template$/i, "vue"],
["lang", /^jade$/i, "jade"], ["lang", /^pug$/i, "pug"],
["lang", /^handlebars$/i, "handlebars"], ["lang", /^handlebars$/i, "handlebars"],
["type", /^(text\/)?(x-)?jade$/i, "jade"], ["type", /^(text\/)?(x-)?pug$/i, "pug"],
["type", /^text\/x-handlebars-template$/i, "handlebars"], ["type", /^text\/x-handlebars-template$/i, "handlebars"],
[null, null, "vue-template"] [null, null, "vue-template"]
] ]
@ -63,7 +63,7 @@
CodeMirror.defineMode("vue", function (config) { CodeMirror.defineMode("vue", function (config) {
return CodeMirror.getMode(config, {name: "htmlmixed", tags: tagLanguages}); return CodeMirror.getMode(config, {name: "htmlmixed", tags: tagLanguages});
}, "htmlmixed", "xml", "javascript", "coffeescript", "css", "sass", "stylus", "jade", "handlebars"); }, "htmlmixed", "xml", "javascript", "coffeescript", "css", "sass", "stylus", "pug", "handlebars");
CodeMirror.defineMIME("script/x-vue", "vue"); CodeMirror.defineMIME("script/x-vue", "vue");
}); });

View file

@ -1,94 +1,85 @@
/* /*
Name: Panda Syntax Name: Panda Syntax
Author: Siamak Mokhtari (http://github.com/siamak/) Author: Siamak Mokhtari (http://github.com/siamak/)
CodeMirror template by Siamak Mokhtari (https://github.com/siamak/atom-panda-syntax) CodeMirror template by Siamak Mokhtari (https://github.com/siamak/atom-panda-syntax)
*/ */
.cm-s-panda-syntax { .cm-s-panda-syntax {
/*font-family: 'Operator Mono', 'Source Sans Pro', Helvetica, Arial, sans-serif;*/
font-family: 'Operator Mono', 'Source Sans Pro', Menlo, Monaco, Consolas, Courier New, monospace;
background: #292A2B; background: #292A2B;
color: #E6E6E6; color: #E6E6E6;
line-height: 1.5;
font-family: 'Operator Mono', 'Source Sans Pro', Menlo, Monaco, Consolas, Courier New, monospace;
} }
.cm-s-panda-syntax .CodeMirror-cursor { border-color: #ff2c6d; }
.cm-s-panda-syntax .CodeMirror-activeline-background { .cm-s-panda-syntax .CodeMirror-activeline-background {
background: #404954; background: rgba(99, 123, 156, 0.1);
}
.cm-s-panda-syntax .CodeMirror-selected {
background: #FFF;
} }
.cm-s-panda-syntax .cm-comment { .cm-s-panda-syntax .cm-comment {
font-style: italic; font-style: italic;
color: #676B79; color: #676B79;
} }
.cm-s-panda-syntax .cm-string, .cm-s-panda-syntax .cm-operator {
.cm-s-panda-syntax .cm-string-2 { color: #f3f3f3;
}
.cm-s-panda-syntax .cm-string {
color: #19F9D8; color: #19F9D8;
} }
.cm-s-panda-syntax .cm-string-2 {
color: #FFB86C;
}
.cm-s-panda-syntax .cm-tag {
color: #ff2c6d;
}
.cm-s-panda-syntax .cm-meta {
color: #b084eb;
}
.cm-s-panda-syntax .cm-number { .cm-s-panda-syntax .cm-number {
color: #FFB86C; color: #FFB86C;
} }
.cm-s-panda-syntax .cm-atom { .cm-s-panda-syntax .cm-atom {
color: #FFB86C; color: #ff2c6d;
} }
.cm-s-panda-syntax .cm-keyword { .cm-s-panda-syntax .cm-keyword {
color: #FF75B5; color: #FF75B5;
} }
.cm-s-panda-syntax .cm-keyword-2 {
color: #FF75B5;
}
.cm-s-panda-syntax .cm-keyword-3 {
color: #B084EB;
}
.cm-s-panda-syntax .cm-variable { .cm-s-panda-syntax .cm-variable {
color: #FF9AC1; color: #ffb86c;
} }
.cm-s-panda-syntax .cm-variable-2 { .cm-s-panda-syntax .cm-variable-2 {
color: #e6e6e6; color: #ff9ac1;
} }
.cm-s-panda-syntax .cm-variable-3 { .cm-s-panda-syntax .cm-variable-3 {
color: #82B1FF; color: #ff9ac1;
} }
.cm-s-panda-syntax .cm-def { .cm-s-panda-syntax .cm-def {
/*font-style: italic;*/
color: #e6e6e6; color: #e6e6e6;
} }
.cm-s-panda-syntax .cm-def-2 {
font-style: italic;
color: #ffcc95;
}
.cm-s-panda-syntax .cm-property { .cm-s-panda-syntax .cm-property {
color: #6FC1FF; color: #f3f3f3;
}
.cm-s-panda-syntax .cm-unit {
color: #ffb86c;
} }
.cm-s-panda-syntax .cm-matchingbracket, .cm-s-panda-syntax .cm-attribute {
.CodeMirror .CodeMirror-matchingbracket { color: #ffb86c;
color: #E6E6E6 !important;
border-bottom: 1px dotted #19f9d8;
padding-bottom: 2px;
} }
.cm-s-panda-syntax .CodeMirror-gutters { .cm-s-panda-syntax .CodeMirror-matchingbracket {
background: #292A2B; border-bottom: 1px dotted #19F9D8;
color: #757575; padding-bottom: 2px;
border: none; color: #e6e6e6;
} }
.cm-s-panda-syntax .CodeMirror-guttermarker, .cm-s-panda-syntax .CodeMirror-guttermarker-subtle, .cm-s-panda-syntax .CodeMirror-linenumber { .CodeMirror-gutters {
color: #757575; background: #292a2b;
border-right-color: rgba(255, 255, 255, 0.1);
} }
.cm-s-panda-syntax .CodeMirror-linenumber { .CodeMirror-linenumber {
padding-right: 10px; color: #e6e6e6;
opacity: 0.6;
} }
.cm-s-panda-syntax .CodeMirror-cursor {
border-left: 1px solid #757575;
}
/*.cm-s-panda-syntax div.CodeMirror-selected { background: rgba(255, 255, 255, 0.5); }*/
.cm-s-panda-syntax.CodeMirror-focused div.CodeMirror-selected { background: rgba(255, 255, 255, 0.25); }
.cm-s-panda-syntax .CodeMirror-line::selection, .cm-s-panda-syntax .CodeMirror-line > span::selection, .cm-s-panda-syntax .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); }
.cm-s-panda-syntax .CodeMirror-line::-moz-selection, .cm-s-panda-syntax .CodeMirror-line > span::-moz-selection, .cm-s-panda-syntax .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); }
.cm-s-panda-syntax .CodeMirror-activeline-background { background: rgba(99, 123, 156, 0.125); }

View file

@ -11,7 +11,6 @@
background: #2c2827; background: #2c2827;
color: #8F938F; color: #8F938F;
line-height: 1.5; line-height: 1.5;
font-size: 14px;
} }
.cm-s-pastel-on-dark div.CodeMirror-selected { background: rgba(221,240,255,0.2); } .cm-s-pastel-on-dark div.CodeMirror-selected { background: rgba(221,240,255,0.2); }
.cm-s-pastel-on-dark .CodeMirror-line::selection, .cm-s-pastel-on-dark .CodeMirror-line > span::selection, .cm-s-pastel-on-dark .CodeMirror-line > span > span::selection { background: rgba(221,240,255,0.2); } .cm-s-pastel-on-dark .CodeMirror-line::selection, .cm-s-pastel-on-dark .CodeMirror-line > span::selection, .cm-s-pastel-on-dark .CodeMirror-line > span > span::selection { background: rgba(221,240,255,0.2); }

View file

@ -155,8 +155,8 @@ http://ethanschoonover.com/solarized/img/solarized-palette.png
.cm-s-solarized .CodeMirror-cursor { border-left: 1px solid #819090; } .cm-s-solarized .CodeMirror-cursor { border-left: 1px solid #819090; }
/* Fat cursor */ /* Fat cursor */
.cm-s-solarized.cm-s-light.cm-fat-cursor .CodeMirror-cursor { background: #fdf6e3; } .cm-s-solarized.cm-s-light.cm-fat-cursor .CodeMirror-cursor { background: #77ee77; }
.cm-s-solarized.cm-s-light .cm-animate-fat-cursor { background-color: #fdf6e3; } .cm-s-solarized.cm-s-light .cm-animate-fat-cursor { background-color: #77ee77; }
.cm-s-solarized.cm-s-dark.cm-fat-cursor .CodeMirror-cursor { background: #586e75; } .cm-s-solarized.cm-s-dark.cm-fat-cursor .CodeMirror-cursor { background: #586e75; }
.cm-s-solarized.cm-s-dark .cm-animate-fat-cursor { background-color: #586e75; } .cm-s-solarized.cm-s-dark .cm-animate-fat-cursor { background-color: #586e75; }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Some files were not shown because too many files have changed in this diff Show more