diff --git a/lib/realtime.js b/lib/realtime.js index 6808957..7e93f20 100644 --- a/lib/realtime.js +++ b/lib/realtime.js @@ -65,7 +65,9 @@ function emitCheck(note) { var out = { updatetime: note.updatetime, lastchangeuser: note.lastchangeuser, - lastchangeuserprofile: note.lastchangeuserprofile + lastchangeuserprofile: note.lastchangeuserprofile, + authors: note.authors, + authorship: note.authorship }; realtime.io.to(note.id).emit('check', out); } @@ -314,6 +316,8 @@ function emitRefresh(socket) { ownerprofile: note.ownerprofile, lastchangeuser: note.lastchangeuser, lastchangeuserprofile: note.lastchangeuserprofile, + authors: note.authors, + authorship: note.authorship, permission: note.permission, createtime: note.createtime, updatetime: note.updatetime diff --git a/public/css/index.css b/public/css/index.css index 27834c8..d586128 100644 --- a/public/css/index.css +++ b/public/css/index.css @@ -49,6 +49,9 @@ body { min-width: 1.5em; text-align: right; } +.CodeMirror-gutter.authorship-gutters { + width: 8px; +} .CodeMirror-matchingtag { background: rgba(255, 255, 255, .1); line-height: 1em; diff --git a/public/js/index.js b/public/js/index.js index 6a1c6e7..ac45d22 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -287,7 +287,7 @@ var editor = CodeMirror.fromTextArea(textit, { }, autoCloseTags: true, foldGutter: true, - gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"], + gutters: ["CodeMirror-linenumbers", "authorship-gutters", "CodeMirror-foldgutter"], extraKeys: defaultExtraKeys, flattenSpans: true, addModeClass: true, @@ -2127,6 +2127,9 @@ socket.on('version', function (data) { } } }); +var authors = []; +var authorship = []; +var authorshipMarks = {}; function updateLastInfo(data) { //console.log(data); if (data.hasOwnProperty('createtime') && createtime !== data.createtime) { @@ -2142,7 +2145,212 @@ function updateLastInfo(data) { lastchangeuserprofile = data.lastchangeuserprofile; updateLastChangeUser(); } + if (data.hasOwnProperty('authors') && authors !== data.authors) { + authors = data.authors; + } + if (data.hasOwnProperty('authorship') && authorship !== data.authorship) { + authorship = data.authorship; + updateAuthorship(); + } } +var updateAuthorship = _.throttle(updateAuthorshipInner, 50); +function initMark() { + return { + gutter: { + userid: null, + timestamp: null + }, + textmarkers: [] + }; +} +function initMarkAndCheckGutter(mark, author, timestamp) { + if (!mark) mark = initMark(); + if (!mark.gutter.userid || mark.gutter.timestamp > timestamp) { + mark.gutter.userid = author.userid; + mark.gutter.timestamp = timestamp; + } + return mark; +} +var gutterStylePrefix = "border-left: 3px solid "; +var gutterStylePostfix = "; height: " + defaultTextHeight + "px; margin-left: 3px;"; +var textMarkderStylePrefix = "background-image: linear-gradient(to top, "; +var textMarkderStylePostfix = " 1px, transparent 1px);"; +var addStyleRule = (function () { + var added = {}; + var styleElement = document.createElement('style'); + document.documentElement.getElementsByTagName('head')[0].appendChild(styleElement); + var styleSheet = styleElement.sheet; + + return function (css) { + if (added[css]) { + return; + } + added[css] = true; + styleSheet.insertRule(css, (styleSheet.cssRules || styleSheet.rules).length); + }; +}()); +function updateAuthorshipInner() { + // ignore when ot not synced yet + if (Object.keys(cmClient.state).length > 0) return; + var authorMarks = {}; + for (var i = 0; i < authorship.length; i++) { + var atom = authorship[i]; + var author = authors[atom[0]]; + if (author) { + var prePos = editor.posFromIndex(atom[1]); + var preLine = editor.getLine(prePos.line); + var postPos = editor.posFromIndex(atom[2]); + var postLine = editor.getLine(postPos.line); + if (prePos.ch == 0 && postPos.ch == postLine.length) { + for (var j = prePos.line; j <= postPos.line; j++) { + if (editor.getLine(j)) { + authorMarks[j] = initMarkAndCheckGutter(authorMarks[j], author, atom[3]); + } + } + } else if (postPos.line - prePos.line >= 1) { + var startLine = prePos.line; + var endLine = postPos.line; + if (prePos.ch == preLine.length) { + startLine++; + } else if (prePos.ch != 0) { + var mark = initMarkAndCheckGutter(authorMarks[prePos.line], author, atom[3]); + var _postPos = { + line: prePos.line, + ch: preLine.length + }; + if (JSON.stringify(prePos) != JSON.stringify(_postPos)) { + mark.textmarkers.push({ + userid: author.userid, + pos: [prePos, _postPos] + }); + startLine++; + } + authorMarks[prePos.line] = mark; + } + if (postPos.ch == 0) { + endLine--; + } else if (postPos.ch != postLine.length) { + var mark = initMarkAndCheckGutter(authorMarks[postPos.line], author, atom[3]); + var _prePos = { + line: postPos.line, + ch: 0 + }; + if (JSON.stringify(_prePos) != JSON.stringify(postPos)) { + mark.textmarkers.push({ + userid: author.userid, + pos: [_prePos, postPos] + }); + endLine--; + } + authorMarks[postPos.line] = mark; + } + for (var j = startLine; j <= endLine; j++) { + if (editor.getLine(j)) { + authorMarks[j] = initMarkAndCheckGutter(authorMarks[j], author, atom[3]); + } + } + } else { + var mark = initMarkAndCheckGutter(authorMarks[prePos.line], author, atom[3]); + if (JSON.stringify(prePos) != JSON.stringify(postPos)) { + mark.textmarkers.push({ + userid: author.userid, + pos: [prePos, postPos] + }); + } + authorMarks[prePos.line] = mark; + } + } + } + var addTextMarkers = []; + editor.eachLine(function (line) { + var lineNumber = editor.getLineNumber(line); + var currMark = authorMarks[lineNumber]; + var author = currMark ? authors[currMark.gutter.userid] : null; + if (currMark && author) { + var className = 'authorship-gutter-' + author.color.substr(1); + var gutters = editor.getLineHandle(lineNumber).gutterMarkers; + if (!gutters || !gutters['authorship-gutters'] || + !gutters['authorship-gutters'].className || + !gutters['authorship-gutters'].className.indexOf(className) < 0) { + var styleString = gutterStylePrefix + author.color + gutterStylePostfix; + var rule = "." + className + "{" + styleString + "}"; + addStyleRule(rule); + var gutter = $('