[refs #616] ugly md preview

This commit is contained in:
Alexander Machehin 2012-08-29 20:58:58 +06:00
parent de5b7cc6fc
commit d775fa0d08
19 changed files with 557 additions and 16 deletions

View File

@ -32,7 +32,7 @@ gem 'diff-display', '~> 0.0.1'
# Wiki
gem "gollum", :git => 'git://github.com/github/gollum.git'
gem "redcarpet", "1.17.2"
gem "redcarpet", "~> 2.1.1"
gem 'creole'
gem 'rdiscount'
# gem 'org-ruby'

View File

@ -251,7 +251,7 @@ GEM
rdiscount (1.6.8)
rdoc (3.12)
json (~> 1.4)
redcarpet (1.17.2)
redcarpet (2.1.1)
redis (3.0.1)
redis-namespace (1.2.0)
redis (~> 3.0.0)
@ -399,7 +399,7 @@ DEPENDENCIES
rails3-generators
rails3-jquery-autocomplete (~> 1.0.7)
rdiscount
redcarpet (= 1.17.2)
redcarpet (~> 2.1.1)
redhillonrails_core!
resque (~> 1.21.0)
resque-status (~> 0.3.3)

View File

@ -81,4 +81,8 @@ $(document).ready(function() {
}
return false;
});
$('.md_and_cm code').each(function (code) {
CodeMirror.runMode(this.innerHTML.replace(/&/gi, '&'), this.className, this);
});
});

View File

@ -1481,3 +1481,29 @@ div.log-wrapper {
}
}
}
.md_and_cm {
overflow: auto;
pre {
background-color: #F8F8F8;
border: 1px solid #D6D6D6;
border-radius: 5px 5px 5px 5px;
color: #333333;
padding: 6px 10px;
overflow: auto;
code {
background-color: transparent;
border: none;
}
}
code {
background-color: #F8F8F8;
border: 1px solid #D6D6D6;
border-radius: 5px 5px 5px 5px;
margin: 0 2px;
padding: 0px 5px;
}
}

View File

@ -86,6 +86,10 @@ class Projects::ProjectsController < Projects::BaseController
redirect_to projects_path
end
def preview
render :inline => view_context.markdown(params[:text]), :layout => false
end
protected
def prepare_list(projects)

View File

@ -39,4 +39,11 @@ module ApplicationHelper
else object.class.name
end
end
def markdown(text)
html_options = {filter_html: true, hard_wrap: true, with_toc_data: true}
options = {no_intraemphasis: true, tables: true, fenced_code_blocks: true, autolink: true, strikethrough: true, lax_html_blocks: true}
Redcarpet::Markdown.new(Redcarpet::Render::HTML.new(html_options), options).render(text).html_safe
end
end

View File

@ -53,7 +53,7 @@ class Ability
can :read, Project, :visibility => 'open'
can [:read, :archive], Project, :owner_type => 'User', :owner_id => user.id
can [:read, :archive], Project, :owner_type => 'Group', :owner_id => user.group_ids
can([:read, :membered], Project, read_relations_for('projects')) {|project| local_reader? project}
can([:read, :membered, :preview], Project, read_relations_for('projects')) {|project| local_reader? project}
can(:write, Project) {|project| local_writer? project} # for grack
can([:update, :sections, :manage_collaborators], Project) {|project| local_admin? project}
can(:fork, Project) {|project| can? :read, project}

View File

@ -9,7 +9,7 @@ class CommentPresenter < ApplicationPresenter
@user = comment.user
@options = opts
@content = simple_format(@comment.body, {}, :sanitize => true).html_safe
@content = @comment.body
end
def expandable?

View File

@ -10,7 +10,7 @@
- subscribe_path = is_subscribed ? unsubscribe_commit_path(project, commentable) : subscribe_commit_path(project, commentable)
= form_for :comment, :url => new_path, :method => :post, :html => { :class => :form } do |f|
= render "projects/comments/form", :f => f
= render "projects/comments/form", :f => f, :id => 'new'
.comment-left
= t("layout.comments.notifications_are")
%span.bold

View File

