Commit a739edce1f172e39cbd3ca8a6a1e43bae8279171
Exists in
master
and in
28 other branches
Merge branch 'article-version' of https://gitlab.com/juniorsilva1001/noosfero in…
…to juniorsilva1001/noosfero-article-version Conflicts: app/views/content_viewer/versioned_article.rhtml db/schema.rb public/stylesheets/application.css
Showing
9 changed files
with
274 additions
and
20 deletions
Show diff stats
app/controllers/public/content_viewer_controller.rb
1 | +require 'diffy' | |
2 | + | |
1 | 3 | class ContentViewerController < ApplicationController |
2 | 4 | |
3 | 5 | needs_profile |
... | ... | @@ -117,6 +119,13 @@ class ContentViewerController < ApplicationController |
117 | 119 | end |
118 | 120 | end |
119 | 121 | |
122 | + def versions_diff | |
123 | + path = params[:page].join('/') | |
124 | + @page = profile.articles.find_by_path(path) | |
125 | + @v1, @v2 = @page.versions.find_by_version(params[:v1]), @page.versions.find_by_version(params[:v2]) | |
126 | + p params | |
127 | + end | |
128 | + | |
120 | 129 | def article_versions |
121 | 130 | path = params[:page].join('/') |
122 | 131 | @page = profile.articles.find_by_path(path) |
... | ... | @@ -184,3 +193,7 @@ class ContentViewerController < ApplicationController |
184 | 193 | end |
185 | 194 | |
186 | 195 | end |
196 | + | |
197 | + | |
198 | + | |
199 | + | ... | ... |
app/views/content_viewer/article_versions.rhtml
1 | +<div class="article-versions"> | |
2 | + <%= button(:back, _('Go back to post'), {:action => 'view_page'}) %> | |
3 | +</div> | |
4 | + | |
1 | 5 | <%= article_title(@page, :no_link => true) %> |
2 | 6 | |
3 | 7 | <p><%= _('This is the list of all versions of this content. Select a version to see it and then revert to it.') %>.</p> |
4 | 8 | |
5 | -<ul class='article-versions'> | |
6 | - <% @versions.each do |v| %> | |
7 | - <li> | |
8 | - <%= link_to(_("Version #{v.version}"), @page.url.merge(:version => v.version)) %> | |
9 | - <%= @page.version == v.version ? _('(current)') : '' %> | |
10 | - <span class='updated-by'><%= _('by %{author}') % {:author => link_to(@page.author_name(v.version), @page.author_url)} %></span> | |
11 | - <div class='updated-on'><%= show_time(v.updated_at) %></div> | |
12 | - </li> | |
13 | - <% end %> | |
14 | -</ul> | |
9 | +<% form_tag({:controller => 'content_viewer', :action => 'versions_diff', :profile => profile.identifier, :page => @page.path.split('/')}, :method => 'get') do %> | |
10 | + <ul id="article-versions"> | |
11 | + <% @versions.each do |v| %> | |
12 | + <li> | |
13 | + <%= radio_button_tag 'v1', v.version, false, :onclick => 'versionInputClicked(this)' %> | |
14 | + <%= radio_button_tag 'v2', v.version, false, :onclick => 'versionInputClicked(this)' %> | |
15 | + <%= link_to(_("Version #{v.version}"), @page.url.merge(:version => v.version)) %> | |
16 | + <%= @page.version == v.version ? _('(current)') : '' %> | |
17 | + <span class='updated-by'><%= _('by %{author}') % {:author => link_to(@page.author_name(v.version), @page.author_url)} %></span> | |
18 | + <span class='updated-on'><%= show_time(v.updated_at) %></span> | |
19 | + </li> | |
20 | + <% end %> | |
21 | + </ul> | |
22 | + | |
23 | +<script> | |
24 | + function versionInputClicked(input) { | |
25 | + var selectedColumn = input.name; | |
26 | + var sisterColumn = (selectedColumn == 'v1') ? 'v2' : 'v1'; | |
27 | + var li = input.parentNode; | |
28 | + var checkedBrotherLi = jQuery('#article-versions input[name=' + sisterColumn + ']:checked').parent()[0]; | |
29 | + updateColumn(selectedColumn, li); | |
30 | + updateColumn(sisterColumn, checkedBrotherLi); | |
31 | + } | |
15 | 32 | |
33 | + function updateColumn(selectedColumn, startLi){ | |
34 | + var sisterColumn = (selectedColumn == 'v1') ? 'v2' : 'v1'; | |
35 | + var walkMethod = (selectedColumn == 'v1') ? 'prev' : 'next'; | |
36 | + var li = startLi; | |
37 | + var foundCheckedBrother = false; | |
38 | + while(li = jQuery(li)[walkMethod]()[0]) { | |
39 | + li.className = ''; | |
40 | + if (!foundCheckedBrother){ | |
41 | + li.className = 'selected'; | |
42 | + foundCheckedBrother = jQuery('input[name=' + sisterColumn + ']', li)[0].checked; | |
43 | + } | |
44 | + jQuery('input[name=' + selectedColumn + ']', li)[0].disabled = foundCheckedBrother; | |
45 | + } | |
46 | + } | |
47 | + | |
48 | + var penultVersion = jQuery('#article-versions input[name=v1]')[1]; | |
49 | + var lastVersion = jQuery('#article-versions input[name=v2]')[0]; | |
50 | + jQuery('#article-versions input').attr('disabled', false); | |
51 | + if (penultVersion && lastVersion) { | |
52 | + penultVersion.checked = lastVersion.checked = true; | |
53 | + lastVersion.onclick(); | |
54 | + } | |
55 | +</script> | |
56 | + <%= submit_button(:clock, _('Show differences between selected versions')) %> | |
57 | +<% end %> | |
16 | 58 | <%= pagination_links @versions, :param_name => 'npage' %> |
59 | + | ... | ... |
app/views/content_viewer/versioned_article.rhtml
1 | +<div class="article-versions"> | |
2 | + <%= button(:back, _('Back to the versions'), {:action => 'article_versions'}) %> | |
3 | +</div> | |
4 | + | |
1 | 5 | <div id="article" class="<%= @page.css_class_name %>"> |
2 | 6 | |
3 | 7 | <div id="article-actions"> |
4 | 8 | <%= button(:clock, _('All versions'), {:controller => 'content_viewer', :profile => profile.identifier, :action => 'article_versions'}, :id => 'article-versions-link') %> |
5 | 9 | |
6 | - <% if @page.allow_edit?(user) && !remove_content_button(:edit) %> | |
10 | + <% if @page.allow_edit?(user) && !remove_content_button(:undo) %> | |
7 | 11 | <% content = content_tag('span', _('Revert to this version')) %> |
8 | 12 | <% url = profile.admin_url.merge({ :controller => 'cms', :action => 'edit', :id => @page.id, :version => @version }) %> |
9 | - <%= expirable_button @page, :edit, content, url, :id => 'article-revert-version-link' %> | |
13 | + <%= expirable_button @page, :undo, content, url, :id => 'article-revert-version-link' %> | |
10 | 14 | <% end %> |
11 | - </div> | |
12 | 15 | |
16 | + <%= button(:forward, _('Go to latest version'), {:action => 'view_page'}) %> | |
17 | +</div> | |
13 | 18 | <div id="article-header"> |
14 | 19 | <h1 class='title'><%= @versioned_article.name %></h1> |
15 | 20 | <%= _("Version %{version} - %{author} on %{date}") % {:version => @version, :author => @page.author_name(@version), :date => show_time(@versioned_article.updated_at) } %> |
16 | 21 | </div> |
17 | 22 | |
23 | + <p id="no-current-version"> | |
24 | + <%= _('This is not the latest version of this content.') %> | |
25 | + </p> | |
26 | +</div> | |
27 | + | |
18 | 28 | <% version_license = @page.version_license(@version) %> |
19 | 29 | <%# This seemingly doubled verification exists because the article-sub-header |
20 | - div must appear only if at least one content inside it will appeart. | |
30 | + div must appear only if at least one content inside it will appear. | |
21 | 31 | Although we have only one content now, we might have others in the future. |
22 | 32 | So we're keeping it like that to avoid mistakes. %> |
23 | 33 | <% if version_license.present? %> | ... | ... |
... | ... | @@ -0,0 +1,16 @@ |
1 | +<div class="article-versions"> | |
2 | +<%= button(:back, _('Back to the versions'), {:action => 'article_versions'}) %> | |
3 | +</div> | |
4 | + | |
5 | +<h1><%= _('Changes on "%s"') % @page.title %></h1> | |
6 | + | |
7 | +<p> <%= _('Changes from %s → %s') % [show_time(@v1.updated_at), show_time(@v2.updated_at)] %> </p> | |
8 | + | |
9 | +<% diffContent = Diffy::Diff.new(@v1.body, @v2.body, :context => 1) %> | |
10 | +<% if diffContent.to_s(:text).blank? %> | |
11 | + <p id="article-versions-no-diff"> | |
12 | + <%= _('These versions range have no differences.')%> | |
13 | + </p> | |
14 | +<% else %> | |
15 | + <%= diffContent.to_s(:html) %> | |
16 | +<% end %> | ... | ... |
config/routes.rb
... | ... | @@ -129,6 +129,9 @@ ActionController::Routing::Routes.draw do |map| |
129 | 129 | map.connect ':profile/*page/versions', :controller => 'content_viewer', :action => 'article_versions', :profile => /#{Noosfero.identifier_format}/, :conditions => { :if => lambda { |env| !Domain.hosting_profile_at(env[:host]) } } |
130 | 130 | map.connect '*page/versions', :controller => 'content_viewer', :action => 'article_versions' |
131 | 131 | |
132 | + map.connect ':profile/*page/versions_diff', :controller => 'content_viewer', :action => 'versions_diff', :profile => /#{Noosfero.identifier_format}/, :conditions => { :if => lambda { |env| !Domain.hosting_profile_at(env[:host]) } } | |
133 | + map.connect '*page/versions_diff', :controller => 'content_viewer', :action => 'versions_diff' | |
134 | + | |
132 | 135 | # match requests for profiles that don't have a custom domain |
133 | 136 | map.homepage ':profile/*page', :controller => 'content_viewer', :action => 'view_page', :profile => /#{Noosfero.identifier_format}/, :conditions => { :if => lambda { |env| !Domain.hosting_profile_at(env[:host]) } } |
134 | 137 | ... | ... |
db/schema.rb
... | ... | @@ -10,7 +10,6 @@ |
10 | 10 | # It's strongly recommended to check this file into your version control system. |
11 | 11 | |
12 | 12 | ActiveRecord::Schema.define(:version => 20140314200103) do |
13 | - | |
14 | 13 | create_table "abuse_reports", :force => true do |t| |
15 | 14 | t.integer "reporter_id" |
16 | 15 | t.integer "abuse_complaint_id" |
... | ... | @@ -148,6 +147,7 @@ ActiveRecord::Schema.define(:version => 20140314200103) do |
148 | 147 | add_index "articles", ["profile_id"], :name => "index_articles_on_profile_id" |
149 | 148 | add_index "articles", ["slug"], :name => "index_articles_on_slug" |
150 | 149 | add_index "articles", ["translation_of_id"], :name => "index_articles_on_translation_of_id" |
150 | + add_index "articles", [nil], :name => "pg_search_plugin_article" | |
151 | 151 | |
152 | 152 | create_table "articles_categories", :id => false, :force => true do |t| |
153 | 153 | t.integer "article_id" |
... | ... | @@ -201,6 +201,8 @@ ActiveRecord::Schema.define(:version => 20140314200103) do |
201 | 201 | t.string "abbreviation" |
202 | 202 | end |
203 | 203 | |
204 | + add_index "categories", [nil], :name => "pg_search_plugin_category" | |
205 | + | |
204 | 206 | create_table "categories_profiles", :id => false, :force => true do |t| |
205 | 207 | t.integer "profile_id" |
206 | 208 | t.integer "category_id" |
... | ... | @@ -219,6 +221,8 @@ ActiveRecord::Schema.define(:version => 20140314200103) do |
219 | 221 | t.datetime "updated_at" |
220 | 222 | end |
221 | 223 | |
224 | + add_index "certifiers", [nil], :name => "pg_search_plugin_certifier" | |
225 | + | |
222 | 226 | create_table "comments", :force => true do |t| |
223 | 227 | t.string "title" |
224 | 228 | t.text "body" |
... | ... | @@ -233,9 +237,11 @@ ActiveRecord::Schema.define(:version => 20140314200103) do |
233 | 237 | t.string "source_type" |
234 | 238 | t.string "user_agent" |
235 | 239 | t.string "referrer" |
240 | + t.integer "group_id" | |
236 | 241 | end |
237 | 242 | |
238 | 243 | add_index "comments", ["source_id", "spam"], :name => "index_comments_on_source_id_and_spam" |
244 | + add_index "comments", [nil], :name => "pg_search_plugin_comment" | |
239 | 245 | |
240 | 246 | create_table "contact_lists", :force => true do |t| |
241 | 247 | t.text "list" |
... | ... | @@ -245,6 +251,50 @@ ActiveRecord::Schema.define(:version => 20140314200103) do |
245 | 251 | t.datetime "updated_at" |
246 | 252 | end |
247 | 253 | |
254 | + create_table "custom_forms_plugin_answers", :force => true do |t| | |
255 | + t.text "value" | |
256 | + t.integer "field_id" | |
257 | + t.integer "submission_id" | |
258 | + end | |
259 | + | |
260 | + create_table "custom_forms_plugin_fields", :force => true do |t| | |
261 | + t.string "name" | |
262 | + t.string "slug" | |
263 | + t.string "type" | |
264 | + t.string "default_value" | |
265 | + t.string "choices" | |
266 | + t.float "minimum" | |
267 | + t.float "maximum" | |
268 | + t.integer "form_id" | |
269 | + t.boolean "mandatory", :default => false | |
270 | + t.boolean "multiple" | |
271 | + t.boolean "list" | |
272 | + t.integer "position", :default => 0 | |
273 | + end | |
274 | + | |
275 | + create_table "custom_forms_plugin_forms", :force => true do |t| | |
276 | + t.string "name" | |
277 | + t.string "slug" | |
278 | + t.text "description" | |
279 | + t.integer "profile_id" | |
280 | + t.datetime "begining" | |
281 | + t.datetime "ending" | |
282 | + t.boolean "report_submissions", :default => false | |
283 | + t.boolean "on_membership", :default => false | |
284 | + t.string "access" | |
285 | + t.datetime "created_at" | |
286 | + t.datetime "updated_at" | |
287 | + end | |
288 | + | |
289 | + create_table "custom_forms_plugin_submissions", :force => true do |t| | |
290 | + t.string "author_name" | |
291 | + t.string "author_email" | |
292 | + t.integer "profile_id" | |
293 | + t.integer "form_id" | |
294 | + t.datetime "created_at" | |
295 | + t.datetime "updated_at" | |
296 | + end | |
297 | + | |
248 | 298 | create_table "delayed_jobs", :force => true do |t| |
249 | 299 | t.integer "priority", :default => 0 |
250 | 300 | t.integer "attempts", :default => 0 |
... | ... | @@ -311,6 +361,10 @@ ActiveRecord::Schema.define(:version => 20140314200103) do |
311 | 361 | t.integer "enterprise_id" |
312 | 362 | end |
313 | 363 | |
364 | + create_table "foo_plugin_bars", :force => true do |t| | |
365 | + t.string "name" | |
366 | + end | |
367 | + | |
314 | 368 | create_table "friendships", :force => true do |t| |
315 | 369 | t.integer "person_id" |
316 | 370 | t.integer "friend_id" |
... | ... | @@ -356,6 +410,8 @@ ActiveRecord::Schema.define(:version => 20140314200103) do |
356 | 410 | t.integer "environment_id", :null => false |
357 | 411 | end |
358 | 412 | |
413 | + add_index "licenses", [nil], :name => "pg_search_plugin_license" | |
414 | + | |
359 | 415 | create_table "mailing_sents", :force => true do |t| |
360 | 416 | t.integer "mailing_id" |
361 | 417 | t.integer "person_id" |
... | ... | @@ -390,6 +446,7 @@ ActiveRecord::Schema.define(:version => 20140314200103) do |
390 | 446 | |
391 | 447 | add_index "national_regions", ["name"], :name => "name_index" |
392 | 448 | add_index "national_regions", ["national_region_code"], :name => "code_index" |
449 | + add_index "national_regions", [nil], :name => "pg_search_plugin_nationalregion" | |
393 | 450 | |
394 | 451 | create_table "price_details", :force => true do |t| |
395 | 452 | t.decimal "price", :default => 0.0 |
... | ... | @@ -488,6 +545,7 @@ ActiveRecord::Schema.define(:version => 20140314200103) do |
488 | 545 | add_index "profiles", ["identifier"], :name => "index_profiles_on_identifier" |
489 | 546 | add_index "profiles", ["members_count"], :name => "index_profiles_on_members_count" |
490 | 547 | add_index "profiles", ["region_id"], :name => "index_profiles_on_region_id" |
548 | + add_index "profiles", [nil], :name => "pg_search_plugin_profile" | |
491 | 549 | |
492 | 550 | create_table "qualifier_certifiers", :force => true do |t| |
493 | 551 | t.integer "qualifier_id" |
... | ... | @@ -501,6 +559,8 @@ ActiveRecord::Schema.define(:version => 20140314200103) do |
501 | 559 | t.datetime "updated_at" |
502 | 560 | end |
503 | 561 | |
562 | + add_index "qualifiers", [nil], :name => "pg_search_plugin_qualifier" | |
563 | + | |
504 | 564 | create_table "refused_join_community", :id => false, :force => true do |t| |
505 | 565 | t.integer "person_id" |
506 | 566 | t.integer "community_id" |
... | ... | @@ -547,6 +607,8 @@ ActiveRecord::Schema.define(:version => 20140314200103) do |
547 | 607 | t.integer "context_id" |
548 | 608 | end |
549 | 609 | |
610 | + add_index "scraps", [nil], :name => "pg_search_plugin_scrap" | |
611 | + | |
550 | 612 | create_table "sessions", :force => true do |t| |
551 | 613 | t.string "session_id", :null => false |
552 | 614 | t.text "data" |
... | ... | @@ -606,6 +668,19 @@ ActiveRecord::Schema.define(:version => 20140314200103) do |
606 | 668 | t.string "thumbnail" |
607 | 669 | end |
608 | 670 | |
671 | + create_table "tolerance_time_plugin_publications", :force => true do |t| | |
672 | + t.integer "target_id" | |
673 | + t.string "target_type" | |
674 | + t.datetime "created_at" | |
675 | + t.datetime "updated_at" | |
676 | + end | |
677 | + | |
678 | + create_table "tolerance_time_plugin_tolerances", :force => true do |t| | |
679 | + t.integer "profile_id" | |
680 | + t.integer "content_tolerance" | |
681 | + t.integer "comment_tolerance" | |
682 | + end | |
683 | + | |
609 | 684 | create_table "units", :force => true do |t| |
610 | 685 | t.string "singular", :null => false |
611 | 686 | t.string "plural", :null => false | ... | ... |
public/designs/icons/tango/style.css
1 | 1 | /******************SMALL ICONS********************/ |
2 | 2 | .icon-edit { background-image: url(Tango/16x16/apps/text-editor.png) } |
3 | +.icon-undo { background-image: url(Tango/16x16/actions/edit-undo.png) } | |
3 | 4 | .icon-home { background-image: url(Tango/16x16/actions/go-home.png) } |
4 | 5 | .icon-home-not { background-image: url(mod/16x16/actions/go-home-not.png) } |
5 | 6 | .icon-new, | ... | ... |
public/stylesheets/application.css
... | ... | @@ -6510,11 +6510,6 @@ li.profile-activity-item.upload_image .activity-gallery-images-count-1 img { |
6510 | 6510 | text-align: center; |
6511 | 6511 | } |
6512 | 6512 | |
6513 | -ul.article-versions li { | |
6514 | - font-size: 13px; | |
6515 | - padding: 3px 0px; | |
6516 | -} | |
6517 | - | |
6518 | 6513 | /* * * Admin manage fields * * */ |
6519 | 6514 | |
6520 | 6515 | .controller-features .manage-fields-batch-actions, |
... | ... | @@ -6526,3 +6521,90 @@ ul.article-versions li { |
6526 | 6521 | .controller-features .manage-fields-batch-actions td { |
6527 | 6522 | font-style: italic; |
6528 | 6523 | } |
6524 | + | |
6525 | +/* * * Article versions * * */ | |
6526 | + | |
6527 | +#article-versions { | |
6528 | + margin: 0 0 15px 0; | |
6529 | + padding: 0; | |
6530 | +} | |
6531 | + | |
6532 | +#article-versions li { | |
6533 | + list-style: none; | |
6534 | + margin: 0; | |
6535 | + padding: 3px 0px; | |
6536 | +} | |
6537 | + | |
6538 | +#article-versions input:disabled { | |
6539 | + opacity: 0.3; | |
6540 | +} | |
6541 | + | |
6542 | +#article-versions .selected { | |
6543 | + background: #ff9; | |
6544 | +} | |
6545 | + | |
6546 | +#article-versions-no-diff { | |
6547 | + text-align: center; | |
6548 | + font-style: italic; | |
6549 | +} | |
6550 | + | |
6551 | +#no-current-version { | |
6552 | + text-align: center; | |
6553 | + font-style: italic; | |
6554 | + background: #faa; | |
6555 | + padding: 3px; | |
6556 | +} | |
6557 | + | |
6558 | +.article-versions { | |
6559 | + margin: 5px 0px; | |
6560 | + text-align: right; | |
6561 | +} | |
6562 | + | |
6563 | +.action-content_viewer-versions_diff .diff { | |
6564 | + text-align: justify; | |
6565 | +} | |
6566 | + | |
6567 | +.diff ul { | |
6568 | + list-style: none; | |
6569 | + margin: 0; | |
6570 | + padding: 0; | |
6571 | +} | |
6572 | + | |
6573 | +.diff del, .diff ins { | |
6574 | + display: block; | |
6575 | + text-decoration: none; | |
6576 | +} | |
6577 | + | |
6578 | +.diff li { | |
6579 | + padding: 5px 10px; | |
6580 | + margin: 0; | |
6581 | + line-height: 150%; | |
6582 | +} | |
6583 | + | |
6584 | +.diff li.ins { | |
6585 | + background: #dfd; | |
6586 | +} | |
6587 | + | |
6588 | +.diff li.del { | |
6589 | + background: #fee; | |
6590 | +} | |
6591 | + | |
6592 | +.diff li.ins:hover { | |
6593 | + background: #8f8; | |
6594 | +} | |
6595 | + | |
6596 | +.diff li.del:hover { | |
6597 | + background: #f99; | |
6598 | +} | |
6599 | + | |
6600 | +.diff strong { | |
6601 | + background: rgba(255, 255, 0, 0.2); | |
6602 | +} | |
6603 | + | |
6604 | +.diff li.diff-comment { | |
6605 | + display: none; | |
6606 | +} | |
6607 | + | |
6608 | +.diff li.diff-block-info { | |
6609 | + background: none repeat scroll 0 0 gray; | |
6610 | +} | ... | ... |
test/functional/content_viewer_controller_test.rb
... | ... | @@ -396,6 +396,17 @@ class ContentViewerControllerTest < ActionController::TestCase |
396 | 396 | assert_tag :tag => 'div', :attributes => { :class => /article-body/ }, :content => /edited article/ |
397 | 397 | end |
398 | 398 | |
399 | + should "display differences between article's versions" do | |
400 | + page = TextArticle.create!(:name => 'myarticle', :body => 'original article', :display_versions => true, :profile => profile) | |
401 | + page.body = 'edited article'; page.save | |
402 | + | |
403 | + get :versions_diff, :profile => profile.identifier, :page => [ 'myarticle' ], :v1 => 1, :v2 => 2; | |
404 | + | |
405 | + assert_tag :tag => 'li', :attributes => { :class => /del/ }, :content => /original article/ | |
406 | + assert_tag :tag => 'li', :attributes => { :class => /ins/ }, :content => /edited article/ | |
407 | + assert_response :success | |
408 | + end | |
409 | + | |
399 | 410 | should 'not return an article of a different user' do |
400 | 411 | p1 = create_user('test_user').person |
401 | 412 | a = p1.articles.create!(:name => 'old-name') | ... | ... |