From a14e7953b5a2196eda001229d44ebb2e9c18fe7c Mon Sep 17 00:00:00 2001 From: Wu Cheng-Han Date: Sat, 30 Jul 2016 11:28:24 +0800 Subject: [PATCH] Add support of showing authorship in editor and adjust related styles --- lib/realtime.js | 6 +- public/css/index.css | 3 + public/js/index.js | 210 +++++++++++++++++- .../spell-checker.min.css | 2 +- 4 files changed, 218 insertions(+), 3 deletions(-) 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 = $('
', { + class: 'authorship-gutter ' + className, + title: author.name + }); + editor.setGutterMarker(lineNumber, "authorship-gutters", gutter[0]); + } + } else { + editor.setGutterMarker(lineNumber, "authorship-gutters", null); + } + if (currMark && currMark.textmarkers.length > 0) { + for (var i = 0; i < currMark.textmarkers.length; i++) { + var textMarker = currMark.textmarkers[i]; + if (textMarker.userid != currMark.gutter.userid) { + addTextMarkers.push(textMarker); + } + } + } + }); + var allTextMarks = editor.getAllMarks(); + for (var i = 0; i < allTextMarks.length; i++) { + var _textMarker = allTextMarks[i]; + var pos = _textMarker.find(); + var found = false; + for (var j = 0; j < addTextMarkers.length; j++) { + var textMarker = addTextMarkers[j]; + var author = authors[textMarker.userid]; + var className = 'authorship-inline-' + author.color.substr(1); + var obj = { + from: textMarker.pos[0], + to: textMarker.pos[1] + }; + if (JSON.stringify(pos) == JSON.stringify(obj) && _textMarker.className && + _textMarker.className.indexOf(className) > -1) { + addTextMarkers.splice(j, 1); + j--; + found = true; + break; + } + } + if (!found && _textMarker.className && _textMarker.className.indexOf('authorship-inline') > -1) { + _textMarker.clear(); + } + } + for (var i = 0; i < addTextMarkers.length; i++) { + var textMarker = addTextMarkers[i]; + var author = authors[textMarker.userid]; + var rgbcolor = hex2rgb(author.color); + var colorString = "rgba(" + rgbcolor.red + "," + rgbcolor.green + "," + rgbcolor.blue + ",0.7)"; + var styleString = textMarkderStylePrefix + colorString + textMarkderStylePostfix; + var className = 'authorship-inline-' + author.color.substr(1); + var rule = "." + className + "{" + styleString + "}"; + addStyleRule(rule); + var _textMarker = editor.markText(textMarker.pos[0], textMarker.pos[1], { + className: 'authorship-inline ' + className, + title: author.name + }); + } + authorshipMarks = authorMarks; +} +editor.on('update', function () { + $('.authorship-gutter:not([data-original-title])').tooltip({ + container: '.CodeMirror-lines', + placement: 'right', + delay: { "show": 500, "hide": 100 } + }); + $('.authorship-inline:not([data-original-title])').tooltip({ + container: '.CodeMirror-lines', + placement: 'bottom', + delay: { "show": 500, "hide": 100 } + }); + // clear tooltip which described element has been removed + $('[id^="tooltip"]').each(function (index, element) { + $ele = $(element); + if ($('[aria-describedby="' + $ele.attr('id') + '"]').length <= 0) $ele.remove(); + }); +}); socket.on('check', function (data) { //console.log(data); updateLastInfo(data); diff --git a/public/vendor/codemirror-spell-checker/spell-checker.min.css b/public/vendor/codemirror-spell-checker/spell-checker.min.css index 643c24f..d6d1226 100644 --- a/public/vendor/codemirror-spell-checker/spell-checker.min.css +++ b/public/vendor/codemirror-spell-checker/spell-checker.min.css @@ -4,4 +4,4 @@ * @link https://github.com/NextStepWebs/codemirror-spell-checker * @license MIT */ -.CodeMirror .cm-spell-error:not(.cm-url):not(.cm-comment):not(.cm-tag):not(.cm-word){border-bottom:1px dashed rgba(255,0,0,.8)} \ No newline at end of file +.CodeMirror .cm-spell-error:not(.cm-url):not(.cm-comment):not(.cm-tag):not(.cm-word){border-bottom:2px dotted rgba(255,0,0,.8)} \ No newline at end of file