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,
|
"unread_ui": false,
|
||||||
"user_events": false,
|
"user_events": false,
|
||||||
"Plotly": false,
|
"Plotly": false,
|
||||||
"emoji_codes": false
|
"emoji_codes": false,
|
||||||
|
"drafts": false
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-restricted-syntax": 0,
|
"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");
|
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() {
|
(function email_address_hint() {
|
||||||
var html = render('email_address_hint');
|
var html = render('email_address_hint');
|
||||||
global.write_handlebars_output("email_address_hint", html);
|
global.write_handlebars_output("email_address_hint", html);
|
||||||
|
|
|
@ -268,6 +268,18 @@ $(function () {
|
||||||
subs.close();
|
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
|
// HOME
|
||||||
|
|
||||||
// Capture both the left-sidebar Home click and the tab breadcrumb 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;
|
exports.all_everyone_warn_threshold = 15;
|
||||||
|
|
||||||
var message_snapshot;
|
|
||||||
|
|
||||||
var uploads_domain = document.location.protocol + '//' + document.location.host;
|
var uploads_domain = document.location.protocol + '//' + document.location.host;
|
||||||
var uploads_path = '/user_uploads';
|
var uploads_path = '/user_uploads';
|
||||||
var uploads_re = new RegExp("\\]\\(" + uploads_domain + "(" + uploads_path + "[^\\)]+)\\)", 'g');
|
var uploads_re = new RegExp("\\]\\(" + uploads_domain + "(" + uploads_path + "[^\\)]+)\\)", 'g');
|
||||||
|
@ -116,11 +114,11 @@ function clear_invites() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function clear_box() {
|
function clear_box() {
|
||||||
exports.snapshot_message();
|
|
||||||
clear_invites();
|
clear_invites();
|
||||||
clear_all_everyone_warnings();
|
clear_all_everyone_warnings();
|
||||||
user_acknowledged_all_everyone = undefined;
|
user_acknowledged_all_everyone = undefined;
|
||||||
$("#compose").find('input[type=text], textarea').val('');
|
$("#compose").find('input[type=text], textarea').val('');
|
||||||
|
$("#new_message_content").removeData("draft-id");
|
||||||
exports.autosize_textarea();
|
exports.autosize_textarea();
|
||||||
$("#send-status").hide(0);
|
$("#send-status").hide(0);
|
||||||
}
|
}
|
||||||
|
@ -131,9 +129,6 @@ function clear_preview_area() {
|
||||||
$("#preview_message_area").hide();
|
$("#preview_message_area").hide();
|
||||||
$("#preview_content").empty();
|
$("#preview_content").empty();
|
||||||
$("#markdown_preview").show();
|
$("#markdown_preview").show();
|
||||||
if (message_snapshot !== undefined) {
|
|
||||||
$('#restore-draft').show();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function hide_box() {
|
function hide_box() {
|
||||||
|
@ -307,9 +302,6 @@ exports.cancel = function () {
|
||||||
notifications.clear_compose_notifications();
|
notifications.clear_compose_notifications();
|
||||||
abort_xhr();
|
abort_xhr();
|
||||||
is_composing_message = false;
|
is_composing_message = false;
|
||||||
if (message_snapshot !== undefined) {
|
|
||||||
$('#restore-draft').show();
|
|
||||||
}
|
|
||||||
$(document).trigger($.Event('compose_canceled.zulip'));
|
$(document).trigger($.Event('compose_canceled.zulip'));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -355,40 +347,10 @@ exports.snapshot_message = function (message) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message !== undefined) {
|
if (message !== undefined) {
|
||||||
message_snapshot = _.extend({}, message);
|
return _.extend({}, message);
|
||||||
} else {
|
}
|
||||||
// Save what we can.
|
// Save what we can.
|
||||||
message_snapshot = create_message_object();
|
return 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();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function compose_error(error_text, bad_input) {
|
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() {
|
function clear_compose_box() {
|
||||||
$("#new_message_content").val('').focus();
|
$("#new_message_content").val('').focus();
|
||||||
|
drafts.delete_draft_after_send();
|
||||||
exports.autosize_textarea();
|
exports.autosize_textarea();
|
||||||
$("#send-status").hide(0);
|
$("#send-status").hide(0);
|
||||||
clear_message_snapshot();
|
|
||||||
$("#compose-send-button").removeAttr('disabled');
|
$("#compose-send-button").removeAttr('disabled');
|
||||||
$("#sending-indicator").hide();
|
$("#sending-indicator").hide();
|
||||||
resize.resize_bottom_whitespace();
|
resize.resize_bottom_whitespace();
|
||||||
|
@ -622,7 +584,7 @@ exports.respond_to_message = function (opts) {
|
||||||
var msg_type;
|
var msg_type;
|
||||||
// Before initiating a reply to a message, if there's an
|
// Before initiating a reply to a message, if there's an
|
||||||
// in-progress composition, snapshot it.
|
// in-progress composition, snapshot it.
|
||||||
compose.snapshot_message();
|
drafts.update_draft();
|
||||||
|
|
||||||
message = current_msg_list.selected_message();
|
message = current_msg_list.selected_message();
|
||||||
|
|
||||||
|
@ -1043,7 +1005,6 @@ $(function () {
|
||||||
var message = $("#new_message_content").val();
|
var message = $("#new_message_content").val();
|
||||||
$("#new_message_content").hide();
|
$("#new_message_content").hide();
|
||||||
$("#markdown_preview").hide();
|
$("#markdown_preview").hide();
|
||||||
$("#restore-draft").hide();
|
|
||||||
$("#undo_markdown_preview").show();
|
$("#undo_markdown_preview").show();
|
||||||
$("#preview_message_area").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":
|
case "#subscriptions":
|
||||||
ui.change_tab_to("#subscriptions");
|
ui.change_tab_to("#subscriptions");
|
||||||
break;
|
break;
|
||||||
|
case "#drafts":
|
||||||
|
ui.change_tab_to("#drafts");
|
||||||
|
break;
|
||||||
case "#administration":
|
case "#administration":
|
||||||
ui.change_tab_to("#administration");
|
ui.change_tab_to("#administration");
|
||||||
break;
|
break;
|
||||||
|
@ -243,7 +246,7 @@ var get_hash_group = (function () {
|
||||||
|
|
||||||
function should_ignore(hash) {
|
function should_ignore(hash) {
|
||||||
// an array of hashes to ignore (eg. ["subscriptions", "settings", "administration"]).
|
// 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);
|
var main_hash = get_main_hash(hash);
|
||||||
|
|
||||||
return (ignore_list.indexOf(main_hash) > -1);
|
return (ignore_list.indexOf(main_hash) > -1);
|
||||||
|
@ -275,6 +278,8 @@ function hashchanged(from_reload, e) {
|
||||||
|
|
||||||
if (base === "subscriptions") {
|
if (base === "subscriptions") {
|
||||||
subs.launch();
|
subs.launch();
|
||||||
|
} else if (base === "drafts") {
|
||||||
|
drafts.launch();
|
||||||
} else if (/settings|administration/.test(base)) {
|
} else if (/settings|administration/.test(base)) {
|
||||||
settings.setup_page();
|
settings.setup_page();
|
||||||
admin.setup_page();
|
admin.setup_page();
|
||||||
|
|
|
@ -211,6 +211,9 @@ function process_hotkey(e) {
|
||||||
} else if ($("#subscription_overlay").hasClass("show")) {
|
} else if ($("#subscription_overlay").hasClass("show")) {
|
||||||
subs.close();
|
subs.close();
|
||||||
return true;
|
return true;
|
||||||
|
} else if ($("#draft_overlay").hasClass("show")) {
|
||||||
|
drafts.close();
|
||||||
|
return true;
|
||||||
} else if ($(".informational-overlays").hasClass("show")) {
|
} else if ($(".informational-overlays").hasClass("show")) {
|
||||||
ui.hide_info_overlay();
|
ui.hide_info_overlay();
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -11,6 +11,12 @@
|
||||||
width: 10px;
|
width: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#compose_controls .drafts-link {
|
||||||
|
position: absolute;
|
||||||
|
left: 10px;
|
||||||
|
bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.new_message_button {
|
.new_message_button {
|
||||||
padding-top: 1.1em;
|
padding-top: 1.1em;
|
||||||
}
|
}
|
||||||
|
@ -355,14 +361,7 @@ input.recipient_box {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sending-indicator {
|
.compose_table .drafts-link {
|
||||||
float: left;
|
|
||||||
font-weight: bold;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#restore-draft {
|
|
||||||
display: none;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
|
@ -370,11 +369,13 @@ input.recipient_box {
|
||||||
}
|
}
|
||||||
|
|
||||||
#sending-indicator {
|
#sending-indicator {
|
||||||
padding-top: 2px;
|
float: left;
|
||||||
|
font-weight: bold;
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#restore-draft:hover {
|
#sending-indicator {
|
||||||
cursor: pointer;
|
padding-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#compose a.message-control-button {
|
#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>
|
||||||
<div id="compose-container">
|
<div id="compose-container">
|
||||||
<div id="compose_controls" class="compose-content">
|
<div id="compose_controls" class="compose-content">
|
||||||
|
<a class="drafts-link" href="#drafts">{{ _('Drafts') }}</a>
|
||||||
<div id="compose_buttons">
|
<div id="compose_buttons">
|
||||||
<span class="new_message_button">
|
<span class="new_message_button">
|
||||||
<button type="button" class="btn btn-default btn-large compose_stream_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 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="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="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>
|
<span id="sending-indicator">{{ _('Sending...') }}</span>
|
||||||
<div id="send_controls">
|
<div id="send_controls">
|
||||||
<label id="enter-sends-label" class="compose_checkbox_label" for="enter_sends">{{ _('Press Enter to send') }} </label>
|
<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-->
|
</div><!--/right sidebar-->
|
||||||
{% include "zerver/image-overlay.html" %}
|
{% include "zerver/image-overlay.html" %}
|
||||||
{% include "zerver/subscriptions.html" %}
|
{% include "zerver/subscriptions.html" %}
|
||||||
|
{% include "zerver/drafts.html" %}
|
||||||
</div><!--/row-->
|
</div><!--/row-->
|
||||||
<div class="informational-overlays new-style">
|
<div class="informational-overlays new-style">
|
||||||
<div class="overlay-content">
|
<div class="overlay-content">
|
||||||
|
|
|
@ -63,6 +63,7 @@ class TemplateTestCase(ZulipTestCase):
|
||||||
|
|
||||||
logged_in = [
|
logged_in = [
|
||||||
'analytics/stats.html',
|
'analytics/stats.html',
|
||||||
|
'zerver/drafts.html',
|
||||||
'zerver/home.html',
|
'zerver/home.html',
|
||||||
'zerver/invite_user.html',
|
'zerver/invite_user.html',
|
||||||
'zerver/keyboard_shortcuts.html',
|
'zerver/keyboard_shortcuts.html',
|
||||||
|
|
|
@ -684,6 +684,7 @@ PIPELINE = {
|
||||||
'styles/zulip.css',
|
'styles/zulip.css',
|
||||||
'styles/settings.css',
|
'styles/settings.css',
|
||||||
'styles/subscriptions.css',
|
'styles/subscriptions.css',
|
||||||
|
'styles/drafts.css',
|
||||||
'styles/informational-overlays.css',
|
'styles/informational-overlays.css',
|
||||||
'styles/compose.css',
|
'styles/compose.css',
|
||||||
'styles/reactions.css',
|
'styles/reactions.css',
|
||||||
|
@ -706,6 +707,7 @@ PIPELINE = {
|
||||||
'styles/zulip.css',
|
'styles/zulip.css',
|
||||||
'styles/settings.css',
|
'styles/settings.css',
|
||||||
'styles/subscriptions.css',
|
'styles/subscriptions.css',
|
||||||
|
'styles/drafts.css',
|
||||||
'styles/informational-overlays.css',
|
'styles/informational-overlays.css',
|
||||||
'styles/compose.css',
|
'styles/compose.css',
|
||||||
'styles/reactions.css',
|
'styles/reactions.css',
|
||||||
|
@ -793,6 +795,7 @@ JS_SPECS = {
|
||||||
'js/dict.js',
|
'js/dict.js',
|
||||||
'js/components.js',
|
'js/components.js',
|
||||||
'js/localstorage.js',
|
'js/localstorage.js',
|
||||||
|
'js/drafts.js',
|
||||||
'js/channel.js',
|
'js/channel.js',
|
||||||
'js/setup.js',
|
'js/setup.js',
|
||||||
'js/unread_ui.js',
|
'js/unread_ui.js',
|
||||||
|
|
Loading…
Reference in New Issue