diff --git a/Gemfile b/Gemfile index 6065c02..612ab66 100644 --- a/Gemfile +++ b/Gemfile @@ -3,3 +3,5 @@ source :rubygems gem "grit", "~> 2.5.0" gem "gollum", :git => 'git://github.com/github/gollum.git' gem "RedCloth" +gem "creole" + diff --git a/app/controllers/gollum_pages_controller.rb b/app/controllers/gollum_pages_controller.rb index a65a898..3f3e71a 100644 --- a/app/controllers/gollum_pages_controller.rb +++ b/app/controllers/gollum_pages_controller.rb @@ -7,6 +7,43 @@ class GollumPagesController < ApplicationController before_filter :find_project, :find_wiki before_filter :authorize, :except => [ :preview ] + # FIXME: rid off stupid gollum prewiki wikiing + # wtf? wtF? why? + # dirty hack + class MyMarkup < Gollum::Markup + def render(no_follow = false, encoding = nil) + data = @data.dup + begin + data = GitHub::Markup.render(@name, data) + if data.nil? + raise "There was an error converting #{@name} to HTML." + end + rescue Object => e + data = %{

#{e.message}

} + end + data + end + end + + class MyGollumFile < Gollum::File + # Find a file in the given Gollum repo. + # + # name - The full String path. + # version - The String version ID to find. + # + # Returns a Gollum::File or nil if the file could not be found. + def find(name, version) + checked = name.downcase + map = @wiki.tree_map_for(version, true) + if entry = map.detect { |entry| entry.path.downcase == checked } + @path = name + @blob = entry.blob(@wiki.repo) + @version = version.is_a?(Grit::Commit) ? version : @wiki.commit_for(version) + self + end + end + end + def index redirect_to :action => :show, :id => "Home" end @@ -21,11 +58,109 @@ def show show_page(params[:id]) end + def file + ext = params[:ext] + @file_name = params[:id] + '.' + ext + dir = @project.gollum_wiki.images_directory + mime_type = Mime::Type.lookup_by_extension(ext) || 'text/plain' + + if file = @wiki.file(File.join(dir, @file_name)) + #if file = file_search(File.join(dir, @file_name)) + name = file.name + url = file.url_path + # FIXME: content-type has 'charset=utf8' why? + render :text => file.raw_data, :content_type=> mime_type + else + render :status => 404, :inline => '404 not found:dir:' + dir +',file:' + @file_name + return + end + end + + def file_search(path, version = @wiki.ref) + file = @wiki.file_class.new(@wiki) + map = @wiki.tree_map_for(version, true) + + if entry = map.detect { |entry| entry.path.downcase == path } + file.path = path + file.blob = entry.blob(@wiki.repo) + file.version = version.is_a?(Grit::Commit) ? version : @wiki.commit_for(version) + self + end + + end + + #
+ def upload + if request.get? + # render upload.html.erb + return + end + + + + @user = User.current + upload = params[:upload] + name = upload.original_filename + data = upload.read + #dir = @wiki.page_file_dir + dir = @project.gollum_wiki.images_directory + + write_file(dir, name, data) + + # FIXME:XSS + ckeditor_num = params[:CKEditorFuncNum] + script = <<-EOT + + EOT + if ckeditor_num + render :inline => script + #Rails.logger.fatal 'script rednered:' + script + return + else + flash[:notice] = name + ' uploaded' + redirect_to :action => :upload + return + end + end + + def newpage + return + end + + def write_file(dir, name, data) + + message = 'write file ' + name + commit = { :message => message, :name => @user.name, :email => @user.mail } + commiter = Gollum::Committer.new(@wiki, commit) + + path = File.join(dir, name) + path = path[1..-1] if path =~ /^\// + + commiter.index.add(path, data) + commiter.commit + @wiki.clear_cache + + # fixme: do it if not bare + #commiter.update_working_dir( ... ) + end + def edit @page_name = params[:id] @page = @wiki.page(@page_name) - @content = @page ? @page.text_data : "" - @page_format = @page ? @page.format : @project.gollum_wiki.markup_language.to_sym + + if @page + @page_format = @page.format + else + @page_format = @project.gollum_wiki.markup_language.to_sym + end + end + + def list + @pages = @wiki.pages + tree = @wiki.tree_map_for(@wiki.ref, true) + dir = @project.gollum_wiki.images_directory + entries = tree.select { |e| e.path.index(dir) == 0 } + @files = entries.map { |e| e.path } end def update @@ -36,22 +171,40 @@ def update commit = { :message => params[:page][:message], :name => @user.name, :email => @user.mail } + # zkonvertuj html -> wiki if needed + if @project.gollum_wiki.store_as_wiki + data = params[:page][:formatted_data] + data = ReverseMarkdown.parse data + else + data = params[:page][:raw_data] + end if @page - @wiki.update_page(@page, @page.name, @page_format, params[:page][:raw_data], commit) + @wiki.update_page(@page, @page.name, @page_format, data, commit) else - @wiki.write_page(@page_name, @page_format, params[:page][:raw_data], commit) + @wiki.write_page(@page_name, @page_format, data, commit) end redirect_to :action => :show, :id => @page_name end + def raw + name = params[:id] + if page = @wiki.page(name) + render :text => page.raw_data, :content_type => 'text/plain' + else + render :status => 404, :inline => '404 not found:' + name + return + end + end + private def project_repository_path return @project.gollum_wiki.git_path end + def show_page(name) if page = @wiki.page(name) @page_name = page.name @@ -87,7 +240,9 @@ def find_wiki gollum_base_path = project_gollum_pages_path @wiki = Gollum::Wiki.new(git_path, :base_path => gollum_base_path, - :page_file_dir => wiki_dir) + :page_file_dir => wiki_dir, + :file_class=>::GollumPagesController::MyGollumFile, + :markup_classes => Hash.new(::GollumPagesController::MyMarkup)) end diff --git a/app/helpers/gollum_pages_helper.rb b/app/helpers/gollum_pages_helper.rb new file mode 100644 index 0000000..ed2968b --- /dev/null +++ b/app/helpers/gollum_pages_helper.rb @@ -0,0 +1,25 @@ +require 'uri' +module GollumPagesHelper + def gollum_include_ckeditor(field_id) + return '' if ! @project.gollum_wiki.use_ckeditor; + ckeditor_include + ckeditor(field_id) + end + + def ckeditor_include + javascript_include_tag('ckeditor/ckeditor', :plugin => 'redmine_ckeditor') + end + + def ckeditor(field_id) + url = url_for( + :controller => 'gollum_pages', + :action => 'upload', + :authenticity_token => form_authenticity_token) + + javascript_tag <<-EOT + CKEDITOR.replace('#{field_id}', { + filebrowserImageUploadUrl : '#{url}', + }); + CKEDITOR.config.height='500px'; + EOT + end +end diff --git a/app/views/gollum_pages/_sidebar.html.erb b/app/views/gollum_pages/_sidebar.html.erb index 66bc13e..4af64d2 100644 --- a/app/views/gollum_pages/_sidebar.html.erb +++ b/app/views/gollum_pages/_sidebar.html.erb @@ -1,3 +1,6 @@

