Marked as 0.2.8

This commit is contained in:
Wu Cheng-Han 2015-05-15 12:58:13 +08:00
parent 2d36d7ce84
commit 4e64583a0b
96 changed files with 3281 additions and 22102 deletions

View file

@ -1,12 +1,25 @@
HackMD 0.2.7
HackMD 0.2.8
===
This is a realtime collaborative markdown notes on all platforms.
But still in early stage, feel free to fork or contribute to it.
Thanks for your using.
Thanks for your using!
There are some config you need to change in below files
```
./run.sh
./config.js
./public/js/common.js
```
You can use SSL to encrypt your site by passing certificate path in the `config.js` and set `usessl=true`.
And there is a script called `run.sh`, it's for someone like me to run the server via npm package `forever`, and can passing environment variable to the server, like heroku does.
To install `forever`, just type `npm install forever -g`
The notes are store in PostgreSQL, and I provided the schema in the `hackmd_schema.sql`.
The users and sessions are store in mongoDB, which don't need schema, so just connect it directly.
License under MIT.
**License under MIT.**

126
app.js
View file

@ -1,6 +1,5 @@
//app
//external modules
var connect = require('connect');
var express = require('express');
var toobusy = require('toobusy-js');
var ejs = require('ejs');
@ -11,24 +10,45 @@ var mongoose = require('mongoose');
var compression = require('compression')
var session = require('express-session');
var MongoStore = require('connect-mongo')(session);
var fs = require('fs');
var shortid = require('shortid');
var imgur = require('imgur');
var formidable = require('formidable');
//core
var config = require("./config.js");
var User = require("./lib/user.js");
var Temp = require("./lib/temp.js");
var auth = require("./lib/auth.js");
var response = require("./lib/response.js");
//server setup
var app = express();
var server = require('http').createServer(app);
if (config.usessl) {
var ca = (function () {
var i, len, results;
results = [];
for (i = 0, len = config.sslcapath.length; i < len; i++) {
results.push(fs.readFileSync(config.sslcapath[i], 'utf8'));
}
return results;
})();
var options = {
key: fs.readFileSync(config.sslkeypath, 'utf8'),
cert: fs.readFileSync(config.sslcertpath, 'utf8'),
ca: ca,
requestCert: false,
rejectUnauthorized: false
};
var app = express();
var server = require('https').createServer(options, app);
} else {
var app = express();
var server = require('http').createServer(app);
}
var io = require('socket.io').listen(server);
var port = process.env.PORT || config.testport;
// connect to the mongodb
if (config.debug)
mongoose.connect(config.mongodbstring);
else
mongoose.connect(process.env.MONGOLAB_URI);
mongoose.connect(process.env.MONGOLAB_URI || config.mongodbstring);
//others
var db = require("./lib/db.js");
@ -53,7 +73,7 @@ app.use(session({
name: config.sessionname,
secret: config.sessionsecret,
resave: false, //don't save session if unmodified
saveUninitialized: true, //don't create session until something stored
saveUninitialized: false, //don't create session until something stored
cookie: {
maxAge: new Date(Date.now() + config.sessionlife),
expires: new Date(Date.now() + config.sessionlife),
@ -111,6 +131,59 @@ app.get("/status", function (req, res, next) {
res.end(JSON.stringify(data));
});
});
//get status
app.get("/temp", function (req, res) {
var host = req.get('host');
if (config.alloworigin.indexOf(host) == -1)
response.errorForbidden(res);
else {
var tempid = req.query.tempid;
if (!tempid)
response.errorForbidden(res);
else {
Temp.findTemp(tempid, function (err, temp) {
if (err || !temp)
response.errorForbidden(res);
else {
res.header("Access-Control-Allow-Origin", "*");
res.send({
temp: temp.data
});
temp.remove(function (err) {
if (err)
console.log('remove temp failed: ' + err);
});
}
});
}
}
});
//post status
app.post("/temp", urlencodedParser, function (req, res) {
var host = req.get('host');
if (config.alloworigin.indexOf(host) == -1)
response.errorForbidden(res);
else {
var id = shortid.generate();
var data = req.body.data;
if (!id || !data)
response.errorForbidden(res);
else {
if (config.debug)
console.log('SERVER received temp from [' + host + ']: ' + req.body.data);
Temp.newTemp(id, data, function (err, temp) {
if (!err && temp) {
res.header("Access-Control-Allow-Origin", "*");
res.send({
status: 'ok',
id: temp.id
});
} else
response.errorInternalError(res);
});
}
}
});
//facebook auth
app.get('/auth/facebook',
passport.authenticate('facebook'),
@ -230,6 +303,29 @@ app.get('/me', function (req, res) {
});
}
});
//upload to imgur
app.post('/uploadimage', function (req, res) {
var form = new formidable.IncomingForm();
form.parse(req, function (err, fields, files) {
if (err || !files.image || !files.image.path) {
response.errorForbidden(res);
} else {
if (config.debug)
console.log('SERVER received uploadimage: ' + JSON.stringify(files.image));
imgur.setClientId(config.imgur.clientID);
imgur.uploadFile(files.image.path)
.then(function (json) {
if (config.debug)
console.log('SERVER uploadimage success: ' + JSON.stringify(json));
res.send({link:json.data.link});
})
.catch(function (err) {
console.error(err);
res.send(err.message);
});
}
});
});
//get new note
app.get("/new", response.newNote);
//get features
@ -248,6 +344,12 @@ io.set('heartbeat timeout', config.heartbeattimeout);
io.sockets.on('connection', realtime.connection);
//listen
server.listen(port, function () {
console.log('Server listening at port %d', port);
});
if (config.usessl) {
server.listen(config.sslport, function () {
console.log('HTTPS Server listening at sslport %d', config.sslport);
});
} else {
server.listen(config.port, function () {
console.log('HTTP Server listening at port %d', config.port);
});
}

