Add preview functionality to rich text controls
authorTom Hughes <tom@compton.nu>
Sat, 3 Mar 2012 18:40:44 +0000 (18:40 +0000)
committerTom Hughes <tom@compton.nu>
Sat, 17 Mar 2012 16:36:57 +0000 (16:36 +0000)
15 files changed:
app/assets/javascripts/site.js
app/assets/stylesheets/common.css.scss
app/controllers/site_controller.rb
app/helpers/application_helper.rb
app/views/diary_entry/edit.html.erb
app/views/diary_entry/view.html.erb
app/views/message/new.html.erb
app/views/site/_markdown_help.html.erb [new file with mode: 0644]
app/views/user/account.html.erb
app/views/user_blocks/new.html.erb
config/locales/en.yml
config/routes.rb
test/functional/diary_entry_controller_test.rb
test/functional/site_controller_test.rb
test/functional/user_controller_test.rb

index 95815d44efd3b3f29c2470a38d43f6845d5a6dde..5d727a0697fa3a982f56dc56c0b66d4a496eabd9 100644 (file)
@@ -205,3 +205,51 @@ function makeShortCode(lat, lon, zoom) {
     }
     return str;
 }
+
+/*
+ * Click handler to switch a rich text control to preview mode
+ */
+function previewRichtext(event) {
+  var editor = $(this).parents(".richtext_container").find("textarea");
+  var preview = $(this).parents(".richtext_container").find(".richtext_preview");
+  var width = editor.outerWidth() - preview.outerWidth() + preview.innerWidth();
+  var minHeight = editor.outerHeight() - preview.outerHeight() + preview.innerHeight();
+
+  editor.hide();
+  preview.load(editor.attr("data-preview-url"), { text: editor.val() });
+  preview.width(width);
+  preview.css("min-height", minHeight + "px");
+  preview.show();
+
+  $(this).siblings(".richtext_doedit").prop("disabled", false);
+  $(this).prop("disabled", true);
+
+  event.preventDefault();
+}
+
+/*
+ * Click handler to switch a rich text control to edit mode
+ */
+function editRichtext(event) {
+  var editor = $(this).parents(".richtext_container").find("textarea");
+  var preview = $(this).parents(".richtext_container").find(".richtext_preview");
+
+  preview.hide();
+  editor.show();
+
+  $(this).siblings(".richtext_dopreview").prop("disabled", false);
+  $(this).prop("disabled", true);
+
+  event.preventDefault();
+}
+
+/*
+ * Setup any rich text controls
+ */
+$(document).ready(function () {
+  $(".richtext_preview").hide();
+  $(".richtext_doedit").prop("disabled", true);
+  $(".richtext_dopreview").prop("disabled", false);
+  $(".richtext_doedit").click(editRichtext);
+  $(".richtext_dopreview").click(previewRichtext);
+});
index 479e24f75f9c4415cf63b8023784260559541024..0c2873acc245f1a56fc157ba5cf66accd577037b 100644 (file)
@@ -1040,3 +1040,61 @@ abbr.geo {
   vertical-align: text-bottom;
   border: 0;
 }
+
+/* Rules for rich text editors */
+
+.richtext_container {
+  white-space: nowrap;
+
+  .richtext_content {
+    display: inline-block;
+    vertical-align: top;
+
+    .richtext_preview {
+      display: inline-block;
+      margin-top: 1px;
+      margin-bottom: 1px;
+      border: 4px solid #eee;
+      background-color: #eee;
+      white-space: normal;
+
+      > :first-child {
+        margin-top: 0px;
+      }
+    }
+  }
+
+  .richtext_help {
+    display: inline-block;
+    vertical-align: top;
+    background-color: #ddd;
+    margin-left: 15px;
+    padding: 5px 10px 10px 10px;
+    font-size: 12px;
+
+    p {
+      margin: 0px;
+    }
+
+    th {
+      vertical-align: top;
+      padding: 0px 15px 0px 0px !important;
+    }
+
+    td {
+      font-family: fixed;
+      line-height: 16px;
+      padding: 0px !important;
+    }
+
+    input.richtext_doedit {
+      margin-top: 5px !important;
+      margin-right: 10px !important;
+    }
+
+    input.richtext_dopreview {
+      margin-top: 5px !important;
+      margin-left: 10px !important;
+    }
+  }
+}
index cf4465b9c5b185bcdd3b760073083b51c1b842aa..fa33deeeeead9b95cca0368de32886529bf557ff 100644 (file)
@@ -85,4 +85,8 @@ class SiteController < ApplicationController
   def copyright
     @locale = params[:copyright_locale] || I18n.locale
   end
