Project

General

Profile

Feature #2222 » wiki_section_edit-v0-9.patch

Wiki section edit patch - Manuel Studer, 2009-06-10 10:46

View differences:

app/helpers/wiki_helper.rb (working copy)
53 53
    end
54 54
    simple_format_without_paragraph(words.join(' '))
55 55
  end
56

  
57
  # Manuel S: parse text to find sections and numerate them
58
  def parse_sections(text)
59
    
60
    # scan text and replace line
61

  
62
    section_index = 1
63

  
64
    doc = Nokogiri::HTML.parse(text,nil,'utf-8')
65
    doc.xpath('//h1 | //h2 | //h3').each do |node|
66

  
67
      span_node = Nokogiri::XML::Node.new('span',doc)
68
      span_node['class'] = 'edit-section'
69
      bracket_open = Nokogiri::XML::Text.new('[',doc)
70
      bracket_close = Nokogiri::XML::Text.new(']',doc)
71
      span_node.add_child(bracket_open)
72

  
73
      link_node = Nokogiri::XML::Node.new('a',doc)
74
      #link_node['href'] = "#{@page.title}/edit?section=#{section_index}"
75
      # ok, this is not very nice, I know.
76
      link_node['href'] = @controller.send(:url_for,{:action => "edit", :page => @page.title}) << "?section=#{section_index}"
77
      #link_node['class'] = 'icon icon-edit'
78
      link_node.content = l(:button_edit)
79
      #link_node['title'] = l(:button_edit) << ": #{node.text.chop.chop}"
80

  
81
      span_node.add_child(link_node)
82
      span_node.add_child(bracket_close)
83
      
84
      node.children.first.add_next_sibling(span_node)
85

  
86
      section_index += 1
87
    end
88

  
89
    return doc.xpath('//body/.').to_xhtml('utf8')
90
  end
91

  
92
  # Manuel S: Collect content between two sections
93
  # section start included, section end removed
94
  def collect_between(first,last)
95
    result = ''
96
    until first == last
97
      result << first.to_s
98
      first = first.next_sibling
99
    end
100
    result
101
  end
102

  
103
  def get_section(html_text,section_number)
104
    doc = Nokogiri::HTML.parse(html_text)
105
    sections = doc.xpath('//h1 | //h2 | //h3')
106
    return collect_between(sections[section_number-1],sections[section_number])
107
  end
108

  
109

  
56 110
end
app/helpers/application_helper.rb (working copy)
306 306

  
307 307
    text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text) { |macro, args| exec_macro(macro, obj, args) }
308 308

  
309
    # Manuel S: wiki edit links for sections h1, h2, and h3
310
    # small hack, so that the content is being parsed for editing sections if allowed
311
    if @content.type == WikiContent && @editable
312
      if authorize_for(:wiki, :edit) && @content.version == @page.content.version
313
        text = parse_sections(text)
314
      end
315
    end
316
    
309 317
    # different methods for formatting wiki links
310 318
    case options[:wiki_links]
311 319
    when :local
app/controllers/wiki_controller.rb (working copy)
53 53
      send_data(@content.text, :type => 'text/plain', :filename => "#{@page.title}.txt")
54 54
      return
55 55
    end
56
	@editable = editable?
56
    @editable = editable?
57 57
    render :action => 'show'
58 58
  end
59

  
59 60
  
60 61
  # edit an existing page or a new one
61 62
  def edit
62
    @page = @wiki.find_or_new_page(params[:page])    
63
    return render_403 unless editable?
64
    @page.content = WikiContent.new(:page => @page) if @page.new_record?
65
    
66
    @content = @page.content_for_version(params[:version])
67
    @content.text = initial_page_content(@page) if @content.text.blank?
68
    # don't keep previous comment
69
    @content.comments = nil
70
    if request.get?
63
    # Manuel S: check if it contains a section number as parameter
64
    if params[:section]
65
      # do special treatment to edit section
66
      @page = @wiki.find_or_new_page(params[:page])
67
      return render_403 unless editable?
68
      @page.content = WikiContent.new(:page => @page) if @page.new_record?
69

  
70
      @content = @page.content_for_version(params[:version])
71
      @content.text = initial_page_content(@page) if @content.text.blank?
72
      # don't keep previous comment
73
      @content.comments = nil
74
      @section_id = params[:section]
75
      # change text here according to section
76
      @content.text = get_textsection(@content.text,@section_id.to_i)
71 77
      # To prevent StaleObjectError exception when reverting to a previous version
72 78
      @content.version = @page.content.version
73
    else