@ -0,0 +1,40 @@
%ul.nav.nav-tabs#md_tabs
%li
%a{"data-toggle" => "tab", :href => "##{id}_edit"} edit
%li
%a{"data-toggle" => "tab", :href => "##{id}_preview"} preview
.tab-content
.tab-pane.active{:id => "#{id}_edit"}
.wrapper= f.text_area :body, :cols => 80, :id => "#{id}_edit_input"
=hidden_field_tag :body_dup, nil, :name => 'text', :id => "#{id}_edit_input_dup"
.tab-pane{:id => "#{id}_preview"}
.formatted.cm-s-default.md_and_cm{:style => 'background: #FFF'}
:javascript
$(document).ready(function() {
$('#md_tabs a:first').tab('show');
$('#md_tabs a[data-toggle="tab"]').on('shown', function (e) {
var hash = e.relatedTarget.hash;
var el = $(hash+'_input');
var el_dup = $(hash+'_input_dup');
if(el.val() != el_dup.val()) {
el_dup.val(el.val());
$.ajax({
type: 'POST',
url: '#{project_md_preview_path @project}',
data: el_dup.serialize(),
success: function(data){
$(e.target.hash+' > .formatted.cm-s-default').html(data)
.find('code').each(function (code) {
CodeMirror.runMode(this.innerHTML.replace(/&amp;/gi, '&'), this.className, this);
});
},
error: function(data){
alert('error'); // TODO remove
}
});
};
});
});

View File

@ -1,2 +1,2 @@
.wrapper= f.text_area :body, :cols => 80
.wrapper=render 'projects/comments/body', :f => f, :id => id
.comment-right= submit_tag t("layout.save")

View File

@ -8,4 +8,4 @@
= t("layout.comments.edit_header")
.inner
= form_for @comment, :url => project_commentable_comment_path(@project, @commentable, @comment), :html => {:class => :form} do |f|
= render "form", :f => f
= render "form", :f => f, :id => "edit_#{@comment.id}"

View File

@ -1,7 +1,10 @@
.leftlist= t('activerecord.attributes.issue.title') + ':'
.rightlist= f.text_field :title
.leftlist= t('activerecord.attributes.issue.body') + ':'
.rightlist= f.text_area :body
#open-comment.comment.view
%h3.tmargin0{:style => 'margin-bottom: 0;'}= t 'activerecord.attributes.issue.title'
.wrapper= f.text_area :title, :cols => 80, :rows => 1, :style => 'height: 16px; margin: 0 0 10px;'
#open-comment.comment.view
%h3.tmargin0= t 'activerecord.attributes.issue.body'
=render 'projects/comments/body', :f => f, :id => 'new'
.both
.leftlist= t('activerecord.attributes.issue.assignee') + ':'
.rightlist

View File

@ -14,7 +14,7 @@
%span.date=@issue.created_at.to_s(:long)
%br/
.both
.fulltext.view.issue_body=simple_format @issue.body
.fulltext.view.issue_body.formatted.cm-s-default.md_and_cm=markdown @issue.body
.both
%br
- if can? :update, @issue
@ -33,3 +33,4 @@
= render "projects/comments/list", :list => @issue.comments, :project => @project, :commentable => @issue
%br
= render "projects/comments/add", :project => @project, :commentable => @issue if current_user

View File

@ -19,5 +19,5 @@
- if presenter.content?
.fulltext{:class => "#{presenter.expandable? ? "hidden" : ''} #{presenter.caption? ? "" : "alone"}",
:id => presenter.expandable? ? "content-expand#{item_no}" : ''}
= presenter.content
.cm-s-default.md_and_cm=markdown presenter.content
.both

View File

@ -181,6 +181,7 @@ Rosa::Application.routes.draw do
resources :collaborators do
get :find, :on => :collection
end
post '/preview' => 'projects#preview', :as => 'md_preview'
end
# Resource
get '/modify' => 'projects#edit', :as => :edit_project

View File

@ -0,0 +1,130 @@
/* ========================================================
* bootstrap-tab.js v2.0.2
* http://twitter.github.com/bootstrap/javascript.html#tabs
* ========================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ======================================================== */
!function( $ ){
"use strict"
/* TAB CLASS DEFINITION
* ==================== */
var Tab = function ( element ) {
this.element = $(element)
}
Tab.prototype = {
constructor: Tab
, show: function () {
var $this = this.element
, $ul = $this.closest('ul:not(.dropdown-menu)')
, selector = $this.attr('data-target')
, previous
, $target
if (!selector) {
selector = $this.attr('href')
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
}
if ( $this.parent('li').hasClass('active') ) return
previous = $ul.find('.active a').last()[0]
$this.trigger({
type: 'show'
, relatedTarget: previous
})
$target = $(selector)
this.activate($this.parent('li'), $ul)
this.activate($target, $target.parent(), function () {
$this.trigger({
type: 'shown'
, relatedTarget: previous
})
})
}
, activate: function ( element, container, callback) {
var $active = container.find('> .active')
, transition = callback
&& $.support.transition
&& $active.hasClass('fade')
function next() {
$active
.removeClass('active')
.find('> .dropdown-menu > .active')
.removeClass('active')
element.addClass('active')
if (transition) {
element[0].offsetWidth // reflow for transition
element.addClass('in')
} else {
element.removeClass('fade')
}
if ( element.parent('.dropdown-menu') ) {
element.closest('li.dropdown').addClass('active')
}
callback && callback()
}
transition ?
$active.one($.support.transition.end, next) :
next()
$active.removeClass('in')
}
}
/* TAB PLUGIN DEFINITION
* ===================== */
$.fn.tab = function ( option ) {
return this.each(function () {
var $this = $(this)
, data = $this.data('tab')
if (!data) $this.data('tab', (data = new Tab(this)))
if (typeof option == 'string') data[option]()
})
}
$.fn.tab.Constructor = Tab
/* TAB DATA-API
* ============ */
$(function () {
$('body').on('click.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) {
e.preventDefault()
$(this).tab('show')
})
})
}( window.jQuery );