+
+  def preview
+    render :text => RichText.new(params[:format], params[:text]).to_html
+  end
 end
index 7a72932f55affbbd6550adc3a50a394bb93cfc1d..37dcf90e09de1c97e4ae4d62dfa96fb79f0b3860 100644 (file)
@@ -121,6 +121,24 @@ module ApplicationHelper
     Math.log(360.0 / (scale.to_f * 512.0)) / Math.log(2.0)
   end
 
+  def richtext_area(object_name, method, options = {})
+    id = "#{object_name.to_s}_#{method.to_s}"
+    format = options.delete(:format) || "markdown"
+
+    content_tag(:div, :id => "#{id}_container", :class => "richtext_container") do
+      output_buffer << content_tag(:div, :id => "#{id}_content", :class => "richtext_content") do
+        output_buffer << text_area(object_name, method, options.merge("data-preview-url" => preview_url(:format => format)))
+        output_buffer << content_tag(:div, "", :id => "#{id}_preview", :class => "richtext_preview")
+      end
+
+      output_buffer << content_tag(:div, :id => "#{id}_help", :class => "richtext_help") do
+        output_buffer << render("site/#{format}_help")
+        output_buffer << submit_tag(I18n.t("site.richtext_area.edit"), :id => "#{id}_doedit", :class => "richtext_doedit", :disabled => true)
+        output_buffer << submit_tag(I18n.t("site.richtext_area.preview"), :id => "#{id}_dopreview", :class => "richtext_dopreview")
+      end
+    end
+  end
+
 private
 
   def javascript_strings_for_key(key)
index f6273a828a849b0dc3d77fafbe167ee80538b56b..da409e61a0d40494b586641f1b26bf4fb11d4a99 100644 (file)
@@ -10,7 +10,7 @@
     </tr>
     <tr valign="top">
       <td class="fieldName"><%= t 'diary_entry.edit.body' -%></td>
-      <td><%= f.text_area :body, :cols => 80 %></td>
+      <td><%= richtext_area :diary_entry, :body, :cols => 80 %></td>
     </tr>
     <tr valign="top">
       <td class="fieldName"><%= t 'diary_entry.edit.language' -%></td>
index 7073aa7405d3a84aa87898faf7c658be88881deb..1bce441b4256d5fc9534f8dc4427626a32ec7aae 100644 (file)
@@ -13,8 +13,8 @@
 
   <%= error_messages_for 'diary_comment' %>
 
-  <%= form_for DiaryComment.new, :url => { :action => 'comment' } do |f| %>
-    <%= f.text_area :body, :cols => 80, :rows => 5 %>
+  <%= form_for :diary_comment, :url => { :action => 'comment' } do |f| %>
+    <%= richtext_area :diary_comment, :body, :cols => 80, :rows => 15 %>
     <br />
     <br />
     <%= submit_tag t('diary_entry.view.save_button') %>
index 113e566d82b74b4bcab5cbb423234ec852f9cddf..a9dfb520f84ee1de9dd13ba5fd725f3a50e461e1 100644 (file)
@@ -10,7 +10,7 @@
     </tr>
     <tr valign="top">
       <td class="fieldName"><%= t'message.new.body' %></td>
-      <td><%= f.text_area :body, :cols => 80, :value => @body %></td>
+      <td><%= richtext_area :message, :body, :cols => 80, :value => @body %></td>
     </tr>
     <tr>
       <td></td>
diff --git a/app/views/site/_markdown_help.html.erb b/app/views/site/_markdown_help.html.erb
new file mode 100644 (file)
index 0000000..9c03ac6
--- /dev/null
@@ -0,0 +1,29 @@
+<table>
+  <thead>
+    <tr>
+      <th colspan="2"><%= t "site.markdown_help.title" %></th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <th><%= t "site.markdown_help.headings" %></th>
+      <td># <%= t "site.markdown_help.heading" %><br />## <%= t "site.markdown_help.subheading" %></td>
+    </tr>
+    <tr>
+      <th><%= t "site.markdown_help.unordered" %></th>
+      <td>* <%= t "site.markdown_help.first" %><br />* <%= t "site.markdown_help.second" %></td>
+    </tr>
+    <tr>
+      <th><%= t "site.markdown_help.ordered" %></th>
+      <td>1. <%= t "site.markdown_help.first" %><br />2. <%= t "site.markdown_help.second" %></td>
+    </tr>
+    <tr>
+      <th><%= t "site.markdown_help.link" %></th>
+      <td>[<%= t "site.markdown_help.text" %>](<%= t "site.markdown_help.url" %>)</td>
+    </tr>
+    <tr>
+      <th><%= t "site.markdown_help.image" %></th>
+      <td>![<%= t "site.markdown_help.alt" %>](<%= t "site.markdown_help.url" %>)</td>
+    </tr>
+  </tbody>
+</table>
index 4f56be22ab1e470567d11b9bc7c95ab986f21025..4889664911747152606ec57348c0e307d638fba8 100644 (file)
@@ -63,7 +63,7 @@
 
   <tr>
     <td class="fieldName"><%= t 'user.account.profile description' %></td>
