Merge pull request #6551 from AugierLe42e/bootstrap-markdown

Adding bootstrap-markdown editor
parents 8deef544 5c2e2411
...@@ -191,6 +191,7 @@ The command will report queues that still have jobs and launch sidekiq process f ...@@ -191,6 +191,7 @@ The command will report queues that still have jobs and launch sidekiq process f
* Return all contacts in people search [#6951](https://github.com/diaspora/diaspora/pull/6951) * Return all contacts in people search [#6951](https://github.com/diaspora/diaspora/pull/6951)
* Make screenreaders read alerts [#6973](https://github.com/diaspora/diaspora/pull/6973) * Make screenreaders read alerts [#6973](https://github.com/diaspora/diaspora/pull/6973)
* Display message when there are no posts in a stream [#6974](https://github.com/diaspora/diaspora/pull/6974) * Display message when there are no posts in a stream [#6974](https://github.com/diaspora/diaspora/pull/6974)
* Add bootstrap-markdown editor to the publisher [#6551](https://github.com/diaspora/diaspora/pull/6551)
# 0.5.10.2 # 0.5.10.2
......
...@@ -106,6 +106,7 @@ source "https://rails-assets.org" do ...@@ -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
......
...@@ -646,6 +646,10 @@ GEM ...@@ -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 ...@@ -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)!
......
// @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
...@@ -18,6 +18,7 @@ app.views.PublisherGettingStarted = Backbone.View.extend({ ...@@ -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",
......
...@@ -20,10 +20,8 @@ app.views.Publisher = Backbone.View.extend({ ...@@ -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({ ...@@ -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({ ...@@ -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({ ...@@ -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({ ...@@ -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({ ...@@ -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({ ...@@ -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({ ...@@ -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({ ...@@ -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({ ...@@ -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({ ...@@ -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());
}, },
......
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;
}
};
...@@ -43,3 +43,5 @@ ...@@ -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
...@@ -61,6 +61,7 @@ ...@@ -61,6 +61,7 @@
// publisher // publisher
@import 'publisher'; @import 'publisher';
@import 'aspects'; @import 'aspects';
@import 'markdown-editor';
// bookmarklet // bookmarklet
@import 'bookmarklet'; @import 'bookmarklet';
......
...@@ -17,13 +17,6 @@ body { ...@@ -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; }
......
.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 {