mirror of https://github.com/zulip/zulip.git
widgets: Add todo widget.
This commit is contained in:
parent
0757d022f5
commit
fe4cad15a4
|
@ -178,6 +178,7 @@
|
|||
"user_events": false,
|
||||
"voting_widget": false,
|
||||
"tictactoe_widget": false,
|
||||
"todo_widget": false,
|
||||
"zform": false,
|
||||
"widgetize": false,
|
||||
"submessage": false,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
set_global('$', global.make_zjquery());
|
||||
set_global('voting_widget', {});
|
||||
set_global('tictactoe_widget', {});
|
||||
set_global('todo_widget', {});
|
||||
set_global('zform', {});
|
||||
set_global('document', 'document-stub');
|
||||
|
||||
|
|
|
@ -65,6 +65,7 @@ import "js/top_left_corner.js";
|
|||
import "js/stream_list.js";
|
||||
import "js/filter.js";
|
||||
import 'js/voting_widget.js';
|
||||
import 'js/todo_widget.js';
|
||||
import 'js/tictactoe_widget.js';
|
||||
import 'js/zform.js';
|
||||
import 'js/widgetize.js';
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
var todo_widget = (function () {
|
||||
|
||||
var exports = {};
|
||||
|
||||
exports.task_data_holder = function (is_my_task_list) {
|
||||
var self = {};
|
||||
|
||||
var all_tasks = [];
|
||||
var pending_tasks = [];
|
||||
var completed_tasks = [];
|
||||
var my_idx = 0;
|
||||
|
||||
self.get_widget_data = function () {
|
||||
|
||||
var widget_data = {
|
||||
pending_tasks: pending_tasks,
|
||||
completed_tasks: completed_tasks,
|
||||
};
|
||||
|
||||
return widget_data;
|
||||
};
|
||||
|
||||
self.check_task = {
|
||||
task_exists: function (task) {
|
||||
var task_exists = _.any(all_tasks, function (item) {
|
||||
return item.task === task;
|
||||
});
|
||||
return task_exists;
|
||||
},
|
||||
};
|
||||
|
||||
self.handle = {
|
||||
new_task: {
|
||||
outbound: function (task) {
|
||||
var event = {
|
||||
type: 'new_task',
|
||||
key: my_idx,
|
||||
task: task,
|
||||
completed: false,
|
||||
};
|
||||
my_idx += 1;
|
||||
|
||||
if (is_my_task_list && !self.check_task.task_exists(task)) {
|
||||
return event;
|
||||
}
|
||||
return;
|
||||
},
|
||||
|
||||
inbound: function (sender_id, data) {
|
||||
var idx = data.key;
|
||||
var task = data.task;
|
||||
var completed = data.completed;
|
||||
|
||||
var task_data = {
|
||||
task: task,
|
||||
user_id: sender_id,
|
||||
key: idx,
|
||||
completed: completed,
|
||||
};
|
||||
|
||||
if (!self.check_task.task_exists(task)) {
|
||||
pending_tasks.push(task_data);
|
||||
all_tasks.push(task_data);
|
||||
|
||||
if (my_idx <= idx) {
|
||||
my_idx = idx + 1;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
strike: {
|
||||
outbound: function (key) {
|
||||
var event = {
|
||||
type: 'strike',
|
||||
key: key,
|
||||
};
|
||||
|
||||
if (is_my_task_list) {
|
||||
return event;
|
||||
}
|
||||
return;
|
||||
},
|
||||
|
||||
inbound: function (sender_id, data) {
|
||||
var key = data.key;
|
||||
var task = all_tasks[key];
|
||||
var index;
|
||||
|
||||
if (task === undefined) {
|
||||
blueslip.error('unknown key for tasks: ' + key);
|
||||
return;
|
||||
}
|
||||
|
||||
all_tasks[key].completed = !all_tasks[key].completed;
|
||||
|
||||
// toggle
|
||||
if (task.completed) {
|
||||
index = pending_tasks.indexOf(task);
|
||||
pending_tasks.splice(index, 1);
|
||||
completed_tasks.unshift(task);
|
||||
} else {
|
||||
index = completed_tasks.indexOf(task);
|
||||
completed_tasks.splice(index, 1);
|
||||
pending_tasks.push(task);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
self.handle_event = function (sender_id, data) {
|
||||
var type = data.type;
|
||||
if (self.handle[type]) {
|
||||
self.handle[type].inbound(sender_id, data);
|
||||
}
|
||||
};
|
||||
|
||||
return self;
|
||||
};
|
||||
|
||||
exports.activate = function (opts) {
|
||||
var elem = opts.elem;
|
||||
var callback = opts.callback;
|
||||
|
||||
var is_my_task_list = people.is_my_user_id(opts.message.sender_id);
|
||||
var task_data = exports.task_data_holder(is_my_task_list);
|
||||
|
||||
function render() {
|
||||
var html = templates.render('todo-widget');
|
||||
elem.html(html);
|
||||
|
||||
elem.find("button.add-task").on('click', function (e) {
|
||||
e.stopPropagation();
|
||||
elem.find(".widget-error").text('');
|
||||
var task = elem.find("input.add-task").val().trim();
|
||||
|
||||
if (task === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
elem.find(".add-task").val('').focus();
|
||||
|
||||
var task_exists = task_data.check_task.task_exists(task);
|
||||
if (task_exists) {
|
||||
elem.find(".widget-error").text(i18n.t('Task already exists'));
|
||||
return;
|
||||
}
|
||||
|
||||
var data = task_data.handle.new_task.outbound(task);
|
||||
callback(data);
|
||||
});
|
||||
}
|
||||
|
||||
function render_results() {
|
||||
var widget_data = task_data.get_widget_data();
|
||||
var html = templates.render('todo-widget-tasks', widget_data);
|
||||
elem.find('ul.todo-widget').html(html);
|
||||
elem.find(".widget-error").text('');
|
||||
|
||||
if (!is_my_task_list) {
|
||||
elem.find(".add-task-bar").hide();
|
||||
elem.find("button.task").attr('disabled', true);
|
||||
}
|
||||
|
||||
elem.find("button.task").on('click', function (e) {
|
||||
e.stopPropagation();
|
||||
var key = $(e.target).attr('data-key');
|
||||
|
||||
var data = task_data.handle.strike.outbound(key);
|
||||
callback(data);
|
||||
});
|
||||
}
|
||||
|
||||
elem.handle_events = function (events) {
|
||||
_.each(events, function (event) {
|
||||
task_data.handle_event(event.sender_id, event.data);
|
||||
});
|
||||
render_results();
|
||||
};
|
||||
|
||||
render();
|
||||
render_results();
|
||||
};
|
||||
|
||||
return exports;
|
||||
|
||||
}());
|
||||
if (typeof module !== 'undefined') {
|
||||
module.exports = todo_widget;
|
||||
}
|
||||
|
||||
window.todo_widget = todo_widget;
|
|
@ -6,6 +6,7 @@ var widgets = {};
|
|||
|
||||
widgets.poll = voting_widget;
|
||||
widgets.tictactoe = tictactoe_widget;
|
||||
widgets.todo = todo_widget;
|
||||
widgets.zform = zform;
|
||||
|
||||
var widget_contents = {};
|
||||
|
|
|
@ -16,23 +16,26 @@
|
|||
font-weight: 600;
|
||||
}
|
||||
|
||||
.todo-widget h4,
|
||||
.poll-widget h4 {
|
||||
font-size: 14px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.todo-widget li,
|
||||
.poll-widget li {
|
||||
list-style: none;
|
||||
margin: 2px 2px 2px 0px;
|
||||
}
|
||||
|
||||
.todo-widget ul,
|
||||
.poll-widget ul {
|
||||
margin: 0px 0px 5px 0px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.poll-widget .poll-names {
|
||||
font-size: 10px;
|
||||
.poll-widget .poll-names .todo-widget {
|
||||
font-size: 14px;
|
||||
color: green;
|
||||
}
|
||||
|
||||
|
@ -41,17 +44,40 @@
|
|||
}
|
||||
|
||||
.poll-vote {
|
||||
border-radius: 2px;
|
||||
color: #3c906e;
|
||||
border-color: #9dc8b7;
|
||||
margin-right: 4px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
button.task {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
background-color: transparent;
|
||||
border-color: #9dc8b7;
|
||||
margin-right: 4px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
button.task:hover {
|
||||
border: 1px solid #2988a4;
|
||||
}
|
||||
|
||||
button.task-completed {
|
||||
border-color: #b9cec6;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
img.task-completed {
|
||||
width: 15px;
|
||||
}
|
||||
|
||||
.poll-vote:hover {
|
||||
border-color: #59a687;
|
||||
}
|
||||
|
||||
input.add-task,
|
||||
button.add-task,
|
||||
input.poll-comment,
|
||||
button.poll-comment,
|
||||
input.poll-question,
|
||||
|
@ -60,13 +86,22 @@ button.poll-question {
|
|||
margin: 2px 0px 2px 0px;
|
||||
}
|
||||
|
||||
button.poll-comment, button.poll-question {
|
||||
button.add-task,
|
||||
button.poll-comment,
|
||||
button.poll-question {
|
||||
border-radius: 3px;
|
||||
border: 1px solid #cccccc;
|
||||
background-color: white;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
button.poll-comment:hover, button.poll-question:hover {
|
||||
button.add-task:hover,
|
||||
button.poll-comment:hover,
|
||||
button.poll-question:hover {
|
||||
border-color: #999999;
|
||||
}
|
||||
|
||||
.widget-error {
|
||||
color: #b94a48;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<br />
|
||||
{{#each pending_tasks}}
|
||||
<li>
|
||||
<button class="task" data-key="{{ key }}">
|
||||
</button>
|
||||
<span class="task">{{ task }}</span>
|
||||
</li>
|
||||
{{/each}}
|
||||
{{#each completed_tasks}}
|
||||
<li>
|
||||
<button class="task-completed task" data-key="{{ key }}">
|
||||
<img class="task-completed" src="/static/images/checkbox-green.svg" data-key="{{ key }}"/>
|
||||
</button>
|
||||
<span class="task"><strike>{{ task }}</strike></span>
|
||||
</li>
|
||||
{{/each}}
|
|
@ -0,0 +1,10 @@
|
|||
<div class="todo-widget">
|
||||
<h4>Task list</h4>
|
||||
<div class="add-task-bar">
|
||||
<input type="text" class="add-task" placeholder="{{t 'New task'}}" />
|
||||
<button class="add-task">{{t "Add task" }}</button>
|
||||
<div class="widget-error"></div>
|
||||
</div>
|
||||
<ul class="todo-widget">
|
||||
</ul>
|
||||
</div>
|
|
@ -18,7 +18,7 @@ def do_widget_pre_save_actions(message: MutableMapping[str, Any]) -> None:
|
|||
return
|
||||
|
||||
def get_widget_data(content: str) -> Tuple[Optional[str], Optional[str]]:
|
||||
valid_widget_types = ['tictactoe', 'poll']
|
||||
valid_widget_types = ['tictactoe', 'poll', 'todo']
|
||||
tokens = content.split(' ')
|
||||
if not tokens:
|
||||
return None, None
|
||||
|
|
Loading…
Reference in New Issue