-    <td><%= f.text_area :description, :rows => '5', :cols => '60' %></td>
+    <td><%= richtext_area :user, :description, :rows => '15', :cols => '80' %></td>
   </tr>
 
   <tr>
index 80854b840c3d3d921c36bd2ca2cc344dcb72cda2..f63298373ae2177e1e9915a0bc0e771b83b96448 100644 (file)
@@ -9,7 +9,7 @@
 
   <p>
     <%= f.label :reason, t('user_block.new.reason', :name => @this_user.display_name) %><br />
-    <%= f.text_area :reason, :cols => 80, :rows => 5 %>
+    <%= richtext_area :user_block, :reason, :cols => 80, :rows => 20 %>
   </p>
   <p>
     <%= label_tag 'user_block_period', t('user_block.new.period') %><br />
index 766d619184047ddf8f51725b52550763e701f0d3..53f3ffb561e69497e0b93212772e3c347491a03a 100644 (file)
@@ -1404,6 +1404,23 @@ en:
           permissive: "Permissive access"
           destination: "Destination access"
           construction: "Roads under construction"
+    richtext_area:
+      edit: Edit
+      preview: Preview
+    markdown_help:
+      title: Parsed with Markdown
+      headings: Headings
+      heading: Heading
+      subheading: Subheading
+      unordered: Unordered list
+      ordered: Ordered list
+      first: First item
+      second: Second item
+      link: Link
+      text: Text
+      image: Image
+      alt: Alt text
+      url: url
   trace:
     visibility:
       private: "Private (only shared as anonymous, unordered points)"
index c8d9a99ee6158ff62e39756c160367b6c6b8fbbf..5408b4e71bd401801690221f54a8ed1efce62c1f 100644 (file)
@@ -122,6 +122,9 @@ OpenStreetMap::Application.routes.draw do
   # permalink
   match '/go/:code' => 'site#permalink', :via => :get, :code => /[a-zA-Z0-9_@~]+[=-]*/
 
+  # rich text preview
+  match '/preview/:format' => 'site#preview', :as => :preview
+
   # traces
   match '/user/:display_name/traces/tag/:tag/page/:page' => 'trace#list', :via => :get
   match '/user/:display_name/traces/tag/:tag' => 'trace#list', :via => :get
index 7981edd958a15b82222b869ca45f08c1a4835d0b..724fb48a5a3387d828b4ef31bca3fdccafc6d6b6 100644 (file)
@@ -159,7 +159,9 @@ class DiaryEntryControllerTest < ActionController::TestCase
             assert_select "input#latitude[name='diary_entry[latitude]']", :count => 1
             assert_select "input#longitude[name='diary_entry[longitude]']", :count => 1
             assert_select "input[name=commit][type=submit][value=Save]", :count => 1
-            assert_select "input", :count => 5
+            assert_select "input[name=commit][type=submit][value=Edit]", :count => 1
+            assert_select "input[name=commit][type=submit][value=Preview]", :count => 1
+            assert_select "input", :count => 7
           end
         end
       end
index 3f9e2a145c74278f6dd6f0a52af349aa7a6826dd..86445e551a4e0480e3feb0e5a8a41ed9d0949ba3 100644 (file)
@@ -54,6 +54,10 @@ class SiteControllerTest < ActionController::TestCase
       { :path => "/go/shortcode", :method => :get },
       { :controller => "site", :action => "permalink", :code => "shortcode" }
     )
+    assert_routing(
+      { :path => "/preview/formatname", :method => :get },
+      { :controller => "site", :action => "preview", :format => "formatname" }
+    )
   end
 
   ## Lets check that we can get all the pages without any errors  
index 24daa1a6c708a718f8240d276aad3dc607e30687..576eb0db9c72371a9778dcf3a08fc3359c5edc5e 100644 (file)
@@ -367,7 +367,7 @@ class UserControllerTest < ActionController::TestCase
     assert_template :account
     assert_select "div#errorExplanation", false
     assert_select "div#notice", /^User information updated successfully/
-    assert_select "table#accountForm > tr > td > textarea#user_description", user.description
+    assert_select "table#accountForm > tr > td > div#user_description_container > div#user_description_content > textarea#user_description", user.description
 
     # Changing name to one that exists should fail
     user.display_name = users(:public_user).display_name