<%= l(:Gollum) %>

-<%= link_to l(:field_start_page), {:action => 'show'} %>
+<%= link_to l(:field_start_page), {:controller=>'gollum_pages', :action => 'show'} %>
+<%= link_to l(:upload), {:controller=>'gollum_pages', :action => 'upload'} %>
+<%= link_to l(:new_page), {:controller=>'gollum_pages', :action => 'newpage'} %>
+<%= link_to l(:list_pages), {:controller=>'gollum_pages', :action => 'list'} %>
diff --git a/app/views/gollum_pages/edit.html.erb b/app/views/gollum_pages/edit.html.erb index 5584be7..6273ba1 100644 --- a/app/views/gollum_pages/edit.html.erb +++ b/app/views/gollum_pages/edit.html.erb @@ -1,11 +1,40 @@ +<% if @project.gollum_wiki.store_as_wiki %> +
+ <%= link_to_if_authorized( + l(:button_raw), + {:action => 'raw', :id => @page_name}, + :class => 'icon icon-file') + %> +

<%= @page_name %>

+<% end %> + <%= form_for :page, :url => project_gollum_page_path(:id => @page_name), :html => { :method => "put" } do |f| -%> + <% if @project.gollum_wiki.use_ckeditor %> + <%= hidden_field_tag 'page[format]', @page_format %> + <% else %>

