# ~*~ encoding: utf-8 ~*~ require 'cgi' require 'sinatra' require 'gollum' require 'mustache/sinatra' require 'useragent' require 'stringex' require 'gollum/frontend/views/layout' require 'gollum/frontend/views/editable' require 'gollum/frontend/views/has_page' require File.expand_path '../helpers', __FILE__ # Fix to_url class String alias :upstream_to_url :to_url # _Header => header which causes errors def to_url return nil if self.nil? upstream_to_url :exclude => ['_Header', '_Footer', '_Sidebar'] end end # Run the frontend, based on Sinatra # # There are a number of wiki options that can be set for the frontend # # Example # require 'gollum/frontend/app' # Precious::App.set(:wiki_options, { # :universal_toc => false, # } # # See the wiki.rb file for more details on wiki options module Precious class App < Sinatra::Base register Mustache::Sinatra include Precious::Helpers dir = File.dirname(File.expand_path(__FILE__)) # Detect unsupported browsers. Browser = Struct.new(:browser, :version) @@min_ua = [ Browser.new('Internet Explorer', '10.0'), Browser.new('Chrome', '7.0'), Browser.new('Firefox', '4.0'), ] def supported_useragent?(user_agent) ua = UserAgent.parse(user_agent) @@min_ua.detect {|min| ua >= min } end # We want to serve public assets for now set :public_folder, "#{dir}/public/gollum" set :static, true set :default_markup, :markdown set :mustache, { # Tell mustache where the Views constant lives :namespace => Precious, # Mustache templates live here :templates => "#{dir}/templates", # Tell mustache where the views are :views => "#{dir}/views" } # Sinatra error handling configure :development, :staging do enable :show_exceptions, :dump_errors disable :raise_errors, :clean_trace end configure :test do enable :logging, :raise_errors, :dump_errors end before do @base_url = url('/', false).chomp('/') # above will detect base_path when it's used with map in a config.ru settings.wiki_options.merge!({ :base_path => @base_url }) @css = settings.wiki_options[:css] end get '/' do page_dir = settings.wiki_options[:page_file_dir].to_s redirect clean_url(::File.join(@base_url, page_dir, 'Home')) end # path is set to name if path is nil. # if path is 'a/b' and a and b are dirs, then # path must have a trailing slash 'a/b/' or # extract_path will trim path to 'a' # name, path, version def wiki_page(name, path = nil, version = nil, exact = true) path = name if path.nil? name = extract_name(name) path = extract_path(path) path = '/' if exact && path.nil? wiki = wiki_new OpenStruct.new(:wiki => wiki, :page => wiki.paged(name, path, exact, version), :name => name, :path => path) end def wiki_new Gollum::Wiki.new(settings.gollum_path, settings.wiki_options) end get '/data/*' do if page = wiki_page(params[:splat].first).page page.raw_data end end get '/edit/*' do wikip = wiki_page(params[:splat].first) @name = wikip.name @path = wikip.path wiki = wikip.wiki if page = wikip.page if wiki.live_preview && page.format.to_s.include?('markdown') && supported_useragent?(request.user_agent) live_preview_url = '/livepreview/index.html?page=' + encodeURIComponent(@name) if @path live_preview_url << '&path=' + encodeURIComponent(@path) end redirect to(live_preview_url) else @page = page @page.version = wiki.repo.log(wiki.ref, @page.path).first raw_data = page.raw_data @content = raw_data.respond_to?(:force_encoding) ? raw_data.force_encoding('UTF-8') : raw_data mustache :edit end else redirect to("/create/#{encodeURIComponent(@name)}") end end post '/edit/*' do path = '/' + clean_url(sanitize_empty_params(params[:path])).to_s page_name = CGI.unescape(params[:page]) wiki = wiki_new page = wiki.paged(page_name, path, exact = true) return if page.nil? rename = params[:rename].to_url if params[:rename] name = rename || page.name committer = Gollum::Committer.new(wiki, commit_message) commit = {:committer => committer} update_wiki_page(wiki, page, params[:content], commit, name, params[:format]) update_wiki_page(wiki, page.header, params[:header], commit) if params[:header] update_wiki_page(wiki, page.footer, params[:footer], commit) if params[:footer] update_wiki_page(wiki, page.sidebar, params[:sidebar], commit) if params[:sidebar] committer.commit page = wiki.page(rename) if rename redirect to("/#{page.escaped_url_path}") unless page.nil? end get '/delete/*' do wikip = wiki_page(params[:splat].first) name = wikip.name wiki = wikip.wiki page = wikip.page wiki.delete_page(page, { :message => "Destroyed #{name} (#{page.format})" }) redirect to('/') end get '/create/*' do wikip = wiki_page(params[:splat].first.gsub('+', '-')) @name = wikip.name.to_url @path = wikip.path # --page-file-dir docs # /docs/Home should be created in /Home # not /docs/Home because write_page will append /docs page_dir = settings.wiki_options[:page_file_dir].to_s @path = @path.sub(page_dir, '/') if @path.start_with? page_dir page = wikip.page if page page_dir = settings.wiki_options[:page_file_dir].to_s redirect to("/#{clean_url(::File.join(page_dir, page.escaped_url_path))}") else mustache :create end end post '/create' do name = params[:page].to_url path = sanitize_empty_params(params[:path]) || '' format = params[:format].intern wiki = wiki_new begin wiki.write_page(name, format, params[:content], commit_message, path) page_dir = settings.wiki_options[:page_file_dir].to_s redirect to("/#{clean_url(::File.join(page_dir, path, name))}") rescue Gollum::DuplicatePageError => e @message = "Duplicate page: #{e.message}" mustache :error end end post '/revert/:page/*' do wikip = wiki_page(params[:page]) @path = wikip.path @name = wikip.name wiki = wikip.wiki @page = wiki.paged(@name,@path) shas = params[:splat].first.split("/") sha1 = shas.shift sha2 = shas.shift if wiki.revert_page(@page, sha1, sha2, commit_message) redirect to("/#{@page.escaped_url_path}") else sha2, sha1 = sha1, "#{sha1}^" if !sha2 @versions = [sha1, sha2] diffs = wiki.repo.diff(@versions.first, @versions.last, @page.path) @diff = diffs.first @message = "The patch does not apply." mustache :compare end end post '/preview' do wiki = wiki_new @name = params[:page] || "Preview" @page = wiki.preview_page(@name, params[:content], params[:format]) @content = @page.formatted_data @toc_content = wiki.universal_toc ? @page.toc_data : nil @mathjax = wiki.mathjax @h1_title = wiki.h1_title @editable = false mustache :page end get '/history/*' do @page = wiki_page(params[:splat].first).page @page_num = [params[:page].to_i, 1].max @versions = @page.versions :page => @page_num mustache :history end post '/compare/*' do @file = params[:splat].first @versions = params[:versions] || [] if @versions.size < 2 redirect to("/history/#{@file}") else redirect to("/compare/%s/%s...%s" % [ @file, @versions.last, @versions.first] ) end end get %r{ /compare/ # match any URL beginning with /compare/ (.+) # extract the full path (including any directories) / # match the final slash ([^.]+) # match the first SHA1 \.{2,3} # match .. or ... (.+) # match the second SHA1 }x do |path, start_version, end_version| wikip = wiki_page(path) @path = wikip.path @name = wikip.name @versions = [start_version, end_version] wiki = wikip.wiki @page = wikip.page diffs = wiki.repo.diff(@versions.first, @versions.last, @page.path) @diff = diffs.first mustache :compare end get %r{/(.+?)/([0-9a-f]{40})} do file_path = params[:captures][0] version = params[:captures][1] wikip = wiki_page(file_path, file_path, version) name = wikip.name path = wikip.path if page = wikip.page @page = page @name = name @content = page.formatted_data @editable = true mustache :page else halt 404 end end get '/search' do @query = params[:q] wiki = wiki_new # Sort wiki search results by count (desc) and then by name (asc) @results = wiki.search(@query).sort{ |a, b| (a[:count] <=> b[:count]).nonzero? || b[:name] <=> a[:name] }.reverse @name = @query mustache :search end get %r{ /pages # match any URL beginning with /pages (?: # begin an optional non-capturing group /(.+) # capture any path after the "/pages" excluding the leading slash )? # end the optional non-capturing group }x do |path| @path = extract_path(path) if path wiki_options = settings.wiki_options.merge({ :page_file_dir => @path }) wiki = Gollum::Wiki.new(settings.gollum_path, wiki_options) @results = wiki.pages @results += wiki.files if settings.wiki_options[:show_all] @ref = wiki.ref mustache :pages end get '/fileview' do wiki = wiki_new options = settings.wiki_options content = wiki.pages # if showing all files include wiki.files content += wiki.files if options[:show_all] # must pass wiki_options to FileView # --show-all and --collapse-tree can be set. @results = Gollum::FileView.new(content, options).render_files @ref = wiki.ref mustache :file_view, { :layout => false } end get '/*' do show_page_or_file(params[:splat].first) end def show_page_or_file(fullpath) name = extract_name(fullpath) path = extract_path(fullpath) || '/' wiki = wiki_new if page = wiki.paged(name, path, exact = true) @page = page @name = name @editable = true @content = page.formatted_data @toc_content = wiki.universal_toc ? @page.toc_data : nil @mathjax = wiki.mathjax @h1_title = wiki.h1_title mustache :page elsif file = wiki.file(fullpath) content_type file.mime_type file.raw_data else page_path = [path, name].compact.join('/') redirect to("/create/#{clean_url(encodeURIComponent(page_path))}") end end def update_wiki_page(wiki, page, content, commit, name = nil, format = nil) return if !page || ((!content || page.raw_data == content) && page.format == format) name ||= page.name format = (format || page.format).to_sym content ||= page.raw_data wiki.update_page(page, name, format, content.to_s, commit) end private # Options parameter to Gollum::Committer#initialize # :message - The String commit message. # :name - The String author full name. # :email - The String email address. # message is sourced from the incoming request parameters # author details are sourced from the session, to be populated by rack middleware ahead of us def commit_message commit_message = { :message => params[:message] } author_parameters = session['gollum.author'] commit_message.merge! author_parameters unless author_parameters.nil? commit_message end end end