74
      if !@page.new_record? && @content.text == params[:content][:text]
79

  
80
      # Manuel S: this is for saving a section which has been edited
81
    elsif request.post? && params[:content][:section_edit].to_i != 0
82
      section_id = params[:content][:section_edit].to_i
83
      new_section_text = params[:content][:text]
84

  
85
      # get the original content from the db
86
      @page = @wiki.find_or_new_page(params[:page])
87
      return render_403 unless editable?
88
      @content = @page.content_for_version(params[:version])
89

  
90
      # don't keep previous comment
91
      @content.comments = nil
92

  
93
      text_original = @content.text
94
      modify_text_endchars(new_section_text)
95
      
96
      # replace the section
97
      rebuilded_text = text_original.sub(get_textsection(text_original, section_id), new_section_text)
98

  
99
      # create anchor so that after saving a page the browser jumps to the
100
      # edited section
101
      anchor = generate_anchor(rebuilded_text, section_id)
102
      
103
      if !@page.new_record? && @content.text == rebuilded_text
75 104
        # don't save if text wasn't changed
76
        redirect_to :action => 'index', :id => @project, :page => @page.title
105
        redirect_to :action => 'index', :id => @project, :page => @page.title, :anchor => anchor
77 106
        return
78 107
      end
79
      #@content.text = params[:content][:text]
80
      #@content.comments = params[:content][:comments]
81
      @content.attributes = params[:content]
108

  
109
      # manually set the content
110
      #@content.attributes = params[:content]
111
      @content.text = rebuilded_text
112
      @content.comments = params[:content][:comments]
113
      @content.version = params[:content][:version]
82 114
      @content.author = User.current
83 115
      # if page is new @page.save will also save content, but not if page isn't a new record
84 116
      if (@page.new_record? ? @page.save : @content.save)
85
        redirect_to :action => 'index', :id => @project, :page => @page.title
117
        redirect_to :action => 'index', :id => @project, :page => @page.title, :anchor => anchor
86 118
      end
119

  
120
      # this is invoked if no section has been chosen
121
    else
122
      # Manuel S: section id 0 means no section has been chosen, this means the whole
123
      # page is selected
124
      @section_id = 0
125

  
126
      @page = @wiki.find_or_new_page(params[:page])
127
      return render_403 unless editable?
128
      @page.content = WikiContent.new(:page => @page) if @page.new_record?
129

  
130
      @content = @page.content_for_version(params[:version])
131
      @content.text = initial_page_content(@page) if @content.text.blank?
132
      # don't keep previous comment
133
      @content.comments = nil
134
      if request.get?
135
        # To prevent StaleObjectError exception when reverting to a previous version
136
        @content.version = @page.content.version
137
      else
138
        if !@page.new_record? && @content.text == params[:content][:text]
139
          # don't save if text wasn't changed
140
          redirect_to :action => 'index', :id => @project, :page => @page.title
141
          return
142
        end
143
        # Manuel S: manually set the content
144
        #@content.attributes = params[:content]
145
        @content.text = params[:content][:text]
146
        @content.comments = params[:content][:comments]
147
        @content.version = params[:content][:version]
148
        
149
        @content.author = User.current
150
        # if page is new @page.save will also save content, but not if page isn't a new record
151
        if (@page.new_record? ? @page.save : @content.save)
152
          redirect_to :action => 'index', :id => @project, :page => @page.title
153
        end
154
      end
87 155
    end
88 156
  rescue ActiveRecord::StaleObjectError
89 157
    # Optimistic locking exception
90 158
    flash[:error] = l(:notice_locking_conflict)
91 159
  end
92
  
160

  
93 161
  # rename a page
94 162
  def rename
95 163
    return render_403 unless editable?
......
208 276
    extend helper unless self.instance_of?(helper)
209 277
    helper.instance_method(:initial_page_content).bind(self).call(page)
210 278
  end
279

  
280
  private
281

  
282
  # Manuel S: get the text of a given section
283
  def get_textsection(textile_text, section_number)
284
    current_section_number = 1
285
    offset = 0
286
    current_index = 0
287

  
288
    # the index where the found section starts
289
    start_section_index = 0
290
    # the level of the found section
291
    section_level = 0
292

  
293
    while !current_index.nil? do
294
      current_index =  textile_text.index(/h[1|2|3|4]\. ./i,offset)
295
      if !current_index.nil? then
296
        
297
        # we have found our start section
298
        if current_section_number == section_number
299
          start_section_index = current_index
300
          section_level = textile_text[current_index+1..current_index+1].to_i