<%= label(:gollum_wiki, :markup_language) %> <%= select(:page, :format, options_for_select(Gollum::Page::FORMAT_NAMES.keys, @page_format), {:disabled => false}) %>

+ <% end %> + +<% if @project.gollum_wiki.store_as_wiki %> + <%= f.text_area :formatted_data, :class => "wiki-edit", :id=> 'raw_data_id', :cols => 100, :rows => 30 %> +<% else %> + <%= f.text_area :raw_data, :class => "wiki-edit", :id=> 'raw_data_id', :cols => 100, :rows => 30 %> +<% end %> + +<% if @project.gollum_wiki.use_ckeditor %> + <% if Redmine::Plugin.installed?('redmine_ckeditor') %> + <% if @project.gollum_wiki.use_ckeditor %> + <%= gollum_include_ckeditor 'raw_data_id' %> + <% end %> + <% else %> +
Alert: redmine_ckeditor plugin is not installed
+ <% end %> +<% end %> -<%= f.text_area :raw_data, :class => "wiki-edit", :cols => 100, :row => 25 %>

<%= f.submit(l(:button_save)) %> <% #preview @@ -15,5 +44,16 @@ <%= link_to(l(:label_preview), previewScript) %>

<% end %> + +
<%= javascript_include_tag "preview.js", :plugin => 'redmine_gollum' %> + + +<% content_for :header_tags do %> + 'index' %>' /> +<% end %> + +<% content_for :sidebar do %> + <%= render :partial => 'sidebar' %> +<% end %> diff --git a/app/views/gollum_pages/list.html.erb b/app/views/gollum_pages/list.html.erb new file mode 100644 index 0000000..e82e1ad --- /dev/null +++ b/app/views/gollum_pages/list.html.erb @@ -0,0 +1,16 @@ + +

<%= l(:page_list) %>

+ +

Pages

+ + +

Files

+ +<% @files.each do |filename| %> + +<% end %> +
<%= filename %>
diff --git a/app/views/gollum_pages/newpage.html.erb b/app/views/gollum_pages/newpage.html.erb new file mode 100644 index 0000000..8bca952 --- /dev/null +++ b/app/views/gollum_pages/newpage.html.erb @@ -0,0 +1,13 @@ + +

<%= l :create_new_page %>

+ +<%= form_for :page, :url => url_for(:controller=>'gollum_pages',:action=>'edit') do |f| -%> + +

+ + + +