View File

@ -13,6 +13,7 @@
// require bootstrap-tooltip
// require bootstrap-popover
//= require bootstrap-alert
//= require bootstrap-tab
//= require chosen.jquery
// require html5shiv
// require_tree .

View File

@ -420,3 +420,327 @@
-moz-border-radius: 4px;
border-radius: 4px;
}
.nav {
margin-left: 0;
margin-bottom: 20px;
list-style: none;
}
.nav > li > a {
display: block;
}
.nav > li > a:hover {
text-decoration: none;
background-color: #eeeeee;
}
.nav > .pull-right {
float: right;
}
.nav-header {
display: block;
padding: 3px 15px;
font-size: 11px;
font-weight: bold;
line-height: 20px;
color: #999999;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
text-transform: uppercase;
}
.nav li + .nav-header {
margin-top: 9px;
}
.nav-list {
padding-left: 15px;
padding-right: 15px;
margin-bottom: 0;
}
.nav-list > li > a,
.nav-list .nav-header {
margin-left: -15px;
margin-right: -15px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
}
.nav-list > li > a {
padding: 3px 15px;
}
.nav-list > .active > a,
.nav-list > .active > a:hover {
color: #ffffff;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
background-color: #0088cc;
}
.nav-list [class^="icon-"] {
margin-right: 2px;
}
.nav-list .divider {
*width: 100%;
height: 1px;
margin: 9px 1px;
*margin: -5px 0 5px;
overflow: hidden;
background-color: #e5e5e5;
border-bottom: 1px solid #ffffff;
}
.nav-tabs,
.nav-pills {
*zoom: 1;
}
.nav-tabs:before,
.nav-pills:before,
.nav-tabs:after,
.nav-pills:after {
display: table;
content: "";
line-height: 0;
}
.nav-tabs:after,
.nav-pills:after {
clear: both;
}
.nav-tabs > li,
.nav-pills > li {
float: left;
}
.nav-tabs > li > a,
.nav-pills > li > a {
padding-right: 12px;
padding-left: 12px;
margin-right: 2px;
line-height: 14px;
}
.nav-tabs {
border-bottom: 1px solid #ddd;
}
.nav-tabs > li {
margin-bottom: -1px;
}
.nav-tabs > li > a {
padding-top: 8px;
padding-bottom: 8px;
line-height: 20px;
border: 1px solid transparent;
-webkit-border-radius: 4px 4px 0 0;
-moz-border-radius: 4px 4px 0 0;
border-radius: 4px 4px 0 0;
}
.nav-tabs > li > a:hover {
border-color: #eeeeee #eeeeee #dddddd;
}
.nav-tabs > .active > a,
.nav-tabs > .active > a:hover {
color: #555555;
background-color: #ffffff;
border: 1px solid #ddd;
border-bottom-color: transparent;
cursor: default;
}
.nav-pills > li > a {
padding-top: 8px;
padding-bottom: 8px;
margin-top: 2px;
margin-bottom: 2px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
.nav-pills > .active > a,
.nav-pills > .active > a:hover {
color: #ffffff;
background-color: #0088cc;
}
.nav-stacked > li {
float: none;
}
.nav-stacked > li > a {
margin-right: 0;
}
.nav-tabs.nav-stacked {
border-bottom: 0;
}
.nav-tabs.nav-stacked > li > a {
border: 1px solid #ddd;
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
}
.nav-tabs.nav-stacked > li:first-child > a {
-webkit-border-top-right-radius: 4px;
-moz-border-radius-topright: 4px;
border-top-right-radius: 4px;
-webkit-border-top-left-radius: 4px;
-moz-border-radius-topleft: 4px;
border-top-left-radius: 4px;
}
.nav-tabs.nav-stacked > li:last-child > a {
-webkit-border-bottom-right-radius: 4px;
-moz-border-radius-bottomright: 4px;
border-bottom-right-radius: 4px;
-webkit-border-bottom-left-radius: 4px;
-moz-border-radius-bottomleft: 4px;
border-bottom-left-radius: 4px;
}
.nav-tabs.nav-stacked > li > a:hover {
border-color: #ddd;
z-index: 2;
}
.nav-pills.nav-stacked > li > a {
margin-bottom: 3px;
}
.nav-pills.nav-stacked > li:last-child > a {
margin-bottom: 1px;
}
.nav-tabs .dropdown-menu {
-webkit-border-radius: 0 0 6px 6px;
-moz-border-radius: 0 0 6px 6px;
border-radius: 0 0 6px 6px;
}
.nav-pills .dropdown-menu {
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
}
.nav .dropdown-toggle .caret {
border-top-color: #0088cc;
border-bottom-color: #0088cc;
margin-top: 6px;
}
.nav .dropdown-toggle:hover .caret {
border-top-color: #005580;
border-bottom-color: #005580;
}
/* move down carets for tabs */
.nav-tabs .dropdown-toggle .caret {
margin-top: 8px;
}
.nav .active .dropdown-toggle .caret {
border-top-color: #fff;
border-bottom-color: #fff;
}
.nav-tabs .active .dropdown-toggle .caret {
border-top-color: #555555;
border-bottom-color: #555555;
}
.nav > .dropdown.active > a:hover {
cursor: pointer;
}
.nav-tabs .open .dropdown-toggle,
.nav-pills .open .dropdown-toggle,
.nav > li.dropdown.open.active > a:hover {
color: #ffffff;
background-color: #999999;
border-color: #999999;
}
.nav li.dropdown.open .caret,
.nav li.dropdown.open.active .caret,
.nav li.dropdown.open a:hover .caret {
border-top-color: #ffffff;
border-bottom-color: #ffffff;
opacity: 1;
filter: alpha(opacity=100);
}
.tabs-stacked .open > a:hover {
border-color: #999999;
}
.tabbable {
*zoom: 1;
}
.tabbable:before,
.tabbable:after {
display: table;
content: "";
line-height: 0;
}
.tabbable:after {
clear: both;
}
.tab-content {
overflow: auto;
}
.tabs-below > .nav-tabs,
.tabs-right > .nav-tabs,
.tabs-left > .nav-tabs {
border-bottom: 0;
}
.tab-content > .tab-pane,
.pill-content > .pill-pane {
display: none;
}
.tab-content > .active,
.pill-content > .active {
display: block;
}
.tabs-below > .nav-tabs {
border-top: 1px solid #ddd;
}
.tabs-below > .nav-tabs > li {
margin-top: -1px;
margin-bottom: 0;
}
.tabs-below > .nav-tabs > li > a {
-webkit-border-radius: 0 0 4px 4px;
-moz-border-radius: 0 0 4px 4px;
border-radius: 0 0 4px 4px;
}
.tabs-below > .nav-tabs > li > a:hover {
border-bottom-color: transparent;
border-top-color: #ddd;
}
.tabs-below > .nav-tabs > .active > a,
.tabs-below > .nav-tabs > .active > a:hover {
border-color: transparent #ddd #ddd #ddd;
}
.tabs-left > .nav-tabs > li,
.tabs-right > .nav-tabs > li {
float: none;
}
.tabs-left > .nav-tabs > li > a,
.tabs-right > .nav-tabs > li > a {
min-width: 74px;
margin-right: 0;
margin-bottom: 3px;
}
.tabs-left > .nav-tabs {
float: left;
margin-right: 19px;
border-right: 1px solid #ddd;
}
.tabs-left > .nav-tabs > li > a {
margin-right: -1px;
-webkit-border-radius: 4px 0 0 4px;
-moz-border-radius: 4px 0 0 4px;
border-radius: 4px 0 0 4px;
}
.tabs-left > .nav-tabs > li > a:hover {
border-color: #eeeeee #dddddd #eeeeee #eeeeee;
}
.tabs-left > .nav-tabs .active > a,
.tabs-left > .nav-tabs .active > a:hover {
border-color: #ddd transparent #ddd #ddd;
*border-right-color: #ffffff;
}
.tabs-right > .nav-tabs {
float: right;
margin-left: 19px;
border-left: 1px solid #ddd;
}
.tabs-right > .nav-tabs > li > a {
margin-left: -1px;
-webkit-border-radius: 0 4px 4px 0;
-moz-border-radius: 0 4px 4px 0;
border-radius: 0 4px 4px 0;
}
.tabs-right > .nav-tabs > li > a:hover {
border-color: #eeeeee #eeeeee #eeeeee #dddddd;
}
.tabs-right > .nav-tabs .active > a,
.tabs-right > .nav-tabs .active > a:hover {
border-color: #ddd #ddd #ddd transparent;
*border-left-color: #ffffff;
}
.nav > .disabled > a {
color: #999999;
}
.nav > .disabled > a:hover {
text-decoration: none;
background-color: transparent;
cursor: default;
}