diff --git a/README.md b/README.md index e7464c0f6..16d1a3df2 100755 --- a/README.md +++ b/README.md @@ -2,6 +2,9 @@ nodePPT - 让你爱上做分享! ============= [This is a readme file in English](./README_EN.md) +[![NPM](https://nodei.co/npm-dl/nodeppt.png)](https://nodei.co/npm/nodeppt/) +[![NPM](https://nodei.co/npm/nodeppt.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/nodeppt/) + ## 为什么选择nodePPT **这可能是迄今为止最好的网页版PPT** @@ -18,7 +21,10 @@ nodePPT - 让你爱上做分享! * [支持进入/退出回调](#callback),做在线demo很方便 * 支持事件update函数,查看[demo](http://qdemo.sinaapp.com/#12) -## 0.9.0新功能 +## 1.0.0新功能 + * 实现watch功能`nodeppt start -w` + +## 0.9.0 新功能 * 添加画板多端同步 * 添加按钮控制进度 * 新增两种转场动效,增加事件绑定方法:`Slide.on` diff --git a/README_EN.md b/README_EN.md index 22be72958..bcface51f 100644 --- a/README_EN.md +++ b/README_EN.md @@ -2,49 +2,38 @@ nodePPT - This is probably the best web presentation tool so far! ================================== 中文说明:[README](./README.md) +[![NPM](https://nodei.co/npm-dl/nodeppt.png)](https://nodei.co/npm/nodeppt/) +[![NPM](https://nodei.co/npm/nodeppt.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/nodeppt/) + ## why nodePPT? * markdown based on GFM; - * mix-code with html and markdown - * export your work with html and pdf format; - * 18 different transition animations, and you can choose single page animation well; - * Setting one page background image different than others; - * overview mode, multiscreen mode, remote control with socket, shake to page-flipping with ipad/iphone; - * canvas is also supported, with socket, we **sync your multiscreen in real time**, and you can type some notes; - * syntax highlighting of course, and you may want to customize your [syntax highlighting style](https://highlightjs.org/), it's supported well; - * Animation in single page, one-step animation; - * [forward and backward callback function](#callback) -## 0.9.0 new features - +## 1.0.0 new features * real time sync canvas drawing across multiple devices - + * watch * add buttons to control page-flipping - * bugs fixed ## demo * http://qdemo.sinaapp.com/ - * sync multiscreen in real time: http://qdemo.sinaapp.com/?_multiscreen=1 (make sure alert is allowed in your browser) - * front-end experience of mobile baidu: http://qdemo.sinaapp.com/box-fe-road.htm ## customize your theme * default theme is not cool? just customize your theme! take a look with [theme.moon](https://github.com/ksky521/nodePPT/blob/master/assets/scss/theme.moon.scss) - * write your customize theme's template path in setting md: ```markdown diff --git a/assets/js/nodeppt.control.js b/assets/js/nodeppt.control.js index ab6627bf1..4c1ba419c 100755 --- a/assets/js/nodeppt.control.js +++ b/assets/js/nodeppt.control.js @@ -46,8 +46,6 @@ t.sendUpdateItem(slideID, buildItem); }).on('slide event keyup', function(e) { t.sendKeyEvent(e.keyCode); - }).on('overview', function(e) { - t.sendKeyEvent(79); }).on('show paint', function(e){ t.sendKeyEvent(80); }).on('remove paint', function(){ diff --git a/assets/js/nodeppt.control.socket.js b/assets/js/nodeppt.control.socket.js index 350ad0b69..9b56df95d 100755 --- a/assets/js/nodeppt.control.socket.js +++ b/assets/js/nodeppt.control.socket.js @@ -90,20 +90,12 @@ Slide.Control.add('socket', function(S, broadcast) { action = action.replace('client', 'control'); broadcast.fire(action, data); - - // switch (action) { - // case 'from client update': - // broadcast.fire('from control update', data); - // break; - // case 'from client updateItem': - // broadcast.fire('from control updateItem', data); - // break; - // } }); }, connect: function() { - webSocket = io.connect(this.host); + webSocket = io.connect(location.host+'/ppt'); + // console.log(io); webSocket.on('UUID', function(uid) { webSocket.uid = uid; if (Socket.role === 'client') { @@ -111,7 +103,7 @@ Slide.Control.add('socket', function(S, broadcast) { qrcodeLink(); var url = location.href.split('#')[0]; url += (!~url.indexOf('?')) ? '?' : '&'; - url += 'iscontroller=1&clientid=' + uid; + url += 'iscontroller=1&clientid=' + encodeURIComponent(uid); var qrcode = new QRCode('qrcode', { text: url, width: 256, @@ -162,7 +154,6 @@ Slide.Control.add('socket', function(S, broadcast) { // console.log(this.clientUID); //角色,是否为控制端 if (args.isControl) { - console.log(this.clientUID); this.role = 'control'; var $body = document.body; $body.classList.add('popup'); @@ -202,9 +193,15 @@ Slide.Control.add('socket', function(S, broadcast) { }); } - MixJS.loadJS(socketIOURL, function() { + if (io && io.connect) { + //已经存在 Socket.connect(); - }); + } else { + MixJS.loadJS(socketIOURL, function() { + Socket.connect(); + }); + } + } }; return Socket; diff --git a/assets/js/nodeppt.js b/assets/js/nodeppt.js index d01376fd7..58ecc5a8b 100755 --- a/assets/js/nodeppt.js +++ b/assets/js/nodeppt.js @@ -185,7 +185,7 @@ build: item.dataset.index }) list = item.classList; - $B.fire('slide.update',curIndex|0, item.dataset.index|0+1); + $B.fire('slide.update', curIndex | 0, item.dataset.index | 0 + 1); list.remove('to-build'); list.add('building'); @@ -240,7 +240,7 @@ if ($doc.body.classList.contains('overview')) { focusOverview_(); return; - }else if(!$doc.body.classList.contains('popup')){ + } else if (!$doc.body.classList.contains('popup')) { $doc.body.classList.remove('with-notes'); } @@ -283,7 +283,7 @@ break; } } - $B.fire('slide.update', curIndex,0, pageClass); + $B.fire('slide.update', curIndex, 0, pageClass); } @@ -345,102 +345,101 @@ /*************************events***************/ //pc键盘翻页事件逻辑 - function evtDocUp(e) { - var key = e.keyCode; - var target = e.target; - //防止input和textarea,和可以编辑tag - if (/^(input|textarea)$/i.test(target.nodeName) || target.isContentEditable) { - return; - } - if (!e.isFromControl) { - switch (key) { - case 13: - case 72: - case 87: - case 79: - case 78: - case 80: - case 67: - $B.fire('slide event keyup', e); - break; - } - } + var key = e.keyCode; + var target = e.target; + //防止input和textarea,和可以编辑tag + if (/^(input|textarea)$/i.test(target.nodeName) || target.isContentEditable) { + return; + } + if (!e.isFromControl) { switch (key) { case 13: - // Enter - if ($doc.body.classList.contains('overview')) { - overview(e.isFromControl); - } - - break; case 72: - // H: Toggle code highlighting - $doc.body.classList.toggle('highlight-code'); - setTimeout(function() { - $doc.body.classList.toggle('highlight-code'); - }, 2000); - break; - // 下掉宽屏模式,默认width:100% case 87: - // W: Toggle widescreen - // Only respect 'w' on body. Don't want to capture keys from an . - if (!(e.shiftKey && e.metaKey)) { - if (!$body.classList.contains('popup')) - $container.classList.toggle('layout-widescreen'); - } - break; case 79: - // O: Toggle overview - overview(e.isFromControl); - - break; case 78: - // N - if (!$body.classList.contains('popup')) - $doc.body.classList.toggle('with-notes'); - break; case 80: - //P - if (!$body.classList.contains('popup')) { - showPaint(e.isFromControl); - } - break; case 67: - //c - if (!$body.classList.contains('popup')) { - removePaint(e.isFromControl); - } - break; - //上一页 - case 33: - // pg up - case 37: - // left - case 38: - // up - prevSlide(); - break; - //下一页 - // case 9: - // tab - case 32: - // space - case 34: - // pg down - case 39: - // right - case 40: - // down - nextSlide() + $B.fire('slide event keyup', e); break; } - - // $container.style.marginLeft = -(curIndex * slideWidth) + 'px'; - // setProgress(); - // setHistory(); } - /******************************** Touch events *********************/ + switch (key) { + case 13: + // Enter + if ($doc.body.classList.contains('overview')) { + overview(e.isFromControl); + } + + break; + case 72: + // H: Toggle code highlighting + $doc.body.classList.toggle('highlight-code'); + setTimeout(function() { + $doc.body.classList.toggle('highlight-code'); + }, 2000); + break; + // 下掉宽屏模式,默认width:100% + case 87: + // W: Toggle widescreen + // Only respect 'w' on body. Don't want to capture keys from an . + if (!(e.shiftKey && e.metaKey)) { + if (!$body.classList.contains('popup')) + $container.classList.toggle('layout-widescreen'); + } + break; + case 79: + // O: Toggle overview + overview(e.isFromControl); + + break; + case 78: + // N + if (!$body.classList.contains('popup')) + $doc.body.classList.toggle('with-notes'); + break; + case 80: + //P + if (!$body.classList.contains('popup')) { + showPaint(e.isFromControl); + } + break; + case 67: + //c + if (!$body.classList.contains('popup')) { + removePaint(e.isFromControl); + } + break; + //上一页 + case 33: + // pg up + case 37: + // left + case 38: + // up + prevSlide(); + break; + //下一页 + // case 9: + // tab + case 32: + // space + case 34: + // pg down + case 39: + // right + case 40: + // down + nextSlide() + break; + } + + // $container.style.marginLeft = -(curIndex * slideWidth) + 'px'; + // setProgress(); + // setHistory(); + }; + /******************************** Touch events *********************/ var isStopTouchEvent = false; function evtTouchStart(event) { diff --git a/bin/nodeppt b/bin/nodeppt index 2fcf28c7a..05fe7d64f 100755 --- a/bin/nodeppt +++ b/bin/nodeppt @@ -70,6 +70,7 @@ program .option('-p, --port [port]', 'set server port ', 8080) .option('-c, --controller [socket]', 'support websocket mutil screen controller') .option('-H, --host [host]', 'set host address', ipv4 || '0.0.0.0') + .option('-w, --watch', 'livereload') .action(function (cmd){ if(typeof cmd !== 'object'){ this.outputHelp(); diff --git a/lib/server.js b/lib/server.js index b48557d80..bf70a8b60 100755 --- a/lib/server.js +++ b/lib/server.js @@ -10,9 +10,10 @@ var libDir = __dirname; var rootDir = path.join(libDir, '../') + path.sep; var templateDir = path.join(rootDir, 'template') + path.sep; +var chokidar = require('chokidar'); var md_parser = require('./md_parser'); -//session 相关 -var cookie = require('cookie'); +//session相关 +var Cookie = require('cookie'); var parseSignedCookie = connect.utils.parseSignedCookie; var MemoryStore = connect.middleware.session.MemoryStore; var Session = connect.middleware.session.Session; @@ -20,9 +21,13 @@ var Session = connect.middleware.session.Session; var storeMemory = new MemoryStore({ reapInterval: 60000 * 10 }); - module.exports.start = function(port, pptDir, host, argvObj) { port = parseInt(port, 10) || 8080; + pptDir = (pptDir || path.join(__dirname, '../ppts')) + path.sep; + try { + pptDir = fs.realpathSync(pptDir) + path.sep; + } catch (e) {} + var app = startApp(port, pptDir, host, argvObj); var io = require('socket.io').listen(app, { log: false, @@ -32,10 +37,10 @@ module.exports.start = function(port, pptDir, host, argvObj) { io.set('authorization', function(handshakeData, accept) { // 通过客户端的cookie字符串来获取其session数据 var ccc = ''; - if(handshakeData.headers && handshakeData.headers.cookie){ + if (handshakeData.headers && handshakeData.headers.cookie) { ccc = handshakeData.headers.cookie; } - handshakeData.cookie = cookie.parse(ccc); + handshakeData.cookie = Cookie.parse(ccc); var connect_sid = parseSignedCookie(handshakeData.cookie['connect.sid'], 'wyq'); if (connect_sid) { storeMemory.get(connect_sid, function(error, session) { @@ -53,19 +58,38 @@ module.exports.start = function(port, pptDir, host, argvObj) { accept('nosession'); } }); + //watcher对应的socket + var watchSockets = {}; + if (argvObj.watch) { + //添加watch功能 + var exclude = ["\\/\\.", "node_modules"]; + + var watcher = chokidar.watch(pptDir, { + ignored: new RegExp(exclude.join('|')), + persistent: true + }); + watcher.on('change', function() { + sendChangedNotice(); + }); + io.of('/watcher').on('connection', function(socket) { + watchSockets[socket.id] = socket; + }); + } + + //用户双屏通信 var now = Date.now(); var sockets = {}; var userMap = {}; - io.sockets.on('connection', function(socket) { - var session = socket.handshake.session; //session - // var uid = now++; - var uid = session.uid; + io.of('/ppt').on('connection', function(socket) { + //解析cookie中的sid + var cookie = socket.handshake.headers.cookie; + cookie = Cookie.parse(cookie || ''); + var uid = cookie['connect.sid']; socket.uid = uid; - console.log('socket.io:' + uid); sockets[uid] = socket; //监听添加map @@ -88,7 +112,6 @@ module.exports.start = function(port, pptDir, host, argvObj) { var otherSocket = sockets[targetUid]; otherSocket && otherSocket.emit('data from another client', data); }); - //将当前分配的uid告知客户端 socket.emit('UUID', uid); @@ -104,15 +127,23 @@ module.exports.start = function(port, pptDir, host, argvObj) { }); }); + + function sendChangedNotice() { + if (!Object.keys(watchSockets)) { + return console.error('[watch-connect]', 'No client connected to socket.io'); + } + Object.keys(watchSockets).forEach(function(s) { + if (watchSockets[s] && watchSockets[s].emit) { + watchSockets[s].emit('file changed'); + } + }); + } }; function startApp(port, dir, host, argvObj) { host = host || '0.0.0.0'; var staticDir = path.normalize(path.join(__dirname, '../assets')) + path.sep; - var pptDir = (dir || path.join(__dirname, '../ppts')) + path.sep; - try { - pptDir = fs.realpathSync(pptDir) + path.sep; - } catch (e) {} + var pptDir = dir; var now = Date.now(); var app = connect( @@ -121,33 +152,16 @@ function startApp(port, dir, host, argvObj) { secret: 'wyq', store: storeMemory }), - function(req, res) { var url = URL.parse(req.url).pathname; var dirname = path.dirname(url); var realPath, ext; - - var uid = req.session.uid; - if (!uid) { - uid = now++; //当前连接用户的UID - req.session.uid = uid; - console.log('new user: ' + uid); - } else { - console.log('welcome back ' + uid); - } - if (url === '/') { + //根目录显示list pptlist(res, pptDir, argvObj); return; } else if (dirname === '/md') { - uid = req.session.uid; - if (!uid) { - uid = now++; //当前连接用户的UID - req.session.uid = uid; - console.log('new user: ' + uid); - } else { - console.log('welcome back ' + uid); - } + //md文件解析 url = URL.parse(req.url).pathname; var basename = path.basename(url); var queryObj = URL.parse(req.url, true).query; @@ -173,7 +187,7 @@ function startApp(port, dir, host, argvObj) { var url = ' http://' + address + ':' + server.port console.log('ppt directory:'.cyan + ' ' + pptDir); - console.log('assets directory:'.cyan + ' '+staticDir); + console.log('assets directory:'.cyan + ' ' + staticDir); console.log('nodeppt server started:'.cyan + url.yellow); if (process.platform === 'win32') { @@ -232,6 +246,15 @@ function markdown(realPath, url, res, argvObj, queryObj) { var content = fs.readFileSync(realPath, 'utf-8').toString(); try { var html = md_parser(content, function() {}, argvObj, queryObj); + //添加socket的html代码 + var extraHtml = ''; + if (argvObj.watch) { + extraHtml += getWatcherHtml(); + } + if (extraHtml.trim() !== '') { + html = html.split('').join(extraHtml); + } + res.writeHead(200, { 'Powered-By': 'nodePPT', 'Content-Type': mimes.html @@ -324,3 +347,17 @@ function pptlist(res, dir, argvObj) { } res.end(); } + + +function getWatcherHtml() { + var html = ['', + '' + ].join(''); + return html; +} diff --git a/package.json b/package.json index b46d0dd4f..2bf48f596 100755 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "nodeppt", "jsname": "nodeppt", "description": "A simple, in-browser, markdown-driven presentation framework", - "version": "0.9.8-5", + "version": "1.0.0", "site": "https://github.com/ksky521/nodePPT", "author": { "name": "Theo Wang", @@ -23,7 +23,8 @@ "ejs": "0.8.4", "ipv4": "0.0.4", "read": ">=1.0.4", - "socket.io": "0.9.13" + "socket.io": "1.3.3", + "chokidar":"1.0.0-rc3" }, "devDependencies": { "grunt": ">=0.4.0", diff --git a/template/markdown.ejs b/template/markdown.ejs index 95bdba6bd..3f7bc39aa 100755 --- a/template/markdown.ejs +++ b/template/markdown.ejs @@ -79,7 +79,7 @@ Slide.init({ args:{ isControl: <% if (query.iscontroller) {%>true<%} else{%> false<%}%>, host: base, - clientId: <% if (query.clientid) {%><%-query.clientid%><%} else{%> 0<%}%>, + clientId: "<% if (query.clientid) {%><%-query.clientid%><%} else{%> 0<%}%>", //摇一摇 shake: <% if (!query.noshake) {%>true<%} else{%> false<%}%> } @@ -102,5 +102,6 @@ MixJS.loadJS('highlight/hljs-0.8.js',function(){ }); <%- files %> +