Displaying the markdown editor on publisher

This commit is contained in:
augier 2015-11-17 22:00:15 +01:00 committed by Augier
parent 8deef544dc
commit 5c2e241106
29 changed files with 927 additions and 210 deletions

View file

@ -106,6 +106,7 @@ source "https://rails-assets.org" do
gem "rails-assets-markdown-it-sub", "1.0.0" gem "rails-assets-markdown-it-sub", "1.0.0"
gem "rails-assets-markdown-it-sup", "1.0.0" gem "rails-assets-markdown-it-sup", "1.0.0"
gem "rails-assets-highlightjs", "9.4.0" gem "rails-assets-highlightjs", "9.4.0"
gem "rails-assets-bootstrap-markdown", "2.9.0"
# jQuery plugins # jQuery plugins

View file

@ -646,6 +646,10 @@ GEM
sprockets-rails sprockets-rails
rails-assets-autosize (3.0.15) rails-assets-autosize (3.0.15)
rails-assets-blueimp-gallery (2.21.2) rails-assets-blueimp-gallery (2.21.2)
rails-assets-bootstrap (3.3.6)
rails-assets-jquery (>= 1.9.1, < 3)
rails-assets-bootstrap-markdown (2.9.0)
rails-assets-bootstrap (~> 3)
rails-assets-diaspora_jsxc (0.1.5.develop.1) rails-assets-diaspora_jsxc (0.1.5.develop.1)
rails-assets-favico.js (~> 0.3.9) rails-assets-favico.js (~> 0.3.9)
rails-assets-jquery (>= 1.11) rails-assets-jquery (>= 1.11)
@ -994,6 +998,7 @@ DEPENDENCIES
rails (= 4.2.7.1) rails (= 4.2.7.1)
rails-assets-autosize (= 3.0.15)! rails-assets-autosize (= 3.0.15)!
rails-assets-blueimp-gallery (= 2.21.2)! rails-assets-blueimp-gallery (= 2.21.2)!
rails-assets-bootstrap-markdown (= 2.9.0)!
rails-assets-diaspora_jsxc (= 0.1.5.develop.1)! rails-assets-diaspora_jsxc (= 0.1.5.develop.1)!
rails-assets-highlightjs (= 9.4.0)! rails-assets-highlightjs (= 9.4.0)!
rails-assets-jasmine-ajax (= 3.2.0)! rails-assets-jasmine-ajax (= 3.2.0)!

View file

@ -0,0 +1,41 @@
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
app.views.PreviewPost = app.views.Post.extend({
templateName: "stream-element",
className: "stream_element loaded",
subviews: {
".feedback": "feedbackView",
".post-content": "postContentView",
".oembed": "oEmbedView",
".opengraph": "openGraphView",
".poll": "pollView",
".status-message-location": "postLocationStreamView"
},
tooltipSelector: [
".timeago",
".delete",
".permalink"
].join(", "),
initialize: function() {
this.model.set("preview", true);
this.oEmbedView = new app.views.OEmbed({model: this.model});
this.openGraphView = new app.views.OpenGraph({model: this.model});
this.pollView = new app.views.Poll({model: this.model});
},
feedbackView: function() {
return new app.views.Feedback({model: this.model});
},
postContentView: function() {
return new app.views.StatusMessage({model: this.model});
},
postLocationStreamView: function() {
return new app.views.LocationStream({model: this.model});
}
});
// @license-end

View file

