diff --git a/Gemfile b/Gemfile index fd3fcc77a..946973ab1 100644 --- a/Gemfile +++ b/Gemfile @@ -106,6 +106,7 @@ source "https://rails-assets.org" do gem "rails-assets-markdown-it-sub", "1.0.0" gem "rails-assets-markdown-it-sup", "1.0.0" gem "rails-assets-highlightjs", "9.4.0" + gem "rails-assets-bootstrap-markdown", "2.9.0" # jQuery plugins diff --git a/Gemfile.lock b/Gemfile.lock index b851160fa..72f632412 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -646,6 +646,10 @@ GEM sprockets-rails rails-assets-autosize (3.0.15) 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-favico.js (~> 0.3.9) rails-assets-jquery (>= 1.11) @@ -994,6 +998,7 @@ DEPENDENCIES rails (= 4.2.7.1) rails-assets-autosize (= 3.0.15)! 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-highlightjs (= 9.4.0)! rails-assets-jasmine-ajax (= 3.2.0)! diff --git a/app/assets/javascripts/app/views/preview_post_view.js b/app/assets/javascripts/app/views/preview_post_view.js new file mode 100644 index 000000000..1f8fe3981 --- /dev/null +++ b/app/assets/javascripts/app/views/preview_post_view.js @@ -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 diff --git a/app/assets/javascripts/app/views/publisher/getting_started_view.js b/app/assets/javascripts/app/views/publisher/getting_started_view.js index 02a025e0a..5ba089efc 100644 --- a/app/assets/javascripts/app/views/publisher/getting_started_view.js +++ b/app/assets/javascripts/app/views/publisher/getting_started_view.js @@ -18,6 +18,7 @@ app.views.PublisherGettingStarted = Backbone.View.extend({ // initiate all the popover message boxes show: function() { + app.publisher.open(); this._addPopover(this.firstMessage, { trigger: "manual", id: "first_message_explain", diff --git a/app/assets/javascripts/app/views/publisher_view.js b/app/assets/javascripts/app/views/publisher_view.js index 3ef2fa798..e89112ece 100644 --- a/app/assets/javascripts/app/views/publisher_view.js +++ b/app/assets/javascripts/app/views/publisher_view.js @@ -20,10 +20,8 @@ app.views.Publisher = Backbone.View.extend({ events : { "keydown #status_message_fake_text" : "keyDown", "focus textarea" : "open", - "click #hide_publisher" : "clear", "submit form" : "createStatusMessage", "click #submit" : "createStatusMessage", - "click .post_preview_button" : "createPostPreview", "textchange #status_message_fake_text": "handleTextchange", "click #locator" : "showLocation", "click #poll_creator" : "togglePollCreator", @@ -41,12 +39,12 @@ app.views.Publisher = Backbone.View.extend({ this.hiddenInputEl = this.$("#status_message_text"); this.wrapperEl = this.$("#publisher_textarea_wrapper"); this.submitEl = this.$("input[type=submit], button#submit"); - this.previewEl = this.$("button.post_preview_button"); this.photozoneEl = this.$("#photodropzone"); // if there is data in the publisher we ask for a confirmation // before the user is able to leave the page $(window).on("beforeunload", _.bind(this._beforeUnload, this)); + $(window).unload(this.clear.bind(this)); // sync textarea content if( this.hiddenInputEl.val() === "" ) { @@ -60,8 +58,6 @@ app.views.Publisher = Backbone.View.extend({ // in case publisher is standalone // (e.g. bookmarklet, mentions popup) if( this.standalone ) { - this.$("#hide_publisher").hide(); - this.previewEl.hide(); this.$(".question_mark").hide(); } @@ -130,6 +126,36 @@ app.views.Publisher = Backbone.View.extend({ }); 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({ el: this.$("#poll_creator_container") }); @@ -251,7 +277,7 @@ app.views.Publisher = Backbone.View.extend({ }, togglePollCreator: function(){ - this.viewPollCreator.$el.toggle(); + this.wrapperEl.toggleClass("with-poll"); this.inputEl.focus(); }, @@ -293,24 +319,21 @@ app.views.Publisher = Backbone.View.extend({ if(pollQuestion && pollAnswers.length) { poll = { "question": pollQuestion, - "poll_answers" : pollAnswers, + "poll_answers": pollAnswers, "participation_count": "0" }; } return poll; }, - createPostPreview : function(evt) { - if(evt){ evt.preventDefault(); } - if(!app.stream) { return; } - + createPostPreview: function() { //add missing mentions at end of post: 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 mentionedPeople = this.mention.mentionedPeople; - var date = (new Date()).toISOString(); var poll = this.getPollData(serializedForm); var locationCoords = serializedForm["location[coords]"]; if(!locationCoords || locationCoords === "") { @@ -325,44 +348,22 @@ app.views.Publisher = Backbone.View.extend({ }; var previewMessage = { - "id" : 0, - "text" : serializedForm["status_message[text]"], - "public" : serializedForm["aspect_ids[]"] === "public", - "created_at" : date, - "interacted_at" : date, - "post_type" : "StatusMessage", - "author" : app.currentUser ? app.currentUser.attributes : {}, - "mentioned_people" : mentionedPeople, - "photos" : photos, - "title" : serializedForm["status_message[text]"], - "location" : location, - "interactions" : {"likes":[],"reshares":[],"comments_count":0,"likes_count":0,"reshares_count":0}, + "id": 0, + "text": text, + "public": serializedForm["aspect_ids[]"] === "public", + "created_at": new Date().toISOString(), + "interacted_at": new Date().toISOString(), + "author": app.currentUser ? app.currentUser.attributes : {}, + "mentioned_people": mentionedPeople, + "photos": photos, + "title": serializedForm["status_message[text]"], + "location": location, + "interactions": {"likes": [], "reshares": [], "comments_count": 0, "likes_count": 0, "reshares_count": 0}, "poll": poll }; - this.removePostPreview(); - app.stream.addNow(previewMessage); - 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; - } + var previewPost = new app.views.PreviewPost({model: new app.models.Post(previewMessage)}).render().el; + return $("
").append(previewPost).html(); }, keyDown : function(evt) { @@ -392,12 +393,10 @@ app.views.Publisher = Backbone.View.extend({ // empty upload-photo this.$("#fileInfo").empty(); - // close publishing area (CSS) + // remove preview and close publishing area (CSS) + this.markdownEditor.hidePreview(); this.close(); - // remove preview - this.removePostPreview(); - // disable submitting this.checkSubmitAvailability(); @@ -445,7 +444,7 @@ app.views.Publisher = Backbone.View.extend({ $(this.el).addClass("closed"); this.wrapperEl.removeClass("active"); this.inputEl.css("height", ""); - this.viewPollCreator.$el.hide(); + this.wrapperEl.removeClass("with-poll"); return this; }, @@ -476,10 +475,8 @@ app.views.Publisher = Backbone.View.extend({ setButtonsEnabled: function(bool) { if (bool) { this.submitEl.removeAttr("disabled"); - this.previewEl.removeAttr("disabled"); } else { this.submitEl.prop("disabled", true); - this.previewEl.prop("disabled", true); } }, @@ -503,8 +500,6 @@ app.views.Publisher = Backbone.View.extend({ }, handleTextchange: function() { - var self = this; - this.checkSubmitAvailability(); this.hiddenInputEl.val(this.mention.getTextForSubmit()); }, diff --git a/app/assets/javascripts/helpers/markdown_editor.js b/app/assets/javascripts/helpers/markdown_editor.js new file mode 100644 index 000000000..55df13480 --- /dev/null +++ b/app/assets/javascripts/helpers/markdown_editor.js @@ -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 = $(""); + + var writeTab = $(""); + this.writeLink = $("") + .attr("title", Diaspora.I18n.t("publisher.markdown_editor.tooltips.write")); + + this.writeLink.append($("")); + this.writeLink.append($("") + .text(Diaspora.I18n.t("publisher.markdown_editor.write"))); + + this.writeLink.click(function(evt) { + evt.preventDefault(); + self.hidePreview(); + }); + + writeTab.append(this.writeLink); + + var previewTab = $(""); + this.previewLink = $("") + .attr("title", Diaspora.I18n.t("publisher.markdown_editor.tooltips.preview")); + + this.previewLink.append($("")); + this.previewLink.append($("") + .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 = $("") + .attr("title", Diaspora.I18n.t("publisher.markdown_editor.tooltips.cancel")); + + button.click(function() { + self.hidePreview(); + self.options.onClose(); + }); + + return button.append($("")); + }, + + 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; + } +}; diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index c0a1af428..07f124d14 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -43,3 +43,5 @@ //= require blueimp-gallery/blueimp-gallery-indicator //= require leaflet //= require api/authorization_page +// = require bootstrap-markdown/bootstrap-markdown +// = require helpers/markdown_editor diff --git a/app/assets/stylesheets/_application.scss b/app/assets/stylesheets/_application.scss index d9408139a..8bd3810d0 100644 --- a/app/assets/stylesheets/_application.scss +++ b/app/assets/stylesheets/_application.scss @@ -61,6 +61,7 @@ // publisher @import 'publisher'; @import 'aspects'; +@import 'markdown-editor'; // bookmarklet @import 'bookmarklet'; diff --git a/app/assets/stylesheets/color_themes/_color_theme_override.scss b/app/assets/stylesheets/color_themes/_color_theme_override.scss index 9525782fa..899885f8e 100644 --- a/app/assets/stylesheets/color_themes/_color_theme_override.scss +++ b/app/assets/stylesheets/color_themes/_color_theme_override.scss @@ -17,13 +17,6 @@ body { 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; } .poll_form .progress .bar { background-color: $main-color-dark; } diff --git a/app/assets/stylesheets/markdown-editor.scss b/app/assets/stylesheets/markdown-editor.scss new file mode 100644 index 000000000..cb731118b --- /dev/null +++ b/app/assets/stylesheets/markdown-editor.scss @@ -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; } diff --git a/app/assets/stylesheets/mentions.scss b/app/assets/stylesheets/mentions.scss index a51cf7617..71028e629 100644 --- a/app/assets/stylesheets/mentions.scss +++ b/app/assets/stylesheets/mentions.scss @@ -78,7 +78,6 @@ color: white; font-size: $font-size-base; font-family: Arial, Helvetica, sans-serif; - line-height: normal; overflow: hidden; width: 100%; white-space: pre-wrap; diff --git a/app/assets/stylesheets/publisher.scss b/app/assets/stylesheets/publisher.scss index df04cf349..e29a64bfd 100644 --- a/app/assets/stylesheets/publisher.scss +++ b/app/assets/stylesheets/publisher.scss @@ -82,7 +82,7 @@ } textarea { - border: none; + border: 0 solid $light-grey; margin: 0; box-shadow: none; resize: none; @@ -101,16 +101,11 @@ a { color: lighten($blue,20%); } } - .mentions-input-box .mentions { - line-height: $line-height-base !important; - } - &.with_attachments #photodropzone_container { border-top: 1px dashed $border-grey; } #poll_creator_container { - display: none; border-top: 1px dashed $border-grey; padding:4px 6px 4px 6px; box-sizing: border-box; @@ -212,7 +207,14 @@ } .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 { height: 20px; @@ -259,7 +261,15 @@ .twitter-typeahead { 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; } } diff --git a/app/assets/stylesheets/stream_element.scss b/app/assets/stylesheets/stream_element.scss index 91f96255c..26899f05f 100644 --- a/app/assets/stylesheets/stream_element.scss +++ b/app/assets/stylesheets/stream_element.scss @@ -1,4 +1,4 @@ -#main_stream .stream_element, +.stream_element, .photo { & > .media { margin: 0px; @@ -61,12 +61,20 @@ } #main_stream .stream_element { - padding: 10px; margin-bottom: 20px; - background-color: $white; border: 1px solid $light-grey; box-shadow: $card-shadow; + &.highlighted { + border-left: 3px solid $brand-primary; + padding-left: 8px; + } +} + +.stream_element { + background-color: $white; + padding: 10px; + & > .media { &.shield-active .nsfw-hidden { 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, .reshares { font-size: 12px; diff --git a/app/assets/templates/feedback_tpl.jst.hbs b/app/assets/templates/feedback_tpl.jst.hbs index 5a7f40aff..724a37638 100644 --- a/app/assets/templates/feedback_tpl.jst.hbs +++ b/app/assets/templates/feedback_tpl.jst.hbs @@ -14,22 +14,28 @@ - -· - -{{#if userCanReshare}} - - {{t "stream.reshare"}} +{{#if preview}} + {{t "stream.like"}} +{{else}} + +{{/if}} +· +{{#if preview}} + {{t "stream.reshare"}} + · +{{else if userCanReshare}} + {{t "stream.reshare"}} · {{/if}} - - {{t "stream.comment"}} - +{{#if preview}} + {{t "stream.comment"}} +{{else}} + {{t "stream.comment"}} +{{/if}} diff --git a/app/assets/templates/poll_tpl.jst.hbs b/app/assets/templates/poll_tpl.jst.hbs index e8410d5f4..a1a9be576 100644 --- a/app/assets/templates/poll_tpl.jst.hbs +++ b/app/assets/templates/poll_tpl.jst.hbs @@ -25,7 +25,11 @@ {{/poll.poll_answers}} {{#if show_form}}
- {{t "poll.show_result"}} + {{#if preview}} + {{t "poll.show_result"}} + {{else}} + {{t "poll.show_result"}} + {{/if}}
diff --git a/app/assets/templates/stream-element_tpl.jst.hbs b/app/assets/templates/stream-element_tpl.jst.hbs index 01dcfb7ef..2520c1e67 100644 --- a/app/assets/templates/stream-element_tpl.jst.hbs +++ b/app/assets/templates/stream-element_tpl.jst.hbs @@ -8,30 +8,32 @@
{{#if loggedIn}}
- {{#if authorIsCurrentUser}} - - - - {{else}} - - - - - - - {{#if participation}} - - + {{#unless preview}} + {{#if authorIsCurrentUser}} + + {{else}} - - + + + + + + + {{#if participation}} + + + + {{else}} + + + + {{/if}} + + {{/if}} - - - - {{/if}} + {{/unless}}
{{/if}} @@ -42,13 +44,17 @@ - - + {{#if preview}} + {{else}} + + - + + {{/if}}
diff --git a/app/views/publisher/_publisher.html.haml b/app/views/publisher/_publisher.html.haml index 3ecec1f40..4d85b0290 100644 --- a/app/views/publisher/_publisher.html.haml +++ b/app/views/publisher/_publisher.html.haml @@ -26,11 +26,11 @@ %input.typeahead-mention-box.hidden{type: "text"} = 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 .location-container.form-group{style: "padding: 4px 6px;"} = hidden_field :location, :coords - #poll_creator_container + .poll-creator-container#poll_creator_container -# handlebars template #button_container .publisher-buttonbar#publisher-images @@ -59,12 +59,8 @@ .spinner .options_and_submit.col-sm-12 .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 = 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") .btn-toolbar.pull-right#publisher-service-icons diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 9f3f8cd92..686871f66 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -2,6 +2,13 @@ # licensed under the Affero General Public License version 3 or later. See # 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 config.serve_static_files = AppConfig.environment.assets.serve? # config.static_cache_control = "public, max-age=3600" if AppConfig[:serve_static_assets].to_s == 'true' diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml index b961cbf91..99419cc2d 100644 --- a/config/locales/diaspora/en.yml +++ b/config/locales/diaspora/en.yml @@ -1031,7 +1031,6 @@ en: formatWithMarkdown: "You can use %{markdown_link} to format your post" posting: "Posting..." share: "Share" - preview: "Preview" upload_photos: "Upload photos" get_location: "Get your location" remove_location: "Remove location" diff --git a/config/locales/javascript/javascript.en.yml b/config/locales/javascript/javascript.en.yml index 7066e750b..0d31f9178 100644 --- a/config/locales/javascript/javascript.en.yml +++ b/config/locales/javascript/javascript.en.yml @@ -138,6 +138,35 @@ en: option: "Answer" add_option: "Add an answer" 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: post_something: "Post to diaspora*" post_submit: "Submitting post..." diff --git a/features/desktop/post_preview.feature b/features/desktop/post_preview.feature index 15c89c540..20e785471 100644 --- a/features/desktop/post_preview.feature +++ b/features/desktop/post_preview.feature @@ -16,26 +16,27 @@ Feature: preview posts in the stream Scenario: preview and post a text-only message Given I expand the publisher When I write the status message "I am eating yogurt" - And I press "Preview" - Then "I am eating yogurt" should be post 1 - And the first post should be a preview + And I preview the post + Then I should see "I am eating yogurt" in the preview + Given I edit the post When I write the status message "This preview rocks" - And I press "Preview" - Then "This preview rocks" should be post 1 - And I should not see "I am eating a yogurt" + And I preview the post + Then I should see "This preview rocks" in the preview + 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" And I press "Share" 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 Given I expand the publisher When I insert an extremely long status message - And I press "Preview" + And I preview the post Then the preview should not be collapsed - When I press "Share" 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 When I fill in the following: | status_message_fake_text | Look at this dog | - And I press "Preview" - Then I should see a "img" within ".stream_element div.photo_attachments" - And I should see "Look at this dog" within ".stream_element" + And I preview the post + Then I should see a "img" within ".md-preview .stream_element .photo_attachments" + And I should see "Look at this dog" within ".md-preview .stream_element" And I close the publisher Scenario: preview a post with mentions Given I expand 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" Then I should see "Alice Smith" @@ -63,9 +64,8 @@ Feature: preview posts in the stream When I expand the publisher And I fill in the following: | status_message_fake_text | This preview rocks | - And I press "Preview" - Then "This preview rocks" should be post 1 - And the first post should be a preview + And I preview the post + Then I should see "This preview rocks" in the preview And I close the publisher 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: | normal | | not normal | - And I press "Preview" - Then I should see a ".poll_form" within ".stream_element" - And I should see a "form" within ".stream_element" + And I preview the post + Then I should see a ".poll_form" within ".md-preview .stream_element" + And I should see a "form" within ".md-preview .stream_element" And I close the publisher Scenario: preview a post with location @@ -93,7 +93,7 @@ Feature: preview posts in the stream When I fill in the following: | status_message_fake_text | I am eating yogurt | | location_address | Some cool place | - And I press "Preview" - Then I should see a ".near-from" within ".stream_element" - And I should see "Some cool place" within ".stream_element .near-from" + And I preview the post + Then I should see a ".near-from" within ".md-preview .stream_element" + And I should see "Some cool place" within ".md-preview .stream_element .near-from" And I close the publisher diff --git a/features/desktop/posts_from_main_page.feature b/features/desktop/posts_from_main_page.feature index 1c51665da..465bf8c0e 100644 --- a/features/desktop/posts_from_main_page.feature +++ b/features/desktop/posts_from_main_page.feature @@ -24,7 +24,8 @@ Feature: posting from the main page When I expand the publisher 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 "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 Given I expand the publisher diff --git a/features/step_definitions/custom_web_steps.rb b/features/step_definitions/custom_web_steps.rb index d8a0f9c79..73e9db02e 100644 --- a/features/step_definitions/custom_web_steps.rb +++ b/features/step_definitions/custom_web_steps.rb @@ -65,7 +65,7 @@ And /^I expand the publisher$/ do end And /^I close the publisher$/ do - find("#publisher #hide_publisher").click + find("#publisher .md-cancel").click end Then /^the publisher should be expanded$/ do diff --git a/features/step_definitions/post_preview_steps.rb b/features/step_definitions/post_preview_steps.rb index 015400e15..f79b4e32e 100644 --- a/features/step_definitions/post_preview_steps.rb +++ b/features/step_definitions/post_preview_steps.rb @@ -1,9 +1,35 @@ -Then /^the first post should be a preview$/ do - find(".post_preview .post-content").text.should == first_post_text +And /^I edit the post$/ do + with_scope(".publisher-textarea-wrapper") do + find(".md-write-tab").click + end end Then /^the preview should not be collapsed$/ do - find(".post_preview").should_not have_selector('.collapsed') - find(".post_preview").should have_selector('.opened') + with_scope(".publisher-textarea-wrapper .collapsible") do + expect(current_scope).not_to have_css(".collapsed") + 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 diff --git a/features/support/publishing_cuke_helpers.rb b/features/support/publishing_cuke_helpers.rb index 88c57d685..395318211 100644 --- a/features/support/publishing_cuke_helpers.rb +++ b/features/support/publishing_cuke_helpers.rb @@ -45,10 +45,8 @@ module PublishingCukeHelpers end def click_publisher - page.execute_script(' - $("#publisher").removeClass("closed"); - $("#publisher").find("#status_message_fake_text").focus(); - ') + find("#status_message_fake_text").click + expect(find("#publisher")).to have_css(".publisher-textarea-wrapper.active") end def publisher_submittable? diff --git a/spec/javascripts/app/views/preview_post_view_spec.js b/spec/javascripts/app/views/preview_post_view_spec.js new file mode 100644 index 000000000..e2c1b5aec --- /dev/null +++ b/spec/javascripts/app/views/preview_post_view_spec.js @@ -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}); + }); + }); +}); diff --git a/spec/javascripts/app/views/publisher_view_spec.js b/spec/javascripts/app/views/publisher_view_spec.js index fdf92826f..b80875f10 100644 --- a/spec/javascripts/app/views/publisher_view_spec.js +++ b/spec/javascripts/app/views/publisher_view_spec.js @@ -12,14 +12,11 @@ describe("app.views.Publisher", function() { this.view = new app.views.Publisher({ standalone: true }); + this.view.open(); }); it("hides the close button in standalone mode", function() { - expect(this.view.$("#hide_publisher").is(":visible")).toBeFalsy(); - }); - - it("hides the post preview button in standalone mode", function() { - expect(this.view.$(".post_preview_button").is(":visible")).toBeFalsy(); + expect(this.view.$(".md-cancel").is(":visible")).toBeFalsy(); }); it("hides the manage services link in standalone mode", function() { @@ -89,6 +86,21 @@ describe("app.views.Publisher", function() { this.view.close($.Event()); 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() { @@ -99,11 +111,11 @@ describe("app.views.Publisher", function() { expect(this.view.close).toHaveBeenCalled(); }); - it("calls removePostPreview", function(){ - spyOn(this.view, "removePostPreview"); + it("calls hidePreview", function() { + spyOn(this.view.markdownEditor, "hidePreview"); this.view.clear($.Event()); - expect(this.view.removePostPreview).toHaveBeenCalled(); + expect(this.view.markdownEditor.hidePreview).toHaveBeenCalled(); }); it("clears all textareas", function(){ @@ -179,39 +191,11 @@ describe("app.views.Publisher", function() { }); describe("createPostPreview", function(){ - beforeEach(function() { - app.stream = { addNow: $.noop }; - }); - it("calls handleTextchange to complete missing mentions", function(){ spyOn(this.view, "handleTextchange"); - this.view.createPostPreview($.Event()); + this.view.createPostPreview(); 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() { @@ -236,11 +220,9 @@ describe("app.views.Publisher", function() { it("disables submitting", function() { this.view.setText("TESTING"); expect(this.view.submitEl.prop("disabled")).toBeFalsy(); - expect(this.view.previewEl.prop("disabled")).toBeFalsy(); this.view.setEnabled(false); expect(this.view.submitEl.prop("disabled")).toBeTruthy(); - expect(this.view.previewEl.prop("disabled")).toBeTruthy(); }); }); @@ -491,7 +473,6 @@ describe("app.views.Publisher", function() { '
'+ '
'+ ' '+ - '
'+ '' ); @@ -540,7 +521,6 @@ describe("app.views.Publisher", function() { it('disables the publisher buttons', function() { 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() { expect(this.view.submitEl.prop("disabled")).toBeFalsy(); - expect(this.view.previewEl.prop("disabled")).toBeFalsy(); }); }); diff --git a/spec/javascripts/helpers/markdown_editor_spec.js b/spec/javascripts/helpers/markdown_editor_spec.js new file mode 100644 index 000000000..9c2ab4cff --- /dev/null +++ b/spec/javascripts/helpers/markdown_editor_spec.js @@ -0,0 +1,237 @@ +describe("Diaspora.MarkdownEditor", function() { + beforeEach(function() { + spec.content().html(""); + 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($(""), {}); + }); + + 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("
"); + 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("
"); + 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(); + }); + }); +}); diff --git a/spec/javascripts/jasmine_helpers/factory.js b/spec/javascripts/jasmine_helpers/factory.js index 1659574ff..d21435651 100644 --- a/spec/javascripts/jasmine_helpers/factory.js +++ b/spec/javascripts/jasmine_helpers/factory.js @@ -76,6 +76,7 @@ var factory = { postAttrs : function(){ return { + "author": {}, "provider_display_name" : null, "created_at" : "2012-01-03T19:53:13Z", "interacted_at" : '2012-01-03T19:53:13Z',