+ +<% end %> + diff --git a/app/views/gollum_pages/show.html.erb b/app/views/gollum_pages/show.html.erb index cecfaa9..3d5f899 100644 --- a/app/views/gollum_pages/show.html.erb +++ b/app/views/gollum_pages/show.html.erb @@ -1,4 +1,11 @@
+<% if @project.gollum_wiki.store_as_wiki %> + <%= link_to_if_authorized( + l(:button_raw), + {:action => 'raw', :id => @page_name}, + :class => 'icon icon-file') + %> +<% end %> <% if @editable %> <%= link_to_if_authorized( l(:button_edit), diff --git a/app/views/gollum_pages/upload.html.erb b/app/views/gollum_pages/upload.html.erb new file mode 100644 index 0000000..76cbbf5 --- /dev/null +++ b/app/views/gollum_pages/upload.html.erb @@ -0,0 +1,9 @@ +

Upload file

+
Upload: + +<%= form_tag({:action => :upload}, :multipart => true) do %> + <%= file_field_tag "upload" %> + +<% end %> + +
diff --git a/app/views/gollum_wikis/_edit.html.erb b/app/views/gollum_wikis/_edit.html.erb index d4de8ba..f394392 100644 --- a/app/views/gollum_wikis/_edit.html.erb +++ b/app/views/gollum_wikis/_edit.html.erb @@ -3,7 +3,7 @@

<%= label(:gollum_wiki, :git_path) %> - <%= f.text_field :git_path %> + <%= f.text_field :git_path, :size => 50 %>

<%= label(:gollum_wiki, :markup_language) %> @@ -13,6 +13,21 @@ <%= label(:gollum_wiki, :page_files_directory) %> <%= f.text_field :directory %>

+

+ <%= label(:gollum_images_directory, :images_directory) %> + <%= f.text_field :images_directory %> +

+

+ <% if @gollum_wiki.use_ckeditor && ! Redmine::Plugin.installed?('redmine_ckeditor') %> + Alert: redmine_ckeditor plugin is not installed + <% end %> + <%= label(:gollum_use_ckeditor, :use_ckeditor) %> + <%= f.check_box :use_ckeditor %> +

+

+ <%= label(:gollum_edit_html_store_as_wiki, :edit_html_store_as_wiki) %> + <%= f.check_box :store_as_wiki %> +

<%= f.submit %>

diff --git a/config/locales/en.yml b/config/locales/en.yml index 65f7ab7..b66811d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -5,4 +5,12 @@ en: field_git_path: 'Git repository path' field_markup_language: 'Markup language' field_page_files_directory: 'Wiki pages directory' + button_raw: 'Raw' + label_raw: 'Raw' + upload: 'Upload' + new_page: 'New Page' + new_page_name: 'New page name' + create_new_page: 'Create new page' + list_pages: 'List Pages' + diff --git a/config/routes.rb b/config/routes.rb index 6f98a14..202fe62 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,12 @@ resources :projects do + match 'img/:id.:ext' => 'gollum_pages#file' + match 'gollum_pages/img/:id.:ext' => 'gollum_pages#file' + match 'list' => 'gollum_pages#list' + + get 'newpage' => 'gollum_pages#newpage' + post 'newpage' => 'gollum_pages#edit' + match 'upload' => 'gollum_pages#upload', :as => :upload + get 'gollum_pages/:id/raw' => 'gollum_pages#raw', :as => :raw resources :gollum_pages do collection do post 'preview' diff --git a/db/migrate/004_ckeditor_to_gollum_wikis.rb b/db/migrate/004_ckeditor_to_gollum_wikis.rb new file mode 100644 index 0000000..4384534 --- /dev/null +++ b/db/migrate/004_ckeditor_to_gollum_wikis.rb @@ -0,0 +1,13 @@ +class CkeditorToGollumWikis < ActiveRecord::Migration + def self.up + add_column :gollum_wikis, :use_ckeditor, :boolean, :default => 0 + add_column :gollum_wikis, :store_as_wiki, :boolean, :default => 0 + add_column :gollum_wikis, :images_directory, :string, :default => 'img' + end + + def self.down + remove_column :gollum_wikis, :use_ckeditor + remove_column :gollum_wikis, :store_as_wiki + remove_column :gollum_wikis, :images_directory + end +end diff --git a/init.rb b/init.rb index 7d045e8..fefd9aa 100644 --- a/init.rb +++ b/init.rb @@ -38,8 +38,8 @@ :partial => 'shared/settings' project_module :gollum_pages do - permission :view_gollum_pages, :gollum_pages => [:index, :show] - permission :add_gollum_pages, :gollum_pages => [:new, :create] + permission :view_gollum_pages, :gollum_pages => [:index, :show, :file, :raw, :list] + permission :add_gollum_pages, :gollum_pages => [:new, :create, :upload, :newpage] permission :edit_gollum_pages, :gollum_pages => [:edit, :update] permission :delete_gollum_pages, :gollum_pages => [:destroy] diff --git a/lib/reverse_markdown.rb b/lib/reverse_markdown.rb new file mode 100644 index 0000000..877ac7d --- /dev/null +++ b/lib/reverse_markdown.rb @@ -0,0 +1,23 @@ +require 'reverse_markdown/version' +require 'reverse_markdown/mapper' +require 'reverse_markdown/errors' +require 'nokogiri' + +module ReverseMarkdown + + def self.parse(input, opts={}) + root = case input + when String then Nokogiri::HTML(input).root + when Nokogiri::XML::Document then input.root + when Nokogiri::XML::Node then input + end + + ReverseMarkdown::Mapper.new(opts).process_root(root) + end + + # 2012/08/11 joe: possibly deprecate in favour of #parse + class << self + alias parse_string parse + alias parse_element parse + end +end diff --git a/lib/reverse_markdown/errors.rb b/lib/reverse_markdown/errors.rb new file mode 100644 index 0000000..411ceea --- /dev/null +++ b/lib/reverse_markdown/errors.rb @@ -0,0 +1,3 @@ +module ReverseMarkdown + class ParserError < StandardError; end +end \ No newline at end of file diff --git a/lib/reverse_markdown/mapper.rb b/lib/reverse_markdown/mapper.rb new file mode 100644 index 0000000..a2d87f0 --- /dev/null +++ b/lib/reverse_markdown/mapper.rb @@ -0,0 +1,248 @@ +module ReverseMarkdown + class Mapper + attr_accessor :raise_errors + attr_accessor :log_enabled, :log_level + attr_accessor :li_counter + attr_accessor :github_style_code_blocks + + def initialize(opts={}) + self.log_level = :info + self.log_enabled = true + self.li_counter = 0 + self.github_style_code_blocks = opts[:github_style_code_blocks] || false + end + + def process_root(element) + return '' if element.nil? + + markdown = process_element(element) # recursively process all elements to get full markdown + + # Extract github style code blocks + extractions = {} + markdown.gsub!(%r{```.*?```}m) do |match| + md5 = Digest::MD5.hexdigest(match) + extractions[md5] = match + "{code-block-extraction-#{md5}}" + end + + markdown = markdown.split("\n").map do |line| + if line.match(/^( {4}|\t)/) + line + else + "#{ ' ' if line.match(/^ {2,3}/) }" + + normalize_whitespace(line).strip + + "#{ ' ' if line.match(/ {2}$/) }" + end + end.join("\n") + + markdown.gsub!(/\n{3,}/, "\n\n") + + # Insert pre block extractions + markdown.gsub!(/\{code-block-extraction-([0-9a-f]{32})\}/){ extractions[$1] } + + markdown + end + + def process_element(element) + output = '' + if element.text? + text = process_text(element) + if output.end_with?(' ') && text.start_with?(' ') + output << text.lstrip + else + output << text + end + else + output << opening(element).to_s + + markdown_chunks = element.children.map { |child| process_element(child) } + remove_adjacent_whitespace!(markdown_chunks) + output << markdown_chunks.join + + output << ending(element).to_s + end + output + end + + private + + # removes whitespace-only chunk if the previous chunk ends with whitespace + def remove_adjacent_whitespace!(chunks) + (chunks.size - 1).downto(1).each do |i| + chunk = chunks[i] + previous_chunk = chunks[i-1] + chunks.delete_at(i) if chunk == ' ' && previous_chunk.end_with?(' ') + end + end + + def opening(element) + parent = element.parent ? element.parent.name.to_sym : nil + case element.name.to_sym + when :html, :body + "" + when :table + "\n" + when :tr + "" + when :td + "|" + when :th + "|=" + when :li +# indent = ' ' * [(element.ancestors('ol').count + element.ancestors('ul').count - 1), 0].max +# if parent == :ol +# "#{indent}#{self.li_counter += 1}. " +# li_counter +# else +# "#{indent}- " +# end + level = element.ancestors('ol').count + element.ancestors('ul').count + star = '*' + star = '#' if parent == :ol + ret = (star * level) + " " + ret + when :pre + "\n{{{\n" + when :ol + self.li_counter = 0 + "\n" + when :ul, :root#, :p + "\n" + when :div + "\n" + when :p + if element.ancestors.map(&:name).include?('blockquote') + "\n\n> " + elsif [nil, :body].include? parent + is_first = true + previous = element.previous + while is_first == true and previous do + is_first = false unless previous.content.strip == "" || previous.text? + previous = previous.previous + end + is_first ? "" : "\n\n" + else + "\n\n" + end + when :h1, :h2, :h3, :h4 # /h(\d)/ for 1.9 + element.name =~ /h(\d)/ + "\n" + ('=' * $1.to_i) + ' ' + when :em, :i + element.text.strip.empty? ? '' : '//' if (element.ancestors('em') + element.ancestors('i')).empty? + when :strong, :b + element.text.strip.empty? ? '' : '**' if (element.ancestors('strong') + element.ancestors('b')).empty? + when :blockquote + "> " + when :code + if parent == :pre + self.github_style_code_blocks ? "\n```\n" : "\n " + else + " `" + end + when :a + if !element.text.strip.empty? && element['href'] + " [" + " [[#{element['href']}|" + else + " " + end + when :img + " ![" + " " + when :hr + "\n----\n" + when :br + " \n" + else + handle_error "unknown start tag: #{element.name.to_s}" + "" + end + end + + def ending(element) + parent = element.parent ? element.parent.name.to_sym : nil + case element.name.to_sym + when :html, :body, :hr + "" + when :pre + "\n}}}\n" + when :table + "" + when :tr + "|\n" + when :td + "" + when :th + "" + when :p + "\n\n" + when :div + "\n" + when :h1, :h2, :h3, :h4 # /h(\d)/ for 1.9 + "\n" + when :em, :i + element.text.strip.empty? ? '' : '_' if (element.ancestors('em') + element.ancestors('i')).empty? + when :strong, :b + element.text.strip.empty? ? '' : '**' if (element.ancestors('strong') + element.ancestors('b')).empty? + when :li, :blockquote, :root, :ol, :ul + "\n" + when :code + if parent == :pre + self.github_style_code_blocks ? "\n```" : "\n" + else + '` ' + end + when :a + "]]" +# if !element.text.strip.empty? && element['href'] && !element['href'].start_with?('#') +# "](#{element['href']}#{title_markdown(element)})" +# else +# "" +# end + when :img + "#{element['alt']}](#{element['src']}#{title_markdown(element)}) " + "{{#{element['src']}|#{title_markdown(element)}}} " + else + handle_error "unknown end tag: #{element.name}" + "" + end + end + + def title_markdown(element) + title = element['title'] + title ? %[ "#{title}"] : '' + end + + def process_text(element) + parent = element.parent ? element.parent.name.to_sym : nil + case + when parent == :code + if self.github_style_code_blocks + element.text + else + element.text.strip.gsub(/\n/,"\n ") + end + else + normalize_whitespace(escape_text(element.text)) + end + end + + def normalize_whitespace(text) + text.tr("\n\t", ' ').squeeze(' ') + end + + def escape_text(text) + text. + gsub('*', '\*'). + gsub('_', '\_') + end + + def handle_error(message) + if raise_errors + raise ReverseMarkdown::ParserError, message + elsif log_enabled && defined?(Rails) + Rails.logger.__send__(log_level, message) + end + end + end +end diff --git a/lib/reverse_markdown/version.rb b/lib/reverse_markdown/version.rb new file mode 100644 index 0000000..c56eea0 --- /dev/null +++ b/lib/reverse_markdown/version.rb @@ -0,0 +1,3 @@ +module ReverseMarkdown + VERSION = "0.4.3" +end