301
        end
302

  
303
        # now find the next section which has a smaller OR equal level than
304
        # the found section
305
        if current_section_number > section_number
306
          current_section_level = textile_text[current_index+1..current_index+1].to_i
307
          if current_section_level <= section_level
308
            return textile_text[start_section_index..current_index-1]
309
          end
310
        end
311
        offset = current_index + 3
312
        current_section_number += 1
313
      end
314
      end
315
      # return all up to the final char if it was the last h tag
316
      return textile_text[start_section_index..textile_text.size-1]
317
    end
318

  
319
  # Manuel S: check if there is whole empty line at the end: which means
320
  # 2x enter key: one enter key is 13 (\r) + 10 (\n)
321
  def modify_text_endchars(text)
322
    nst_last_char = text[text.size-1]
323
    nst_2last_char = text[text.size-2]
324
    nst_3last_char = text[text.size-3]
325
    nst_4last_char = text[text.size-4]
326

  
327
    if (nst_last_char == 10 && nst_2last_char == 13) &&
328
        (nst_3last_char == 10 && nst_4last_char == 13)
329
      # all ok do nothing
330
    elsif nst_last_char == 10 && nst_2last_char == 13
331
      # add another one
332
      text << "\r\n"
333
    else
334
      text << "\r\n\r\n"
335
    end
336
    return text
337
  end
338

  
339
  # generate the anchor from a given section
340
  def generate_anchor(textile_text, section_number)
341
    current_section_number = 1
342
    offset = 0
343
    current_index = 0
344

  
345
    anchor = ''
346
    
347
    while !current_index.nil? do
348
      current_index =  textile_text.index(/h[1|2|3|4]\. ./i,offset)
349
      if !current_index.nil? then
350

  
351
        # we have found our start section
352
        if current_section_number == section_number
353
          # find index of next newline
354
          index_next_newline = textile_text.index("\n",current_index)
355

  
356
          if !index_next_newline.nil? then
357
            anchor = textile_text[current_index+3..index_next_newline].strip
358
            # replaces non word caracters by dashes
359
            anchor = anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
360
            return anchor
361
          else
362
            anchor = textile_text[current_index+3..textile_text.size-1].strip
363
            # replaces non word caracters by dashes
364
            anchor = anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
365
            return anchor
366
          end
367

  
368
        end
369
        offset = current_index + 3
370
        current_section_number += 1
371

  
372
      else return anchor
373
      end
374
    end
375

  
376
  end
377

  
211 378
end
app/controllers/application.rb (working copy)
17 17

  
18 18
require 'uri'
19 19
require 'cgi'
20
require 'nokogiri'
20 21

  
21 22
class ApplicationController < ActionController::Base
22 23
  layout 'base'
app/views/wiki/edit.rhtml (working copy)
2 2

  
3 3
<% form_for :content, @content, :url => {:action => 'edit', :page => @page.title}, :html => {:id => 'wiki_form'} do |f| %>
4 4
<%= f.hidden_field :version %>
5
<%= error_messages_for 'content' %>
5
<%= f.hidden_field :section_edit,:value => @section_id%>
6
  <%= error_messages_for 'content' %>
6 7

  
7 8
<p><%= f.text_area :text, :cols => 100, :rows => 25, :class => 'wiki-edit', :accesskey => accesskey(:edit) %></p>
8 9
<p><label><%= l(:field_comments) %></label><br /><%= f.text_field :comments, :size => 120 %></p>
public/stylesheets/application.css (working copy)
507 507
}
508 508
div.wiki ul.toc a:hover { color: #c61a1a; text-decoration: underline;}
509 509

  
510
a.wiki-anchor { display: none; margin-left: 6px; text-decoration: none; }
511
a.wiki-anchor:hover { color: #aaa !important; text-decoration: none; }
510
a.wiki-anchor {color:white; margin-left: 6px; text-decoration: none; }
511
/*a.wiki-anchor:hover { color: #aaa !important; text-decoration: none; }*/
512 512
h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display: inline; color: #ddd; }
513 513

  
514 514
/***** My page layout *****/
......
539 539
background-image:url('../images/close.png');
540 540
}
541 541

  
542
span.edit-section{
543
    margin-left: 5px;
544
    font-size:11px;
545
    font-weight: normal;
546
}
547

  
542 548
a.close-icon:hover {
543 549
background-image:url('../images/close_hl.png');
544 550
}
545 551

  
552

  
553

  
546 554
/***** Gantt chart *****/
547 555
.gantt_hdr {
548 556
  position:absolute;
(2-2/4)