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,58 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'instiki_errors'
|
3
|
+
|
4
|
+
class FileYard
|
5
|
+
|
6
|
+
attr_reader :files_path
|
7
|
+
|
8
|
+
def initialize(files_path, max_upload_size)
|
9
|
+
@files_path = files_path
|
10
|
+
@max_upload_size = max_upload_size
|
11
|
+
FileUtils.mkdir_p(files_path) unless File.exist?(files_path)
|
12
|
+
@files = Dir["#{files_path}/*"].collect{|path| File.basename(path) if File.file?(path) }.compact
|
13
|
+
end
|
14
|
+
|
15
|
+
def upload_file(name, io)
|
16
|
+
sanitize_file_name(name)
|
17
|
+
if io.kind_of?(Tempfile)
|
18
|
+
io.close
|
19
|
+
check_upload_size(io.size)
|
20
|
+
FileUtils.mv(io.path, file_path(name))
|
21
|
+
else
|
22
|
+
content = io.read
|
23
|
+
check_upload_size(content.length)
|
24
|
+
File.open(file_path(name), 'wb') { |f| f.write(content) }
|
25
|
+
end
|
26
|
+
# just in case, restrict read access and prohibit write access to the uploaded file
|
27
|
+
FileUtils.chmod(0440, file_path(name))
|
28
|
+
end
|
29
|
+
|
30
|
+
def files
|
31
|
+
Dir["#{files_path}/*"].collect{|path| File.basename(path) if File.file?(path)}.compact
|
32
|
+
end
|
33
|
+
|
34
|
+
def has_file?(name)
|
35
|
+
files.include?(name)
|
36
|
+
end
|
37
|
+
|
38
|
+
def file_path(name)
|
39
|
+
"#{files_path}/#{name}"
|
40
|
+
end
|
41
|
+
|
42
|
+
SANE_FILE_NAME = /[-_\.A-Za-z0-9]{1,255}/
|
43
|
+
|
44
|
+
def sanitize_file_name(name)
|
45
|
+
unless name =~ SANE_FILE_NAME
|
46
|
+
raise Instiki::ValidationError.new("Invalid file name: '#{name}'.\n" +
|
47
|
+
"Only latin characters, digits, dots, underscores and dashes are accepted.")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def check_upload_size(actual_upload_size)
|
52
|
+
if actual_upload_size > @max_upload_size.kilobytes
|
53
|
+
raise Instiki::ValidationError.new("Uploaded file size (#{actual_upload_size / 1024} " +
|
54
|
+
"kbytes) exceeds the maximum (#{@max_upload_size} kbytes) set for this wiki")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
data/app/models/page.rb
CHANGED
@@ -1,86 +1,112 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
|
7
|
-
class Page
|
8
|
-
include PageLock
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
if
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
revisions.
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
end
|
1
|
+
require 'date'
|
2
|
+
require 'page_lock'
|
3
|
+
require 'revision'
|
4
|
+
require 'wiki_words'
|
5
|
+
require 'chunks/wiki'
|
6
|
+
|
7
|
+
class Page
|
8
|
+
include PageLock
|
9
|
+
|
10
|
+
attr_reader :name, :web
|
11
|
+
attr_accessor :revisions
|
12
|
+
|
13
|
+
def initialize(web, name, content, created_at, author)
|
14
|
+
@web, @name, @revisions = web, name, []
|
15
|
+
revise(content, created_at, author)
|
16
|
+
end
|
17
|
+
|
18
|
+
def revise(content, created_at, author)
|
19
|
+
|
20
|
+
if not @revisions.empty? and content == @revisions.last.content
|
21
|
+
raise Instiki::ValidationError.new(
|
22
|
+
"You have tried to save page '#{name}' without changing its content")
|
23
|
+
end
|
24
|
+
|
25
|
+
# A user may change a page, look at it and make some more changes - several times.
|
26
|
+
# Not to record every such iteration as a new revision, if the previous revision was done
|
27
|
+
# by the same author, not more than 30 minutes ago, then update the last revision instead of
|
28
|
+
# creating a new one
|
29
|
+
if !@revisions.empty? && continous_revision?(created_at, author)
|
30
|
+
@revisions.last.created_at = created_at
|
31
|
+
@revisions.last.content = content
|
32
|
+
@revisions.last.clear_display_cache
|
33
|
+
else
|
34
|
+
@revisions << Revision.new(self, @revisions.length, content, created_at, author)
|
35
|
+
end
|
36
|
+
|
37
|
+
self.revisions.last.force_rendering
|
38
|
+
# at this point the page may not be inserted in the web yet, and therefore
|
39
|
+
# references to the page itself are rendered as "unresolved". Clearing the cache allows
|
40
|
+
# the page to re-render itself once again, hopefully _after_ it is inserted in the web
|
41
|
+
self.revisions.last.clear_display_cache
|
42
|
+
|
43
|
+
@web.refresh_pages_with_references(@name) if @revisions.length == 1
|
44
|
+
end
|
45
|
+
|
46
|
+
def rollback(revision_number, created_at, author_ip = nil)
|
47
|
+
roll_back_revision = @revisions[revision_number].dup
|
48
|
+
revise(roll_back_revision.content, created_at, Author.new(roll_back_revision.author, author_ip))
|
49
|
+
end
|
50
|
+
|
51
|
+
def revisions?
|
52
|
+
revisions.length > 1
|
53
|
+
end
|
54
|
+
|
55
|
+
def revised_on
|
56
|
+
created_on
|
57
|
+
end
|
58
|
+
|
59
|
+
def in_category?(cat)
|
60
|
+
cat.nil? || cat.empty? || categories.include?(cat)
|
61
|
+
end
|
62
|
+
|
63
|
+
def categories
|
64
|
+
display_content.find_chunks(Category).map { |cat| cat.list }.flatten
|
65
|
+
end
|
66
|
+
|
67
|
+
def authors
|
68
|
+
revisions.collect { |rev| rev.author }
|
69
|
+
end
|
70
|
+
|
71
|
+
def references
|
72
|
+
@web.select.pages_that_reference(name)
|
73
|
+
end
|
74
|
+
|
75
|
+
def linked_from
|
76
|
+
@web.select.pages_that_link_to(name)
|
77
|
+
end
|
78
|
+
|
79
|
+
def included_from
|
80
|
+
@web.select.pages_that_include(name)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns the original wiki-word name as separate words, so "MyPage" becomes "My Page".
|
84
|
+
def plain_name
|
85
|
+
@web.brackets_only ? name : WikiWords.separate(name)
|
86
|
+
end
|
87
|
+
|
88
|
+
# used to build chunk ids.
|
89
|
+
def id
|
90
|
+
@id ||= name.unpack('H*').first
|
91
|
+
end
|
92
|
+
|
93
|
+
def link(options = {})
|
94
|
+
@web.make_link(name, nil, options)
|
95
|
+
end
|
96
|
+
|
97
|
+
def author_link(options = {})
|
98
|
+
@web.make_link(author, nil, options)
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def continous_revision?(created_at, author)
|
104
|
+
@revisions.last.author == author && @revisions.last.created_at + 30.minutes > created_at
|
105
|
+
end
|
106
|
+
|
107
|
+
# Forward method calls to the current revision, so the page responds to all revision calls
|
108
|
+
def method_missing(method_symbol)
|
109
|
+
revisions.last.send(method_symbol)
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
data/app/models/page_lock.rb
CHANGED
@@ -1,24 +1,23 @@
|
|
1
|
-
# Contains all the lock methods to be mixed in with the page
|
2
|
-
module PageLock
|
3
|
-
LOCKING_PERIOD = 30 * 60 # 30 minutes
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
end
|
1
|
+
# Contains all the lock methods to be mixed in with the page
|
2
|
+
module PageLock
|
3
|
+
LOCKING_PERIOD = 30 * 60 # 30 minutes
|
4
|
+
|
5
|
+
attr_reader :locked_by
|
6
|
+
|
7
|
+
def lock(time, locked_by)
|
8
|
+
@locked_at, @locked_by = time, locked_by
|
9
|
+
end
|
10
|
+
|
11
|
+
def lock_duration(time)
|
12
|
+
((time - @locked_at) / 60).to_i unless @locked_at.nil?
|
13
|
+
end
|
14
|
+
|
15
|
+
def unlock
|
16
|
+
@locked_at = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def locked?(comparison_time)
|
20
|
+
@locked_at + LOCKING_PERIOD > comparison_time unless @locked_at.nil?
|
21
|
+
end
|
22
|
+
|
24
23
|
end
|
data/app/models/page_set.rb
CHANGED
@@ -1,64 +1,89 @@
|
|
1
|
-
# Container for a set of pages with methods for manipulation.
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
super(pages.
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
def
|
31
|
-
|
32
|
-
end
|
33
|
-
|
34
|
-
def
|
35
|
-
self.
|
36
|
-
end
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
def
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
1
|
+
# Container for a set of pages with methods for manipulation.
|
2
|
+
|
3
|
+
class PageSet < Array
|
4
|
+
attr_reader :web
|
5
|
+
|
6
|
+
def initialize(web, pages = nil, condition = nil)
|
7
|
+
@web = web
|
8
|
+
# if pages is not specified, make a list of all pages in the web
|
9
|
+
if pages.nil?
|
10
|
+
super(web.pages.values)
|
11
|
+
# otherwise use specified pages and condition to produce a set of pages
|
12
|
+
elsif condition.nil?
|
13
|
+
super(pages)
|
14
|
+
else
|
15
|
+
super(pages.select { |page| condition[page] })
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def most_recent_revision
|
20
|
+
self.map { |page| page.created_at }.max || Time.at(0)
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def by_name
|
25
|
+
PageSet.new(@web, sort_by { |page| page.name })
|
26
|
+
end
|
27
|
+
|
28
|
+
alias :sort :by_name
|
29
|
+
|
30
|
+
def by_revision
|
31
|
+
PageSet.new(@web, sort_by { |page| page.created_at }).reverse
|
32
|
+
end
|
33
|
+
|
34
|
+
def pages_that_reference(page_name)
|
35
|
+
self.select { |page| page.wiki_references.include?(page_name) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def pages_that_link_to(page_name)
|
39
|
+
self.select { |page| page.wiki_words.include?(page_name) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def pages_that_include(page_name)
|
43
|
+
self.select { |page| page.wiki_includes.include?(page_name) }
|
44
|
+
end
|
45
|
+
|
46
|
+
def pages_authored_by(author)
|
47
|
+
self.select { |page| page.authors.include?(author) }
|
48
|
+
end
|
49
|
+
|
50
|
+
def characters
|
51
|
+
self.inject(0) { |chars,page| chars += page.content.size }
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns all the orphaned pages in this page set. That is,
|
55
|
+
# pages in this set for which there is no reference in the web.
|
56
|
+
# The HomePage and author pages are always assumed to have
|
57
|
+
# references and so cannot be orphans
|
58
|
+
# Pages that refer to themselves and have no links from outside are oprphans.
|
59
|
+
def orphaned_pages
|
60
|
+
never_orphans = web.select.authors + ['HomePage']
|
61
|
+
self.select { |page|
|
62
|
+
if never_orphans.include? page.name
|
63
|
+
false
|
64
|
+
else
|
65
|
+
references = pages_that_reference(page.name)
|
66
|
+
references.empty? or references == [page]
|
67
|
+
end
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns all the wiki words in this page set for which
|
72
|
+
# there are no pages in this page set's web
|
73
|
+
def wanted_pages
|
74
|
+
wiki_words - web.select.names
|
75
|
+
end
|
76
|
+
|
77
|
+
def names
|
78
|
+
self.map { |page| page.name }
|
79
|
+
end
|
80
|
+
|
81
|
+
def wiki_words
|
82
|
+
self.inject([]) { |wiki_words, page| wiki_words << page.wiki_words }.flatten.uniq
|
83
|
+
end
|
84
|
+
|
85
|
+
def authors
|
86
|
+
self.inject([]) { |authors, page| authors << page.authors }.flatten.uniq.sort
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|