Display other users' cursor
parent
0674f410f5
commit
4690392cd9
|
@ -2,6 +2,7 @@
|
|||
@import (reference) "../../customize/src/less2/include/framework.less";
|
||||
@import (reference) "../../customize/src/less2/include/tools.less";
|
||||
@import (reference) "../../customize/src/less2/include/markdown.less";
|
||||
@import (reference) "../../customize/src/less2/include/avatar.less";
|
||||
|
||||
// body
|
||||
&.cp-app-kanban {
|
||||
|
@ -99,6 +100,16 @@
|
|||
flex-flow: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
#cp-kanban-edit-conflicts {
|
||||
padding: 5px;
|
||||
background: #eee;
|
||||
color: @cryptpad_text_col;
|
||||
font-size: 14px;
|
||||
.cp-kanban-cursors {
|
||||
margin-top: 5px;
|
||||
}
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
#cp-kanban-edit-body {
|
||||
border: 1px solid @colortheme_modal-input;
|
||||
.CodeMirror {
|
||||
|
@ -154,12 +165,30 @@
|
|||
padding: 5px;
|
||||
}
|
||||
|
||||
.cp-kanban-cursors {
|
||||
&:empty { display: none; }
|
||||
order: 2;
|
||||
width: 100%;
|
||||
&> span {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
text-align: center;
|
||||
line-height: 20px;
|
||||
margin-right: 5px;
|
||||
.tools_unselectable();
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
.kanban-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 5px;
|
||||
flex-wrap: wrap;
|
||||
.cp-kanban-cursors {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.kanban-item-body, .kanban-item-tags {
|
||||
.tools_unselectable();
|
||||
width: 100%;
|
||||
|
@ -216,6 +245,7 @@
|
|||
}
|
||||
header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
padding: 5px 10px;
|
||||
.kanban-title-board {
|
||||
|
|
|
@ -8,6 +8,7 @@ define([
|
|||
'/common/common-util.js',
|
||||
'/common/common-hash.js',
|
||||
'/common/common-interface.js',
|
||||
'/common/common-ui-elements.js',
|
||||
'/common/modes.js',
|
||||
'/customize/messages.js',
|
||||
'/common/hyperscript.js',
|
||||
|
@ -38,6 +39,7 @@ define([
|
|||
Util,
|
||||
Hash,
|
||||
UI,
|
||||
UIElements,
|
||||
Modes,
|
||||
Messages,
|
||||
h,
|
||||
|
@ -51,6 +53,8 @@ define([
|
|||
var verbose = function (x) { console.log(x); };
|
||||
verbose = function () {}; // comment out to enable verbose logging
|
||||
var onRedraw = Util.mkEvent();
|
||||
var onCursorUpdate = Util.mkEvent();
|
||||
var remoteCursors = {};
|
||||
|
||||
Messages.kanban_title = "Title"; // XXX
|
||||
Messages.kanban_body = "Body"; // XXX
|
||||
|
@ -59,6 +63,7 @@ define([
|
|||
Messages.kanban_delete = "Delete"; // XXX
|
||||
Messages.kanban_tags = "Filter tags"; // XXX
|
||||
Messages.kanban_noTags = "No tags"; // XXX
|
||||
Messages.kanban_conflicts = "Currently editing:"; // XXX
|
||||
|
||||
// XXX
|
||||
// Conflicts
|
||||
|
@ -83,6 +88,46 @@ define([
|
|||
input.selectionEnd = selects[1];
|
||||
};
|
||||
|
||||
var getTextColor = function (hex) {
|
||||
if (hex && /^#/.test(hex)) { hex = hex.slice(1); }
|
||||
if (!/^[0-9a-f]{6}$/i.test(hex)) {
|
||||
return '#000000';
|
||||
}
|
||||
var r = parseInt(hex.slice(0,2), 16);
|
||||
var g = parseInt(hex.slice(2,4), 16);
|
||||
var b = parseInt(hex.slice(4,6), 16);
|
||||
if ((r*0.213 + g*0.715 + b*0.072) > 255/2) {
|
||||
return '#000000';
|
||||
}
|
||||
return '#FFFFFF';
|
||||
};
|
||||
|
||||
var getAvatar = function (cursor, noClear) {
|
||||
// Tippy
|
||||
var html = '<span class="cp-cursor-avatar">';
|
||||
if (cursor.avatar && UIElements.getAvatar(cursor.avatar)) {
|
||||
html += UIElements.getAvatar(cursor.avatar);
|
||||
}
|
||||
html += cursor.name + '</span>';
|
||||
|
||||
var l = (cursor.name || Messages.anonymous).slice(0,1).toUpperCase();
|
||||
|
||||
var text = '';
|
||||
if (cursor.color) {
|
||||
text = 'color:'+getTextColor(cursor.color)+';';
|
||||
}
|
||||
var avatar = h('span.cp-cursor.cp-tippy-html', {
|
||||
style: "background-color: " + (cursor.color || 'red') + ";"+text,
|
||||
title: html
|
||||
}, l);
|
||||
if (!noClear) {
|
||||
cursor.clear = function () {
|
||||
$(avatar).remove();
|
||||
};
|
||||
}
|
||||
return avatar;
|
||||
};
|
||||
|
||||
var getExistingTags = function (boards) {
|
||||
var tags = [];
|
||||
boards = boards || {};
|
||||
|
@ -112,8 +157,12 @@ define([
|
|||
addEditItemButton(framework, kanban);
|
||||
};
|
||||
if (editModal) { return editModal; }
|
||||
var titleInput, tagsDiv, colors, text;
|
||||
var conflicts, conflictContainer, titleInput, tagsDiv, colors, text;
|
||||
var content = h('div', [
|
||||
conflictContainer = h('div#cp-kanban-edit-conflicts', [
|
||||
h('div', Messages.kanban_conflicts),
|
||||
conflicts = h('div.cp-kanban-cursors')
|
||||
]),
|
||||
h('label', {for:'cp-kanban-edit-title'}, Messages.kanban_title),
|
||||
titleInput = h('input#cp-kanban-edit-title'),
|
||||
h('label', {for:'cp-kanban-edit-body'}, Messages.kanban_body),
|
||||
|
@ -126,6 +175,27 @@ define([
|
|||
colors = h('div#cp-kanban-edit-colors'),
|
||||
]);
|
||||
|
||||
|
||||
var $conflict = $(conflicts);
|
||||
var $cc = $(conflictContainer);
|
||||
var conflict = {
|
||||
setValue: function () {
|
||||
$conflict.empty();
|
||||
var i = 0;
|
||||
$cc.hide();
|
||||
Object.keys(remoteCursors).forEach(function (nid) {
|
||||
var c = remoteCursors[nid];
|
||||
var avatar = getAvatar(c, true);
|
||||
if (Number(c.item) === Number(id) || Number(c.board) === Number(id)) {
|
||||
$conflict.append(avatar);
|
||||
i++;
|
||||
}
|
||||
});
|
||||
if (!i) { return; }
|
||||
$cc.show();
|
||||
}
|
||||
};
|
||||
|
||||
// Title
|
||||
var $title = $(titleInput);
|
||||
$title.on('change keyup', function () {
|
||||
|
@ -248,11 +318,17 @@ define([
|
|||
isBoard = _isBoard;
|
||||
id = _id;
|
||||
if (_isBoard) {
|
||||
onCursorUpdate.fire({
|
||||
board: _id
|
||||
});
|
||||
dataObject = kanban.getBoardJSON(id);
|
||||
$(content)
|
||||
.find('#cp-kanban-edit-body, #cp-kanban-edit-tags, [for="cp-kanban-edit-body"], [for="cp-kanban-edit-tags"]')
|
||||
.hide();
|
||||
} else {
|
||||
onCursorUpdate.fire({
|
||||
item: _id
|
||||
});
|
||||
dataObject = kanban.getItemJSON(id);
|
||||
$(content)
|
||||
.find('#cp-kanban-edit-body, #cp-kanban-edit-tags, [for="cp-kanban-edit-body"], [for="cp-kanban-edit-tags"]')
|
||||
|
@ -287,6 +363,7 @@ define([
|
|||
className: 'primary',
|
||||
name: Messages.filePicker_close,
|
||||
onClick: function () {
|
||||
onCursorUpdate.fire({});
|
||||
},
|
||||
keys: []
|
||||
}];
|
||||
|
@ -310,6 +387,7 @@ define([
|
|||
return;
|
||||
}
|
||||
// Not deleted, apply updates
|
||||
editModal.conflict.setValue();
|
||||
PROPERTIES.forEach(function (type) {
|
||||
editModal[type].setValue(dataObject[type], true);
|
||||
});
|
||||
|
@ -321,7 +399,8 @@ define([
|
|||
title: title,
|
||||
body: body,
|
||||
tags: tags,
|
||||
color: color
|
||||
color: color,
|
||||
conflict: conflict
|
||||
};
|
||||
};
|
||||
var getItemEditModal = function (framework, kanban, eid) {
|
||||
|
@ -331,6 +410,7 @@ define([
|
|||
var boards = kanban.options.boards || {};
|
||||
var item = (boards.items || {})[eid];
|
||||
if (!item) { return void UI.warn(Messages.error); }
|
||||
editModal.conflict.setValue();
|
||||
PROPERTIES.forEach(function (type) {
|
||||
if (!editModal[type]) { return; }
|
||||
editModal[type].setValue(item[type]);
|
||||
|
@ -345,6 +425,7 @@ define([
|
|||
var boards = kanban.options.boards || {};
|
||||
var board = (boards.data || {})[id];
|
||||
if (!board) { return void UI.warn(Messages.error); }
|
||||
editModal.conflict.setValue();
|
||||
BOARD_PROPERTIES.forEach(function (type) {
|
||||
if (!editModal[type]) { return; }
|
||||
editModal[type].setValue(board[type]);
|
||||
|
@ -482,6 +563,12 @@ define([
|
|||
}
|
||||
var eid = $(el).attr('data-eid');
|
||||
kanban.inEditMode = eid;
|
||||
setTimeout(function () {
|
||||
// Make sure the click is sent after the "blur" in case we move from a card to another
|
||||
onCursorUpdate.fire({
|
||||
item: eid
|
||||
});
|
||||
});
|
||||
var name = $(el).text();
|
||||
$(el).html('');
|
||||
|
||||
|
@ -500,8 +587,9 @@ define([
|
|||
kanban.onChange();
|
||||
// Unlock edit mode
|
||||
kanban.inEditMode = false;
|
||||
onCursorUpdate.fire({});
|
||||
};
|
||||
$input.blur(save);
|
||||
//$input.blur(save);
|
||||
$input.keydown(function (e) {
|
||||
if (e.which === 13) {
|
||||
e.preventDefault();
|
||||
|
@ -515,10 +603,7 @@ define([
|
|||
if (e.which === 27) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
$(el).text(name);
|
||||
kanban.inEditMode = false;
|
||||
addEditItemButton(framework, kanban);
|
||||
return;
|
||||
save();
|
||||
}
|
||||
});
|
||||
$input.on('change keyup', function () {
|
||||
|
@ -540,6 +625,12 @@ define([
|
|||
}
|
||||
var boardId = $(el).closest('.kanban-board').attr("data-id");
|
||||
kanban.inEditMode = boardId;
|
||||
setTimeout(function () {
|
||||
// Make sure the click is sent after the "blur" in case we move from a card to another
|
||||
onCursorUpdate.fire({
|
||||
board: boardId
|
||||
});
|
||||
});
|
||||
|
||||
var name = $(el).text();
|
||||
$(el).html('');
|
||||
|
@ -559,6 +650,7 @@ define([
|
|||
kanban.onChange();
|
||||
// Unlock edit mode
|
||||
kanban.inEditMode = false;
|
||||
onCursorUpdate.fire({});
|
||||
};
|
||||
$input.blur(save);
|
||||
$input.keydown(function (e) {
|
||||
|
@ -571,8 +663,7 @@ define([
|
|||
if (e.which === 27) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
$(el).text(name);
|
||||
kanban.inEditMode = false;
|
||||
save();
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
@ -604,6 +695,7 @@ define([
|
|||
var save = function () {
|
||||
$item.remove();
|
||||
kanban.inEditMode = false;
|
||||
onCursorUpdate.fire({});
|
||||
if (!$input.val()) { return; }
|
||||
var id = Util.createRandomInteger();
|
||||
var item = {
|
||||
|
@ -630,6 +722,7 @@ define([
|
|||
e.stopPropagation();
|
||||
$item.remove();
|
||||
kanban.inEditMode = false;
|
||||
onCursorUpdate.fire({});
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
@ -638,6 +731,9 @@ define([
|
|||
return DiffMd.render(md, true, false);
|
||||
},
|
||||
addItemButton: true,
|
||||
getTextColor: getTextColor,
|
||||
getAvatar: getAvatar,
|
||||
cursors: remoteCursors,
|
||||
boards: boards
|
||||
});
|
||||
|
||||
|
@ -664,9 +760,6 @@ define([
|
|||
});
|
||||
|
||||
var $container = $('#cp-app-kanban-content');
|
||||
$container[0].onclick = function (e) {
|
||||
console.warn(e);
|
||||
};
|
||||
var $cContainer = $('#cp-app-kanban-container');
|
||||
var addControls = function () {
|
||||
// Quick or normal mode
|
||||
|
@ -986,6 +1079,47 @@ define([
|
|||
kanban = initKanban(framework);
|
||||
});
|
||||
|
||||
var myCursor = {};
|
||||
onCursorUpdate.reg(function (data) {
|
||||
myCursor = data;
|
||||
framework.updateCursor();
|
||||
});
|
||||
framework.onCursorUpdate(function (data) {
|
||||
if (!data) { return; }
|
||||
var id = data.id;
|
||||
|
||||
// Clear existing cursor
|
||||
if (remoteCursors[id] && remoteCursors[id].clear) {
|
||||
remoteCursors[id].clear();
|
||||
}
|
||||
delete remoteCursors[id];
|
||||
|
||||
var cursor = data.cursor;
|
||||
if (data.leave || !cursor) { return; }
|
||||
if (!cursor.item && !cursor.board) { return; }
|
||||
|
||||
// Add new cursor
|
||||
var avatar = getAvatar(cursor);
|
||||
var $item = $('.kanban-item[data-eid="'+cursor.item+'"]');
|
||||
var $board = $('.kanban-board[data-id="'+cursor.board+'"]');
|
||||
if ($item.length) {
|
||||
remoteCursors[id] = cursor;
|
||||
$item.find('.cp-kanban-cursors').append(avatar);
|
||||
return;
|
||||
}
|
||||
if ($board.length) {
|
||||
remoteCursors[id] = cursor;
|
||||
$board.find('header .cp-kanban-cursors').append(avatar);
|
||||
}
|
||||
});
|
||||
framework.onCursorUpdate(function () {
|
||||
if (!editModal || !editModal.conflict) { return; }
|
||||
editModal.conflict.setValue();
|
||||
});
|
||||
framework.setCursorGetter(function () {
|
||||
return myCursor;
|
||||
});
|
||||
|
||||
framework.start();
|
||||
};
|
||||
|
||||
|
|
|
@ -56,6 +56,9 @@
|
|||
items: {},
|
||||
list: []
|
||||
},
|
||||
getAvatar: function () {},
|
||||
getTextColor: function () { return '#000'; },
|
||||
cursors: {},
|
||||
tags: [],
|
||||
dragBoards: true,
|
||||
addItemButton: false,
|
||||
|
@ -351,19 +354,6 @@
|
|||
}
|
||||
};
|
||||
|
||||
var getTextColor = function (hex) {
|
||||
if (!/^[0-9a-f]{6}$/.test(hex)) {
|
||||
return '#000000';
|
||||
}
|
||||
var r = parseInt(hex.slice(0,2), 16);
|
||||
var g = parseInt(hex.slice(2,4), 16);
|
||||
var b = parseInt(hex.slice(4,6), 16);
|
||||
if ((r*0.213 + g*0.715 + b*0.072) > 255/2) {
|
||||
return '#000000';
|
||||
}
|
||||
return '#FFFFFF';
|
||||
};
|
||||
|
||||
var getElementNode = function (element) {
|
||||
var nodeItem = document.createElement('div');
|
||||
nodeItem.classList.add('kanban-item');
|
||||
|
@ -374,10 +364,18 @@
|
|||
nodeItem.classList.add('cp-kanban-palette-'+element.color);
|
||||
} else {
|
||||
// Hex color code
|
||||
var textColor = getTextColor(element.color);
|
||||
var textColor = self.options.getTextColor(element.color);
|
||||
nodeItem.setAttribute('style', 'background-color:#'+element.color+';color:'+textColor+';');
|
||||
}
|
||||
}
|
||||
var nodeCursors = document.createElement('div');
|
||||
nodeCursors.classList.add('cp-kanban-cursors');
|
||||
Object.keys(self.options.cursors).forEach(function (id) {
|
||||
var c = self.options.cursors[id];
|
||||
if (Number(c.item) !== Number(element.id)) { return; }
|
||||
var el = self.options.getAvatar(c);
|
||||
nodeCursors.appendChild(el);
|
||||
});
|
||||
var nodeItemText = document.createElement('div');
|
||||
nodeItemText.classList.add('kanban-item-text');
|
||||
nodeItemText.dataset.eid = element.id;
|
||||
|
@ -413,6 +411,7 @@
|
|||
});
|
||||
nodeItem.appendChild(nodeTags);
|
||||
}
|
||||
nodeItem.appendChild(nodeCursors);
|
||||
//add function
|
||||
nodeItem.clickfn = element.click;
|
||||
nodeItem.dragfn = element.drag;
|
||||
|
@ -482,10 +481,11 @@
|
|||
headerBoard.classList.add("kanban-header-" + board.color);
|
||||
} else {
|
||||
// Hex color code
|
||||
var textColor = getTextColor(board.color);
|
||||
var textColor = self.options.getTextColor(board.color);
|
||||
headerBoard.setAttribute('style', 'background-color:#'+board.color+';color:'+textColor+';');
|
||||
}
|
||||
}
|
||||
|
||||
titleBoard = document.createElement('div');
|
||||
titleBoard.classList.add('kanban-title-board');
|
||||
titleBoard.innerText = board.title;
|
||||
|
@ -494,6 +494,16 @@
|
|||
__onboardTitleClickHandler(titleBoard);
|
||||
headerBoard.appendChild(titleBoard);
|
||||
|
||||
var nodeCursors = document.createElement('div');
|
||||
nodeCursors.classList.add('cp-kanban-cursors');
|
||||
Object.keys(self.options.cursors).forEach(function (id) {
|
||||
var c = self.options.cursors[id];
|
||||
if (Number(c.board) !== Number(board.id)) { return; }
|
||||
var el = self.options.getAvatar(c);
|
||||
nodeCursors.appendChild(el);
|
||||
});
|
||||
headerBoard.appendChild(nodeCursors);
|
||||
|
||||
//content board
|
||||
var contentBoard = document.createElement('main');
|
||||
contentBoard.classList.add('kanban-drag');
|
||||
|
|
Loading…
Reference in New Issue