View file

@ -1,11 +1,33 @@
//config
var path = require('path');
var domain = process.env.DOMAIN;
var testport = '3000';
var testsslport = '3001';
var port = process.env.PORT || testport;
var sslport = process.env.SSLPORT || testsslport;
var usessl = false;
var urladdport = true; //add port on getserverurl
var config = {
debug: true,
version: '0.2.7',
domain: 'http://localhost:3000',
testport: '3000',
version: '0.2.8',
domain: domain,
alloworigin: ['add here to allow origin to cross'],
testport: testport,
testsslport: testsslport,
port: port,
sslport: sslport,
sslkeypath: 'change this',
sslcertpath: 'change this',
sslcapath: ['change this'],
usessl: usessl,
getserverurl: function() {
if(usessl)
return 'https://' + domain + (sslport == 443 || !urladdport ? '' : ':' + sslport);
else
return 'http://' + domain + (port == 80 || !urladdport ? '' : ':' + port);
},
//path
tmppath: "./tmp/",
defaultnotepath: path.join(__dirname, '/public', "default.md"),
@ -14,36 +36,39 @@ var config = {
errorpath: path.join(__dirname, '/public/views', "error.ejs"),
prettypath: path.join(__dirname, '/public/views', 'pretty.ejs'),
//db string
postgresqlstring: "postgresql://localhost:5432/hackmd",
mongodbstring: "mongodb://localhost/hackmd",
postgresqlstring: "change this",
mongodbstring: "change this",
//constants
featuresnotename: "features",
sessionname: 'please set this',
sessionsecret: 'please set this',
sessionname: 'change this',
sessionsecret: 'change this',
sessionlife: 14 * 24 * 60 * 60 * 1000, //14 days
sessiontouch: 1 * 3600, //1 hour
heartbeatinterval: 5000,
heartbeattimeout: 10000,
//auth
facebook: {
clientID: 'get yourself one',
clientSecret: 'get yourself one',
clientID: 'change this',
clientSecret: 'change this',
callbackPath: '/auth/facebook/callback'
},
twitter: {
consumerKey: 'get yourself one',
consumerSecret: 'get yourself one',
consumerKey: 'change this',
consumerSecret: 'change this',
callbackPath: '/auth/twitter/callback'
},
github: {
clientID: 'get yourself one',
clientSecret: 'get yourself one',
clientID: 'change this',
clientSecret: 'change this',
callbackPath: '/auth/github/callback'
},
dropbox: {
clientID: 'get yourself one',
clientSecret: 'get yourself one',
clientID: 'change this',
clientSecret: 'change this',
callbackPath: '/auth/dropbox/callback'
},
imgur: {
clientID: 'change this'
}
};

View file

@ -27,23 +27,23 @@ function callback(accessToken, refreshToken, profile, done) {
module.exports = passport.use(new FacebookStrategy({
clientID: config.facebook.clientID,
clientSecret: config.facebook.clientSecret,
callbackURL: config.domain + config.facebook.callbackPath
callbackURL: config.getserverurl() + config.facebook.callbackPath
}, callback));
//twitter
passport.use(new TwitterStrategy({
consumerKey: config.twitter.consumerKey,
consumerSecret: config.twitter.consumerSecret,
callbackURL: config.domain + config.twitter.callbackPath
callbackURL: config.getserverurl() + config.twitter.callbackPath
}, callback));
//github
passport.use(new GithubStrategy({
clientID: config.github.clientID,
clientSecret: config.github.clientSecret,
callbackURL: config.domain + config.github.callbackPath
callbackURL: config.getserverurl() + config.github.callbackPath
}, callback));
//dropbox
passport.use(new DropboxStrategy({
clientID: config.dropbox.clientID,
clientSecret: config.dropbox.clientSecret,
callbackURL: config.domain + config.dropbox.callbackPath
callbackURL: config.getserverurl() + config.dropbox.callbackPath
}, callback));

View file

@ -18,10 +18,7 @@ var db = {
};
function getDBClient() {
if (config.debug)
return new pg.Client(config.postgresqlstring);
else
return new pg.Client(process.env.DATABASE_URL);
return new pg.Client(process.env.DATABASE_URL || config.postgresqlstring);
}
function readFromFile(callback) {
@ -49,12 +46,14 @@ function newToDB(id, owner, body, callback) {
var client = getDBClient();
client.connect(function (err) {
if (err) {
client.end();
callback(err, null);
return console.error('could not connect to postgres', err);
}
var newnotequery = util.format(insertquery, id, owner, body);
//console.log(newnotequery);
client.query(newnotequery, function (err, result) {
client.end();
if (err) {
callback(err, null);
return console.error("new note to db failed: " + err);
@ -62,7 +61,6 @@ function newToDB(id, owner, body, callback) {
if (config.debug)
console.log("new note to db success");
callback(null, result);
client.end();
}
});
});
@ -72,23 +70,25 @@ function readFromDB(id, callback) {
var client = getDBClient();
client.connect(function (err) {
if (err) {
client.end();
callback(err, null);
return console.error('could not connect to postgres', err);
}
var readquery = util.format(selectquery, id);
//console.log(readquery);
client.query(readquery, function (err, result) {
client.end();
if (err) {
callback(err, null);
return console.error("read from db failed: " + err);
} else {
//console.log(result.rows);
if (result.rows.length <= 0) {
callback("not found note in db", null);
callback("not found note in db: " + id, null);
} else {
console.log("read from db success");
if(config.debug)
console.log("read from db success");
callback(null, result);
client.end();
}
}
});
@ -99,12 +99,14 @@ function saveToDB(id, title, data, callback) {
var client = getDBClient();
client.connect(function (err) {
if (err) {
client.end();
callback(err, null);
return console.error('could not connect to postgres', err);
}
var savequery = util.format(updatequery, title, data, id);
//console.log(savequery);
client.query(savequery, function (err, result) {
client.end();
if (err) {
callback(err, null);
return console.error("save to db failed: " + err);
@ -112,7 +114,6 @@ function saveToDB(id, title, data, callback) {
if (config.debug)
console.log("save to db success");
callback(null, result);
client.end();
}
});
});
@ -122,10 +123,12 @@ function countFromDB(callback) {
var client = getDBClient();
client.connect(function (err) {
if (err) {
client.end();
callback(err, null);
return console.error('could not connect to postgres', err);
}
client.query(countquery, function (err, result) {
client.end();
if (err) {
callback(err, null);
return console.error("count from db failed: " + err);
@ -134,9 +137,9 @@ function countFromDB(callback) {
if (result.rows.length <= 0) {
callback("not found note in db", null);
} else {
console.log("count from db success");
if(config.debug)
console.log("count from db success");
callback(null, result);
client.end();
}
}
});

View file

@ -81,10 +81,11 @@ function getStatus(callback) {
break;
}
}
if (!found)
if (!found) {
distinctaddresses.push(value.address);
if(!found && value.login)
distinctregusers++;
if(value.login)
distinctregusers++;
}
});
User.getUserCount(function (err, regcount) {
if (err) {
@ -372,11 +373,19 @@ function connection(socket) {
switch (op.origin) {
case '+input':
case '+delete':
case '+transpose':
case 'paste':
case 'cut':
case 'undo':
case 'redo':
case 'drag':
case '*compose':
case 'case':
case '+insertLine':
case '+swapLine':
case '+joinLines':
case '+duplicateLine':
case '+sortLines':
notes[notename].socks.forEach(function (sock) {
if (sock != socket) {
if (config.debug)

View file

@ -17,16 +17,16 @@ var Note = require("./note.js");
//public
var response = {
errorForbidden: function (res) {
res.status(403).send("Forbidden, oh no.")
res.status(403).send("Forbidden, oh no.");
},
errorNotFound: function (res) {
responseError(res, "404", "Not Found", "oops.")
responseError(res, "404", "Not Found", "oops.");
},
errorInternalError: function (res) {
responseError(res, "500", "Internal Error", "wtf.")
responseError(res, "500", "Internal Error", "wtf.");
},
errorServiceUnavailable: function (res) {
res.status(503).send("I'm busy right now, try again later.")
res.status(503).send("I'm busy right now, try again later.");
},
newNote: newNote,
showFeatures: showFeatures,

83
lib/temp.js Normal file
View file

@ -0,0 +1,83 @@
//temp
//external modules
var mongoose = require('mongoose');
//core
var config = require("../config.js");
// create a temp model
var model = mongoose.model('temp', {
id: String,
data: String,
created: Date
});
//public
var temp = {
model: model,
findTemp: findTemp,
newTemp: newTemp,
removeTemp: removeTemp,
getTempCount: getTempCount
};
function getTempCount(callback) {
model.count(function(err, count){
if(err) callback(err, null);
else callback(null, count);
});
}
function findTemp(id, callback) {
model.findOne({
id: id
}, function (err, temp) {
if (err) {
console.log('find temp failed: ' + err);
callback(err, null);
}
if (!err && temp) {
callback(null, temp);
} else {
console.log('find temp failed: ' + err);
callback(err, null);
};
});
}
function newTemp(id, data, callback) {
var temp = new model({
id: id,
data: data,
created: Date.now()
});
temp.save(function (err) {
if (err) {
console.log('new temp failed: ' + err);
callback(err, null);
} else {
console.log("new temp success: " + temp.id);
callback(null, temp);
};
});
}
function removeTemp(id, callback) {
findTemp(id, function(err, temp) {
if(!err && temp) {
temp.remove(function(err) {
if(err) {
console.log('remove temp failed: ' + err);
callback(err, null);
} else {
callback(null, null);
}
});
} else {
console.log('remove temp failed: ' + err);
callback(err, null);
}
});
}
module.exports = temp;

View file

@ -37,7 +37,7 @@ function findUser(id, callback) {
console.log('find user failed: ' + err);
callback(err, null);
}
if (!err && user != null) {
if (!err && user) {
callback(null, user);
} else {
console.log('find user failed: ' + err);
@ -65,7 +65,7 @@ function newUser(id, profile, callback) {
function findOrNewUser(id, profile, callback) {
findUser(id, function(err, user) {
if(err || user == null) {
if(err || !user) {
newUser(id, profile, function(err, user) {
if(err) {
console.log('find or new user failed: ' + err);

View file

@ -1,9 +1,9 @@
{
"name": "hackmd",
"version": "0.2.7",
"version": "0.2.8",
"description": "Realtime collaborative markdown notes on all platforms.",
"main": "server.js",
"author": "jackymaxj",
"main": "app.js",
"author": "jackycute",
"private": true,
"license": "MIT",
"dependencies": {
@ -11,7 +11,6 @@
"body-parser": "^1.12.3",
"cheerio": "^0.19.0",
"compression": "^1.4.3",
"connect": "3.x",
"connect-mongo": "^0.8.1",
"cookie": "0.1.2",
"cookie-parser": "1.3.3",
@ -19,8 +18,9 @@
"emojify.js": "^1.0.1",
"express": "4.x",
"express-session": "^1.11.1",
"formidable": "^1.0.17",
"highlight.js": "^8.4.0",
"html": "0.0.7",
"imgur": "^0.1.5",
"jsdom-nogyp": "^0.8.3",
"lz-string": "1.3.6",
"markdown-pdf": "^5.2.0",

View file

@ -23,13 +23,15 @@ a:hover {
/*
* Base structure
*/
html {
height: 100%;
}
html,
body {
height: 100%;
background-color: #333;
}
body {
min-height: 100%;
color: #fff;
text-align: center;
text-shadow: 0 1px 3px rgba(0, 0, 0, .5);
@ -40,7 +42,7 @@ body {
padding: 10px;
display: table;
width: 100%;
height: 100%;
height: 100vh;
/* For at least Firefox */
min-height: 100%;
-webkit-box-shadow: inset 0 0 100px rgba(0, 0, 0, .5);
@ -156,7 +158,7 @@ body {
.masthead,
.mastfoot,
.cover-container {
width: 700px;
width: 1000px;
}
}
.section ul {
@ -168,41 +170,14 @@ html,
body {
overflow-x: hidden;
}
.select2-selection,
.select2-search__field {
outline: 0;
}
.select2-search__field:hover {
border: 1px solid #b9b9b9 !important;
border-top-color: #a0a0a0 !important;
}
.select2-search__field:focus {
border: 1px solid #4d90fe !important;
}
input {
color: black;
}
.mastfoot {
position: relative;
}
.select2 {
width: 100% !important;
max-width: 400px;
}
.select2-selection {
height: 32px !important;
}
.select2-selection__rendered {
line-height: 32px !important;
}
.select2-selection__arrow {
height: 30px !important;
}
.select2-selection__rendered,
.select2-selection__placeholder,
.select2-results__option {
color: #000;
text-shadow: none;
.select2-container {
margin: 0 auto !important;
}
.list {
width: 100%;
@ -275,4 +250,24 @@ input {
.modal-title {
text-align: left;
color: black;
}
.btn-file {
position: relative;
overflow: hidden;
}
.btn-file input[type=file] {
position: absolute;
top: 0;
right: 0;
min-width: 100%;
min-height: 100%;
font-size: 100px;
text-align: right;
filter: alpha(opacity=0);
opacity: 0;
outline: none;
background: white;
cursor: inherit;
display: block;
}

View file

@ -1,6 +1,5 @@
.markdown-body {
overflow: hidden;
font-family: "Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif;
font-size: 16px;
line-height: 1.6;
word-wrap: break-word;
@ -339,7 +338,7 @@
}
.task-list-item-checkbox {
float: left;
margin: 0.4em 0 0.2em -1.3em !important;
margin: 0.31em 0 0.2em -1.3em !important;
vertical-align: middle;
cursor: default !important;
}

View file

@ -20,7 +20,7 @@ form,
font-family: 'Source Code Pro', Consolas, monaco, monospace;
line-height: 18px;
font-size: 16px;
height: auto;
/*height: auto;*/
min-height: 100%;
overflow-y: hidden !important;
-webkit-overflow-scrolling: touch;
@ -30,7 +30,7 @@ form,
overflow-y: auto !important;
}
.CodeMirror-code {
padding-bottom: 100px;
/*padding-bottom: 72px;*/
}
.CodeMirror-linenumber {
opacity: 0.5;
@ -43,7 +43,7 @@ form,
.CodeMirror-foldmarker {
color: #d0d0d0;
text-shadow: none;
font-family: arial;
font-family: Arial;
line-height: .3;
cursor: pointer;
margin: 2px;

View file

@ -6,6 +6,7 @@ body {
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.004);
/*text-rendering: optimizeLegibility;*/
-webkit-overflow-scrolling: touch;
font-family: "Helvetica Neue", Helvetica, Arial, "Microsoft JhengHei", sans-serif !important;
}
:focus {
outline: none !important;

View file

@ -10,13 +10,6 @@
<meta name="mobile-web-app-capable" content="yes">
<meta name="description" content="Realtime collaborative markdown notes on all platforms.">
<meta name="author" content="jackycute">
<!-- Open Graph data -->
<meta property="og:title" content="HackMD - Collaborative notes">
<meta property="og:type" content="website">
<meta property="og:url" content="https://hackmd.herokuapp.com/">
<meta property="og:description" content="Realtime collaborative markdown notes on all platforms.">
<meta property="og:site_name" content="HackMD">
<meta property="fb:admins" content="1463801565">
<title>HackMD - Collaborative notes</title>
<link rel="icon" type="image/png" href="/favicon.png">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
@ -24,7 +17,8 @@
<!-- Bootstrap core CSS -->
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
<link rel="stylesheet" href="/vendor/select2/css/select2.min.css">
<link rel="stylesheet" href="/vendor/select2/select2.css">
<link rel="stylesheet" href="/vendor/select2/select2-bootstrap.css">
<!-- Custom styles for this template -->
<link rel="stylesheet" href="/css/cover.css">
<link rel="stylesheet" href="/css/bootstrap-social.css">
@ -56,9 +50,7 @@
<div class="inner cover">
<h1 class="cover-heading"><i class="fa fa-file-text"></i> HackMD</h1>
<p class="lead">
Realtime collaborate notes.
<br> Using markdown syntax.
<br> On all platforms.
Realtime collaborative markdown notes on all platforms.
</p>
<a type="button" class="btn btn-lg btn-success ui-signin" data-toggle="modal" data-target=".bs-example-modal-sm" style="display:none;">Sign In</a>
<div class="ui-or" style="display:none;">Or</div>
@ -76,12 +68,12 @@
<h4>
<a type="button" class="btn btn-success" data-toggle="modal" data-target=".bs-example-modal-sm">Sign In</a> to get own history!
</h4>
<p>Below are history from cookie</p>
<p>Below are history from browser</p>
</div>
<div class="ui-signout" style="display:none;">
<h4 class="ui-welcome">Welcome! <span class="ui-name"></span></h4>
<a href="/new" class="btn btn-default">Start new note</a> Or
<a href="/logout" class="btn btn-danger">Sign Out</a>
<a href="#" class="btn btn-danger ui-logout">Sign Out</a>
<br>
<br>
<a href="/features">See all features here <i class="fa fa-info-circle"></i></a>
@ -89,24 +81,34 @@
</div>
<hr>
<form class="form-inline">
<div class="form-group">
<input class="form-control ui-use-tags" style="width:172px;" />
</div>
<div class="form-group">
<input class="search form-control" placeholder="Search anything..." />
</div>
<a href="#" class="sort btn btn-default" data-sort="text">
Sort by title
<a href="#" class="sort btn btn-default" data-sort="text" title="Sort by title">
Title
</a>
<a href="#" class="sort btn btn-default" data-sort="timestamp">
Sort by time
<a href="#" class="sort btn btn-default" data-sort="timestamp" title="Sort by time">
Time
</a>
<span class="hidden-xs hidden-sm">
<a href="#" class="btn btn-default ui-save-history" title="Export history"><i class="fa fa-save"></i></a>
<span class="btn btn-default btn-file ui-open-history" title="Import history">
<i class="fa fa-folder-open-o"></i><input type="file" />
</span>
<a href="#" class="btn btn-default ui-clear-history" title="Clear history"><i class="fa fa-trash-o"></i></a>
</span>
<a href="#" class="btn btn-default ui-refresh-history" title="Refresh history"><i class="fa fa-refresh"></i></a>
</form>
<h4 class="ui-nohistory">
<h4 class="ui-nohistory" style="display:none;">
No history
</h4>
<a href="#" class="btn btn-primary ui-import-from-cookie" style="display:none;">Import from cookie</a>
<a href="#" class="btn btn-primary ui-import-from-browser" style="display:none;">Import from browser</a>
<ul id="history-list" class="list">
</ul>
</div>
<div id="releasenotes" class="section" style="display:none;">
<div id="template" style="display:none;">
{{#each release}}
@ -140,6 +142,11 @@
<div class="mastfoot">
<div class="inner">
<h6>
<div class="fb-like" data-href="https://www.facebook.com/TakeHackMD" data-width="80" data-layout="button_count" data-action="like" data-show-faces="true" data-share="false" style="vertical-align:middle;"></div>
&nbsp;
<iframe src="//ghbtns.com/github-btn.html?user=jackycute&repo=hackmd&type=star&count=true" frameborder="0" scrolling="0" width="80px" height="20px" style="vertical-align:middle;"></iframe>
</h6>
<p>&copy; 2015 <a href="https://www.facebook.com/TakeHackMD" target="_blank"><i class="fa fa-facebook-square"></i> HackMD</a> by <a href="https://github.com/jackycute" target="_blank"><i class="fa fa-github-square"></i> jackycute</a>
</p>
</div>
@ -174,21 +181,25 @@
</div>
</div>
</div>
<div id="fb-root"></div>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<!--<script src="/js/ga.js"></script>-->
<script src="//code.jquery.com/jquery-1.11.3.min.js"></script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
<script src="/vendor/select2/js/select2.min.js"></script>
<script src="/vendor/select2/js/i18n/en.js"></script>
<script src="/vendor/jquery.cookie-1.4.1.min.js"></script>
<script src="/vendor/moment-with-locales.js"></script>
<script src="/vendor/handlebars-v3.0.0.js"></script>
<script src="/vendor/list.min.js"></script>
<script src="/js/history.js"></script>
<script src="/js/cover.js"></script>
<script src="/js/fb.js" async defer></script>
<script src="//code.jquery.com/jquery-1.11.3.min.js" defer></script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js" defer></script>
<script src="/vendor/select2/select2.min.js" defer></script>
<script src="/vendor/js.cookie.js" defer></script>
<script src="/vendor/moment-with-locales.js" defer></script>
<script src="/vendor/handlebars-v3.0.0.js" defer></script>
<script src="/vendor/list.min.js" defer></script>
<script src="/vendor/FileSaver.min.js" defer></script>
<script src="/vendor/store.min.js" defer></script>
<script src="/vendor/url.min.js" defer></script>
<script src="/js/common.js" defer></script>
<script src="/js/history.js" defer></script>
<script src="/js/cover.js" defer></script>
</body>
</html>

53
public/js/common.js Normal file
View file

@ -0,0 +1,53 @@
//common
var domain = 'change this';
var checkAuth = false;
var profile = null;
var lastLoginState = getLoginState();
var loginStateChangeEvent = null;
function resetCheckAuth() {
checkAuth = false;
}
function setLoginState(bool) {
Cookies.set('loginstate', bool, {
expires: 14
});
if (loginStateChangeEvent && bool != lastLoginState)
loginStateChangeEvent();
lastLoginState = bool;
}
function getLoginState() {
return Cookies.get('loginstate') === "true";
}
function clearLoginState() {
Cookies.remove('loginstate');
}
function checkIfAuth(yesCallback, noCallback) {
var cookieLoginState = getLoginState();
if (!checkAuth || typeof cookieLoginState == 'undefined') {
$.get('/me')
.done(function (data) {
if (data && data.status == 'ok') {
profile = data;
yesCallback(profile);
setLoginState(true);
} else {
noCallback();
setLoginState(false);
}
})
.fail(function () {
noCallback();
setLoginState(false);
});
checkAuth = true;
} else if (cookieLoginState) {
yesCallback(profile);
} else {
noCallback();
}
}

View file

@ -1,3 +1,47 @@
var options = {
valueNames: ['id', 'text', 'timestamp', 'fromNow', 'time', 'tags'],
item: '<li class="col-xs-12 col-sm-6 col-md-6 col-lg-4">\
<span class="id" style="display:none;"></span>\
<a href="#">\
<div class="item">\
<div class="ui-history-close fa fa-close fa-fw"></div>\
<h4 class="text"></h4>\
<p><i class="fromNow"><i class="fa fa-clock-o"></i></i>\
<br>\
<i class="timestamp" style="display:none;"></i><i class="time"></i></p>\
<p class="tags"></p>\
</div>\
</a>\
</li>'
};
var historyList = new List('history', options);
migrateHistoryFromTempCallback = pageInit;
loginStateChangeEvent = pageInit;
pageInit();
function pageInit() {
checkIfAuth(
function (data) {
$('.ui-signin').hide();
$('.ui-or').hide();
$('.ui-welcome').show();
$('.ui-name').html(data.name);
$('.ui-signout').show();
$(".ui-history").click();
parseServerToHistory(historyList, parseHistoryCallback);
},
function () {
$('.ui-signin').slideDown();
$('.ui-or').slideDown();
$('.ui-welcome').hide();
$('.ui-name').html('');
$('.ui-signout').hide();
parseStorageToHistory(historyList, parseHistoryCallback);
}
);
}
$(".masthead-nav li").click(function () {
$(this).siblings().removeClass("active");
$(this).addClass("active");
@ -19,63 +63,202 @@ $(".ui-releasenotes").click(function () {
});
function checkHistoryList() {
if ($("#history-list").children().length > 0)
if ($("#history-list").children().length > 0) {
$(".ui-nohistory").hide();
else if ($("#history-list").children().length == 0) {
$(".ui-import-from-browser").hide();
} else if ($("#history-list").children().length == 0) {
$(".ui-nohistory").slideDown();
var cookienotehistory = JSON.parse($.cookie('notehistory'));
if (login && cookienotehistory && cookienotehistory.length > 0) {
$(".ui-import-from-cookie").slideDown();
}
getStorageHistory(function (data) {
if (data && data.length > 0 && getLoginState() && historyList.items.length == 0) {
$(".ui-import-from-browser").slideDown();
}
});
}
}
function parseHistoryCallback() {
function parseHistoryCallback(list, notehistory) {
checkHistoryList();
list.sort('timestamp', {
order: "desc"
});
var filtertags = [];
$(".item").each(function (key, value) {
var a = $(this).closest("a");
var id = a.siblings("span").html();
var tagsEl = $(this).find(".tags");
var item = historyList.get('id', id);
if (item.length > 0 && item[0]) {
var values = item[0].values();
//parse link to element a
a.attr('href', '/' + values.id);
//parse tags
if (values.tags) {
var tags = values.tags;
if (tags.length > 0) {
var labels = [];
for (var j = 0; j < tags.length; j++) {
//push info filtertags if not found
var found = false;
if (filtertags.indexOf(tags[j]) != -1)
found = true;
if (!found)
filtertags.push(tags[j]);
//push into the item label
labels.push("<span class='label label-default'>" + tags[j] + "</span>");
}
tagsEl.html(labels.join(' '));
}
}
}
});
$(".ui-history-close").click(function (e) {
e.preventDefault();
var id = $(this).closest("a").attr("href").split('/')[1];
var id = $(this).closest("a").siblings("span").html();
getHistory(function (notehistory) {
var newnotehistory = removeHistory(id, notehistory);
saveHistory(newnotehistory);
});
$(this).closest("li").remove();
list.remove('id', id);
checkHistoryList();
});
buildTagsFilter(filtertags);
}
var login = false;
checkIfAuth(
function (data) {
$('.ui-signin').hide();
$('.ui-or').hide();
$('.ui-welcome').show();
$('.ui-name').html(data.name);
$('.ui-signout').show();
$(".ui-history").click();
login = true;
},
function () {
$('.ui-signin').slideDown();
$('.ui-or').slideDown();
login = false;
}
);
parseHistory(parseHistoryCallback);
$(".ui-import-from-cookie").click(function () {
saveCookieHistoryToServer(function() {
parseCookieToHistory(parseHistoryCallback);
$(".ui-import-from-cookie").hide();
$(".ui-import-from-browser").click(function () {
saveStorageHistoryToServer(function () {
parseStorageToHistory(historyList, parseHistoryCallback);
});
});
$(".ui-save-history").click(function () {
getHistory(function (data) {
var history = JSON.stringify(data);
var blob = new Blob([history], {
type: "application/json;charset=utf-8"
});
saveAs(blob, 'hackmd_history_' + moment().format('YYYYMMDDHHmmss'));
});
});
$(".ui-open-history").bind("change", function (e) {
var files = e.target.files || e.dataTransfer.files;
var file = files[0];
var reader = new FileReader();
reader.onload = function () {
var notehistory = JSON.parse(reader.result);
//console.log(notehistory);
if (!reader.result) return;
getHistory(function (data) {
var mergedata = data.concat(notehistory);
mergedata = clearDuplicatedHistory(mergedata);
saveHistory(mergedata);
parseHistory(historyList, parseHistoryCallback);
});
$(".ui-open-history").replaceWith($(".ui-open-history").val('').clone(true));
};
reader.readAsText(file);
});
$(".ui-clear-history").click(function () {
saveHistory([]);
historyList.clear();
checkHistoryList();
});
$(".ui-refresh-history").click(function () {
resetCheckAuth();
historyList.clear();
parseHistory(historyList, parseHistoryCallback);
});
$(".ui-logout").click(function () {
clearLoginState();
location.href = '/logout';
});
var filtertags = [];
$(".ui-use-tags").select2({
placeholder: 'Use tags...',
multiple: true,
data: function () {
return {
results: filtertags
};
}
});
$('.select2-input').css('width', 'inherit');
buildTagsFilter([]);
function buildTagsFilter(tags) {
for (var i = 0; i < tags.length; i++)
tags[i] = {
id: i,
text: tags[i]
};
filtertags = tags;
}
$(".ui-use-tags").on('change', function () {
var tags = [];
var data = $(this).select2('data');
for (var i = 0; i < data.length; i++)
tags.push(data[i].text);
if (tags.length > 0) {
historyList.filter(function (item) {
var values = item.values();
if (!values.tags) return false;
var found = false;
for (var i = 0; i < tags.length; i++) {
if (values.tags.indexOf(tags[i]) != -1) {
found = true;
break;
}
}
return found;
});
} else {
historyList.filter();
}
checkHistoryList();
});
$('.search').keyup(function () {
checkHistoryList();
});
var source = $("#template").html();
var template = Handlebars.compile(source);
var context = {
release: [
{
version: "0.2.8",
tag: "flame",
date: moment("201505151200", 'YYYYMMDDhhmm').fromNow(),
detail: [
{
title: "Features",
item: [
"+ Support drag-n-drop(exclude firefox) and paste image inline",
"+ Support tags filter in history",
"+ Support sublime-like shortcut keys"
]
},
{
title: "Enhancements",
item: [
"* Adjust index description",
"* Adjust toolbar ui and view font",
"* Remove scroll sync delay and gain accuracy"
]
},
{
title: "Fixes",
item: [
"* Partial update in the front and the end might not render properly",
"* Server not handle some editor events"
]
}
]
},
{
version: "0.2.7",
tag: "fuel",

View file

@ -28,6 +28,8 @@ function renderFilename(view) {
return filename;
}
var viewAjaxCallback = null;
//dynamic event or object binding here
function finishView(view) {
//youtube
@ -42,7 +44,7 @@ function finishView(view) {
.each(function (key, value) {
$.ajax({
type: 'GET',
url: 'http://vimeo.com/api/v2/video/' + $(value).attr('videoid') + '.json',
url: '//vimeo.com/api/v2/video/' + $(value).attr('videoid') + '.json',
jsonp: 'callback',
dataType: 'jsonp',
success: function (data) {