instiki 0.9.2 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +165 -0
- data/README +68 -172
- data/app/controllers/admin_controller.rb +94 -0
- data/app/controllers/application.rb +131 -0
- data/app/controllers/file_controller.rb +129 -0
- data/app/controllers/wiki_controller.rb +354 -0
- data/{libraries/view_helper.rb → app/helpers/application_helper.rb} +68 -33
- data/app/models/author.rb +3 -3
- data/app/models/chunks/category.rb +33 -31
- data/app/models/chunks/chunk.rb +86 -20
- data/app/models/chunks/engines.rb +54 -38
- data/app/models/chunks/include.rb +41 -29
- data/app/models/chunks/literal.rb +31 -19
- data/app/models/chunks/nowiki.rb +28 -31
- data/app/models/chunks/test.rb +18 -18
- data/app/models/chunks/uri.rb +182 -97
- data/app/models/chunks/wiki.rb +141 -82
- data/app/models/file_yard.rb +58 -0
- data/app/models/page.rb +112 -86
- data/app/models/page_lock.rb +22 -23
- data/app/models/page_set.rb +89 -64
- data/app/models/revision.rb +123 -90
- data/app/models/web.rb +176 -89
- data/app/models/wiki_content.rb +207 -105
- data/app/models/wiki_service.rb +233 -83
- data/app/models/wiki_words.rb +23 -25
- data/app/views/{wiki/new_system.rhtml → admin/create_system.rhtml} +83 -78
- data/app/views/{wiki/new_web.rhtml → admin/create_web.rhtml} +69 -64
- data/app/views/admin/edit_web.rhtml +136 -0
- data/app/views/file/file.rhtml +19 -0
- data/app/views/file/import.rhtml +23 -0
- data/app/views/layouts/default.rhtml +85 -0
- data/app/views/markdown_help.rhtml +12 -16
- data/app/views/mixed_help.rhtml +7 -0
- data/app/views/navigation.rhtml +30 -19
- data/app/views/rdoc_help.rhtml +12 -16
- data/app/views/textile_help.rhtml +24 -28
- data/app/views/wiki/authors.rhtml +11 -13
- data/app/views/wiki/edit.rhtml +39 -31
- data/app/views/wiki/export.rhtml +12 -14
- data/app/views/wiki/feeds.rhtml +14 -10
- data/app/views/wiki/list.rhtml +64 -57
- data/app/views/wiki/locked.rhtml +23 -14
- data/app/views/wiki/login.rhtml +14 -11
- data/app/views/wiki/new.rhtml +31 -27
- data/app/views/wiki/page.rhtml +115 -81
- data/app/views/wiki/print.rhtml +14 -16
- data/app/views/wiki/published.rhtml +9 -10
- data/app/views/wiki/recently_revised.rhtml +27 -30
- data/app/views/wiki/revision.rhtml +103 -81
- data/app/views/wiki/rollback.rhtml +14 -9
- data/app/views/wiki/rss_feed.rhtml +22 -22
- data/app/views/wiki/search.rhtml +38 -15
- data/app/views/wiki/tex.rhtml +22 -22
- data/app/views/wiki/tex_web.rhtml +34 -34
- data/app/views/wiki/web_list.rhtml +18 -13
- data/app/views/wiki_words_help.rhtml +9 -8
- data/config/environment.rb +82 -0
- data/config/environments/development.rb +5 -0
- data/config/environments/production.rb +4 -0
- data/config/environments/test.rb +17 -0
- data/config/routes.rb +18 -0
- data/instiki +6 -67
- data/instiki.rb +3 -0
- data/lib/active_record_stub.rb +31 -0
- data/{libraries/diff → lib}/diff.rb +444 -475
- data/lib/instiki_errors.rb +15 -0
- data/{libraries → lib}/rdocsupport.rb +151 -155
- data/lib/redcloth_for_tex.rb +736 -0
- data/natives/osx/desktop_launcher/AppDelegate.h +18 -0
- data/natives/osx/desktop_launcher/AppDelegate.mm +109 -0
- data/natives/osx/desktop_launcher/Credits.html +16 -0
- data/natives/osx/desktop_launcher/English.lproj/InfoPlist.strings +0 -0
- data/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/classes.nib +13 -0
- data/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/info.nib +24 -0
- data/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/objects.nib +0 -0
- data/natives/osx/desktop_launcher/Info.plist +13 -0
- data/natives/osx/desktop_launcher/Instiki.xcode/project.pbxproj +592 -0
- data/natives/osx/desktop_launcher/Instiki_Prefix.pch +7 -0
- data/natives/osx/desktop_launcher/MakeDMG.sh +9 -0
- data/natives/osx/desktop_launcher/main.mm +14 -0
- data/natives/osx/desktop_launcher/version.plist +16 -0
- data/public/404.html +6 -0
- data/public/500.html +6 -0
- data/public/dispatch.rb +10 -0
- data/public/favicon.ico +0 -0
- data/public/javascripts/edit_web.js +52 -0
- data/public/javascripts/prototype.js +336 -0
- data/{app/views/static_style_sheet.rhtml → public/stylesheets/instiki.css} +221 -198
- data/script/breakpointer +4 -0
- data/script/server +93 -0
- metadata +59 -32
- data/app/controllers/wiki.rb +0 -389
- data/app/models/chunks/match.rb +0 -19
- data/app/views/bottom.rhtml +0 -4
- data/app/views/top.rhtml +0 -49
- data/app/views/wiki/edit_web.rhtml +0 -138
- data/libraries/action_controller_servlet.rb +0 -177
- data/libraries/erb.rb +0 -490
- data/libraries/madeleine_service.rb +0 -68
- data/libraries/redcloth_for_tex.rb +0 -869
- data/libraries/web_controller_server.rb +0 -81
@@ -0,0 +1,131 @@
|
|
1
|
+
# The filters added to this controller will be run for all controllers in the application.
|
2
|
+
# Likewise will all the methods added be available for all controllers.
|
3
|
+
class ApplicationController < ActionController::Base
|
4
|
+
|
5
|
+
before_filter :set_utf8_http_header, :connect_to_model
|
6
|
+
after_filter :remember_location
|
7
|
+
|
8
|
+
# For injecting a different wiki model implementation. Intended for use in tests
|
9
|
+
def self.wiki=(the_wiki)
|
10
|
+
# a global variable is used here because Rails reloads controller and model classes in the
|
11
|
+
# development environment; therefore, storing it as a class variable does not work
|
12
|
+
# class variable is, anyway, not much different from a global variable
|
13
|
+
$instiki_wiki_service = the_wiki
|
14
|
+
logger.debug("Wiki service: #{the_wiki.to_s}")
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.wiki
|
18
|
+
$instiki_wiki_service
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
def authorized?
|
24
|
+
@web.nil? ||
|
25
|
+
@web.password.nil? ||
|
26
|
+
cookies['web_address'] == @web.password ||
|
27
|
+
password_check(@params['password'])
|
28
|
+
end
|
29
|
+
|
30
|
+
def check_authorization
|
31
|
+
if in_a_web? and needs_authorization?(@action_name) and not authorized? and
|
32
|
+
redirect_to :controller => 'wiki', :action => 'login', :web => @web_name
|
33
|
+
return false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def connect_to_model
|
38
|
+
@action_name = @params['action'] || 'index'
|
39
|
+
@web_name = @params['web']
|
40
|
+
@wiki = wiki
|
41
|
+
if @web_name
|
42
|
+
@web = @wiki.webs[@web_name]
|
43
|
+
if @web.nil?
|
44
|
+
render_text "Unknown web '#{@web_name}'", '404 Not Found'
|
45
|
+
return false
|
46
|
+
end
|
47
|
+
end
|
48
|
+
@page_name = @file_name = @params['id']
|
49
|
+
@page = @wiki.read_page(@web_name, @page_name) unless @page_name.nil?
|
50
|
+
@author = cookies['author'] || 'AnonymousCoward'
|
51
|
+
check_authorization
|
52
|
+
end
|
53
|
+
|
54
|
+
FILE_TYPES = {
|
55
|
+
'.exe' => 'application/octet-stream',
|
56
|
+
'.gif' => 'image/gif',
|
57
|
+
'.jpg' => 'image/jpeg',
|
58
|
+
'.pdf' => 'application/pdf',
|
59
|
+
'.png' => 'image/png',
|
60
|
+
'.txt' => 'text/plain',
|
61
|
+
'.zip' => 'application/zip'
|
62
|
+
} unless defined? FILE_TYPES
|
63
|
+
|
64
|
+
def send_file(file, options = {})
|
65
|
+
options[:type] ||= (FILE_TYPES[File.extname(file)] || 'application/octet-stream')
|
66
|
+
super(file, options)
|
67
|
+
end
|
68
|
+
|
69
|
+
def in_a_web?
|
70
|
+
not @web_name.nil?
|
71
|
+
end
|
72
|
+
|
73
|
+
def password_check(password)
|
74
|
+
if password == @web.password
|
75
|
+
cookies['web_address'] = password
|
76
|
+
true
|
77
|
+
else
|
78
|
+
false
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def password_error(password)
|
83
|
+
if password.nil? or password.empty?
|
84
|
+
'Please enter the password.'
|
85
|
+
else
|
86
|
+
'You entered a wrong password. Please enter the right one.'
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def redirect_show(page_name = @page_name, web = @web_name)
|
91
|
+
redirect_to :web => web, :controller => 'wiki', :action => 'show',
|
92
|
+
:id => (page_name || 'HomePage')
|
93
|
+
end
|
94
|
+
|
95
|
+
@@REMEMBER_NOT = ['locked', 'save', 'back', 'file', 'pic', 'import']
|
96
|
+
def remember_location
|
97
|
+
if @response.headers['Status'] == '200 OK'
|
98
|
+
unless @@REMEMBER_NOT.include? action_name or @request.method != :get
|
99
|
+
@session[:return_to] = @request.request_uri
|
100
|
+
logger.debug("Session ##{session.object_id}: remembered URL '#{@session[:return_to]}'")
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def return_to_last_remembered
|
106
|
+
# Forget the redirect location
|
107
|
+
redirect_target, @session[:return_to] = @session[:return_to], nil
|
108
|
+
# then try to redirect to it
|
109
|
+
if redirect_target.nil?
|
110
|
+
logger.debug("Session ##{session.object_id}: no remembered redirect location, trying /")
|
111
|
+
redirect_to_url '/'
|
112
|
+
else
|
113
|
+
logger.debug("Session ##{session.object_id}: " +
|
114
|
+
"redirect to the last remembered URL #{redirect_target}")
|
115
|
+
redirect_to_url(redirect_target)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def set_utf8_http_header
|
120
|
+
@response.headers['Content-Type'] = 'text/html; charset=UTF-8'
|
121
|
+
end
|
122
|
+
|
123
|
+
def wiki
|
124
|
+
$instiki_wiki_service
|
125
|
+
end
|
126
|
+
|
127
|
+
def needs_authorization?(action)
|
128
|
+
not %w( login authenticate published rss_with_content rss_with_headlines ).include?(action)
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'application'
|
3
|
+
require 'instiki_errors'
|
4
|
+
|
5
|
+
# Controller that is responsible for serving files and pictures.
|
6
|
+
# Disabled in version 0.10
|
7
|
+
|
8
|
+
class FileController < ApplicationController
|
9
|
+
|
10
|
+
layout 'default'
|
11
|
+
|
12
|
+
before_filter :check_allow_uploads
|
13
|
+
|
14
|
+
def file
|
15
|
+
check_path
|
16
|
+
if @params['file']
|
17
|
+
# form supplied
|
18
|
+
file_yard.upload_file(@file_name, @params['file'])
|
19
|
+
flash[:info] = "File '#{@file_name}' successfully uploaded"
|
20
|
+
@web.refresh_pages_with_references(@file_name)
|
21
|
+
return_to_last_remembered
|
22
|
+
elsif file_yard.has_file?(@file_name)
|
23
|
+
send_file(file_yard.file_path(@file_name))
|
24
|
+
else
|
25
|
+
logger.debug("File not found: #{file_yard.files_path}/#{@file_name}")
|
26
|
+
# go to the template, which is a file upload form
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def cancel_upload
|
31
|
+
return_to_last_remembered
|
32
|
+
end
|
33
|
+
|
34
|
+
def pic
|
35
|
+
check_path
|
36
|
+
if @params['file']
|
37
|
+
# form supplied
|
38
|
+
file_yard.upload_file(@file_name, @params['file'])
|
39
|
+
flash[:info] = "Image '#{@file_name}' successfully uploaded"
|
40
|
+
@web.refresh_pages_with_references(@file_name)
|
41
|
+
return_to_last_remembered
|
42
|
+
elsif file_yard.has_file?(@file_name)
|
43
|
+
send_file(file_yard.file_path(@file_name))
|
44
|
+
else
|
45
|
+
logger.debug("Image not found: #{file_yard.files_path}/#{@file_name}")
|
46
|
+
render_action 'file'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def import
|
51
|
+
return if file_uploads_disabled?
|
52
|
+
|
53
|
+
check_authorization
|
54
|
+
if @params['file']
|
55
|
+
@problems = []
|
56
|
+
import_file_name = "#{@web.address}-import-#{Time.now.strftime('%Y-%m-%d-%H-%M-%S')}.zip"
|
57
|
+
file_yard.upload_file(import_file_name, @params['file'])
|
58
|
+
import_from_archive(file_yard.file_path(import_file_name))
|
59
|
+
if @problems.empty?
|
60
|
+
flash[:info] = 'Import successfully finished'
|
61
|
+
else
|
62
|
+
flash[:info] = "Import finished, but some pages were not imported:<li>" +
|
63
|
+
@problems.join('</li><li>') + '</li>'
|
64
|
+
end
|
65
|
+
return_to_last_remembered
|
66
|
+
else
|
67
|
+
# to template
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
protected
|
72
|
+
|
73
|
+
def check_allow_uploads
|
74
|
+
|
75
|
+
# TODO enable file uploads again after 0.10 release
|
76
|
+
unless RAILS_ENV == 'test'
|
77
|
+
render_text 'File uploads are not ready for general use in Instiki 0.10', '403 Forbidden'
|
78
|
+
return false
|
79
|
+
end
|
80
|
+
|
81
|
+
unless @web.allow_uploads
|
82
|
+
render_text 'File uploads are blocked by the webmaster', '403 Forbidden'
|
83
|
+
return false
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def check_path
|
91
|
+
raise Instiki::ValidationError.new("Invalid path: no file name") unless @file_name
|
92
|
+
raise Instiki::ValidationError.new("Invalid path: no web name") unless @web_name
|
93
|
+
raise Instiki::ValidationError.new("Invalid path: unknown web name") unless @web
|
94
|
+
end
|
95
|
+
|
96
|
+
def file_yard
|
97
|
+
@wiki.file_yard(@web)
|
98
|
+
end
|
99
|
+
|
100
|
+
def import_from_archive(archive)
|
101
|
+
logger.info "Importing pages from #{archive}"
|
102
|
+
zip = Zip::ZipInputStream.open(archive)
|
103
|
+
while (entry = zip.get_next_entry) do
|
104
|
+
ext_length = File.extname(entry.name).length
|
105
|
+
page_name = entry.name[0..-(ext_length + 1)]
|
106
|
+
page_content = entry.get_input_stream.read
|
107
|
+
logger.info "Processing page '#{page_name}'"
|
108
|
+
begin
|
109
|
+
existing_page = @wiki.read_page(@web.address, page_name)
|
110
|
+
if existing_page
|
111
|
+
if existing_page.content == page_content
|
112
|
+
logger.info "Page '#{page_name}' with the same content already exists. Skipping."
|
113
|
+
next
|
114
|
+
else
|
115
|
+
logger.info "Page '#{page_name}' already exists. Adding a new revision to it."
|
116
|
+
wiki.revise_page(@web.address, page_name, page_content, Time.now, @author)
|
117
|
+
end
|
118
|
+
else
|
119
|
+
wiki.write_page(@web.address, page_name, page_content, Time.now, @author)
|
120
|
+
end
|
121
|
+
rescue => e
|
122
|
+
logger.error(e)
|
123
|
+
@problems << "#{page_name} : #{e.message}"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
logger.info "Import from #{archive} finished"
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
@@ -0,0 +1,354 @@
|
|
1
|
+
require 'application'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'redcloth_for_tex'
|
4
|
+
require 'parsedate'
|
5
|
+
|
6
|
+
class WikiController < ApplicationController
|
7
|
+
|
8
|
+
layout 'default', :except => [:rss_feed, :rss_with_content, :rss_with_headlines, :tex, :export_tex, :export_html]
|
9
|
+
|
10
|
+
def index
|
11
|
+
if @web_name
|
12
|
+
redirect_show 'HomePage'
|
13
|
+
elsif not @wiki.setup?
|
14
|
+
redirect_to :controller => 'admin', :action => 'create_system'
|
15
|
+
elsif @wiki.webs.length == 1
|
16
|
+
redirect_show 'HomePage', @wiki.webs.values.first.address
|
17
|
+
else
|
18
|
+
redirect_to :action => 'web_list'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Outside a single web --------------------------------------------------------
|
23
|
+
|
24
|
+
def authenticate
|
25
|
+
if password_check(@params['password'])
|
26
|
+
redirect_show('HomePage')
|
27
|
+
else
|
28
|
+
flash[:info] = password_error(@params['password'])
|
29
|
+
redirect_to :action => 'login', :web => @web_name
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def login
|
34
|
+
# to template
|
35
|
+
end
|
36
|
+
|
37
|
+
def web_list
|
38
|
+
@webs = wiki.webs.values.sort_by { |web| web.name }
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
# Within a single web ---------------------------------------------------------
|
43
|
+
|
44
|
+
def authors
|
45
|
+
@authors = @web.select.authors
|
46
|
+
end
|
47
|
+
|
48
|
+
def export_html
|
49
|
+
export_pages_as_zip('html') do |page|
|
50
|
+
@page = page
|
51
|
+
@link_mode = :export
|
52
|
+
render_to_string 'wiki/print'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def export_markup
|
57
|
+
export_pages_as_zip(@web.markup) { |page| page.content }
|
58
|
+
end
|
59
|
+
|
60
|
+
def export_pdf
|
61
|
+
file_name = "#{@web.address}-tex-#{@web.revised_on.strftime('%Y-%m-%d-%H-%M-%S')}"
|
62
|
+
file_path = File.join(@wiki.storage_path, file_name)
|
63
|
+
|
64
|
+
export_web_to_tex "#{file_path}.tex" unless FileTest.exists? "#{file_path}.tex"
|
65
|
+
convert_tex_to_pdf "#{file_path}.tex"
|
66
|
+
send_file "#{file_path}.pdf"
|
67
|
+
end
|
68
|
+
|
69
|
+
def export_tex
|
70
|
+
file_name = "#{@web.address}-tex-#{@web.revised_on.strftime('%Y-%m-%d-%H-%M-%S')}.tex"
|
71
|
+
file_path = File.join(@wiki.storage_path, file_name)
|
72
|
+
export_web_to_tex(file_path) unless FileTest.exists?(file_path)
|
73
|
+
send_file file_path
|
74
|
+
end
|
75
|
+
|
76
|
+
def feeds
|
77
|
+
@rss_with_content_allowed = rss_with_content_allowed?
|
78
|
+
# show the template
|
79
|
+
end
|
80
|
+
|
81
|
+
def list
|
82
|
+
parse_category
|
83
|
+
@pages_by_name = @pages_in_category.by_name
|
84
|
+
@page_names_that_are_wanted = @pages_in_category.wanted_pages
|
85
|
+
@pages_that_are_orphaned = @pages_in_category.orphaned_pages
|
86
|
+
end
|
87
|
+
|
88
|
+
def recently_revised
|
89
|
+
parse_category
|
90
|
+
@pages_by_revision = @pages_in_category.by_revision
|
91
|
+
end
|
92
|
+
|
93
|
+
def rss_with_content
|
94
|
+
if rss_with_content_allowed?
|
95
|
+
render_rss(hide_description = false, *parse_rss_params)
|
96
|
+
else
|
97
|
+
render_text 'RSS feed with content for this web is blocked for security reasons. ' +
|
98
|
+
'The web is password-protected and not published', '403 Forbidden'
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def rss_with_headlines
|
103
|
+
render_rss(hide_description = true, *parse_rss_params)
|
104
|
+
end
|
105
|
+
|
106
|
+
def search
|
107
|
+
@query = @params['query']
|
108
|
+
@title_results = @web.select { |page| page.name =~ /#{@query}/i }.sort
|
109
|
+
@results = @web.select { |page| page.content =~ /#{@query}/i }.sort
|
110
|
+
all_pages_found = (@results + @title_results).uniq
|
111
|
+
if all_pages_found.size == 1
|
112
|
+
redirect_show(all_pages_found.first.name)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Within a single page --------------------------------------------------------
|
117
|
+
|
118
|
+
def cancel_edit
|
119
|
+
@page.unlock
|
120
|
+
redirect_show
|
121
|
+
end
|
122
|
+
|
123
|
+
def edit
|
124
|
+
if @page.nil?
|
125
|
+
redirect_to :action => 'index'
|
126
|
+
elsif @page.locked?(Time.now) and not @params['break_lock']
|
127
|
+
redirect_to :web => @web_name, :action => 'locked', :id => @page_name
|
128
|
+
else
|
129
|
+
@page.lock(Time.now, @author)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def locked
|
134
|
+
# to template
|
135
|
+
end
|
136
|
+
|
137
|
+
def new
|
138
|
+
# to template
|
139
|
+
end
|
140
|
+
|
141
|
+
def pdf
|
142
|
+
page = wiki.read_page(@web_name, @page_name)
|
143
|
+
safe_page_name = @page.name.gsub(/\W/, '')
|
144
|
+
file_name = "#{safe_page_name}-#{@web.address}-#{@page.created_at.strftime('%Y-%m-%d-%H-%M-%S')}"
|
145
|
+
file_path = File.join(@wiki.storage_path, file_name)
|
146
|
+
|
147
|
+
export_page_to_tex("#{file_path}.tex") unless FileTest.exists?("#{file_path}.tex")
|
148
|
+
# NB: this is _very_ slow
|
149
|
+
convert_tex_to_pdf("#{file_path}.tex")
|
150
|
+
send_file "#{file_path}.pdf"
|
151
|
+
end
|
152
|
+
|
153
|
+
def print
|
154
|
+
@link_mode ||= :show
|
155
|
+
# to template
|
156
|
+
end
|
157
|
+
|
158
|
+
def published
|
159
|
+
if @web.published
|
160
|
+
@page = wiki.read_page(@web_name, @page_name || 'HomePage')
|
161
|
+
else
|
162
|
+
redirect_show('HomePage')
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def revision
|
167
|
+
get_page_and_revision
|
168
|
+
end
|
169
|
+
|
170
|
+
def rollback
|
171
|
+
get_page_and_revision
|
172
|
+
end
|
173
|
+
|
174
|
+
def save
|
175
|
+
redirect_to :action => 'index' if @page_name.nil?
|
176
|
+
cookies['author'] = @params['author']
|
177
|
+
|
178
|
+
begin
|
179
|
+
page = @web.pages[@page_name]
|
180
|
+
if @web.pages[@page_name]
|
181
|
+
wiki.revise_page(
|
182
|
+
@web_name, @page_name, @params['content'], Time.now,
|
183
|
+
Author.new(@params['author'], remote_ip)
|
184
|
+
)
|
185
|
+
page.unlock
|
186
|
+
else
|
187
|
+
wiki.write_page(
|
188
|
+
@web_name, @page_name, @params['content'], Time.now,
|
189
|
+
Author.new(@params['author'], remote_ip)
|
190
|
+
)
|
191
|
+
end
|
192
|
+
redirect_show(@page_name)
|
193
|
+
rescue Instiki::ValidationError => e
|
194
|
+
page.unlock if defined? page
|
195
|
+
flash[:error] = e
|
196
|
+
return_to_last_remembered
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def show
|
201
|
+
if @page
|
202
|
+
begin
|
203
|
+
render_action 'page'
|
204
|
+
# TODO this rescue should differentiate between errors due to rendering and errors in
|
205
|
+
# the application itself (for application errors, it's better not to rescue the error at all)
|
206
|
+
rescue => e
|
207
|
+
logger.error e
|
208
|
+
if in_a_web?
|
209
|
+
redirect_to :web => @web_name, :action => 'edit',
|
210
|
+
:action_suffix => "#{@page_name}?msg=#{e.message}"
|
211
|
+
else
|
212
|
+
raise e
|
213
|
+
end
|
214
|
+
end
|
215
|
+
else
|
216
|
+
if not @page_name.nil? and not @page_name.empty?
|
217
|
+
redirect_to :web => @web_name, :action => 'new', :id => @page_name
|
218
|
+
else
|
219
|
+
render_text 'Page name is not specified', '404 Not Found'
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def tex
|
225
|
+
@tex_content = RedClothForTex.new(@page.content).to_tex
|
226
|
+
end
|
227
|
+
|
228
|
+
|
229
|
+
private
|
230
|
+
|
231
|
+
def convert_tex_to_pdf(tex_path)
|
232
|
+
# TODO remove earlier PDF files with the same prefix
|
233
|
+
# TODO handle gracefully situation where pdflatex is not available
|
234
|
+
begin
|
235
|
+
wd = Dir.getwd
|
236
|
+
Dir.chdir(File.dirname(tex_path))
|
237
|
+
logger.info `pdflatex --interaction=nonstopmode #{File.basename(tex_path)}`
|
238
|
+
ensure
|
239
|
+
Dir.chdir(wd)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def export_page_to_tex(file_path)
|
244
|
+
tex
|
245
|
+
File.open(file_path, 'w') { |f| f.write(render_to_string('wiki/tex')) }
|
246
|
+
end
|
247
|
+
|
248
|
+
def export_pages_as_zip(file_type, &block)
|
249
|
+
|
250
|
+
file_prefix = "#{@web.address}-#{file_type}-"
|
251
|
+
timestamp = @web.revised_on.strftime('%Y-%m-%d-%H-%M-%S')
|
252
|
+
file_path = File.join(@wiki.storage_path, file_prefix + timestamp + '.zip')
|
253
|
+
tmp_path = "#{file_path}.tmp"
|
254
|
+
|
255
|
+
Zip::ZipOutputStream.open(tmp_path) do |zip_out|
|
256
|
+
@web.select.by_name.each do |page|
|
257
|
+
zip_out.put_next_entry("#{CGI.escape(page.name)}.#{file_type}")
|
258
|
+
zip_out.puts(block.call(page))
|
259
|
+
end
|
260
|
+
# add an index file, if exporting to HTML
|
261
|
+
if file_type.to_s.downcase == 'html'
|
262
|
+
zip_out.put_next_entry 'index.html'
|
263
|
+
zip_out.puts <<-EOL
|
264
|
+
<html>
|
265
|
+
<head>
|
266
|
+
<META HTTP-EQUIV="Refresh" CONTENT="0;URL=HomePage.#{file_type}">
|
267
|
+
</head>
|
268
|
+
</html>
|
269
|
+
EOL
|
270
|
+
end
|
271
|
+
end
|
272
|
+
FileUtils.rm_rf(Dir[File.join(@wiki.storage_path, file_prefix + '*.zip')])
|
273
|
+
FileUtils.mv(tmp_path, file_path)
|
274
|
+
send_file file_path
|
275
|
+
end
|
276
|
+
|
277
|
+
def export_web_to_tex(file_path)
|
278
|
+
@tex_content = table_of_contents(@web.pages['HomePage'].content, render_tex_web)
|
279
|
+
File.open(file_path, 'w') { |f| f.write(render_to_string('wiki/tex_web')) }
|
280
|
+
end
|
281
|
+
|
282
|
+
def get_page_and_revision
|
283
|
+
@revision = @page.revisions[@params['rev'].to_i]
|
284
|
+
end
|
285
|
+
|
286
|
+
def parse_category
|
287
|
+
@categories = @web.categories
|
288
|
+
@category = @params['category']
|
289
|
+
if @categories.include?(@category)
|
290
|
+
@pages_in_category = @web.select { |page| page.in_category?(@category) }
|
291
|
+
@set_name = "category '#{@category}'"
|
292
|
+
else
|
293
|
+
@pages_in_category = PageSet.new(@web).by_name
|
294
|
+
@set_name = 'the web'
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
def parse_rss_params
|
299
|
+
if @params.include? 'limit'
|
300
|
+
limit = @params['limit'].to_i rescue nil
|
301
|
+
limit = nil if limit == 0
|
302
|
+
else
|
303
|
+
limit = 15
|
304
|
+
end
|
305
|
+
start_date = Time.local(*ParseDate::parsedate(@params['start'])) rescue nil
|
306
|
+
end_date = Time.local(*ParseDate::parsedate(@params['end'])) rescue nil
|
307
|
+
[ limit, start_date, end_date ]
|
308
|
+
end
|
309
|
+
|
310
|
+
def remote_ip
|
311
|
+
ip = @request.remote_ip
|
312
|
+
logger.info(ip)
|
313
|
+
ip
|
314
|
+
end
|
315
|
+
|
316
|
+
def render_rss(hide_description = false, limit = 15, start_date = nil, end_date = nil)
|
317
|
+
if limit && !start_date && !end_date
|
318
|
+
@pages_by_revision = @web.select.by_revision.first(limit)
|
319
|
+
else
|
320
|
+
@pages_by_revision = @web.select.by_revision
|
321
|
+
@pages_by_revision.reject! { |page| page.created_at < start_date } if start_date
|
322
|
+
@pages_by_revision.reject! { |page| page.created_at > end_date } if end_date
|
323
|
+
end
|
324
|
+
|
325
|
+
@hide_description = hide_description
|
326
|
+
@response.headers['Content-Type'] = 'text/xml'
|
327
|
+
@link_action = @web.password ? 'published' : 'show'
|
328
|
+
|
329
|
+
render 'wiki/rss_feed'
|
330
|
+
end
|
331
|
+
|
332
|
+
def render_tex_web
|
333
|
+
@web.select.by_name.inject({}) do |tex_web, page|
|
334
|
+
tex_web[page.name] = RedClothForTex.new(page.content).to_tex
|
335
|
+
tex_web
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
def render_to_string(template_name)
|
340
|
+
add_variables_to_assigns
|
341
|
+
render template_name
|
342
|
+
@performed_render = false
|
343
|
+
@template.render_file(template_name)
|
344
|
+
end
|
345
|
+
|
346
|
+
def rss_with_content_allowed?
|
347
|
+
@web.password.nil? or @web.published
|
348
|
+
end
|
349
|
+
|
350
|
+
def truncate(text, length = 30, truncate_string = '...')
|
351
|
+
if text.length > length then text[0..(length - 3)] + truncate_string else text end
|
352
|
+
end
|
353
|
+
|
354
|
+
end
|