@ -18,6 +18,7 @@ app.views.PublisherGettingStarted = Backbone.View.extend({
// initiate all the popover message boxes // initiate all the popover message boxes
show: function() { show: function() {
app.publisher.open();
this._addPopover(this.firstMessage, { this._addPopover(this.firstMessage, {
trigger: "manual", trigger: "manual",
id: "first_message_explain", id: "first_message_explain",

View file

@ -20,10 +20,8 @@ app.views.Publisher = Backbone.View.extend({
events : { events : {
"keydown #status_message_fake_text" : "keyDown", "keydown #status_message_fake_text" : "keyDown",
"focus textarea" : "open", "focus textarea" : "open",
"click #hide_publisher" : "clear",
"submit form" : "createStatusMessage", "submit form" : "createStatusMessage",
"click #submit" : "createStatusMessage", "click #submit" : "createStatusMessage",
"click .post_preview_button" : "createPostPreview",
"textchange #status_message_fake_text": "handleTextchange", "textchange #status_message_fake_text": "handleTextchange",
"click #locator" : "showLocation", "click #locator" : "showLocation",
"click #poll_creator" : "togglePollCreator", "click #poll_creator" : "togglePollCreator",
@ -41,12 +39,12 @@ app.views.Publisher = Backbone.View.extend({
this.hiddenInputEl = this.$("#status_message_text"); this.hiddenInputEl = this.$("#status_message_text");
this.wrapperEl = this.$("#publisher_textarea_wrapper"); this.wrapperEl = this.$("#publisher_textarea_wrapper");
this.submitEl = this.$("input[type=submit], button#submit"); this.submitEl = this.$("input[type=submit], button#submit");
this.previewEl = this.$("button.post_preview_button");
this.photozoneEl = this.$("#photodropzone"); this.photozoneEl = this.$("#photodropzone");
// if there is data in the publisher we ask for a confirmation // if there is data in the publisher we ask for a confirmation
// before the user is able to leave the page // before the user is able to leave the page
$(window).on("beforeunload", _.bind(this._beforeUnload, this)); $(window).on("beforeunload", _.bind(this._beforeUnload, this));
$(window).unload(this.clear.bind(this));
// sync textarea content // sync textarea content
if( this.hiddenInputEl.val() === "" ) { if( this.hiddenInputEl.val() === "" ) {
@ -60,8 +58,6 @@ app.views.Publisher = Backbone.View.extend({
// in case publisher is standalone // in case publisher is standalone
// (e.g. bookmarklet, mentions popup) // (e.g. bookmarklet, mentions popup)
if( this.standalone ) { if( this.standalone ) {
this.$("#hide_publisher").hide();
this.previewEl.hide();
this.$(".question_mark").hide(); this.$(".question_mark").hide();
} }
@ -130,6 +126,36 @@ app.views.Publisher = Backbone.View.extend({
}); });
this.viewUploader.on("change", this.checkSubmitAvailability, this); this.viewUploader.on("change", this.checkSubmitAvailability, this);
var self = this;
var mdEditorOptions = {
onPreview: function() {
self.wrapperEl.addClass("markdown-preview");
return self.createPostPreview();
},
onHidePreview: function() {
self.wrapperEl.removeClass("markdown-preview");
},
onPostPreview: function() {
var photoAttachments = self.wrapperEl.find(".photo_attachments");
if (photoAttachments.length > 0) {
new app.views.Gallery({el: photoAttachments});
}
},
onChange: function() {
self.inputEl.trigger("textchange");
}
};
if (!this.standalone) {
mdEditorOptions.onClose = function() {
self.clear();
};
}
this.markdownEditor = new Diaspora.MarkdownEditor(this.inputEl, mdEditorOptions);
this.viewPollCreator = new app.views.PublisherPollCreator({ this.viewPollCreator = new app.views.PublisherPollCreator({
el: this.$("#poll_creator_container") el: this.$("#poll_creator_container")
}); });
@ -251,7 +277,7 @@ app.views.Publisher = Backbone.View.extend({
}, },
togglePollCreator: function(){ togglePollCreator: function(){
this.viewPollCreator.$el.toggle(); this.wrapperEl.toggleClass("with-poll");
this.inputEl.focus(); this.inputEl.focus();
}, },
@ -293,24 +319,21 @@ app.views.Publisher = Backbone.View.extend({
if(pollQuestion && pollAnswers.length) { if(pollQuestion && pollAnswers.length) {
poll = { poll = {
"question": pollQuestion, "question": pollQuestion,
"poll_answers" : pollAnswers, "poll_answers": pollAnswers,
"participation_count": "0" "participation_count": "0"
}; };
} }
return poll; return poll;
}, },
createPostPreview : function(evt) { createPostPreview: function() {
if(evt){ evt.preventDefault(); }
if(!app.stream) { return; }
//add missing mentions at end of post: //add missing mentions at end of post:
this.handleTextchange(); this.handleTextchange();
var serializedForm = $(evt.target).closest("form").serializeObject(); var serializedForm = $("#new_status_message").serializeObject();
var text = this.mention.getTextForSubmit();
var photos = this.getUploadedPhotos(); var photos = this.getUploadedPhotos();
var mentionedPeople = this.mention.mentionedPeople; var mentionedPeople = this.mention.mentionedPeople;
var date = (new Date()).toISOString();
var poll = this.getPollData(serializedForm); var poll = this.getPollData(serializedForm);
var locationCoords = serializedForm["location[coords]"]; var locationCoords = serializedForm["location[coords]"];
if(!locationCoords || locationCoords === "") { if(!locationCoords || locationCoords === "") {
@ -325,44 +348,22 @@ app.views.Publisher = Backbone.View.extend({
}; };
var previewMessage = { var previewMessage = {
"id" : 0, "id": 0,
"text" : serializedForm["status_message[text]"], "text": text,
"public" : serializedForm["aspect_ids[]"] === "public", "public": serializedForm["aspect_ids[]"] === "public",
"created_at" : date, "created_at": new Date().toISOString(),
"interacted_at" : date, "interacted_at": new Date().toISOString(),
"post_type" : "StatusMessage", "author": app.currentUser ? app.currentUser.attributes : {},
"author" : app.currentUser ? app.currentUser.attributes : {}, "mentioned_people": mentionedPeople,
"mentioned_people" : mentionedPeople, "photos": photos,
"photos" : photos, "title": serializedForm["status_message[text]"],
"title" : serializedForm["status_message[text]"], "location": location,
"location" : location, "interactions": {"likes": [], "reshares": [], "comments_count": 0, "likes_count": 0, "reshares_count": 0},
"interactions" : {"likes":[],"reshares":[],"comments_count":0,"likes_count":0,"reshares_count":0},
"poll": poll "poll": poll
}; };
this.removePostPreview(); var previewPost = new app.views.PreviewPost({model: new app.models.Post(previewMessage)}).render().el;
app.stream.addNow(previewMessage); return $("<div/>").append(previewPost).html();
this.recentPreview=previewMessage;
this.modifyPostPreview($(".stream_element:first",$(".stream_container")));
},
modifyPostPreview : function(post) {
post.addClass("post_preview");
$(".collapsible",post).removeClass("collapsed").addClass("opened");
$("a.delete.remove_post",post).hide();
$("a.like, a.focus_comment_textarea",post).removeAttr("href");
$("a.like",post).addClass("like_preview")
.removeClass("like");
$("a.focus_comment_textarea",post).addClass("focus_comment_textarea_preview")
.removeClass("focus_comment_textarea");
$("a",$("span.details.grey",post)).removeAttr("href");
},
removePostPreview : function() {
if(app.stream && this.recentPreview) {
app.stream.items.remove(this.recentPreview);
delete this.recentPreview;
}
}, },
keyDown : function(evt) { keyDown : function(evt) {
@ -392,12 +393,10 @@ app.views.Publisher = Backbone.View.extend({
// empty upload-photo // empty upload-photo
this.$("#fileInfo").empty(); this.$("#fileInfo").empty();
// close publishing area (CSS) // remove preview and close publishing area (CSS)
this.markdownEditor.hidePreview();
this.close(); this.close();
// remove preview
this.removePostPreview();
// disable submitting // disable submitting
this.checkSubmitAvailability(); this.checkSubmitAvailability();
@ -445,7 +444,7 @@ app.views.Publisher = Backbone.View.extend({
$(this.el).addClass("closed"); $(this.el).addClass("closed");
this.wrapperEl.removeClass("active"); this.wrapperEl.removeClass("active");
this.inputEl.css("height", ""); this.inputEl.css("height", "");
this.viewPollCreator.$el.hide(); this.wrapperEl.removeClass("with-poll");
return this; return this;
}, },
@ -476,10 +475,8 @@ app.views.Publisher = Backbone.View.extend({
setButtonsEnabled: function(bool) { setButtonsEnabled: function(bool) {
if (bool) { if (bool) {
this.submitEl.removeAttr("disabled"); this.submitEl.removeAttr("disabled");
this.previewEl.removeAttr("disabled");
} else { } else {
this.submitEl.prop("disabled", true); this.submitEl.prop("disabled", true);
this.previewEl.prop("disabled", true);
} }
}, },
@ -503,8 +500,6 @@ app.views.Publisher = Backbone.View.extend({
}, },
handleTextchange: function() { handleTextchange: function() {
var self = this;
this.checkSubmitAvailability(); this.checkSubmitAvailability();
this.hiddenInputEl.val(this.mention.getTextForSubmit()); this.hiddenInputEl.val(this.mention.getTextForSubmit());
}, },

View file

@ -0,0 +1,162 @@
Diaspora.MarkdownEditor = function(element, opts) {
this.initialize(element, opts);
};
Diaspora.MarkdownEditor.prototype = {
constructor: Diaspora.MarkdownEditor,
initialize: function(element, opts) {
this.options = {
resize: "none",
onHidePreview: $.noop,
onPostPreview: $.noop
};
$.extend(this.options, opts);
this.options.fullscreen = {enable: false, icons: {}};
this.options.language = this.localize();
this.options.hiddenButtons = ["cmdPreview"];
this.options.onShow = this.onShow.bind(this);
$(element).markdown(this.options);
},
/**
* Attach the $.fn.markdown instance to the current MarkdownEditor instance
* and initializes the preview and edit tabs after the editor is shown.
* @param instance
*/
onShow: function(instance) {
this.instance = instance;
if (_.isFunction(this.options.onPreview)) {
instance.$editor.find(".md-header").remove(".write-preview-tabs").prepend(this.createTabsElement());
}
if (_.isFunction(this.options.onClose)) {
instance.$editor.find(".md-header").remove(".md-cancel").append(this.createCloseElement());
}
// Monkey patch to change icons. Will have to PR upstream
var icons = {
cmdUrl: ["glyphicon-link", "entypo-link"],
cmdImage: ["glyphicon-picture", "entypo-picture"],
cmdList: ["glyphicon-list", "entypo-list"],
cmdListO: ["glyphicon-th-list", "entypo-numbered-list"],
cmdCode: ["glyphicon-asterisk", "entypo-code"],
cmdQuote: ["glyphicon-comment", "entypo-comment"]
};
Object.keys(icons).forEach(function(key) {
instance.$editor.find("[data-handler='bootstrap-markdown-" + key + "']").find(".glyphicon")
.removeClass("glyphicon").removeClass(icons[key][0])
.addClass(icons[key][1]);
});
},
/**
* Creates write and preview tabs inside the markdown editor header.
* @returns {jQuery} The created tabs
*/
createTabsElement: function() {
var self = this;
var tabElement = $("<ul class='nav nav-tabs btn-group write-preview-tabs'></ul>");
var writeTab = $("<li class='active full-height' role='presentation'></li>");
this.writeLink = $("<a class='full-height md-write-tab' href='#'></a>")
.attr("title", Diaspora.I18n.t("publisher.markdown_editor.tooltips.write"));
this.writeLink.append($("<i class='visible-sm visible-xs visible-md diaspora-custom-compose'></i>"));
this.writeLink.append($("<span class='hidden-sm hidden-xs hidden-md tab-help-text'></span>")
.text(Diaspora.I18n.t("publisher.markdown_editor.write")));
this.writeLink.click(function(evt) {
evt.preventDefault();
self.hidePreview();
});
writeTab.append(this.writeLink);
var previewTab = $("<li class='full-height' role='presentation'></li>");
this.previewLink = $("<a class='full-height md-preview-tab' href='#'></a>")
.attr("title", Diaspora.I18n.t("publisher.markdown_editor.tooltips.preview"));
this.previewLink.append($("<i class='visible-sm visible-xs visible-md entypo-search'>"));
this.previewLink.append($("<span class='hidden-sm hidden-xs hidden-md tab-help-text'></span>")
.text(Diaspora.I18n.t("publisher.markdown_editor.preview")));
this.previewLink.click(function(evt) {
evt.preventDefault();
self.showPreview();
});
previewTab.append(this.previewLink);
return tabElement.append(writeTab).append(previewTab);
},
/**
* Creates a cancel button that executes {options#onClose} on click.
* @returns {jQuery} The created cancel button
*/
createCloseElement: function() {
var self = this;
var button = $("<a class='md-cancel btn btn-sm btn-link hidden-xs pull-right'></a>")
.attr("title", Diaspora.I18n.t("publisher.markdown_editor.tooltips.cancel"));
button.click(function() {
self.hidePreview();
self.options.onClose();
});
return button.append($("<i class='entypo-cross'></i>"));
},
hidePreview: function() {
if (this.writeLink) {
this.writeLink.tab("show");
this.instance.hidePreview();
this.options.onHidePreview();
}
},
showPreview: function() {
if (this.previewLink) {
this.previewLink.tab("show");
this.instance.showPreview();
this.options.onPostPreview();
}
},
localize: function() {
var locale = Diaspora.I18n.language;
$.fn.markdown.messages[locale] = {
"Bold": Diaspora.I18n.t("publisher.markdown_editor.tooltips.bold"),
"Italic": Diaspora.I18n.t("publisher.markdown_editor.tooltips.italic"),
"Heading": Diaspora.I18n.t("publisher.markdown_editor.tooltips.heading"),
"URL/Link": Diaspora.I18n.t("publisher.markdown_editor.tooltips.insert_link"),
"Image": Diaspora.I18n.t("publisher.markdown_editor.tooltips.insert_image"),
"Ordered List": Diaspora.I18n.t("publisher.markdown_editor.tooltips.insert_ordered_list"),
"Unordered List": Diaspora.I18n.t("publisher.markdown_editor.tooltips.insert_unordered_list"),
"Preview": Diaspora.I18n.t("publisher.markdown_editor.tooltips.preview"),
"Quote": Diaspora.I18n.t("publisher.markdown_editor.tooltips.quote"),
"Code": Diaspora.I18n.t("publisher.markdown_editor.tooltips.code"),
"strong text": Diaspora.I18n.t("publisher.markdown_editor.texts.strong"),
"emphasized text": Diaspora.I18n.t("publisher.markdown_editor.texts.italic"),
"heading text": Diaspora.I18n.t("publisher.markdown_editor.texts.heading"),
"enter link description here": Diaspora.I18n.t("publisher.markdown_editor.texts.insert_link_description_text"),
"Insert Hyperlink": Diaspora.I18n.t("publisher.markdown_editor.texts.insert_link_help_text"),
"enter image description here": Diaspora.I18n.t("publisher.markdown_editor.texts.insert_image_description_text"),
"Insert Image Hyperlink": Diaspora.I18n.t("publisher.markdown_editor.texts.insert_image_help_text"),
"enter image title here": Diaspora.I18n.t("publisher.markdown_editor.texts.insert_image_title"),
"list text here": Diaspora.I18n.t("publisher.markdown_editor.texts.list"),
"quote here": Diaspora.I18n.t("publisher.markdown_editor.texts.quote"),
"code text here": Diaspora.I18n.t("publisher.markdown_editor.texts.code")
};
return locale;
}
};

View file

@ -43,3 +43,5 @@
//= require blueimp-gallery/blueimp-gallery-indicator //= require blueimp-gallery/blueimp-gallery-indicator
//= require leaflet //= require leaflet
//= require api/authorization_page //= require api/authorization_page
// = require bootstrap-markdown/bootstrap-markdown
// = require helpers/markdown_editor

View file

@ -61,6 +61,7 @@
// publisher // publisher
@import 'publisher'; @import 'publisher';
@import 'aspects'; @import 'aspects';
@import 'markdown-editor';
// bookmarklet // bookmarklet
@import 'bookmarklet'; @import 'bookmarklet';

View file

@ -17,13 +17,6 @@ body {
color: fade-out($link-color, 0.4); color: fade-out($link-color, 0.4);
} }
#main_stream .stream_element {
&.post_preview {
background-color: $main-color-essence;
border-color: darken($main-color-essence, 5%);
}
}
.left-navbar .hoverable:hover { background-color: $main-color-essence; } .left-navbar .hoverable:hover { background-color: $main-color-essence; }
.poll_form .progress .bar { background-color: $main-color-dark; } .poll_form .progress .bar { background-color: $main-color-dark; }

View file

@ -0,0 +1,141 @@
.md-footer,
.md-header {
background: $sidebars-background;
border: 0;
display: block;
height: 42px;
margin: 0;
padding: 6px 6px 0;
[class^="entypo-"],
[class*="entypo-"],
.glyphicon {
color: $black;
}
}
.md-header,
.nav-tabs {
border-bottom: 0;
margin: 0;
z-index: 1;
}
.md-header.btn-toolbar {
background-color: $background-grey;
overflow: hidden;
.btn-group {
margin-bottom: 8px;
[class^="entypo-"],
[class*="entypo-"] {
font-size: 13px;
}
[data-handler="bootstrap-markdown-cmdUrl"],
[data-handler="bootstrap-markdown-cmdImage"],
[data-handler="bootstrap-markdown-cmdList"],
[data-handler="bootstrap-markdown-cmdListO"],
[data-handler="bootstrap-markdown-cmdCode"],
[data-handler="bootstrap-markdown-cmdQuote"] {
height: 28.5px;
line-height: 1.25;
}
}
@media(max-width: $screen-xs) {
[data-handler="bootstrap-markdown-cmdList"],
[data-handler="bootstrap-markdown-cmdListO"] {
display: none;
}
[data-handler="bootstrap-markdown-cmdCode"] {
// !important is needed to override BS' specific rules
// scss-lint:disable ImportantRule
border-bottom-left-radius: $border-radius-small !important;
border-top-left-radius: $border-radius-small !important;
// scss-lint:enable ImportantRule
}
}
}
.md-cancel {
box-sizing: content-box;
&,
.entypo-cross {
color: $text-grey;
font-size: 18px;
height: 18px;
line-height: 18px;
width: 18px;
}
&:hover .entypo-cross { color: $text; }
}
.md-preview {
background: $white;
color: $text-color;
// !important is needed to override the CSS rules dynamically added to the element
// scss-lint:disable ImportantRule
height: auto !important;
// scss-lint:enable ImportantRule
min-height: 90px;
overflow: auto;
position: relative;
// !important is needed to override the CSS rules dynamically added to the element
// scss-lint:disable ImportantRule
width: 100% !important;
// scss-lint:enable ImportantRule
z-index: 10;
}
.md-controls {
float: right;
padding: 3px;
.md-control {
color: $text-grey;
padding: 3px;
padding-left: 10px;
right: 5px;
}
}
.write-preview-tabs {
&,
& .full-height {
height: 36px;
}
> li {
> a { padding: 7px 15px; }
&:not(.active) * { color: $brand-primary; }
&.active * { color: $black; }
}
a:focus { outline: none; }
li {
&:not(.active) a:focus,
&:not(.active) a:hover {
background-color: transparent;
border: 1px solid transparent;
}
&.active:focus { color: $black; }
}
.diaspora-custom-compose::before {
bottom: -2px;
position: relative;
}
}
.publisher-textarea-wrapper:not(.active) .md-header { display: none; }

View file

@ -78,7 +78,6 @@
color: white; color: white;
font-size: $font-size-base; font-size: $font-size-base;
font-family: Arial, Helvetica, sans-serif; font-family: Arial, Helvetica, sans-serif;
line-height: normal;
overflow: hidden; overflow: hidden;
width: 100%; width: 100%;
white-space: pre-wrap; white-space: pre-wrap;

View file

@ -82,7 +82,7 @@
} }
textarea { textarea {
border: none; border: 0 solid $light-grey;
margin: 0; margin: 0;
box-shadow: none; box-shadow: none;
resize: none; resize: none;
@ -101,16 +101,11 @@
a { color: lighten($blue,20%); } a { color: lighten($blue,20%); }
} }
.mentions-input-box .mentions {
line-height: $line-height-base !important;
}
&.with_attachments #photodropzone_container { &.with_attachments #photodropzone_container {
border-top: 1px dashed $border-grey; border-top: 1px dashed $border-grey;
} }
#poll_creator_container { #poll_creator_container {
display: none;
border-top: 1px dashed $border-grey; border-top: 1px dashed $border-grey;
padding:4px 6px 4px 6px; padding:4px 6px 4px 6px;
box-sizing: border-box; box-sizing: border-box;
@ -212,7 +207,14 @@
} }
.publisher-textarea-wrapper { .publisher-textarea-wrapper {
&:not(.with-location) .location-container { display: none; } &:not(.with-location) .location-container,
&.markdown-preview .location-container,
&:not(.with-poll) .poll-creator-container,
&.markdown-preview .poll-creator-container,
&.markdown-preview .photodropzone-container,
&.markdown-preview .publisher-buttonbar {
display: none;
}
&.with-location .loader { &.with-location .loader {
height: 20px; height: 20px;
@ -259,7 +261,15 @@
.twitter-typeahead { .twitter-typeahead {
left: -1px; left: -1px;
position: absolute; // Override inline rule of Typeahead
// scss-lint:disable ImportantRule
position: absolute !important;
// scss-lint:enable ImportantRule
}
.mentions-box {
// Leave space for markdown editor header
margin-top: 42px;
} }
} }

View file

@ -1,4 +1,4 @@
#main_stream .stream_element, .stream_element,
.photo { .photo {
& > .media { & > .media {
margin: 0px; margin: 0px;
@ -61,12 +61,20 @@
} }
#main_stream .stream_element { #main_stream .stream_element {
padding: 10px;
margin-bottom: 20px; margin-bottom: 20px;
background-color: $white;
border: 1px solid $light-grey; border: 1px solid $light-grey;
box-shadow: $card-shadow; box-shadow: $card-shadow;
&.highlighted {
border-left: 3px solid $brand-primary;
padding-left: 8px;
}
}
.stream_element {
background-color: $white;
padding: 10px;
& > .media { & > .media {
&.shield-active .nsfw-hidden { display: none; } &.shield-active .nsfw-hidden { display: none; }
&:not(.shield-active) .nsfw-shield { display: none; } &:not(.shield-active) .nsfw-shield { display: none; }
@ -146,18 +154,6 @@
} }
} }
&.highlighted {
padding-left: 8px;
border-left: 3px solid $brand-primary;
}
&.post_preview {
background-color: lighten($brand-primary,45%);
border: 1px solid $brand-primary;
}
}
.stream_element {
.likes, .likes,
.reshares { .reshares {
font-size: 12px; font-size: 12px;

View file

@ -14,22 +14,28 @@
</span> </span>
<a href="#" class="like" rel='nofollow'> {{#if preview}}
<span>{{t "stream.like"}}</span>
{{else}}
<a href="#" class="like" rel='nofollow'>
{{#if userLike}} {{#if userLike}}
{{t "stream.unlike"}} {{t "stream.unlike"}}
{{else}} {{else}}
{{t "stream.like"}} {{t "stream.like"}}
{{/if}} {{/if}}
</a>
·
{{#if userCanReshare}}
<a href="#" class="reshare" rel='nofollow'>
{{t "stream.reshare"}}
</a> </a>
{{/if}}
·
{{#if preview}}
<span>{{t "stream.reshare"}}</span>
·
{{else if userCanReshare}}
<a href="#" class="reshare" rel='nofollow'>{{t "stream.reshare"}}</a>
· ·
{{/if}} {{/if}}
<a href="#" class="focus_comment_textarea" rel="nofollow"> {{#if preview}}
{{t "stream.comment"}} <span>{{t "stream.comment"}}</span>
</a> {{else}}
<a href="#" class="focus_comment_textarea" rel="nofollow">{{t "stream.comment"}}</a>
{{/if}}

View file

@ -25,7 +25,11 @@
{{/poll.poll_answers}} {{/poll.poll_answers}}
{{#if show_form}} {{#if show_form}}
<div class="toggle-result-wrapper"> <div class="toggle-result-wrapper">
{{#if preview}}
<span>{{t "poll.show_result"}}</span>
{{else}}
<a class="toggle_result" href="#">{{t "poll.show_result"}}</a> <a class="toggle_result" href="#">{{t "poll.show_result"}}</a>
{{/if}}
</div> </div>
<input type="submit" class="submit pull-right btn btn-default" value="{{t "poll.vote"}}"/> <input type="submit" class="submit pull-right btn btn-default" value="{{t "poll.vote"}}"/>
<div class="clearfix"></div> <div class="clearfix"></div>

View file

@ -8,6 +8,7 @@
<div class="bd"> <div class="bd">
{{#if loggedIn}} {{#if loggedIn}}
<div class="control-icons"> <div class="control-icons">
{{#unless preview}}
{{#if authorIsCurrentUser}} {{#if authorIsCurrentUser}}
<a href="#" rel="nofollow" class="delete remove_post" title="{{t "delete"}}"> <a href="#" rel="nofollow" class="delete remove_post" title="{{t "delete"}}">
<i class="entypo-trash"></i> <i class="entypo-trash"></i>
@ -32,6 +33,7 @@
<i class="entypo-cross"></i> <i class="entypo-cross"></i>
</a> </a>
{{/if}} {{/if}}
{{/unless}}
</div> </div>
{{/if}} {{/if}}
@ -42,6 +44,9 @@
<span class="details gray"> <span class="details gray">
- -
{{#if preview}}
<time class="timeago" data-original-title="{{{localTime created_at}}}" datetime="{{created_at}}" />
{{else}}
<a href="/posts/{{id}}"> <a href="/posts/{{id}}">
<time class="timeago" data-original-title="{{{localTime created_at}}}" datetime="{{created_at}}" /> <time class="timeago" data-original-title="{{{localTime created_at}}}" datetime="{{created_at}}" />
</a> </a>
@ -49,6 +54,7 @@
<a href="/posts/{{id}}" class="permalink" title="{{t "stream.permalink"}}"> <a href="/posts/{{id}}" class="permalink" title="{{t "stream.permalink"}}">
<i class="entypo-link"></i> <i class="entypo-link"></i>
</a> </a>
{{/if}}
</span> </span>
</div> </div>

View file

@ -26,11 +26,11 @@
%input.typeahead-mention-box.hidden{type: "text"} %input.typeahead-mention-box.hidden{type: "text"}
= status.hidden_field :text, value: h(publisher_hidden_text), class: "clear_on_submit" = status.hidden_field :text, value: h(publisher_hidden_text), class: "clear_on_submit"
.container-fluid#photodropzone_container .container-fluid.photodropzone-container#photodropzone_container
%ul#photodropzone %ul#photodropzone
.location-container.form-group{style: "padding: 4px 6px;"} .location-container.form-group{style: "padding: 4px 6px;"}
= hidden_field :location, :coords = hidden_field :location, :coords
#poll_creator_container .poll-creator-container#poll_creator_container
-# handlebars template -# handlebars template
#button_container #button_container
.publisher-buttonbar#publisher-images .publisher-buttonbar#publisher-images
@ -59,12 +59,8 @@
.spinner .spinner
.options_and_submit.col-sm-12 .options_and_submit.col-sm-12
.public_toggle.clearfix .public_toggle.clearfix
.btn.btn-default.pull-left.hidden-xs#hide_publisher{title: t("shared.publisher.discard_post")}
%span.text= t("cancel")
.btn-toolbar.pull-right .btn-toolbar.pull-right
= render partial: "publisher/aspect_dropdown", locals: {selected_aspects: selected_aspects} = render partial: "publisher/aspect_dropdown", locals: {selected_aspects: selected_aspects}
%button.btn.btn-default.btn-group.post_preview_button= t("shared.publisher.preview")
%button.btn.btn-group.btn-primary#submit= t("shared.publisher.share") %button.btn.btn-group.btn-primary#submit= t("shared.publisher.share")
.btn-toolbar.pull-right#publisher-service-icons .btn-toolbar.pull-right#publisher-service-icons

View file

@ -2,6 +2,13 @@
# licensed under the Affero General Public License version 3 or later. See # licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file. # the COPYRIGHT file.
# bootstrap-markdown plugin relies on rails-assets-bootstrap gem but we use
# bootstrap-sass this line makes sure we exclude every asset comming
# from rails-assets-bootstrap to prevent conflicts with bootstrap-sass
Rails.configuration.assets.paths.reject! do |path|
path.include?("rails-assets-bootstrap") && !path.include?("rails-assets-bootstrap-markdown")
end
Diaspora::Application.configure do Diaspora::Application.configure do
config.serve_static_files = AppConfig.environment.assets.serve? config.serve_static_files = AppConfig.environment.assets.serve?
# config.static_cache_control = "public, max-age=3600" if AppConfig[:serve_static_assets].to_s == 'true' # config.static_cache_control = "public, max-age=3600" if AppConfig[:serve_static_assets].to_s == 'true'

View file

@ -1031,7 +1031,6 @@ en:
formatWithMarkdown: "You can use %{markdown_link} to format your post" formatWithMarkdown: "You can use %{markdown_link} to format your post"
posting: "Posting..." posting: "Posting..."
share: "Share" share: "Share"
preview: "Preview"
upload_photos: "Upload photos" upload_photos: "Upload photos"
get_location: "Get your location" get_location: "Get your location"
remove_location: "Remove location" remove_location: "Remove location"

View file

@ -138,6 +138,35 @@ en:
option: "Answer" option: "Answer"
add_option: "Add an answer" add_option: "Add an answer"
question: "Question" question: "Question"
markdown_editor:
preview: "Preview"
write: "Write"
tooltips:
bold: "Bold"
italic: "Italic"
heading: "Heading"
insert_link: "Insert link"
insert_image: "Insert image"
insert_ordered_list: "Insert ordered list"
insert_unordered_list: "Insert unordered list"
preview: "Preview message"
write: "Edit message"
cancel: "Cancel message"
quote: "Insert quotation"
code: "Insert code"
texts:
strong: "strong text"
italic: "italic text"
heading: "heading text"
insert_link_description_text: "enter link description here"
insert_link_help_text: "Insert link here"
insert_image_description_text: "enter image description here"
insert_image_help_text: "Insert image link here"
insert_image_title: "enter image title here"
list: "list text here"
quote: "quotation text here"
code: "code here"
bookmarklet: bookmarklet:
post_something: "Post to diaspora*" post_something: "Post to diaspora*"
post_submit: "Submitting post..." post_submit: "Submitting post..."

View file

@ -16,26 +16,27 @@ Feature: preview posts in the stream
Scenario: preview and post a text-only message Scenario: preview and post a text-only message
Given I expand the publisher Given I expand the publisher
When I write the status message "I am eating yogurt" When I write the status message "I am eating yogurt"
And I press "Preview" And I preview the post
Then "I am eating yogurt" should be post 1 Then I should see "I am eating yogurt" in the preview
And the first post should be a preview
Given I edit the post
When I write the status message "This preview rocks" When I write the status message "This preview rocks"
And I press "Preview" And I preview the post
Then "This preview rocks" should be post 1 Then I should see "This preview rocks" in the preview
And I should not see "I am eating a yogurt" And I should not see "I am eating a yogurt" in the preview
Given I edit the post
When I write the status message "I like rocks" When I write the status message "I like rocks"
And I press "Share" And I press "Share"
Then "I like rocks" should be post 1 Then "I like rocks" should be post 1
And I should not see "This preview rocks" When I expand the publisher
Then I should not be in preview mode
Scenario: preview a very long message Scenario: preview a very long message
Given I expand the publisher Given I expand the publisher
When I insert an extremely long status message When I insert an extremely long status message
And I press "Preview" And I preview the post
Then the preview should not be collapsed Then the preview should not be collapsed
When I press "Share" When I press "Share"
Then the post should be collapsed Then the post should be collapsed
@ -44,15 +45,15 @@ Feature: preview posts in the stream
And I attach "spec/fixtures/button.png" to the publisher And I attach "spec/fixtures/button.png" to the publisher
When I fill in the following: When I fill in the following:
| status_message_fake_text | Look at this dog | | status_message_fake_text | Look at this dog |
And I press "Preview" And I preview the post
Then I should see a "img" within ".stream_element div.photo_attachments" Then I should see a "img" within ".md-preview .stream_element .photo_attachments"
And I should see "Look at this dog" within ".stream_element" And I should see "Look at this dog" within ".md-preview .stream_element"
And I close the publisher And I close the publisher
Scenario: preview a post with mentions Scenario: preview a post with mentions
Given I expand the publisher Given I expand the publisher
And I mention Alice in the publisher And I mention Alice in the publisher
And I press "Preview" And I preview the post
And I confirm the alert after I follow "Alice Smith" And I confirm the alert after I follow "Alice Smith"
Then I should see "Alice Smith" Then I should see "Alice Smith"
@ -63,9 +64,8 @@ Feature: preview posts in the stream
When I expand the publisher When I expand the publisher
And I fill in the following: And I fill in the following:
| status_message_fake_text | This preview rocks | | status_message_fake_text | This preview rocks |
And I press "Preview" And I preview the post
Then "This preview rocks" should be post 1 Then I should see "This preview rocks" in the preview
And the first post should be a preview
And I close the publisher And I close the publisher
Scenario: preview a post with the poll Scenario: preview a post with the poll
@ -79,9 +79,9 @@ Feature: preview posts in the stream
And I fill in the following for the options: And I fill in the following for the options:
| normal | | normal |
| not normal | | not normal |
And I press "Preview" And I preview the post
Then I should see a ".poll_form" within ".stream_element" Then I should see a ".poll_form" within ".md-preview .stream_element"
And I should see a "form" within ".stream_element" And I should see a "form" within ".md-preview .stream_element"
And I close the publisher And I close the publisher
Scenario: preview a post with location Scenario: preview a post with location
@ -93,7 +93,7 @@ Feature: preview posts in the stream
When I fill in the following: When I fill in the following:
| status_message_fake_text | I am eating yogurt | | status_message_fake_text | I am eating yogurt |
| location_address | Some cool place | | location_address | Some cool place |
And I press "Preview" And I preview the post
Then I should see a ".near-from" within ".stream_element" Then I should see a ".near-from" within ".md-preview .stream_element"
And I should see "Some cool place" within ".stream_element .near-from" And I should see "Some cool place" within ".md-preview .stream_element .near-from"
And I close the publisher And I close the publisher

View file

@ -24,7 +24,8 @@ Feature: posting from the main page
When I expand the publisher When I expand the publisher
Then I should see "You can use Markdown to format your post" within ".markdownIndications" Then I should see "You can use Markdown to format your post" within ".markdownIndications"
Then I should see "All aspects" within ".options_and_submit" Then I should see "All aspects" within ".options_and_submit"
Then I should see "Preview" within ".options_and_submit" Then I should see a ".md-write-tab" within ".md-header"
Then I should see a ".md-preview-tab" within ".md-header"
Scenario: post a text-only message to all aspects Scenario: post a text-only message to all aspects
Given I expand the publisher Given I expand the publisher

View file

@ -65,7 +65,7 @@ And /^I expand the publisher$/ do
end end
And /^I close the publisher$/ do And /^I close the publisher$/ do
find("#publisher #hide_publisher").click find("#publisher .md-cancel").click
end end
Then /^the publisher should be expanded$/ do Then /^the publisher should be expanded$/ do

View file

@ -1,9 +1,35 @@
Then /^the first post should be a preview$/ do And /^I edit the post$/ do
find(".post_preview .post-content").text.should == first_post_text with_scope(".publisher-textarea-wrapper") do
find(".md-write-tab").click
end
end end
Then /^the preview should not be collapsed$/ do Then /^the preview should not be collapsed$/ do
find(".post_preview").should_not have_selector('.collapsed') with_scope(".publisher-textarea-wrapper .collapsible") do
find(".post_preview").should have_selector('.opened') expect(current_scope).not_to have_css(".collapsed")
end
end end
And /^I preview the post$/ do
with_scope(".publisher-textarea-wrapper") do
find(".md-preview-tab").click
end
end
Then /^I should see "([^"]*)" in the preview$/ do |text|
with_scope(".publisher-textarea-wrapper .md-preview") do
expect(current_scope).to have_content(text)
end
end
Then /^I should not see "([^"]*)" in the preview$/ do |text|
with_scope(".publisher-textarea-wrapper .md-preview") do
expect(current_scope).to_not have_content(text)
end
end
Then /^I should not be in preview mode$/ do
with_scope(".publisher-textarea-wrapper") do
expect(current_scope).to_not have_css(".md-preview")
end
end

View file

@ -45,10 +45,8 @@ module PublishingCukeHelpers
end end
def click_publisher def click_publisher
page.execute_script(' find("#status_message_fake_text").click
$("#publisher").removeClass("closed"); expect(find("#publisher")).to have_css(".publisher-textarea-wrapper.active")
$("#publisher").find("#status_message_fake_text").focus();
')
end end
def publisher_submittable? def publisher_submittable?

View file

@ -0,0 +1,81 @@
describe("app.views.PreviewPost", function() {
beforeEach(function() {
this.model = new app.models.Post(factory.postAttrs());
this.view = new app.views.PreviewPost({model: this.model});
});
describe("initialize", function() {
it("sets preview property in model", function() {
this.view.initialize();
expect(this.view.model.get("preview")).toBe(true);
});
it("calls app.views.OEmbed.initialize", function() {
spyOn(app.views.OEmbed.prototype, "initialize");
this.view.initialize();
expect(app.views.OEmbed.prototype.initialize).toHaveBeenCalledWith({model: this.model});
});
it("calls app.views.OpenGraph.initialize", function() {
spyOn(app.views.OpenGraph.prototype, "initialize");
this.view.initialize();
expect(app.views.OpenGraph.prototype.initialize).toHaveBeenCalledWith({model: this.model});
});
it("calls app.views.Poll.initialize", function() {
spyOn(app.views.Poll.prototype, "initialize");
this.view.initialize();
expect(app.views.Poll.prototype.initialize).toHaveBeenCalledWith({model: this.model});
});
it("calls app.views.Poll.initialize", function() {
spyOn(app.views.Poll.prototype, "initialize");
this.view.initialize();
expect(app.views.Poll.prototype.initialize).toHaveBeenCalledWith({model: this.model});
});
});
describe("render", function() {
it("calls feedbackView", function() {
spyOn(app.views.PreviewPost.prototype, "feedbackView");
this.view.render();
expect(app.views.PreviewPost.prototype.feedbackView).toHaveBeenCalled();
});
it("calls postContentView", function() {
spyOn(app.views.PreviewPost.prototype, "postContentView");
this.view.render();
expect(app.views.PreviewPost.prototype.postContentView).toHaveBeenCalled();
});
it("calls postLocationStreamView", function() {
spyOn(app.views.PreviewPost.prototype, "postLocationStreamView");
this.view.render();
expect(app.views.PreviewPost.prototype.postLocationStreamView).toHaveBeenCalled();
});
});
describe("feedbackView", function() {
it("calls app.views.Feedback.initialise", function() {
spyOn(app.views.Feedback.prototype, "initialize");
this.view.feedbackView();
expect(app.views.Feedback.prototype.initialize).toHaveBeenCalledWith({model: this.model});
});
});
describe("postContentView", function() {
it("calls app.views.Feedback.initialise", function() {
spyOn(app.views.StatusMessage.prototype, "initialize");
this.view.postContentView();
expect(app.views.StatusMessage.prototype.initialize).toHaveBeenCalledWith({model: this.model});
});
});
describe("postLocationStreamView", function() {
it("calls app.views.Feedback.initialise", function() {
spyOn(app.views.LocationStream.prototype, "initialize");
this.view.postLocationStreamView();
expect(app.views.LocationStream.prototype.initialize).toHaveBeenCalledWith({model: this.model});
});
});
});

View file

@ -12,14 +12,11 @@ describe("app.views.Publisher", function() {
this.view = new app.views.Publisher({ this.view = new app.views.Publisher({
standalone: true standalone: true
}); });
this.view.open();
}); });
it("hides the close button in standalone mode", function() { it("hides the close button in standalone mode", function() {
expect(this.view.$("#hide_publisher").is(":visible")).toBeFalsy(); expect(this.view.$(".md-cancel").is(":visible")).toBeFalsy();
});
it("hides the post preview button in standalone mode", function() {
expect(this.view.$(".post_preview_button").is(":visible")).toBeFalsy();
}); });
it("hides the manage services link in standalone mode", function() { it("hides the manage services link in standalone mode", function() {
@ -89,6 +86,21 @@ describe("app.views.Publisher", function() {
this.view.close($.Event()); this.view.close($.Event());
expect($(this.view.el).find("#status_message_fake_text").attr("style")).not.toContain("height"); expect($(this.view.el).find("#status_message_fake_text").attr("style")).not.toContain("height");
}); });
it("should hide the poll container correctly", function() {
this.view.$el.find(".poll-creator").click();
expect(this.view.$el.find(".publisher-textarea-wrapper")).toHaveClass("with-poll");
expect(this.view.$el.find(".poll-creator-container")).toBeVisible();
this.view.close();
expect(this.view.$el.find(".publisher-textarea-wrapper")).not.toHaveClass("with-poll");
expect(this.view.$el.find(".poll-creator-container")).not.toBeVisible();
this.view.open();
expect(this.view.$el.find(".publisher-textarea-wrapper")).not.toHaveClass("with-poll");
expect(this.view.$el.find(".poll-creator-container")).not.toBeVisible();
this.view.$el.find(".poll-creator").click();
expect(this.view.$el.find(".publisher-textarea-wrapper")).toHaveClass("with-poll");
expect(this.view.$el.find(".poll-creator-container")).toBeVisible();
});
}); });
describe("#clear", function() { describe("#clear", function() {
@ -99,11 +111,11 @@ describe("app.views.Publisher", function() {
expect(this.view.close).toHaveBeenCalled(); expect(this.view.close).toHaveBeenCalled();
}); });
it("calls removePostPreview", function(){ it("calls hidePreview", function() {
spyOn(this.view, "removePostPreview"); spyOn(this.view.markdownEditor, "hidePreview");
this.view.clear($.Event()); this.view.clear($.Event());
expect(this.view.removePostPreview).toHaveBeenCalled(); expect(this.view.markdownEditor.hidePreview).toHaveBeenCalled();
}); });
it("clears all textareas", function(){ it("clears all textareas", function(){
@ -179,39 +191,11 @@ describe("app.views.Publisher", function() {
}); });
describe("createPostPreview", function(){ describe("createPostPreview", function(){
beforeEach(function() {
app.stream = { addNow: $.noop };
});
it("calls handleTextchange to complete missing mentions", function(){ it("calls handleTextchange to complete missing mentions", function(){
spyOn(this.view, "handleTextchange"); spyOn(this.view, "handleTextchange");
this.view.createPostPreview($.Event()); this.view.createPostPreview();
expect(this.view.handleTextchange).toHaveBeenCalled(); expect(this.view.handleTextchange).toHaveBeenCalled();
}); });
it("calls removePostPreview to remove the last preview", function(){
spyOn(this.view, "removePostPreview");
this.view.createPostPreview($.Event());
expect(this.view.removePostPreview).toHaveBeenCalled();
});
it("adds the status message to the stream", function() {
spyOn(app.stream, "addNow");
this.view.createPostPreview($.Event());
expect(app.stream.addNow).toHaveBeenCalled();
});
it("sets recentPreview", function(){
expect(this.view.recentPreview).toBeUndefined();
this.view.createPostPreview($.Event());
expect(this.view.recentPreview).toBeDefined();
});
it("calls modifyPostPreview to apply the preview style to the post", function(){
spyOn(this.view, "modifyPostPreview");
this.view.createPostPreview($.Event());
expect(this.view.modifyPostPreview).toHaveBeenCalled();
});
}); });
describe('#setText', function() { describe('#setText', function() {
@ -236,11 +220,9 @@ describe("app.views.Publisher", function() {
it("disables submitting", function() { it("disables submitting", function() {
this.view.setText("TESTING"); this.view.setText("TESTING");
expect(this.view.submitEl.prop("disabled")).toBeFalsy(); expect(this.view.submitEl.prop("disabled")).toBeFalsy();
expect(this.view.previewEl.prop("disabled")).toBeFalsy();
this.view.setEnabled(false); this.view.setEnabled(false);
expect(this.view.submitEl.prop("disabled")).toBeTruthy(); expect(this.view.submitEl.prop("disabled")).toBeTruthy();
expect(this.view.previewEl.prop("disabled")).toBeTruthy();
}); });
}); });
@ -491,7 +473,6 @@ describe("app.views.Publisher", function() {
' <div id="publisher_textarea_wrapper"></div>'+ ' <div id="publisher_textarea_wrapper"></div>'+
' <div id="photodropzone"></div>'+ ' <div id="photodropzone"></div>'+
' <input type="submit" />'+ ' <input type="submit" />'+
' <button class="post_preview_button" />'+
' </form></div>'+ ' </form></div>'+
'</div>' '</div>'
); );
@ -540,7 +521,6 @@ describe("app.views.Publisher", function() {
it('disables the publisher buttons', function() { it('disables the publisher buttons', function() {
expect(this.view.submitEl.prop("disabled")).toBeTruthy(); expect(this.view.submitEl.prop("disabled")).toBeTruthy();
expect(this.view.previewEl.prop("disabled")).toBeTruthy();
}); });
}); });
@ -577,7 +557,6 @@ describe("app.views.Publisher", function() {
it('re-enables the buttons', function() { it('re-enables the buttons', function() {
expect(this.view.submitEl.prop("disabled")).toBeFalsy(); expect(this.view.submitEl.prop("disabled")).toBeFalsy();
expect(this.view.previewEl.prop("disabled")).toBeFalsy();
}); });
}); });

View file

@ -0,0 +1,237 @@
describe("Diaspora.MarkdownEditor", function() {
beforeEach(function() {
spec.content().html("<textarea id='fake-textarea'></textarea>");
this.$el = $("#fake-textarea");
});
describe("constructor", function() {
it("calls initialize", function() {
spyOn(Diaspora.MarkdownEditor.prototype, "initialize");
new Diaspora.MarkdownEditor(this.$el, {});
expect(Diaspora.MarkdownEditor.prototype.initialize).toHaveBeenCalledWith(this.$el, {});
});
});
describe("initialize", function() {
beforeEach(function() {
this.target = new Diaspora.MarkdownEditor($("<textarea></textarea>"), {});
});
it("calls localize", function() {
spyOn(Diaspora.MarkdownEditor.prototype, "localize");
this.target.initialize(this.$el, {});
expect(Diaspora.MarkdownEditor.prototype.localize).toHaveBeenCalled();
});
it("calls onShow", function() {
spyOn(Diaspora.MarkdownEditor.prototype, "onShow");
this.target.initialize(this.$el, {});
expect(Diaspora.MarkdownEditor.prototype.onShow).toHaveBeenCalled();
});
it("call $.fn.markdown with correct default options", function() {
spyOn($.fn, "markdown");
this.target.initialize(this.$el, {});
expect($.fn.markdown).toHaveBeenCalled();
var args = $.fn.markdown.calls.mostRecent().args[0];
expect(args.resize).toBe("none");
expect(args.language).toBe("en");
expect(args.onHidePreview).toBe($.noop);
expect(args.onPostPreview).toBe($.noop);
expect(args.fullscreen).toEqual({enable: false, icons: {}});
expect(args.hiddenButtons).toEqual(["cmdPreview"]);
});
it("overrides fullscreen, hiddenButtons, language and onShow options", function() {
spyOn($.fn, "markdown").and.callThrough();
spyOn(Diaspora.MarkdownEditor.prototype, "onShow");
spyOn(Diaspora.MarkdownEditor.prototype, "localize").and.callThrough();
this.target.initialize(this.$el, {
fullscreen: {enabled: true, icons: {somekey: "somevalue"}},
hiddenButtons: [],
language: "fr",
onShow: $.noop
});
var args = $.fn.markdown.calls.mostRecent().args[0];
expect(args.fullscreen).toEqual({enable: false, icons: {}});
expect(args.hiddenButtons).toEqual(["cmdPreview"]);
expect(args.language).toBe("en");
expect(args.onShow).not.toBe($.noop);
expect(Diaspora.MarkdownEditor.prototype.onShow).toHaveBeenCalled();
expect(Diaspora.MarkdownEditor.prototype.localize).toHaveBeenCalled();
});
});
describe("onShow", function() {
beforeEach(function() {
this.target = new Diaspora.MarkdownEditor(this.$el, {});
this.$el.find(".md-header").remove(".write-preview-tabs");
this.$el.find(".md-header").remove(".md-cancel");
});
it("retreives the $.fn.markdown instance back", function() {
var fakeInstance = {$editor: $("body")};
this.target.onShow(fakeInstance);
expect(this.target.instance).toBe(fakeInstance);
});
it("calls createTabsElement createCloseElement if preview and close functions are given", function() {
spyOn(Diaspora.MarkdownEditor.prototype, "createTabsElement");
spyOn(Diaspora.MarkdownEditor.prototype, "createCloseElement");
this.target.options.onPreview = $.noop;
this.target.options.onClose = $.noop;
this.target.onShow(this.target.instance);
expect(Diaspora.MarkdownEditor.prototype.createTabsElement).toHaveBeenCalled();
expect(Diaspora.MarkdownEditor.prototype.createCloseElement).toHaveBeenCalled();
});
it("does not call createTabsElement createCloseElement if no preview and close functions are given", function() {
spyOn(Diaspora.MarkdownEditor.prototype, "createTabsElement");
spyOn(Diaspora.MarkdownEditor.prototype, "createCloseElement");
delete this.target.options.onPreview;
delete this.target.options.onClose;
this.target.onShow(this.target.instance);
expect(Diaspora.MarkdownEditor.prototype.createCloseElement).not.toHaveBeenCalled();
expect(Diaspora.MarkdownEditor.prototype.createTabsElement).not.toHaveBeenCalled();
});
it("creates the preview and write tabs", function() {
this.target.options.onPreview = $.noop;
this.target.onShow(this.target.instance);
expect($(".md-header .write-preview-tabs").length).toBe(1);
});
it("removes preview tabs if already existing", function() {
this.target.options.onPreview = $.noop;
this.$el.find(".md-header").prepend("<div id='fake-write-preview-tabs' class='write-preview-tabs'/>");
this.target.onShow(this.target.instance);
expect($(".md-header .write-preview-tabs").length).toBe(1);
expect($("#fake-write-preview-tabs").length).toBe(0);
});
it("creates the cancel button", function() {
this.target.options.onClose = $.noop;
this.target.onShow(this.target.instance);
expect($(".md-header .md-cancel").length).toBe(1);
});
it("removes cancel button if already existing", function() {
this.target.options.onClose = $.noop;
this.$el.find(".md-header").prepend("<div id='fake-md-cancel' class='md-cancel'/>");
this.target.onShow(this.target.instance);
expect($(".md-header .md-cancel").length).toBe(1);
expect($("#fake-md-cancel").length).toBe(0);
});
});
describe("createTabsElement", function() {
beforeEach(function() {
this.target = new Diaspora.MarkdownEditor(this.$el, {});
});
it("correctly creates the preview tabs", function() {
var tabsElement = this.target.createTabsElement();
expect(tabsElement).toHaveClass("write-preview-tabs");
expect(tabsElement.find("> li > a.md-write-tab").attr("title")).toBe("Edit message");
expect(tabsElement.find("> li > a.md-write-tab > i.diaspora-custom-compose").length).toBe(1);
expect(tabsElement.find("> li > a.md-write-tab > span.tab-help-text").length).toBe(1);
expect(tabsElement.find("> li > a.md-write-tab > span.tab-help-text").text()).toBe("Write");
expect(tabsElement.find("> li > a.md-preview-tab").attr("title")).toBe("Preview message");
expect(tabsElement.find("> li > a.md-preview-tab > i.entypo-search").length).toBe(1);
expect(tabsElement.find("> li > a.md-preview-tab > span.tab-help-text").length).toBe(1);
expect(tabsElement.find("> li > a.md-preview-tab > span.tab-help-text").text()).toBe("Preview");
});
it("correctly binds onclick events", function() {
var tabsElement = this.target.createTabsElement();
spyOn(Diaspora.MarkdownEditor.prototype, "hidePreview");
spyOn(Diaspora.MarkdownEditor.prototype, "showPreview");
tabsElement.find("> li > a.md-write-tab").click();
expect(Diaspora.MarkdownEditor.prototype.hidePreview).toHaveBeenCalled();
tabsElement.find("> li > a.md-preview-tab").click();
expect(Diaspora.MarkdownEditor.prototype.showPreview).toHaveBeenCalled();
});
});
describe("createCloseElement", function() {
beforeEach(function() {
this.target = new Diaspora.MarkdownEditor(this.$el, {});
});
it("correctly creates the close button", function() {
var closeElement = this.target.createCloseElement();
expect(closeElement).toHaveClass("md-cancel");
expect(closeElement.get(0).tagName).toBe("A");
expect(closeElement.attr("title")).toBe("Cancel message");
expect(closeElement.find("> i.entypo-cross").length).toBe(1);
});
it("correctly binds onclick events", function() {
this.target.options.onClose = jasmine.createSpy();
var closeElement = this.target.createCloseElement();
closeElement.click();
expect(this.target.options.onClose).toHaveBeenCalled();
});
});
describe("hidePreview", function() {
beforeEach(function() {
this.target = new Diaspora.MarkdownEditor(this.$el, {onPreview: $.noop, onHidePreview: jasmine.createSpy()});
spyOn(this.target.instance, "hidePreview");
spyOn(this.target.writeLink, "tab");
});
it("calls writeLink.tab", function() {
this.target.hidePreview();
expect(this.target.writeLink.tab).toHaveBeenCalledWith("show");
});
it("calls instance.hidePreview", function() {
this.target.hidePreview();
expect(this.target.instance.hidePreview).toHaveBeenCalled();
});
it("calls instance.onHidePreview", function() {
this.target.hidePreview();
expect(this.target.options.onHidePreview).toHaveBeenCalled();
});
});
describe("showPreview", function() {
beforeEach(function() {
this.target = new Diaspora.MarkdownEditor(this.$el, {onPreview: $.noop, onPostPreview: jasmine.createSpy()});
spyOn(this.target.instance, "showPreview");
spyOn(this.target.previewLink, "tab");
});
it("calls previewLink.tab", function() {
this.target.showPreview();
expect(this.target.previewLink.tab).toHaveBeenCalledWith("show");
});
it("calls instance.showPreview", function() {
this.target.showPreview();
expect(this.target.instance.showPreview).toHaveBeenCalled();
});
it("calls instance.onPostPreview", function() {
this.target.showPreview();
expect(this.target.options.onPostPreview).toHaveBeenCalled();
});
});
describe("localize", function() {
beforeEach(function() {
this.target = new Diaspora.MarkdownEditor(this.$el, {});
});
it("returns the correct locale", function() {
expect(this.target.localize()).toBe(Diaspora.I18n.language);
});
it("creates translation messages for the current locale", function() {
this.target.localize();
expect($.fn.markdown.messages[Diaspora.I18n.language]).toBeDefined();
});
});
});

View file

@ -76,6 +76,7 @@ var factory = {
postAttrs : function(){ postAttrs : function(){
return { return {
"author": {},
"provider_display_name" : null, "provider_display_name" : null,
"created_at" : "2012-01-03T19:53:13Z", "created_at" : "2012-01-03T19:53:13Z",
"interacted_at" : '2012-01-03T19:53:13Z', "interacted_at" : '2012-01-03T19:53:13Z',