Comments realtime sync
This commit is contained in:
parent
9039cac7f5
commit
e310d234cf
18
.eslintrc
Normal file
18
.eslintrc
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"amd": true
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"curly": 2,
|
||||||
|
"no-comma-dangle": 0,
|
||||||
|
"strict": 0,
|
||||||
|
"quotes": 0,
|
||||||
|
"no-underscore-dangle": 0,
|
||||||
|
"consistent-return": 0,
|
||||||
|
"no-unused-expressions": 0,
|
||||||
|
"no-use-before-define": 0,
|
||||||
|
"camelcase": 0,
|
||||||
|
"eqeqeq": 0
|
||||||
|
}
|
||||||
|
}
|
@ -16,13 +16,6 @@ module.exports = function(grunt) {
|
|||||||
jshint: {
|
jshint: {
|
||||||
options: {
|
options: {
|
||||||
jshintrc: true,
|
jshintrc: true,
|
||||||
ignores: [
|
|
||||||
'node_modules/**/*.js',
|
|
||||||
'public/libs/**/*.js',
|
|
||||||
'public/res/libs/**/*.js',
|
|
||||||
'public/res/bower-libs/**/*.js',
|
|
||||||
'public/res-min/**/*.js'
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
client: ['public/**/*.js'],
|
client: ['public/**/*.js'],
|
||||||
},
|
},
|
||||||
|
@ -12,6 +12,7 @@ define([
|
|||||||
this._editorEnd = parseInt(storage[fileIndex + ".editorEnd"]) || 0;
|
this._editorEnd = parseInt(storage[fileIndex + ".editorEnd"]) || 0;
|
||||||
this._previewScrollTop = parseInt(storage[fileIndex + ".previewScrollTop"]) || 0;
|
this._previewScrollTop = parseInt(storage[fileIndex + ".previewScrollTop"]) || 0;
|
||||||
this._selectTime = parseInt(storage[fileIndex + ".selectTime"]) || 0;
|
this._selectTime = parseInt(storage[fileIndex + ".selectTime"]) || 0;
|
||||||
|
this._discussionList = JSON.parse(storage[fileIndex + ".discussionList"] || '{}');
|
||||||
this.syncLocations = syncLocations || {};
|
this.syncLocations = syncLocations || {};
|
||||||
this.publishLocations = publishLocations || {};
|
this.publishLocations = publishLocations || {};
|
||||||
Object.defineProperty(this, 'title', {
|
Object.defineProperty(this, 'title', {
|
||||||
@ -76,6 +77,15 @@ define([
|
|||||||
storage[this.fileIndex + ".selectTime"] = selectTime;
|
storage[this.fileIndex + ".selectTime"] = selectTime;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Object.defineProperty(this, 'discussionList', {
|
||||||
|
get: function() {
|
||||||
|
return this._discussionList;
|
||||||
|
},
|
||||||
|
set: function(discussionList) {
|
||||||
|
this._discussionList = discussionList;
|
||||||
|
storage[this.fileIndex + ".discussionList"] = JSON.stringify(discussionList);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
FileDescriptor.prototype.addSyncLocation = function(syncAttributes) {
|
FileDescriptor.prototype.addSyncLocation = function(syncAttributes) {
|
||||||
|
@ -799,7 +799,7 @@ define([
|
|||||||
});
|
});
|
||||||
$(".action-import-docs-settings-confirm").click(function() {
|
$(".action-import-docs-settings-confirm").click(function() {
|
||||||
storage.clear();
|
storage.clear();
|
||||||
var allowedKeys = /^file\.|^folder\.|^publish\.|^settings$|^sync\.|^google\.|^themeV3$|^mode$|^version$|^welcomeTour$/;
|
var allowedKeys = /^file\.|^folder\.|^publish\.|^settings$|^sync\.|^google\.|^themeV3$|^version$/;
|
||||||
_.each(newstorage, function(value, key) {
|
_.each(newstorage, function(value, key) {
|
||||||
if(allowedKeys.test(key)) {
|
if(allowedKeys.test(key)) {
|
||||||
storage[key] = value;
|
storage[key] = value;
|
||||||
|
@ -10,11 +10,11 @@ define([
|
|||||||
'libs/prism-markdown'
|
'libs/prism-markdown'
|
||||||
], function ($, _, settings, eventMgr, Prism, crel) {
|
], function ($, _, settings, eventMgr, Prism, crel) {
|
||||||
|
|
||||||
String.prototype.splice = function (i, remove, add) {
|
function strSplice(str, i, remove, add) {
|
||||||
remove = +remove || 0;
|
remove = +remove || 0;
|
||||||
add = add || '';
|
add = add || '';
|
||||||
return this.slice(0, i) + add + this.slice(i + remove);
|
return str.slice(0, i) + add + str.slice(i + remove);
|
||||||
};
|
}
|
||||||
|
|
||||||
var editor = {};
|
var editor = {};
|
||||||
var selectionStart = 0;
|
var selectionStart = 0;
|
||||||
@ -87,14 +87,14 @@ define([
|
|||||||
currentTextContent += '\n';
|
currentTextContent += '\n';
|
||||||
}
|
}
|
||||||
fileDesc.content = currentTextContent;
|
fileDesc.content = currentTextContent;
|
||||||
eventMgr.onContentChanged(fileDesc);
|
eventMgr.onContentChanged(fileDesc, currentTextContent);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if(!/\n$/.test(currentTextContent)) {
|
if(!/\n$/.test(currentTextContent)) {
|
||||||
currentTextContent += '\n';
|
currentTextContent += '\n';
|
||||||
fileDesc.content = currentTextContent;
|
fileDesc.content = currentTextContent;
|
||||||
}
|
}
|
||||||
eventMgr.onFileOpen(fileDesc);
|
eventMgr.onFileOpen(fileDesc, currentTextContent);
|
||||||
previewElt.scrollTop = fileDesc.previewScrollTop;
|
previewElt.scrollTop = fileDesc.previewScrollTop;
|
||||||
selectionStart = fileDesc.editorStart;
|
selectionStart = fileDesc.editorStart;
|
||||||
selectionEnd = fileDesc.editorEnd;
|
selectionEnd = fileDesc.editorEnd;
|
||||||
@ -105,12 +105,108 @@ define([
|
|||||||
previousTextContent = currentTextContent;
|
previousTextContent = currentTextContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findOffset(ss) {
|
||||||
|
var offset = 0,
|
||||||
|
element = editor.contentElt,
|
||||||
|
container;
|
||||||
|
|
||||||
|
do {
|
||||||
|
container = element;
|
||||||
|
element = element.firstChild;
|
||||||
|
|
||||||
|
if (element) {
|
||||||
|
do {
|
||||||
|
var len = element.textContent.length;
|
||||||
|
|
||||||
|
if (offset <= ss && offset + len > ss) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += len;
|
||||||
|
} while (element = element.nextSibling);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!element) {
|
||||||
|
// It's the container's lastChild
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (element && element.hasChildNodes() && element.nodeType != 3);
|
||||||
|
|
||||||
|
if (element) {
|
||||||
|
return {
|
||||||
|
element: element,
|
||||||
|
offset: ss - offset
|
||||||
|
};
|
||||||
|
} else if (container) {
|
||||||
|
element = container;
|
||||||
|
|
||||||
|
while (element && element.lastChild) {
|
||||||
|
element = element.lastChild;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.nodeType === 3) {
|
||||||
|
return {
|
||||||
|
element: element,
|
||||||
|
offset: element.textContent.length
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
element: element,
|
||||||
|
offset: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
element: editor.contentElt,
|
||||||
|
offset: 0,
|
||||||
|
error: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCoordinates(inputOffset, element, offset) {
|
||||||
|
var x = 0;
|
||||||
|
var y = 0;
|
||||||
|
if(element.textContent == '\n') {
|
||||||
|
y = element.parentNode.offsetTop + element.parentNode.offsetHeight / 2;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var selectedChar = inputElt.textContent[inputOffset];
|
||||||
|
var selectionRange;
|
||||||
|
if(selectedChar === undefined || selectedChar == '\n') {
|
||||||
|
selectionRange = inputElt.createRange(inputOffset - 1, {
|
||||||
|
element: element,
|
||||||
|
offset: offset
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
selectionRange = inputElt.createRange({
|
||||||
|
element: element,
|
||||||
|
offset: offset
|
||||||
|
}, inputOffset + 1);
|
||||||
|
}
|
||||||
|
var selectionRect = selectionRange.getBoundingClientRect();
|
||||||
|
y = selectionRect.top + selectionRect.height / 2 - inputElt.offsetTop + inputElt.scrollTop;
|
||||||
|
selectionRange.detach();
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
x: x,
|
||||||
|
y: y
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
var cursorY = 0;
|
var cursorY = 0;
|
||||||
function saveCursorCoordinates() {
|
var isBackwardSelection = false;
|
||||||
|
function updateCursorCoordinates() {
|
||||||
saveEditorState();
|
saveEditorState();
|
||||||
$inputElt.toggleClass('has-selection', selectionStart !== selectionEnd);
|
$inputElt.toggleClass('has-selection', selectionStart !== selectionEnd);
|
||||||
|
|
||||||
var backwards = false;
|
var element;
|
||||||
|
var offset;
|
||||||
|
var inputOffset;
|
||||||
|
if(inputElt.focused) {
|
||||||
|
isBackwardSelection = false;
|
||||||
var selection = window.getSelection();
|
var selection = window.getSelection();
|
||||||
if(!selection.rangeCount) {
|
if(!selection.rangeCount) {
|
||||||
return;
|
return;
|
||||||
@ -119,34 +215,28 @@ define([
|
|||||||
var range = document.createRange();
|
var range = document.createRange();
|
||||||
range.setStart(selection.anchorNode, selection.anchorOffset);
|
range.setStart(selection.anchorNode, selection.anchorOffset);
|
||||||
range.setEnd(selection.focusNode, selection.focusOffset);
|
range.setEnd(selection.focusNode, selection.focusOffset);
|
||||||
backwards = range.collapsed;
|
isBackwardSelection = range.collapsed;
|
||||||
range.detach();
|
range.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
var selectionRange = selection.getRangeAt(0);
|
var selectionRange = selection.getRangeAt(0);
|
||||||
var container = backwards ? selectionRange.startContainer : selectionRange.endContainer;
|
element = isBackwardSelection ? selectionRange.startContainer : selectionRange.endContainer;
|
||||||
if(container.textContent == '\n') {
|
offset = isBackwardSelection ? selectionRange.startOffset : selectionRange.endOffset;
|
||||||
cursorY = container.parentNode.offsetTop + container.parentNode.offsetHeight / 2;
|
inputOffset = isBackwardSelection ? selectionStart : selectionEnd;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var cursorOffset = backwards ? selectionStart : selectionEnd;
|
inputOffset = isBackwardSelection ? selectionStart : selectionEnd;
|
||||||
var selectedChar = inputElt.textContent[cursorOffset];
|
var elementOffset = findOffset(inputOffset);
|
||||||
if(selectedChar === undefined || selectedChar == '\n') {
|
element = elementOffset.element;
|
||||||
selectionRange = inputElt.createRange(cursorOffset - 1, cursorOffset);
|
offset = elementOffset.offset;
|
||||||
}
|
}
|
||||||
else {
|
var coordinates = getCoordinates(inputOffset, element, offset);
|
||||||
selectionRange = inputElt.createRange(cursorOffset, cursorOffset + 1);
|
cursorY = coordinates.y;
|
||||||
}
|
eventMgr.onCursorCoordinates(coordinates.x, coordinates.y);
|
||||||
var selectionRect = selectionRange.getBoundingClientRect();
|
|
||||||
cursorY = selectionRect.top + selectionRect.height / 2 - inputElt.offsetTop + inputElt.scrollTop;
|
|
||||||
selectionRange.detach();
|
|
||||||
}
|
|
||||||
eventMgr.onCursorCoordinates(0, cursorY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function adjustCursorPosition() {
|
function adjustCursorPosition() {
|
||||||
inputElt && setTimeout(function() {
|
inputElt && setTimeout(function() {
|
||||||
saveCursorCoordinates();
|
updateCursorCoordinates();
|
||||||
|
|
||||||
var adjust = inputElt.offsetHeight / 2;
|
var adjust = inputElt.offsetHeight / 2;
|
||||||
if(adjust > 130) {
|
if(adjust > 130) {
|
||||||
@ -254,7 +344,7 @@ define([
|
|||||||
offset = range.startOffset;
|
offset = range.startOffset;
|
||||||
|
|
||||||
if (!(this.compareDocumentPosition(element) & 0x10)) {
|
if (!(this.compareDocumentPosition(element) & 0x10)) {
|
||||||
return 0;
|
return selectionStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
@ -269,7 +359,7 @@ define([
|
|||||||
|
|
||||||
return offset;
|
return offset;
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return selectionStart;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
set: function (value) {
|
set: function (value) {
|
||||||
@ -287,7 +377,7 @@ define([
|
|||||||
if (selection.rangeCount) {
|
if (selection.rangeCount) {
|
||||||
return this.selectionStart + (selection.getRangeAt(0) + '').length;
|
return this.selectionStart + (selection.getRangeAt(0) + '').length;
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return selectionEnd;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
set: function (value) {
|
set: function (value) {
|
||||||
@ -312,82 +402,29 @@ define([
|
|||||||
return {
|
return {
|
||||||
selectionStart: selectionStart,
|
selectionStart: selectionStart,
|
||||||
selectionEnd: selectionEnd
|
selectionEnd: selectionEnd
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
inputElt.createRange = function(ss, se) {
|
inputElt.createRange = function(ss, se) {
|
||||||
function findOffset(ss) {
|
|
||||||
var offset = 0,
|
|
||||||
element = editor.contentElt,
|
|
||||||
container;
|
|
||||||
|
|
||||||
do {
|
|
||||||
container = element;
|
|
||||||
element = element.firstChild;
|
|
||||||
|
|
||||||
if (element) {
|
|
||||||
do {
|
|
||||||
var len = element.textContent.length;
|
|
||||||
|
|
||||||
if (offset <= ss && offset + len > ss) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
offset += len;
|
|
||||||
} while (element = element.nextSibling);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!element) {
|
|
||||||
// It's the container's lastChild
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} while (element && element.hasChildNodes() && element.nodeType != 3);
|
|
||||||
|
|
||||||
if (element) {
|
|
||||||
return {
|
|
||||||
element: element,
|
|
||||||
offset: ss - offset
|
|
||||||
};
|
|
||||||
} else if (container) {
|
|
||||||
element = container;
|
|
||||||
|
|
||||||
while (element && element.lastChild) {
|
|
||||||
element = element.lastChild;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (element.nodeType === 3) {
|
|
||||||
return {
|
|
||||||
element: element,
|
|
||||||
offset: element.textContent.length
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
element: element,
|
|
||||||
offset: 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
element: editor.contentElt,
|
|
||||||
offset: 0,
|
|
||||||
error: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var range = document.createRange(),
|
var range = document.createRange(),
|
||||||
offset = findOffset(ss);
|
offset = _.isObject(ss) ? ss : findOffset(ss);
|
||||||
|
|
||||||
range.setStart(offset.element, offset.offset);
|
range.setStart(offset.element, offset.offset);
|
||||||
|
|
||||||
if (se && se != ss) {
|
if (se && se != ss) {
|
||||||
offset = findOffset(se);
|
offset = _.isObject(se) ? se : findOffset(se);
|
||||||
}
|
}
|
||||||
|
|
||||||
range.setEnd(offset.element, offset.offset);
|
range.setEnd(offset.element, offset.offset);
|
||||||
return range;
|
return range;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inputElt.getOffsetCoordinates = function(ss) {
|
||||||
|
var offset = findOffset(ss);
|
||||||
|
return getCoordinates(ss, offset.element, offset.offset);
|
||||||
|
};
|
||||||
|
|
||||||
var clearNewline = false;
|
var clearNewline = false;
|
||||||
editor.$contentElt.on('keydown', function (evt) {
|
editor.$contentElt.on('keydown', function (evt) {
|
||||||
if(
|
if(
|
||||||
@ -423,7 +460,7 @@ define([
|
|||||||
})
|
})
|
||||||
.on('mouseup', function() {
|
.on('mouseup', function() {
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
saveCursorCoordinates();
|
updateCursorCoordinates();
|
||||||
}, 0);
|
}, 0);
|
||||||
})
|
})
|
||||||
.on('paste', function () {
|
.on('paste', function () {
|
||||||
@ -463,7 +500,7 @@ define([
|
|||||||
|
|
||||||
if (options.inverse) {
|
if (options.inverse) {
|
||||||
if (/\s/.test(state.before.charAt(lf))) {
|
if (/\s/.test(state.before.charAt(lf))) {
|
||||||
state.before = state.before.splice(lf, 1);
|
state.before = strSplice(state.before, lf, 1);
|
||||||
|
|
||||||
state.ss--;
|
state.ss--;
|
||||||
state.se--;
|
state.se--;
|
||||||
@ -471,7 +508,7 @@ define([
|
|||||||
|
|
||||||
state.selection = state.selection.replace(/^[ \t]/gm, '');
|
state.selection = state.selection.replace(/^[ \t]/gm, '');
|
||||||
} else if (state.selection) {
|
} else if (state.selection) {
|
||||||
state.before = state.before.splice(lf, 0, '\t');
|
state.before = strSplice(state.before, lf, 0, '\t');
|
||||||
state.selection = state.selection.replace(/\r?\n(?=[\s\S])/g, '\n\t');
|
state.selection = state.selection.replace(/\r?\n(?=[\s\S])/g, '\n\t');
|
||||||
|
|
||||||
state.ss++;
|
state.ss++;
|
||||||
|
@ -12,29 +12,75 @@ define([
|
|||||||
var comments = new Extension("comments", 'Comments');
|
var comments = new Extension("comments", 'Comments');
|
||||||
|
|
||||||
var offsetMap = {};
|
var offsetMap = {};
|
||||||
|
function setCommentEltCoordinates(commentElt, y) {
|
||||||
|
var lineIndex = Math.round(y/10);
|
||||||
|
var top = (y - 8) + 'px';
|
||||||
|
var right = ((offsetMap[lineIndex] || 0) * 25 + 10) + 'px';
|
||||||
|
commentElt.style.top = top;
|
||||||
|
commentElt.style.right = right;
|
||||||
|
return lineIndex;
|
||||||
|
}
|
||||||
|
|
||||||
var inputElt;
|
var inputElt;
|
||||||
var marginElt;
|
var marginElt;
|
||||||
|
var commentEltList = [];
|
||||||
var newCommentElt = crel('a', {
|
var newCommentElt = crel('a', {
|
||||||
class: 'icon-comment new'
|
class: 'icon-comment new'
|
||||||
});
|
});
|
||||||
comments.onCursorCoordinates = function(cursorX, cursorY) {
|
var cursorY;
|
||||||
var top = (cursorY - 8) + 'px';
|
comments.onCursorCoordinates = function(x, y) {
|
||||||
var right = 10 + 'px';
|
cursorY = y;
|
||||||
newCommentElt.style.top = top;
|
setCommentEltCoordinates(newCommentElt, cursorY);
|
||||||
newCommentElt.style.right = right;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var fileDesc;
|
var fileDesc;
|
||||||
comments.onFileSelected = function(selectedFileDesc) {
|
comments.onFileSelected = function(selectedFileDesc) {
|
||||||
fileDesc = selectedFileDesc;
|
fileDesc = selectedFileDesc;
|
||||||
|
refreshDiscussions();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var openedPopover;
|
||||||
|
comments.onLayoutResize = function() {
|
||||||
|
openedPopover && openedPopover.popover('toggle').popover('destroy');
|
||||||
|
refreshDiscussions();
|
||||||
|
};
|
||||||
|
|
||||||
|
var refreshId;
|
||||||
|
function refreshDiscussions() {
|
||||||
|
clearTimeout(refreshId);
|
||||||
|
commentEltList.forEach(function(commentElt) {
|
||||||
|
marginElt.removeChild(commentElt);
|
||||||
|
});
|
||||||
|
commentEltList = [];
|
||||||
|
offsetMap = {};
|
||||||
|
var discussionList = _.map(fileDesc.discussionList, _.identity);
|
||||||
|
function refreshOne() {
|
||||||
|
if(discussionList.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var discussion = discussionList.pop();
|
||||||
|
var commentElt = crel('a', {
|
||||||
|
class: 'icon-comment'
|
||||||
|
});
|
||||||
|
commentElt.discussion = discussion;
|
||||||
|
var coordinates = inputElt.getOffsetCoordinates(discussion.selectionEnd);
|
||||||
|
var lineIndex = setCommentEltCoordinates(commentElt, coordinates.y);
|
||||||
|
offsetMap[lineIndex] = (offsetMap[lineIndex] || 0) + 1;
|
||||||
|
marginElt.appendChild(commentElt);
|
||||||
|
commentEltList.push(commentElt);
|
||||||
|
|
||||||
|
// Replace newCommentElt
|
||||||
|
setCommentEltCoordinates(newCommentElt, cursorY);
|
||||||
|
|
||||||
|
refreshId = setTimeout(refreshOne, 0);
|
||||||
|
}
|
||||||
|
refreshId = setTimeout(refreshOne, 50);
|
||||||
|
}
|
||||||
|
|
||||||
comments.onReady = function() {
|
comments.onReady = function() {
|
||||||
var cssApplier = rangy.createCssClassApplier("comment-highlight", {
|
var cssApplier = rangy.createCssClassApplier("comment-highlight", {
|
||||||
normalize: false
|
normalize: false
|
||||||
});
|
});
|
||||||
var openedPopover;
|
|
||||||
var selectionRange;
|
var selectionRange;
|
||||||
var rangyRange;
|
var rangyRange;
|
||||||
var currentDiscussion;
|
var currentDiscussion;
|
||||||
@ -42,7 +88,9 @@ define([
|
|||||||
inputElt = document.getElementById('wmd-input');
|
inputElt = document.getElementById('wmd-input');
|
||||||
marginElt = document.querySelector('#wmd-input > .editor-margin');
|
marginElt = document.querySelector('#wmd-input > .editor-margin');
|
||||||
marginElt.appendChild(newCommentElt);
|
marginElt.appendChild(newCommentElt);
|
||||||
$(document.body).append($('<div class="comments-popover">')).popover({
|
$(document.body).append(crel('div', {
|
||||||
|
class: 'comments-popover'
|
||||||
|
})).popover({
|
||||||
placement: 'auto top',
|
placement: 'auto top',
|
||||||
container: '.comments-popover',
|
container: '.comments-popover',
|
||||||
html: true,
|
html: true,
|
||||||
@ -51,8 +99,8 @@ define([
|
|||||||
return '...';
|
return '...';
|
||||||
}
|
}
|
||||||
var titleLength = currentDiscussion.selectionEnd - currentDiscussion.selectionStart;
|
var titleLength = currentDiscussion.selectionEnd - currentDiscussion.selectionStart;
|
||||||
var title = fileDesc.content.substr(currentDiscussion.selectionStart, titleLength > 18 ? 18 : titleLength);
|
var title = inputElt.textContent.substr(currentDiscussion.selectionStart, titleLength > 20 ? 20 : titleLength);
|
||||||
if(titleLength > 18) {
|
if(titleLength > 20) {
|
||||||
title += '...';
|
title += '...';
|
||||||
}
|
}
|
||||||
return title.replace(/&/g, '&').replace(/</g, '<').replace(/\u00a0/g, ' ');
|
return title.replace(/&/g, '&').replace(/</g, '<').replace(/\u00a0/g, ' ');
|
||||||
@ -65,7 +113,14 @@ define([
|
|||||||
selector: '#wmd-input > .editor-margin > .icon-comment'
|
selector: '#wmd-input > .editor-margin > .icon-comment'
|
||||||
}).on('show.bs.popover', '#wmd-input > .editor-margin', function(evt) {
|
}).on('show.bs.popover', '#wmd-input > .editor-margin', function(evt) {
|
||||||
$(evt.target).addClass('active');
|
$(evt.target).addClass('active');
|
||||||
inputElt.scrollTop += parseInt(evt.target.style.top) - inputElt.scrollTop - inputElt.offsetHeight * 2/3;
|
inputElt.scrollTop += parseInt(evt.target.style.top) - inputElt.scrollTop - inputElt.offsetHeight * 3 / 4;
|
||||||
|
|
||||||
|
// If it's an existing discussion
|
||||||
|
if(evt.target.discussion) {
|
||||||
|
currentDiscussion = evt.target.discussion;
|
||||||
|
selectionRange = inputElt.createRange(currentDiscussion.selectionStart, currentDiscussion.selectionEnd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Get selected text
|
// Get selected text
|
||||||
var inputSelection = inputElt.getSelectionStartEnd();
|
var inputSelection = inputElt.getSelectionStartEnd();
|
||||||
@ -88,12 +143,12 @@ define([
|
|||||||
currentDiscussion = {
|
currentDiscussion = {
|
||||||
selectionStart: selectionStart,
|
selectionStart: selectionStart,
|
||||||
selectionEnd: selectionEnd,
|
selectionEnd: selectionEnd,
|
||||||
comments: []
|
commentList: []
|
||||||
};
|
};
|
||||||
}).on('shown.bs.popover', '#wmd-input > .editor-margin', function(evt) {
|
}).on('shown.bs.popover', '#wmd-input > .editor-margin', function(evt) {
|
||||||
|
|
||||||
// Move the popover in the margin
|
// Move the popover in the margin
|
||||||
var popoverElt = document.querySelector('.comments-popover .popover');
|
var popoverElt = document.querySelector('.comments-popover .popover:last-child');
|
||||||
var left = -10;
|
var left = -10;
|
||||||
if(popoverElt.offsetWidth < marginElt.offsetWidth) {
|
if(popoverElt.offsetWidth < marginElt.offsetWidth) {
|
||||||
left = marginElt.offsetWidth - popoverElt.offsetWidth - 10;
|
left = marginElt.offsetWidth - popoverElt.offsetWidth - 10;
|
||||||
@ -101,25 +156,47 @@ define([
|
|||||||
popoverElt.style.left = left + 'px';
|
popoverElt.style.left = left + 'px';
|
||||||
popoverElt.querySelector('.arrow').style.left = (marginElt.offsetWidth - parseInt(evt.target.style.right) - evt.target.offsetWidth / 2 - left) + 'px';
|
popoverElt.querySelector('.arrow').style.left = (marginElt.offsetWidth - parseInt(evt.target.style.right) - evt.target.offsetWidth / 2 - left) + 'px';
|
||||||
|
|
||||||
var textarea = popoverElt.querySelector('.input-comment-content');
|
var $textarea = $(popoverElt.querySelector('.input-comment-content'));
|
||||||
$(popoverElt.querySelector('.action-add-comment')).click(function(evt) {
|
var $addButton = $(popoverElt.querySelector('.action-add-comment'));
|
||||||
|
$textarea.keydown(function(evt) {
|
||||||
|
// Enter key
|
||||||
|
switch(evt.which) {
|
||||||
|
case 13:
|
||||||
|
evt.preventDefault();
|
||||||
|
$addButton.click();
|
||||||
|
return;
|
||||||
|
case 27:
|
||||||
|
evt.preventDefault();
|
||||||
|
openedPopover && openedPopover.popover('toggle').popover('destroy');
|
||||||
|
inputElt.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$addButton.click(function(evt) {
|
||||||
var author = utils.getInputTextValue(popoverElt.querySelector('.input-comment-author'));
|
var author = utils.getInputTextValue(popoverElt.querySelector('.input-comment-author'));
|
||||||
var content = utils.getInputTextValue(textarea, evt);
|
var content = utils.getInputTextValue($textarea, evt);
|
||||||
if(evt.isPropagationStopped()) {
|
if(evt.isPropagationStopped()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileDiscussions = fileDesc.discussions || {};
|
var discussionList = fileDesc.discussionList || {};
|
||||||
if(!currentDiscussion.discussionIndex) {
|
if(!currentDiscussion.discussionIndex) {
|
||||||
|
// Create discussion index
|
||||||
|
var discussionIndex;
|
||||||
do {
|
do {
|
||||||
currentDiscussion.discussionIndex = utils.randomString();
|
discussionIndex = utils.randomString();
|
||||||
} while(_.has(fileDiscussions, currentDiscussion.discussionIndex));
|
} while(_.has(discussionList, discussionIndex));
|
||||||
|
currentDiscussion.discussionIndex = discussionIndex;
|
||||||
|
discussionList[discussionIndex] = currentDiscussion;
|
||||||
}
|
}
|
||||||
currentDiscussion.push({
|
currentDiscussion.commentList.push({
|
||||||
author: author,
|
author: author,
|
||||||
content: content
|
content: content
|
||||||
});
|
});
|
||||||
|
fileDesc.discussionList = discussionList;
|
||||||
openedPopover.popover('toggle').popover('destroy');
|
openedPopover.popover('toggle').popover('destroy');
|
||||||
|
refreshDiscussions();
|
||||||
|
inputElt.focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Prevent from closing on click inside the popover
|
// Prevent from closing on click inside the popover
|
||||||
@ -136,7 +213,7 @@ define([
|
|||||||
cssApplier.applyToRange(rangyRange);
|
cssApplier.applyToRange(rangyRange);
|
||||||
|
|
||||||
// Focus on textarea
|
// Focus on textarea
|
||||||
textarea.focus();
|
$textarea.focus();
|
||||||
}, 10);
|
}, 10);
|
||||||
}).on('hide.bs.popover', '#wmd-input > .editor-margin', function(evt) {
|
}).on('hide.bs.popover', '#wmd-input > .editor-margin', function(evt) {
|
||||||
$(evt.target).removeClass('active');
|
$(evt.target).removeClass('active');
|
||||||
|
@ -41,9 +41,9 @@ define([
|
|||||||
};
|
};
|
||||||
|
|
||||||
var sectionCounter = 0;
|
var sectionCounter = 0;
|
||||||
function parseFileContent(fileDesc) {
|
function parseFileContent(fileDesc, content) {
|
||||||
var frontMatter = (fileDesc.frontMatter || {})._frontMatter || '';
|
var frontMatter = (fileDesc.frontMatter || {})._frontMatter || '';
|
||||||
var text = fileDesc.content.substring(frontMatter.length);
|
var text = content.substring(frontMatter.length);
|
||||||
var tmpText = text + "\n\n";
|
var tmpText = text + "\n\n";
|
||||||
function addSection(startOffset, endOffset) {
|
function addSection(startOffset, endOffset) {
|
||||||
var sectionText = tmpText.substring(offset, endOffset);
|
var sectionText = tmpText.substring(offset, endOffset);
|
||||||
|
@ -18,8 +18,8 @@ define([
|
|||||||
|
|
||||||
var regex = /^(\s*-{3}\s*\n([\w\W]+?)\n\s*-{3}\s*?\n)?([\w\W]*)$/;
|
var regex = /^(\s*-{3}\s*\n([\w\W]+?)\n\s*-{3}\s*?\n)?([\w\W]*)$/;
|
||||||
|
|
||||||
function parseFrontMatter(fileDesc) {
|
function parseFrontMatter(fileDesc, content) {
|
||||||
var results = regex.exec(fileDesc.content);
|
var results = regex.exec(content);
|
||||||
var yaml = results[2];
|
var yaml = results[2];
|
||||||
|
|
||||||
if(!yaml) {
|
if(!yaml) {
|
||||||
|
@ -135,7 +135,7 @@ define([
|
|||||||
storage.removeItem(fileDesc.fileIndex + ".editorEnd");
|
storage.removeItem(fileDesc.fileIndex + ".editorEnd");
|
||||||
storage.removeItem(fileDesc.fileIndex + ".editorScrollTop");
|
storage.removeItem(fileDesc.fileIndex + ".editorScrollTop");
|
||||||
storage.removeItem(fileDesc.fileIndex + ".previewScrollTop");
|
storage.removeItem(fileDesc.fileIndex + ".previewScrollTop");
|
||||||
storage.removeItem(fileDesc.fileIndex + ".editorSelectRange");
|
storage.removeItem(fileDesc.fileIndex + ".discussionList");
|
||||||
|
|
||||||
eventMgr.onFileDeleted(fileDesc);
|
eventMgr.onFileDeleted(fileDesc);
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<div class="form-horizontal">
|
<div class="form-horizontal">
|
||||||
|
<div class="discussion-history"></div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input class="form-control input-comment-author" placeholder="Anonymous"></input>
|
<input class="form-control input-comment-author" placeholder="Your name"></input>
|
||||||
<textarea class="form-control input-comment-content"></textarea>
|
<textarea class="form-control input-comment-content"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group text-right">
|
<div class="form-group text-right">
|
||||||
|
@ -191,7 +191,7 @@ define([
|
|||||||
// If file content changed
|
// If file content changed
|
||||||
if(fileContentChanged && remoteContentChanged === true) {
|
if(fileContentChanged && remoteContentChanged === true) {
|
||||||
fileDesc.content = file.content;
|
fileDesc.content = file.content;
|
||||||
eventMgr.onContentChanged(fileDesc);
|
eventMgr.onContentChanged(fileDesc, file.content);
|
||||||
eventMgr.onMessage('"' + localTitle + '" has been updated from Dropbox.');
|
eventMgr.onMessage('"' + localTitle + '" has been updated from Dropbox.');
|
||||||
if(fileMgr.currentFile === fileDesc) {
|
if(fileMgr.currentFile === fileDesc) {
|
||||||
fileMgr.selectFile(); // Refresh editor
|
fileMgr.selectFile(); // Refresh editor
|
||||||
|
@ -232,7 +232,7 @@ define([
|
|||||||
// If file content changed
|
// If file content changed
|
||||||
if(!syncAttributes.isRealtime && fileContentChanged && remoteContentChanged === true) {
|
if(!syncAttributes.isRealtime && fileContentChanged && remoteContentChanged === true) {
|
||||||
fileDesc.content = file.content;
|
fileDesc.content = file.content;
|
||||||
eventMgr.onContentChanged(fileDesc);
|
eventMgr.onContentChanged(fileDesc, file.content);
|
||||||
eventMgr.onMessage('"' + file.title + '" has been updated from ' + providerName + '.');
|
eventMgr.onMessage('"' + file.title + '" has been updated from ' + providerName + '.');
|
||||||
if(fileMgr.currentFile === fileDesc) {
|
if(fileMgr.currentFile === fileDesc) {
|
||||||
fileMgr.selectFile(); // Refresh editor
|
fileMgr.selectFile(); // Refresh editor
|
||||||
@ -283,10 +283,16 @@ define([
|
|||||||
pagedownEditor = pagedownEditorParam;
|
pagedownEditor = pagedownEditorParam;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var realtimeContext;
|
||||||
|
eventMgr.addListener('onContentChanged', function(aceEditorParam) {
|
||||||
|
});
|
||||||
|
|
||||||
// Start realtime synchronization
|
// Start realtime synchronization
|
||||||
gdriveProvider.startRealtimeSync = function(fileDesc, syncAttributes) {
|
gdriveProvider.startRealtimeSync = function(fileDesc, syncAttributes) {
|
||||||
var localContext = {};
|
var context = {
|
||||||
realtimeContext = localContext;
|
fileDesc: fileDesc
|
||||||
|
};
|
||||||
|
realtimeContext = context;
|
||||||
googleHelper.loadRealtime(syncAttributes.id, fileDesc.content, accountId, function(err, doc) {
|
googleHelper.loadRealtime(syncAttributes.id, fileDesc.content, accountId, function(err, doc) {
|
||||||
if(err || !doc) {
|
if(err || !doc) {
|
||||||
return;
|
return;
|
||||||
@ -294,15 +300,16 @@ define([
|
|||||||
|
|
||||||
// If user just switched to another document or file has just been
|
// If user just switched to another document or file has just been
|
||||||
// reselected
|
// reselected
|
||||||
if(localContext.isStopped === true) {
|
if(context !== realtimeContext) {
|
||||||
doc.close();
|
return doc.close();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.log("Starting Google Drive realtime synchronization");
|
logger.log("Starting Google Drive realtime synchronization");
|
||||||
localContext.document = doc;
|
context.document = doc;
|
||||||
var model = doc.getModel();
|
var model = doc.getModel();
|
||||||
|
context.model = realtimeString;
|
||||||
var realtimeString = model.getRoot().get('content');
|
var realtimeString = model.getRoot().get('content');
|
||||||
|
context.string = realtimeString;
|
||||||
|
|
||||||
// Saves model content checksum
|
// Saves model content checksum
|
||||||
function updateContentState() {
|
function updateContentState() {
|
||||||
@ -359,6 +366,7 @@ define([
|
|||||||
var remoteContentCRC = utils.crc32(remoteContent);
|
var remoteContentCRC = utils.crc32(remoteContent);
|
||||||
var remoteContentChanged = syncAttributes.contentCRC != remoteContentCRC;
|
var remoteContentChanged = syncAttributes.contentCRC != remoteContentCRC;
|
||||||
var fileContentChanged = localContent != remoteContent;
|
var fileContentChanged = localContent != remoteContent;
|
||||||
|
model.beginCompoundOperation('Open and merge');
|
||||||
if(fileContentChanged === true && localContentChanged === true) {
|
if(fileContentChanged === true && localContentChanged === true) {
|
||||||
if(remoteContentChanged === true) {
|
if(remoteContentChanged === true) {
|
||||||
// Conflict detected
|
// Conflict detected
|
||||||
@ -371,11 +379,6 @@ define([
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(aceEditor === undefined) {
|
|
||||||
// Binds model with textarea
|
|
||||||
localContext.binding = gapi.drive.realtime.databinding.bindString(realtimeString, document.getElementById("wmd-input"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update content state according to collaborators changes
|
// Update content state according to collaborators changes
|
||||||
if(remoteContentChanged === true) {
|
if(remoteContentChanged === true) {
|
||||||
logger.log("Google Drive realtime document updated from server");
|
logger.log("Google Drive realtime document updated from server");
|
||||||
@ -384,10 +387,15 @@ define([
|
|||||||
aceEditor === undefined && debouncedRefreshPreview();
|
aceEditor === undefined && debouncedRefreshPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(aceEditor !== undefined) {
|
var realtimeDiscussionList = model.getRoot().get('discussionList');
|
||||||
// Tell ACE to update realtime string on each change
|
context.discussionList = realtimeDiscussionList;
|
||||||
localContext.string = realtimeString;
|
|
||||||
|
if(!realtimeDiscussionList) {
|
||||||
|
realtimeDiscussionList = model.createMap();
|
||||||
|
model.getRoot().set('discussionList', realtimeDiscussionList);
|
||||||
}
|
}
|
||||||
|
mergeDiscussionList(context, remoteContentChanged === true);
|
||||||
|
model.endCompoundOperation();
|
||||||
|
|
||||||
// Save undo/redo buttons default actions
|
// Save undo/redo buttons default actions
|
||||||
undoExecute = pagedownEditor.uiManager.buttons.undo.execute;
|
undoExecute = pagedownEditor.uiManager.buttons.undo.execute;
|
||||||
@ -425,7 +433,7 @@ define([
|
|||||||
});
|
});
|
||||||
|
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
console.error(err);
|
logger.error(err);
|
||||||
if(err.type == "token_refresh_required") {
|
if(err.type == "token_refresh_required") {
|
||||||
googleHelper.refreshGdriveToken(accountId);
|
googleHelper.refreshGdriveToken(accountId);
|
||||||
}
|
}
|
||||||
@ -442,12 +450,89 @@ define([
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function mergeDiscussion(localDiscussion, realtimeDiscussion, takeServer) {
|
||||||
|
if(takeServer) {
|
||||||
|
localDiscussion.selectionStart = realtimeDiscussion.get('selectionStart');
|
||||||
|
localDiscussion.selectionEnd = realtimeDiscussion.get('selectionEnd');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
realtimeDiscussion.set('selectionStart', localDiscussion.selectionStart);
|
||||||
|
realtimeDiscussion.set('selectionEnd', localDiscussion.selectionEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCommentInDiscussion(comment, commentList) {
|
||||||
|
return commentList.some(function(commentInDiscussion) {
|
||||||
|
return comment.author == commentInDiscussion.author && comment.content == commentInDiscussion.content;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var realtimeCommentList = realtimeDiscussion.get('commentList').asArray();
|
||||||
|
localDiscussion.commentList.forEach(function(comment) {
|
||||||
|
if(!isCommentInDiscussion(comment, realtimeCommentList)) {
|
||||||
|
realtimeDiscussion.get('commentList').push(comment);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
realtimeCommentList.forEach(function(comment) {
|
||||||
|
if(!isCommentInDiscussion(comment, localDiscussion.commentList)) {
|
||||||
|
localDiscussion.commentList.push(comment);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRealtimeDiscussion(context, discussion) {
|
||||||
|
var commentList = context.model.createList(discussion.commentList);
|
||||||
|
var realtimeDiscussion = context.model.createMap({
|
||||||
|
selectionStart: discussion.selectionStart,
|
||||||
|
selectionEnd: discussion.selectionEnd,
|
||||||
|
commentList: commentList
|
||||||
|
});
|
||||||
|
context.discussionList.set(discussion.discussionIndex, realtimeDiscussion);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeDiscussionList(context, takeServer) {
|
||||||
|
var localDiscussionList = context.fileDesc.discussionList;
|
||||||
|
_.each(localDiscussionList, function(localDiscussion) {
|
||||||
|
var realtimeDiscussion = context.discussionList.get(localDiscussion.discussionIndex);
|
||||||
|
if(realtimeDiscussion) {
|
||||||
|
mergeDiscussion(localDiscussion, realtimeDiscussion, takeServer);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
createRealtimeDiscussion(context, localDiscussion);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
context.discussionList.keys().forEach(function(discussionIndex) {
|
||||||
|
var localDiscussion = localDiscussionList[discussionIndex];
|
||||||
|
var realtimeDiscussion = context.discussionList.get(discussionIndex);
|
||||||
|
if(localDiscussion) {
|
||||||
|
mergeDiscussion(localDiscussion, realtimeDiscussion, takeServer);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var discussion = {
|
||||||
|
discussionIndex: discussionIndex,
|
||||||
|
selectionStart: realtimeDiscussion.get('selectionStart'),
|
||||||
|
selectionEnd: realtimeDiscussion.get('selectionEnd'),
|
||||||
|
commentList: realtimeDiscussion.get('commentList').asArray()
|
||||||
|
};
|
||||||
|
localDiscussionList[discussionIndex] = discussion;
|
||||||
|
eventMgr.onDiscussionCreated(context.fileDesc, discussion);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
context.fileDesc.discussionList = localDiscussionList; // Write in localStorage
|
||||||
|
}
|
||||||
|
|
||||||
|
eventMgr.addListener('onDiscussionCreated', function(fileDesc, discussion) {
|
||||||
|
if(realtimeContext === undefined || realtimeContext.fileDesc !== fileDesc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!realtimeContext.discussionList.has(discussion.discussionIndex)) {
|
||||||
|
createRealtimeDiscussion(realtimeContext, discussion);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Stop realtime synchronization
|
// Stop realtime synchronization
|
||||||
gdriveProvider.stopRealtimeSync = function() {
|
gdriveProvider.stopRealtimeSync = function() {
|
||||||
logger.log("Stopping Google Drive realtime synchronization");
|
logger.log("Stopping Google Drive realtime synchronization");
|
||||||
if(realtimeContext !== undefined) {
|
if(realtimeContext !== undefined) {
|
||||||
realtimeContext.isStopped = true;
|
|
||||||
realtimeContext.binding && realtimeContext.binding.unbind();
|
|
||||||
realtimeContext.document && realtimeContext.document.close();
|
realtimeContext.document && realtimeContext.document.close();
|
||||||
realtimeContext = undefined;
|
realtimeContext = undefined;
|
||||||
}
|
}
|
||||||
|
@ -127,6 +127,8 @@
|
|||||||
@popover-arrow-outer-color: @secondary-border-color;
|
@popover-arrow-outer-color: @secondary-border-color;
|
||||||
@popover-title-bg: @transparent;
|
@popover-title-bg: @transparent;
|
||||||
@alert-border-radius: 0;
|
@alert-border-radius: 0;
|
||||||
|
@label-warning-bg: #d90;
|
||||||
|
@label-danger-bg: #d00;
|
||||||
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@ -1057,23 +1059,33 @@ a {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
.icon-comment {
|
.icon-comment {
|
||||||
.opacity(0.4);
|
&.new {
|
||||||
|
color: fade(@tertiary-color, 12%);
|
||||||
|
&:hover {
|
||||||
|
color: fade(@tertiary-color, 35%) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.replied {
|
||||||
|
color: fade(@label-danger-bg, 30%);
|
||||||
|
&:hover, &.active, &.active:hover {
|
||||||
|
color: fade(@label-danger-bg, 45%) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
position: absolute;
|
position: absolute;
|
||||||
color: fade(@tertiary-color, 25%);
|
color: fade(@label-warning-bg, 40%);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover, &.active {
|
&:hover, &.active, &.active:hover {
|
||||||
.opacity(1);
|
color: fade(@label-warning-bg, 60%) !important;
|
||||||
color: fade(@tertiary-color, 35%);
|
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.has-selection > .editor-margin .icon-comment {
|
&.has-selection > .editor-margin .icon-comment.new {
|
||||||
.opacity(1);
|
color: fade(@tertiary-color, 25%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.comment-highlight {
|
.comment-highlight {
|
||||||
background-color: fade(@label-danger-bg, 25%);
|
background-color: fade(@label-warning-bg, 25%);
|
||||||
//border-radius: @border-radius-base;
|
//border-radius: @border-radius-base;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1387,7 +1399,7 @@ input[type="file"] {
|
|||||||
*********************/
|
*********************/
|
||||||
|
|
||||||
.popover {
|
.popover {
|
||||||
max-width: 350px;
|
max-width: 240px;
|
||||||
padding: 10px 20px 0;
|
padding: 10px 20px 0;
|
||||||
//.box-shadow(0 5px 30px rgba(0,0,0,.175));
|
//.box-shadow(0 5px 30px rgba(0,0,0,.175));
|
||||||
.popover-title {
|
.popover-title {
|
||||||
@ -1395,6 +1407,7 @@ input[type="file"] {
|
|||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
padding: 5px 0 15px;
|
padding: 5px 0 15px;
|
||||||
border-bottom: 1px solid @hr-border;
|
border-bottom: 1px solid @hr-border;
|
||||||
|
line-height: @headings-line-height;
|
||||||
}
|
}
|
||||||
.icon-lock {
|
.icon-lock {
|
||||||
font-size: 38px;
|
font-size: 38px;
|
||||||
|
Loading…
Reference in New Issue
Block a user