mirror of https://github.com/zulip/zulip.git
Implement persistent drafts functionality
* Created a drafts modal to display/restore/delete drafts * Created a Draft model to support storing draft data in localstorage * Removed existing restore-draft functionality * Added casper and node tests for drafts functionality Fixes #1717.
This commit is contained in:
parent
8b22b94ab1
commit
1929cc5190
|
@ -94,7 +94,8 @@
|
|||
"unread_ui": false,
|
||||
"user_events": false,
|
||||
"Plotly": false,
|
||||
"emoji_codes": false
|
||||
"emoji_codes": false,
|
||||
"drafts": false
|
||||
},
|
||||
"rules": {
|
||||
"no-restricted-syntax": 0,
|
||||
|
|
|
@ -0,0 +1,209 @@
|
|||
var common = require('../casper_lib/common.js').common;
|
||||
|
||||
function waitWhileDraftsVisible(then) {
|
||||
casper.waitFor(function () {
|
||||
return casper.evaluate(function () {
|
||||
return $("#draft_overlay").length === 0 ||
|
||||
$("#draft_overlay").css("opacity") === "0";
|
||||
});
|
||||
}, then);
|
||||
}
|
||||
|
||||
function waitUntilDraftsVisible(then) {
|
||||
casper.waitFor(function () {
|
||||
return casper.evaluate(function () {
|
||||
return $("#draft_overlay").length === 1 &&
|
||||
$("#draft_overlay").css("opacity") === "1";
|
||||
});
|
||||
}, then);
|
||||
}
|
||||
|
||||
common.start_and_log_in();
|
||||
|
||||
casper.then(function () {
|
||||
casper.test.info('Drafts page');
|
||||
|
||||
casper.waitUntilVisible('.drafts-link', function () {
|
||||
casper.click('.drafts-link');
|
||||
});
|
||||
});
|
||||
|
||||
casper.then(function () {
|
||||
casper.test.assertUrlMatch(/^http:\/\/[^/]+\/#drafts/,
|
||||
'URL suggests we are on drafts page');
|
||||
waitUntilDraftsVisible(function () {
|
||||
casper.test.assertExists('#draft_overlay', 'Drafts page is active');
|
||||
casper.test.assertSelectorHasText('.no-drafts', 'No Drafts.');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
casper.then(function () {
|
||||
casper.click('#draft_overlay .exit');
|
||||
waitWhileDraftsVisible();
|
||||
});
|
||||
|
||||
casper.then(function () {
|
||||
casper.test.info('Creating Stream Message Draft');
|
||||
casper.click('body');
|
||||
casper.page.sendEvent('keypress', "c");
|
||||
casper.waitUntilVisible('#stream-message', function () {
|
||||
casper.test.info('Creating Private Message Draft');
|
||||
casper.fill('form#send_message_form', {
|
||||
stream: 'all',
|
||||
subject: 'tests',
|
||||
content: 'Test Stream Message',
|
||||
}, false);
|
||||
casper.click("#compose_close");
|
||||
});
|
||||
});
|
||||
|
||||
casper.then(function () {
|
||||
casper.test.info('Creating Private Message Draft');
|
||||
casper.click('body');
|
||||
casper.page.sendEvent('keypress', "C");
|
||||
casper.waitUntilVisible('#private-message', function () {
|
||||
casper.fill('form#send_message_form', {
|
||||
recipient: 'cordelia@zulip.com, hamlet@zulip.com',
|
||||
content: 'Test Private Message',
|
||||
}, false);
|
||||
casper.click("#compose_close");
|
||||
});
|
||||
});
|
||||
|
||||
casper.then(function () {
|
||||
casper.waitUntilVisible('.drafts-link', function () {
|
||||
casper.click('.drafts-link');
|
||||
});
|
||||
});
|
||||
|
||||
casper.then(function () {
|
||||
waitUntilDraftsVisible(function () {
|
||||
casper.test.assertElementCount('.draft-row', 2, 'Drafts loaded');
|
||||
|
||||
casper.test.assertSelectorHasText('.draft-row .message_header_stream .stream_label', 'all');
|
||||
casper.test.assertSelectorHasText('.draft-row .message_header_stream .stream_topic', 'tests');
|
||||
casper.test.assertTextExists('Test Stream Message', 'Stream draft contains message content');
|
||||
|
||||
casper.test.assertSelectorHasText('.draft-row .message_header_private_message .stream_label',
|
||||
'You and Cordelia Lear, King Hamlet');
|
||||
casper.test.assertTextExists('Test Private Message', 'Private draft contains message content');
|
||||
});
|
||||
});
|
||||
|
||||
casper.then(function () {
|
||||
casper.test.info('Restoring Stream Message Draft');
|
||||
casper.click("#drafts_table .message_row:not(.private-message) .restore-draft");
|
||||
waitWhileDraftsVisible(function () {
|
||||
casper.test.assertVisible('#stream-message', 'Stream Message Box Restored');
|
||||
common.check_form('form#send_message_form', {
|
||||
stream: 'all',
|
||||
subject: 'tests',
|
||||
content: 'Test Stream Message',
|
||||
}, "Stream message box filled with draft content");
|
||||
});
|
||||
});
|
||||
|
||||
casper.then(function () {
|
||||
casper.test.info('Editing Stream Message Draft');
|
||||
casper.fill('form#send_message_form', {
|
||||
stream: 'all',
|
||||
subject: 'tests',
|
||||
content: 'Updated Stream Message',
|
||||
}, false);
|
||||
casper.click("#compose_close");
|
||||
});
|
||||
|
||||
casper.then(function () {
|
||||
casper.waitUntilVisible('.drafts-link', function () {
|
||||
casper.click('.drafts-link');
|
||||
});
|
||||
});
|
||||
|
||||
casper.then(function () {
|
||||
waitUntilDraftsVisible(function () {
|
||||
casper.test.assertSelectorHasText('.draft-row .message_header_stream .stream_label', 'all');
|
||||
casper.test.assertSelectorHasText('.draft-row .message_header_stream .stream_topic', 'tests');
|
||||
casper.test.assertTextExists('Updated Stream Message', 'Stream draft contains message content');
|
||||
});
|
||||
});
|
||||
|
||||
casper.then(function () {
|
||||
casper.test.info('Restoring Private Message Draft');
|
||||
casper.click("#drafts_table .message_row.private-message .restore-draft");
|
||||
waitWhileDraftsVisible(function () {
|
||||
casper.test.assertVisible('#private-message', 'Private Message Box Restored');
|
||||
common.check_form('form#send_message_form', {
|
||||
recipient: 'cordelia@zulip.com, hamlet@zulip.com',
|
||||
content: 'Test Private Message',
|
||||
}, "Private message box filled with draft content");
|
||||
});
|
||||
});
|
||||
|
||||
casper.then(function () {
|
||||
casper.click("#compose_close");
|
||||
casper.waitUntilVisible('.drafts-link', function () {
|
||||
casper.click('.drafts-link');
|
||||
});
|
||||
});
|
||||
|
||||
casper.then(function () {
|
||||
casper.test.info('Deleting Draft');
|
||||
casper.click("#drafts_table .message_row.private-message .delete-draft");
|
||||
casper.test.assertElementCount('.draft-row', 1, 'Draft deleted');
|
||||
casper.test.assertDoesntExist("#drafts_table .message_row.private-message");
|
||||
});
|
||||
|
||||
casper.then(function () {
|
||||
casper.test.info('Saving Draft by Reloading');
|
||||
casper.click('#draft_overlay .exit');
|
||||
waitWhileDraftsVisible(function () {
|
||||
casper.click('body');
|
||||
casper.page.sendEvent('keypress', "C");
|
||||
casper.waitUntilVisible('#private-message', function () {
|
||||
casper.fill('form#send_message_form', {
|
||||
recipient: 'cordelia@zulip.com',
|
||||
content: 'Test Private Message',
|
||||
}, false);
|
||||
casper.reload();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
casper.then(function () {
|
||||
casper.waitUntilVisible('.drafts-link', function () {
|
||||
casper.click('.drafts-link');
|
||||
});
|
||||
waitUntilDraftsVisible(function () {
|
||||
casper.test.assertElementCount('.draft-row', 2, 'Drafts loaded');
|
||||
casper.test.assertSelectorHasText('.draft-row .message_header_private_message .stream_label',
|
||||
'You and Cordelia Lear');
|
||||
casper.test.assertTextExists('Test Private Message');
|
||||
});
|
||||
});
|
||||
|
||||
casper.then(function () {
|
||||
casper.test.info('Deleting Draft after Sending Message');
|
||||
casper.click("#drafts_table .message_row.private-message .restore-draft");
|
||||
waitWhileDraftsVisible(function () {
|
||||
casper.test.assertVisible('#private-message');
|
||||
casper.click('#compose-send-button');
|
||||
});
|
||||
});
|
||||
|
||||
casper.then(function () {
|
||||
// This tests the second drafts link in the compose area
|
||||
casper.waitUntilVisible('.compose_table .drafts-link', function () {
|
||||
casper.click('.compose_table .drafts-link');
|
||||
});
|
||||
waitUntilDraftsVisible(function () {
|
||||
casper.test.assertElementCount('.draft-row', 1, 'Drafts loaded');
|
||||
casper.test.assertDoesntExist("#drafts_table .message_row.private-message");
|
||||
});
|
||||
});
|
||||
|
||||
common.then_log_out();
|
||||
|
||||
casper.run(function () {
|
||||
casper.test.done();
|
||||
});
|
|
@ -0,0 +1,95 @@
|
|||
global.stub_out_jquery();
|
||||
|
||||
add_dependencies({
|
||||
localstorage: 'js/localstorage',
|
||||
drafts: 'js/drafts',
|
||||
});
|
||||
|
||||
var ls_container = {};
|
||||
set_global('localStorage', {
|
||||
getItem: function (key) {
|
||||
return ls_container[key];
|
||||
},
|
||||
setItem: function (key, val) {
|
||||
ls_container[key] = val;
|
||||
},
|
||||
removeItem: function (key) {
|
||||
delete ls_container[key];
|
||||
},
|
||||
clear: function () {
|
||||
ls_container = {};
|
||||
},
|
||||
});
|
||||
|
||||
function stub_timestamp(model, timestamp, func) {
|
||||
var original_func = model.getTimestamp;
|
||||
model.getTimestamp = function () {
|
||||
return timestamp;
|
||||
};
|
||||
func();
|
||||
model.getTimestamp = original_func;
|
||||
}
|
||||
|
||||
var draft_1 = {
|
||||
stream: "stream",
|
||||
subject: "topic",
|
||||
type: "stream",
|
||||
content: "Test Stream Message",
|
||||
};
|
||||
var draft_2 = {
|
||||
private_message_recipient: "aaron@zulip.com",
|
||||
type: "private",
|
||||
content: "Test Private Message",
|
||||
};
|
||||
|
||||
(function test_draft_model() {
|
||||
var draft_model = drafts.draft_model;
|
||||
var ls = localstorage();
|
||||
|
||||
localStorage.clear();
|
||||
(function test_get() {
|
||||
var expected = { id1: draft_1, id2: draft_2 };
|
||||
ls.set("drafts", expected);
|
||||
|
||||
assert.deepEqual(draft_model.get(), expected);
|
||||
}());
|
||||
|
||||
localStorage.clear();
|
||||
(function test_get() {
|
||||
ls.set("drafts", { id1: draft_1 });
|
||||
|
||||
assert.deepEqual(draft_model.getDraft("id1"), draft_1);
|
||||
assert.equal(draft_model.getDraft("id2"), false);
|
||||
}());
|
||||
|
||||
localStorage.clear();
|
||||
(function test_addDraft() {
|
||||
stub_timestamp(draft_model, 1, function () {
|
||||
var expected = draft_1;
|
||||
expected.updatedAt = 1;
|
||||
var id = draft_model.addDraft(draft_1);
|
||||
|
||||
assert.deepEqual(ls.get("drafts")[id], expected);
|
||||
});
|
||||
}());
|
||||
|
||||
localStorage.clear();
|
||||
(function test_editDraft() {
|
||||
stub_timestamp(draft_model, 2, function () {
|
||||
ls.set("drafts", { id1: draft_1 } );
|
||||
var expected = draft_2;
|
||||
expected.updatedAt = 2;
|
||||
draft_model.editDraft("id1", draft_2);
|
||||
|
||||
assert.deepEqual(ls.get("drafts").id1, expected);
|
||||
});
|
||||
}());
|
||||
|
||||
localStorage.clear();
|
||||
(function test_deleteDraft() {
|
||||
ls.set("drafts", { id1: draft_1 } );
|
||||
draft_model.deleteDraft("id1");
|
||||
|
||||
assert.deepEqual(ls.get("drafts"), {});
|
||||
}());
|
||||
}());
|
|
@ -393,6 +393,51 @@ function render(template_name, args) {
|
|||
assert.equal(a.text(), "Narrow to here");
|
||||
}());
|
||||
|
||||
(function draft_table_body() {
|
||||
var args = {
|
||||
drafts: [
|
||||
{
|
||||
draft_id: '1',
|
||||
is_stream: true,
|
||||
stream: 'all',
|
||||
stream_color: '#FF0000', // rgb(255, 0, 0)
|
||||
topic: 'tests',
|
||||
content: 'Public draft',
|
||||
},
|
||||
{
|
||||
draft_id: '2',
|
||||
is_stream: false,
|
||||
recipients: 'Jordan, Michael',
|
||||
content: 'Private draft',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
var html = '';
|
||||
html += '<div id="drafts_table">';
|
||||
html += render('draft_table_body', args);
|
||||
html += '</div>';
|
||||
|
||||
global.write_handlebars_output("draft_table_body", html);
|
||||
|
||||
var row_1 = $(html).find(".draft-row[data-draft-id='1']");
|
||||
assert.equal(row_1.find(".stream_label").text().trim(), "all");
|
||||
assert.equal(row_1.find(".stream_label").css("background"), "rgb(255, 0, 0)");
|
||||
assert.equal(row_1.find(".stream_topic").text().trim(), "tests");
|
||||
assert(!row_1.find(".message_row").hasClass("private-message"));
|
||||
assert.equal(row_1.find(".messagebox").css("box-shadow"),
|
||||
"inset 2px 0px 0px 0px #FF0000, -1px 0px 0px 0px #FF0000");
|
||||
assert.equal(row_1.find(".message_content").text().trim(), "Public draft");
|
||||
|
||||
var row_2 = $(html).find(".draft-row[data-draft-id='2']");
|
||||
assert.equal(row_2.find(".stream_label").text().trim(), "You and Jordan, Michael");
|
||||
assert(row_2.find(".message_row").hasClass("private-message"));
|
||||
assert.equal(row_2.find(".messagebox").css("box-shadow"),
|
||||
"inset 2px 0px 0px 0px #444444, -1px 0px 0px 0px #444444");
|
||||
assert.equal(row_2.find(".message_content").text().trim(), "Private draft");
|
||||
}());
|
||||
|
||||
|
||||
(function email_address_hint() {
|
||||
var html = render('email_address_hint');
|
||||
global.write_handlebars_output("email_address_hint", html);
|
||||
|
|
|
@ -268,6 +268,18 @@ $(function () {
|
|||
subs.close();
|
||||
}
|
||||
});
|
||||
|
||||
$("#drafts_table").on("click", ".exit, #draft_overlay", function (e) {
|
||||
if (meta.focusing) {
|
||||
meta.focusing = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if ($(e.target).is(".exit, .exit-sign, #draft_overlay, #draft_overlay > .flex")) {
|
||||
drafts.close();
|
||||
}
|
||||
});
|
||||
|
||||
// HOME
|
||||
|
||||
// Capture both the left-sidebar Home click and the tab breadcrumb Home
|
||||
|
|
|
@ -15,8 +15,6 @@ var user_acknowledged_all_everyone;
|
|||
|
||||
exports.all_everyone_warn_threshold = 15;
|
||||
|
||||
var message_snapshot;
|
||||
|
||||
var uploads_domain = document.location.protocol + '//' + document.location.host;
|
||||
var uploads_path = '/user_uploads';
|
||||
var uploads_re = new RegExp("\\]\\(" + uploads_domain + "(" + uploads_path + "[^\\)]+)\\)", 'g');
|
||||
|
@ -116,11 +114,11 @@ function clear_invites() {
|
|||
}
|
||||
|
||||
function clear_box() {
|
||||
exports.snapshot_message();
|
||||
clear_invites();
|
||||
clear_all_everyone_warnings();
|
||||
user_acknowledged_all_everyone = undefined;
|
||||
$("#compose").find('input[type=text], textarea').val('');
|
||||
$("#new_message_content").removeData("draft-id");
|
||||
exports.autosize_textarea();
|
||||
$("#send-status").hide(0);
|
||||
}
|
||||
|
@ -131,9 +129,6 @@ function clear_preview_area() {
|
|||
$("#preview_message_area").hide();
|
||||
$("#preview_content").empty();
|
||||
$("#markdown_preview").show();
|
||||
if (message_snapshot !== undefined) {
|
||||
$('#restore-draft').show();
|
||||
}
|
||||
}
|
||||
|
||||
function hide_box() {
|
||||
|
@ -307,9 +302,6 @@ exports.cancel = function () {
|
|||
notifications.clear_compose_notifications();
|
||||
abort_xhr();
|
||||
is_composing_message = false;
|
||||
if (message_snapshot !== undefined) {
|
||||
$('#restore-draft').show();
|
||||
}
|
||||
$(document).trigger($.Event('compose_canceled.zulip'));
|
||||
};
|
||||
|
||||
|
@ -355,40 +347,10 @@ exports.snapshot_message = function (message) {
|
|||
}
|
||||
|
||||
if (message !== undefined) {
|
||||
message_snapshot = _.extend({}, message);
|
||||
} else {
|
||||
// Save what we can.
|
||||
message_snapshot = create_message_object();
|
||||
}
|
||||
};
|
||||
|
||||
function clear_message_snapshot() {
|
||||
$("#restore-draft").hide();
|
||||
message_snapshot = undefined;
|
||||
}
|
||||
|
||||
exports.restore_message = function () {
|
||||
if (!message_snapshot) {
|
||||
return;
|
||||
}
|
||||
var snapshot_copy = _.extend({}, message_snapshot);
|
||||
if ((snapshot_copy.type === "stream" &&
|
||||
snapshot_copy.stream.length > 0 &&
|
||||
snapshot_copy.subject.length > 0) ||
|
||||
(snapshot_copy.type === "private" &&
|
||||
snapshot_copy.reply_to.length > 0)) {
|
||||
snapshot_copy = _.extend({replying_to_message: snapshot_copy},
|
||||
snapshot_copy);
|
||||
}
|
||||
clear_message_snapshot();
|
||||
compose_fade.clear_compose();
|
||||
compose.start(snapshot_copy.type, snapshot_copy);
|
||||
exports.autosize_textarea();
|
||||
|
||||
if (snapshot_copy.content !== undefined &&
|
||||
util.is_all_or_everyone_mentioned(snapshot_copy.content)) {
|
||||
show_all_everyone_warnings();
|
||||
return _.extend({}, message);
|
||||
}
|
||||
// Save what we can.
|
||||
return create_message_object();
|
||||
};
|
||||
|
||||
function compose_error(error_text, bad_input) {
|
||||
|
@ -525,9 +487,9 @@ function process_send_time(message_id, start_time, locally_echoed) {
|
|||
|
||||
function clear_compose_box() {
|
||||
$("#new_message_content").val('').focus();
|
||||
drafts.delete_draft_after_send();
|
||||
exports.autosize_textarea();
|
||||
$("#send-status").hide(0);
|
||||
clear_message_snapshot();
|
||||
$("#compose-send-button").removeAttr('disabled');
|
||||
$("#sending-indicator").hide();
|
||||
resize.resize_bottom_whitespace();
|
||||
|
@ -622,7 +584,7 @@ exports.respond_to_message = function (opts) {
|
|||
var msg_type;
|
||||
// Before initiating a reply to a message, if there's an
|
||||
// in-progress composition, snapshot it.
|
||||
compose.snapshot_message();
|
||||
drafts.update_draft();
|
||||
|
||||
message = current_msg_list.selected_message();
|
||||
|
||||
|
@ -1043,7 +1005,6 @@ $(function () {
|
|||
var message = $("#new_message_content").val();
|
||||
$("#new_message_content").hide();
|
||||
$("#markdown_preview").hide();
|
||||
$("#restore-draft").hide();
|
||||
$("#undo_markdown_preview").show();
|
||||
$("#preview_message_area").show();
|
||||
|
||||
|
|
|
@ -0,0 +1,230 @@
|
|||
var drafts = (function () {
|
||||
|
||||
var exports = {};
|
||||
|
||||
var draft_model = (function () {
|
||||
var exports = {};
|
||||
|
||||
// the key that the drafts are stored under.
|
||||
var KEY = "drafts";
|
||||
var ls = localstorage();
|
||||
ls.version = 1;
|
||||
|
||||
function getTimestamp() {
|
||||
return new Date().getTime();
|
||||
}
|
||||
|
||||
function get() {
|
||||
return ls.get(KEY) || {};
|
||||
}
|
||||
exports.get = get;
|
||||
|
||||
exports.getDraft = function (id) {
|
||||
return get()[id] || false;
|
||||
};
|
||||
|
||||
function save(drafts) {
|
||||
ls.set(KEY, drafts);
|
||||
}
|
||||
|
||||
exports.addDraft = function (draft) {
|
||||
var drafts = get();
|
||||
|
||||
// use the base16 of the current time + a random string to reduce
|
||||
// collisions to essentially zero.
|
||||
var id = getTimestamp().toString(16) + "-" + Math.random().toString(16).split(/\./).pop();
|
||||
|
||||
draft.updatedAt = getTimestamp();
|
||||
drafts[id] = draft;
|
||||
save(drafts);
|
||||
|
||||
return id;
|
||||
};
|
||||
|
||||
exports.editDraft = function (id, draft) {
|
||||
var drafts = get();
|
||||
|
||||
if (drafts[id]) {
|
||||
draft.updatedAt = getTimestamp();
|
||||
drafts[id] = draft;
|
||||
save(drafts);
|
||||
}
|
||||
};
|
||||
|
||||
exports.deleteDraft = function (id) {
|
||||
var drafts = get();
|
||||
|
||||
delete drafts[id];
|
||||
save(drafts);
|
||||
};
|
||||
|
||||
return exports;
|
||||
}());
|
||||
|
||||
exports.draft_model = draft_model;
|
||||
|
||||
exports.update_draft = function () {
|
||||
var draft = compose.snapshot_message();
|
||||
var draft_id = $("#new_message_content").data("draft-id");
|
||||
|
||||
if (draft_id !== undefined) {
|
||||
if (draft !== undefined) {
|
||||
draft_model.editDraft(draft_id, draft);
|
||||
} else {
|
||||
draft_model.deleteDraft(draft_id);
|
||||
}
|
||||
} else {
|
||||
if (draft !== undefined) {
|
||||
var new_draft_id = draft_model.addDraft(draft);
|
||||
$("#new_message_content").data("draft-id", new_draft_id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.delete_draft_after_send = function () {
|
||||
var draft_id = $("#new_message_content").data("draft-id");
|
||||
if (draft_id) {
|
||||
draft_model.deleteDraft(draft_id);
|
||||
}
|
||||
$("#new_message_content").removeData("draft-id");
|
||||
};
|
||||
|
||||
exports.restore_draft = function (draft_id) {
|
||||
var draft = draft_model.getDraft(draft_id);
|
||||
if (!draft) {
|
||||
return;
|
||||
}
|
||||
|
||||
var draft_copy = _.extend({}, draft);
|
||||
if ((draft_copy.type === "stream" &&
|
||||
draft_copy.stream.length > 0 &&
|
||||
draft_copy.subject.length > 0) ||
|
||||
(draft_copy.type === "private" &&
|
||||
draft_copy.reply_to.length > 0)) {
|
||||
draft_copy = _.extend({replying_to_message: draft_copy},
|
||||
draft_copy);
|
||||
}
|
||||
|
||||
exports.close();
|
||||
compose_fade.clear_compose();
|
||||
if (draft.type === "stream" && draft.stream === "") {
|
||||
draft_copy.subject = "";
|
||||
}
|
||||
compose.start(draft_copy.type, draft_copy);
|
||||
compose.autosize_textarea();
|
||||
$("#new_message_content").data("draft-id", draft_id);
|
||||
};
|
||||
|
||||
exports.setup_page = function (callback) {
|
||||
function setup_event_handlers() {
|
||||
$(".draft_controls .restore-draft").on("click", function (e) {
|
||||
e.stopPropagation();
|
||||
|
||||
var draft_row = $(this).closest(".draft-row");
|
||||
var draft_id = draft_row.data("draft-id");
|
||||
exports.restore_draft(draft_id);
|
||||
});
|
||||
|
||||
$(".draft_controls .delete-draft").on("click", function () {
|
||||
var draft_row = $(this).closest(".draft-row");
|
||||
var draft_id = draft_row.data("draft-id");
|
||||
|
||||
exports.draft_model.deleteDraft(draft_id);
|
||||
draft_row.remove();
|
||||
|
||||
if ($("#drafts_table .draft-row").length === 0) {
|
||||
$('#drafts_table .no-drafts').show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function format_drafts(data) {
|
||||
var drafts = _.mapObject(data, function (draft, id) {
|
||||
var formatted;
|
||||
if (draft.type === "stream") {
|
||||
// In case there is no stream for the draft, we need a
|
||||
// single space char for proper rendering of the stream label
|
||||
var space_string = new Handlebars.SafeString(" ");
|
||||
var stream = (draft.stream.length > 0 ? draft.stream : space_string);
|
||||
formatted = {
|
||||
draft_id: id,
|
||||
is_stream: true,
|
||||
stream: stream,
|
||||
stream_color: stream_data.get_color(draft.stream),
|
||||
topic: draft.subject,
|
||||
raw_content: draft.content,
|
||||
};
|
||||
echo.apply_markdown(formatted);
|
||||
} else {
|
||||
var emails = util.extract_pm_recipients(draft.private_message_recipient);
|
||||
var recipients = _.map(emails, function (email) {
|
||||
email = email.trim();
|
||||
var person = people.get_by_email(email);
|
||||
if (person !== undefined) {
|
||||
return person.full_name;
|
||||
}
|
||||
return email;
|
||||
}).join(', ');
|
||||
|
||||
formatted = {
|
||||
draft_id: id,
|
||||
is_stream: false,
|
||||
recipients: recipients,
|
||||
raw_content: draft.content,
|
||||
};
|
||||
echo.apply_markdown(formatted);
|
||||
}
|
||||
return formatted;
|
||||
});
|
||||
return drafts;
|
||||
}
|
||||
|
||||
function _populate_and_fill() {
|
||||
$('#drafts_table').empty();
|
||||
var drafts = format_drafts(draft_model.get());
|
||||
var rendered = templates.render('draft_table_body', { drafts: drafts });
|
||||
$('#drafts_table').append(rendered);
|
||||
if ($("#drafts_table .draft-row").length > 0) {
|
||||
$('#drafts_table .no-drafts').hide();
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
|
||||
setup_event_handlers();
|
||||
}
|
||||
|
||||
function populate_and_fill() {
|
||||
i18n.ensure_i18n(function () {
|
||||
_populate_and_fill();
|
||||
});
|
||||
}
|
||||
populate_and_fill();
|
||||
};
|
||||
|
||||
exports.launch = function () {
|
||||
exports.setup_page(function () {
|
||||
$("#draft_overlay").addClass("show");
|
||||
});
|
||||
};
|
||||
|
||||
exports.close = function () {
|
||||
hashchange.exit_settings();
|
||||
$("#draft_overlay").removeClass("show");
|
||||
};
|
||||
|
||||
$(function () {
|
||||
window.addEventListener("beforeunload", function () {
|
||||
exports.update_draft();
|
||||
});
|
||||
|
||||
$("#new_message_content").focusout(exports.update_draft);
|
||||
});
|
||||
|
||||
return exports;
|
||||
|
||||
}());
|
||||
if (typeof module !== 'undefined') {
|
||||
module.exports = drafts;
|
||||
}
|
|
@ -184,6 +184,9 @@ function do_hashchange(from_reload) {
|
|||
case "#subscriptions":
|
||||
ui.change_tab_to("#subscriptions");
|
||||
break;
|
||||
case "#drafts":
|
||||
ui.change_tab_to("#drafts");
|
||||
break;
|
||||
case "#administration":
|
||||
ui.change_tab_to("#administration");
|
||||
break;
|
||||
|
@ -243,7 +246,7 @@ var get_hash_group = (function () {
|
|||
|
||||
function should_ignore(hash) {
|
||||
// an array of hashes to ignore (eg. ["subscriptions", "settings", "administration"]).
|
||||
var ignore_list = ["subscriptions", "settings", "administration"];
|
||||
var ignore_list = ["subscriptions", "drafts", "settings", "administration"];
|
||||
var main_hash = get_main_hash(hash);
|
||||
|
||||
return (ignore_list.indexOf(main_hash) > -1);
|
||||
|
@ -275,6 +278,8 @@ function hashchanged(from_reload, e) {
|
|||
|
||||
if (base === "subscriptions") {
|
||||
subs.launch();
|
||||
} else if (base === "drafts") {
|
||||
drafts.launch();
|
||||
} else if (/settings|administration/.test(base)) {
|
||||
settings.setup_page();
|
||||
admin.setup_page();
|
||||
|
|
|
@ -211,6 +211,9 @@ function process_hotkey(e) {
|
|||
} else if ($("#subscription_overlay").hasClass("show")) {
|
||||
subs.close();
|
||||
return true;
|
||||
} else if ($("#draft_overlay").hasClass("show")) {
|
||||
drafts.close();
|
||||
return true;
|
||||
} else if ($(".informational-overlays").hasClass("show")) {
|
||||
ui.hide_info_overlay();
|
||||
return true;
|
||||
|
|
|
@ -11,6 +11,12 @@
|
|||
width: 10px;
|
||||
}
|
||||
|
||||
#compose_controls .drafts-link {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
bottom: 5px;
|
||||
}
|
||||
|
||||
.new_message_button {
|
||||
padding-top: 1.1em;
|
||||
}
|
||||
|
@ -355,14 +361,7 @@ input.recipient_box {
|
|||
display: none;
|
||||
}
|
||||
|
||||
#sending-indicator {
|
||||
float: left;
|
||||
font-weight: bold;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#restore-draft {
|
||||
display: none;
|
||||
.compose_table .drafts-link {
|
||||
position: relative;
|
||||
margin-right: 1em;
|
||||
margin-left: 5px;
|
||||
|
@ -370,11 +369,13 @@ input.recipient_box {
|
|||
}
|
||||
|
||||
#sending-indicator {
|
||||
padding-top: 2px;
|
||||
float: left;
|
||||
font-weight: bold;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#restore-draft:hover {
|
||||
cursor: pointer;
|
||||
#sending-indicator {
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
#compose a.message-control-button {
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
.drafts {
|
||||
margin-top: 55px;
|
||||
padding-left: 15px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.drafts-container {
|
||||
position: relative;
|
||||
height: 95%;
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 0px;
|
||||
width: 60%;
|
||||
overflow: hidden;
|
||||
max-width: 1200px;
|
||||
max-height: 1000px;
|
||||
}
|
||||
|
||||
.drafts-header {
|
||||
padding: 20px;
|
||||
margin-bottom: 10px;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
font-weight: 700;
|
||||
font-size: 1.25em;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.drafts-container .exit {
|
||||
font-weight: 400;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
color: #AAA;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.drafts-container .exit-sign {
|
||||
position: relative;
|
||||
top: 3px;
|
||||
margin-left: 3px;
|
||||
font-size: 1.7em;
|
||||
font-weight: 300;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.drafts-list {
|
||||
overflow: auto;
|
||||
height: calc(100% - 90px);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.drafts-list .no-drafts {
|
||||
display: block;
|
||||
margin-top: calc(45vh - 30px - 1.5em);
|
||||
text-align: center;
|
||||
font-size: 1.5em;
|
||||
color: #aaa;
|
||||
pointer-events: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.draft-row {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.draft-row.active {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.draft-row > div {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.draft-row .draft-info-box {
|
||||
width: 100%;
|
||||
background: #f1f1f1;
|
||||
border-bottom: 1px solid #e2e2e2;
|
||||
border-top: 1px solid #e2e2e2;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.draft-info-box .messagebox {
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.draft-info-box .message_content {
|
||||
line-height: 1;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
padding-left: 0px;
|
||||
}
|
||||
|
||||
.draft-row .draft-info-box .draft_controls {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: -55px;
|
||||
}
|
||||
|
||||
.draft_controls .restore-draft {
|
||||
cursor: pointer;
|
||||
margin-right: 5px;
|
||||
color: #037C2C;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.draft_controls .restore-draft:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.draft_controls .delete-draft {
|
||||
cursor: pointer;
|
||||
margin-left: 5px;
|
||||
color: #D8000C;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.draft_controls .delete-draft:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#draft_overlay {
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: rgba(0,0,0,0.8);
|
||||
overflow: auto;
|
||||
z-index: 102;
|
||||
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
#draft_overlay.show {
|
||||
display: block;
|
||||
pointer-events: all;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#draft_overlay .drafts-container {
|
||||
-webkit-transform: scale(0.5);
|
||||
transform: scale(0.5);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
#draft_overlay.show .drafts-container {
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
@media (max-width: 1130px) {
|
||||
.drafts-container {
|
||||
max-width: 60%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
#draft_overlay .drafts-container {
|
||||
height: 95%;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<div class="draft-row" data-draft-id="{{draft_id}}">
|
||||
<div class="draft-info-box">
|
||||
{{#if is_stream}}
|
||||
<div class="message_header message_header_stream">
|
||||
<div class="message-header-contents">
|
||||
<div class="message_label_clickable stream_label"
|
||||
style="background: {{stream_color}}; border-left-color: {{stream_color}};">
|
||||
{{stream}}
|
||||
</div>
|
||||
|
||||
<span class="stream_topic">
|
||||
<div class="message_label_clickable narrows_by_subject">
|
||||
{{topic}}
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="message_header message_header_private_message dark_background">
|
||||
<div class="message-header-contents">
|
||||
<div class="message_label_clickable stream_label">
|
||||
{{#tr this}}You and __recipients__{{/tr}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="message_row{{^is_stream}} private-message{{/is_stream}}">
|
||||
<div class="messagebox"
|
||||
style="box-shadow: inset 2px 0px 0px 0px {{#if is_stream}}{{stream_color}}{{else}}#444444{{/if}}, -1px 0px 0px 0px {{#if is_stream}}{{stream_color}}{{else}}#444444{{/if}};">
|
||||
<div class="messagebox-content">
|
||||
<div class="message_top_line">
|
||||
<div class="draft_controls">
|
||||
<i class="icon-vector-large icon-vector-pencil restore-draft" data-toggle="tooltip" title="{{t 'Restore Draft' }}"></i>
|
||||
<i class="icon-vector-large icon-vector-trash delete-draft" data-toggle="tooltip" title="{{t 'Delete Draft' }}"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="message_content">{{{content}}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,21 @@
|
|||
<div id="draft_overlay" class="new-style" data-overlay="drafts">
|
||||
<div class="flex">
|
||||
<div class="drafts-container">
|
||||
<div class="drafts-header">
|
||||
{{t 'Drafts' }}
|
||||
<div class="exit">
|
||||
<span class="exit-sign">×</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="drafts-list">
|
||||
<div class="no-drafts">
|
||||
{{t 'No Drafts.'}}
|
||||
</div>
|
||||
|
||||
{{#each drafts}}
|
||||
{{partial "draft"}}
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -3,6 +3,7 @@
|
|||
</div>
|
||||
<div id="compose-container">
|
||||
<div id="compose_controls" class="compose-content">
|
||||
<a class="drafts-link" href="#drafts">{{ _('Drafts') }}</a>
|
||||
<div id="compose_buttons">
|
||||
<span class="new_message_button">
|
||||
<button type="button" class="btn btn-default btn-large compose_stream_button"
|
||||
|
@ -87,7 +88,7 @@
|
|||
<a class="message-control-button icon-vector-font" title="{{ _('Formatting') }}" data-overlay-trigger="markdown-help"></a>
|
||||
<a id="undo_markdown_preview" class="icon-vector-edit" style="display:none;" title="{{ _('Write') }}"></a>
|
||||
<a id="markdown_preview" class="icon-vector-eye-open" title="{{ _('Preview') }}"></a>
|
||||
<a id="restore-draft" onclick="compose.restore_message();">{{ _('Restore draft') }}</a>
|
||||
<a class="drafts-link" href="#drafts">{{ _('Drafts') }}</a>
|
||||
<span id="sending-indicator">{{ _('Sending...') }}</span>
|
||||
<div id="send_controls">
|
||||
<label id="enter-sends-label" class="compose_checkbox_label" for="enter_sends">{{ _('Press Enter to send') }} </label>
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
{# List of drafts for an user. #}
|
||||
<div class="drafts">
|
||||
<div id="drafts_table">
|
||||
</div>
|
||||
</div>
|
|
@ -122,6 +122,7 @@ var page_params = {{ page_params }};
|
|||
</div><!--/right sidebar-->
|
||||
{% include "zerver/image-overlay.html" %}
|
||||
{% include "zerver/subscriptions.html" %}
|
||||
{% include "zerver/drafts.html" %}
|
||||
</div><!--/row-->
|
||||
<div class="informational-overlays new-style">
|
||||
<div class="overlay-content">
|
||||
|
|
|
@ -63,6 +63,7 @@ class TemplateTestCase(ZulipTestCase):
|
|||
|
||||
logged_in = [
|
||||
'analytics/stats.html',
|
||||
'zerver/drafts.html',
|
||||
'zerver/home.html',
|
||||
'zerver/invite_user.html',
|
||||
'zerver/keyboard_shortcuts.html',
|
||||
|
|
|
@ -684,6 +684,7 @@ PIPELINE = {
|
|||
'styles/zulip.css',
|
||||
'styles/settings.css',
|
||||
'styles/subscriptions.css',
|
||||
'styles/drafts.css',
|
||||
'styles/informational-overlays.css',
|
||||
'styles/compose.css',
|
||||
'styles/reactions.css',
|
||||
|
@ -706,6 +707,7 @@ PIPELINE = {
|
|||
'styles/zulip.css',
|
||||
'styles/settings.css',
|
||||
'styles/subscriptions.css',
|
||||
'styles/drafts.css',
|
||||
'styles/informational-overlays.css',
|
||||
'styles/compose.css',
|
||||
'styles/reactions.css',
|
||||
|
@ -793,6 +795,7 @@ JS_SPECS = {
|
|||
'js/dict.js',
|
||||
'js/components.js',
|
||||
'js/localstorage.js',
|
||||
'js/drafts.js',
|
||||
'js/channel.js',
|
||||
'js/setup.js',
|
||||
'js/unread_ui.js',
|
||||
|
|
Loading…
Reference in New Issue