Soks 0.0.2 → 0.0.3

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.
Files changed (132) hide show
  1. data/README.txt +5 -4
  2. data/bin/soks-create-wiki.rb +153 -19
  3. data/contrib/easyprompt.rb +58 -0
  4. data/contrib/easyprompt_licence.txt +504 -0
  5. data/contrib/redcloth-2.0.11.rb +3 -1
  6. data/lib/authenticators.rb +18 -2
  7. data/lib/soks-helpers.rb +207 -157
  8. data/lib/soks-model.rb +131 -114
  9. data/lib/soks-servlet.rb +54 -35
  10. data/lib/soks-storage.rb +134 -0
  11. data/lib/soks-upgrade-0.0.2.rb +70 -0
  12. data/lib/soks-utils.rb +129 -19
  13. data/lib/soks-view.rb +136 -62
  14. data/lib/soks.rb +3 -1
  15. data/{template → templates/default}/attachment/logo.png +0 -0
  16. data/templates/default/attachment/logo.tiff +0 -0
  17. data/templates/default/attachment/newpage.js +41 -0
  18. data/templates/default/attachment/print_stylesheet.css +7 -0
  19. data/templates/default/attachment/rss.png +0 -0
  20. data/{template → templates/default}/attachment/stylesheet.css +44 -17
  21. data/templates/default/banned_titles.txt +31 -0
  22. data/templates/default/content/Bug%3A%20In%20a%20list%20of%20links%2C%20the%20last%20link%20is%20sometimes%20not%20linked.textile +10 -0
  23. data/templates/default/content/Bug%3A%20Symbols%20are%20not%20always%20correctly%20rendered%20in%20html.textile +3 -0
  24. data/templates/default/content/Bug%3A%20Uploads%20are%20not%20password%20protected.textile +3 -0
  25. data/templates/default/content/How%20to%20administrate%20this%20wiki.textile +62 -0
  26. data/templates/default/content/How%20to%20change%20the%20way%20this%20wiki%20looks.textile +30 -0
  27. data/templates/default/content/How%20to%20export%20a%20site%20from%20this%20wiki.textile +60 -0
  28. data/{template → templates/default}/content/How%20to%20hack%20soks.textile +3 -2
  29. data/{template → templates/default}/content/How%20to%20import%20a%20site%20from%20instiki.textile +1 -1
  30. data/templates/default/content/How%20to%20use%20this%20wiki.textile +27 -0
  31. data/templates/default/content/List%20of%20changes.textile +35 -0
  32. data/{template → templates/default}/content/Picture%20of%20a%20pair%20of%20soks.textile +0 -0
  33. data/{template → templates/default}/content/Soks%20Licence.textile +0 -0
  34. data/templates/default/content/home%20page.textile +17 -0
  35. data/templates/default/start.rb +94 -0
  36. data/templates/default/version.txt +1 -0
  37. data/{template → templates/default}/views/Page_content.rhtml +0 -0
  38. data/templates/default/views/Page_edit.rhtml +61 -0
  39. data/templates/default/views/Page_meta.rhtml +40 -0
  40. data/templates/default/views/Page_print.rhtml +6 -0
  41. data/templates/default/views/Page_revisions.rhtml +19 -0
  42. data/templates/default/views/Page_rss.rhtml +55 -0
  43. data/{template → templates/default}/views/Page_search_results.rhtml +1 -1
  44. data/templates/default/views/Page_view.rhtml +4 -0
  45. data/templates/default/views/UploadPage_edit.rhtml +38 -0
  46. data/templates/default/views/frame.rhtml +41 -0
  47. data/templates/default/views/messages.yaml +6 -0
  48. data/templates/instiki/attachment/header_backdrop.png +0 -0
  49. data/templates/instiki/attachment/instiki_style_sheet.css +199 -0
  50. data/templates/instiki/attachment/logo.tiff +0 -0
  51. data/templates/instiki/attachment/logotext.png +0 -0
  52. data/templates/instiki/attachment/newpage.js +41 -0
  53. data/templates/instiki/attachment/rss.png +0 -0
  54. data/templates/instiki/banned_titles.txt +31 -0
  55. data/templates/instiki/content/AutomaticSummary.textile +24 -0
  56. data/templates/instiki/content/How%20to%20export%20a%20site%20from%20this%20wiki.textile +60 -0
  57. data/templates/instiki/content/How%20to%20hack%20soks.textile +61 -0
  58. data/templates/instiki/content/How%20to%20import%20a%20site%20from%20instiki.textile +13 -0
  59. data/{template → templates/instiki}/content/Improving%20the%20style%20of%20this%20wiki.textile +2 -2
  60. data/templates/instiki/content/Known%20bugs.textile +8 -0
  61. data/templates/instiki/content/List%20of%20changes.textile +34 -0
  62. data/templates/instiki/content/Picture%20of%20a%20pair%20of%20soks.textile +1 -0
  63. data/templates/instiki/content/Pointers%20on%20adjusting%20the%20settings.textile +62 -0
  64. data/templates/instiki/content/Pointers%20on%20how%20to%20use%20this%20wiki.textile +27 -0
  65. data/templates/instiki/content/Recent%20Blog%20Entries.textile +3 -0
  66. data/templates/instiki/content/Recent%20Changes%20to%20This%20Site.textile +48 -0
  67. data/templates/instiki/content/Site%20Index.textile +16 -0
  68. data/templates/instiki/content/Soks%20Licence.textile +64 -0
  69. data/{template → templates/instiki}/content/home%20page.textile +9 -4
  70. data/templates/instiki/start.rb +85 -0
  71. data/templates/instiki/version.txt +1 -0
  72. data/templates/instiki/views/Page_content.rhtml +1 -0
  73. data/templates/instiki/views/Page_edit.rhtml +8 -0
  74. data/templates/instiki/views/Page_meta.rhtml +34 -0
  75. data/templates/instiki/views/Page_print.rhtml +6 -0
  76. data/templates/instiki/views/Page_revisions.rhtml +17 -0
  77. data/templates/instiki/views/Page_rss.rhtml +55 -0
  78. data/templates/instiki/views/Page_search_results.rhtml +18 -0
  79. data/templates/instiki/views/Page_view.rhtml +2 -0
  80. data/templates/instiki/views/UploadPage_edit.rhtml +16 -0
  81. data/templates/instiki/views/frame.rhtml +90 -0
  82. data/templates/instiki/views/messages.yaml +6 -0
  83. data/templates/rails/attachment/2colheader.css +77 -0
  84. data/templates/rails/attachment/basics.css +98 -0
  85. data/templates/rails/attachment/header_backdrop.png +0 -0
  86. data/templates/rails/attachment/logo.tiff +0 -0
  87. data/templates/rails/attachment/logotext.png +0 -0
  88. data/templates/rails/attachment/newpage.js +41 -0
  89. data/templates/rails/attachment/rss.png +0 -0
  90. data/templates/rails/banned_titles.txt +31 -0
  91. data/templates/rails/content/AutomaticSummary.textile +24 -0
  92. data/templates/rails/content/How%20to%20export%20a%20site%20from%20this%20wiki.textile +60 -0
  93. data/templates/rails/content/How%20to%20hack%20soks.textile +61 -0
  94. data/templates/rails/content/How%20to%20import%20a%20site%20from%20instiki.textile +13 -0
  95. data/templates/rails/content/Improving%20the%20style%20of%20this%20wiki.textile +30 -0
  96. data/templates/rails/content/Known%20bugs.textile +8 -0
  97. data/templates/rails/content/List%20of%20changes.textile +34 -0
  98. data/templates/rails/content/Picture%20of%20a%20pair%20of%20soks.textile +1 -0
  99. data/templates/rails/content/Pointers%20on%20adjusting%20the%20settings.textile +62 -0
  100. data/templates/rails/content/Pointers%20on%20how%20to%20use%20this%20wiki.textile +27 -0
  101. data/templates/rails/content/Recent%20Blog%20Entries.textile +3 -0
  102. data/templates/rails/content/Recent%20Changes%20to%20This%20Site.textile +48 -0
  103. data/templates/rails/content/Site%20Index.textile +16 -0
  104. data/templates/rails/content/Soks%20Licence.textile +64 -0
  105. data/templates/rails/content/home%20page.textile +23 -0
  106. data/templates/rails/start.rb +85 -0
  107. data/templates/rails/version.txt +1 -0
  108. data/templates/rails/views/Page_content.rhtml +1 -0
  109. data/templates/rails/views/Page_edit.rhtml +61 -0
  110. data/templates/rails/views/Page_meta.rhtml +38 -0
  111. data/templates/rails/views/Page_print.rhtml +6 -0
  112. data/templates/rails/views/Page_revisions.rhtml +19 -0
  113. data/templates/rails/views/Page_rss.rhtml +55 -0
  114. data/templates/rails/views/Page_search_results.rhtml +19 -0
  115. data/templates/rails/views/Page_view.rhtml +3 -0
  116. data/templates/rails/views/UploadPage_edit.rhtml +38 -0
  117. data/templates/rails/views/frame.rhtml +60 -0
  118. data/templates/rails/views/messages.yaml +6 -0
  119. metadata +122 -28
  120. data/template/content/How%20to%20export%20a%20site%20from%20this%20wiki.textile +0 -5
  121. data/template/content/Pointers%20on%20adjusting%20the%20settings.textile +0 -39
  122. data/template/content/Pointers%20on%20how%20to%20use%20this%20wiki.textile +0 -21
  123. data/template/content/Recent%20Changes%20to%20This%20Site.textile +0 -203
  124. data/template/start.rb +0 -74
  125. data/template/views/AttachmentPage_edit.rhtml +0 -36
  126. data/template/views/ImagePage_edit.rhtml +0 -36
  127. data/template/views/Page_edit.rhtml +0 -34
  128. data/template/views/Page_print.rhtml +0 -5
  129. data/template/views/Page_revisions.rhtml +0 -18
  130. data/template/views/Page_rss.rhtml +0 -34
  131. data/template/views/Page_view.rhtml +0 -3
  132. data/template/views/frame.rhtml +0 -34
@@ -0,0 +1,134 @@
1
+ module WikiFlatFileStore
2
+
3
+ CONTENT_EXTENSION = '.textile'
4
+ REVISIONS_EXTENSION = '.yaml'
5
+ DEFAULT_AUTHOR = 'AutomaticImport'
6
+
7
+ def save( page )
8
+ add_page_to_index( page )
9
+ save_content( page )
10
+ save_revisions( page )
11
+ end
12
+
13
+ def load_all_pages
14
+ move_files_if_names_are_not_url_encoded
15
+ pages_on_disk = Dir[ File.join( @folder, "*#{CONTENT_EXTENSION}" ) ].map { |filename| page_name_for( filename )}
16
+ pages_in_memory = @pages.values.map { |page| page && page.name }
17
+ ( pages_in_memory.compact | pages_on_disk ).each do |pagename|
18
+ page = page_named( pagename )
19
+
20
+ if page && !File.exists?( filename_for_content( pagename ) )
21
+ @pages[ page.name.downcase ] = nil
22
+ notify :page_revised, page, page.revisions.last
23
+ notify :page_deleted, page, page.revisions.last
24
+ next
25
+ end
26
+ unless page
27
+ page = load_page( filename_for_content( pagename ) )
28
+ notify :page_revised, page, page.revisions.last
29
+ notify :page_created, page, page.revisions.last
30
+ next
31
+ end
32
+ if check_disk_for_updated_page pagename
33
+ page = page_named( pagename )
34
+ notify :page_revised, page, page.revisions.last
35
+ end
36
+ end
37
+ end
38
+
39
+ def load_page( filename )
40
+ page = new_page( page_name_for( filename ) )
41
+ page.revisions = load_revisions( page )
42
+ content = load_content( page )
43
+
44
+ if page_named( page.name ) # Save having to find this out again
45
+ page.inserted_into = page_named( page.name ).inserted_into
46
+ end
47
+
48
+ if page.revisions.empty? # There is a textile file but no array file
49
+ page.revise(content, DEFAULT_AUTHOR )
50
+ save_revisions( page )
51
+ elsif content_newer_than_revisions?( page ) # The textile file has been modified, but the array file has not been updated to match
52
+ page.content = reconstruct_content_from_revisions( page.revisions )
53
+ page.revise( content, DEFAULT_AUTHOR )
54
+ save_revisions( page )
55
+ else # The textile file and the array file are in sync.
56
+ page.content = content
57
+ end
58
+
59
+ add_page_to_index( page )
60
+ page
61
+ end
62
+
63
+ def load_content( page )
64
+ IO.readlines( filename_for_content( page.name ) ).join
65
+ end
66
+
67
+ def load_revisions( page )
68
+ return [] unless File.exists?( filename_for_revisions( page.name ) )
69
+ revisions = []
70
+ File.open( filename_for_revisions( page.name ) ) { |file|
71
+ YAML.each_document( file ) { |array|
72
+ revisions[ array[0] ] = Revision.new( page, *array )
73
+ }
74
+ }
75
+ revisions.each_with_index { |r,i| $stderr.puts "#{page.name} missing revision #{i}" unless r }
76
+ revisions
77
+ end
78
+
79
+ def save_content( page )
80
+ File.open(filename_for_content( page.name ), 'w' ) { |file| file.puts page.content }
81
+ end
82
+
83
+ # Appends the last revision onto the yaml file
84
+ def save_revisions( page )
85
+ $stderr.puts "Saving revisions for #{page.name}"
86
+ File.open(filename_for_revisions( page.name ), 'a' ) { |file|
87
+ YAML.dump( page.revisions.last.to_a, file )
88
+ file.puts
89
+ }
90
+ end
91
+
92
+ def check_disk_for_updated_page( pagename )
93
+ return unless $SETTINGS[:check_files_every] # We don't care about file changes
94
+ filename = filename_for_content( pagename )
95
+ return nil unless File.exists?( filename ) # File doesn't exist on disk
96
+ return load_page( filename ) unless page_named pagename # File is new on the disk, but not yet in memory
97
+ return load_page( filename ) if ( page_named(pagename).revised_on < File.ctime( filename ) ) # File is newer on disk
98
+ return nil
99
+ end
100
+
101
+ def content_newer_than_revisions?( page )
102
+ page_file = filename_for_content page.name
103
+ revision_file = filename_for_revisions page.name
104
+ File.ctime(page_file) > File.ctime(revision_file)
105
+ end
106
+
107
+ def reconstruct_content_from_revisions( revisions )
108
+ content = []
109
+ revisions.each { |revision| content = Diff::LCS.patch( content, revision.changes, :patch ) }
110
+ content.join("\n")
111
+ end
112
+
113
+ def move_files_if_names_are_not_url_encoded
114
+ Dir[ File.join( @folder, "*#{CONTENT_EXTENSION}" ) ].each do |filename|
115
+ basename = File.basename( filename, '.*')
116
+ next if basename.url_decode.url_encode == basename # All ok, so no worry
117
+ new_name = File.join( File.dirname(filename), basename.url_decode.url_encode) + File.extname( filename)
118
+ File.rename(filename, new_name )
119
+ end
120
+ end
121
+
122
+ def page_name_for( filename )
123
+ File.basename( filename, '.*').url_decode
124
+ end
125
+
126
+ def filename_for_content( pagename )
127
+ File.join( @folder, "#{pagename.url_encode}#{CONTENT_EXTENSION}" )
128
+ end
129
+
130
+ def filename_for_revisions( pagename )
131
+ File.join( @folder, "#{pagename.url_encode}#{REVISIONS_EXTENSION}" )
132
+ end
133
+ end
134
+
@@ -0,0 +1,70 @@
1
+ require 'soks-utils'
2
+ require 'yaml'
3
+
4
+ # This is the definition of Revision from v-0-0-2.
5
+ # Much smaller than the current definition. sigh.
6
+ class Revision
7
+ attr_reader :number, :changes, :created_at, :author
8
+
9
+ def initialize( number, changes, author )
10
+ @number, @changes, @author = number, changes, author
11
+ @created_at = Time.now
12
+ end
13
+
14
+ def content( page )
15
+ page.revision( @number + 1 ) ? page.revision( @number + 1 ).previous_content( page ) : page.content
16
+ end
17
+
18
+ def previous_content( page )
19
+ content( page ).split("\n").unpatch!(@changes).join("\n")
20
+ end
21
+ end
22
+
23
+ class SoksUpgrade
24
+
25
+ def load_old_revisions( filename )
26
+ File.open( filename ) { |file| return Marshal.load( file ) }
27
+ end
28
+
29
+ def save_new_revisions( old_filename, revisions )
30
+ File.open(new_filename_for_old( old_filename ), 'w' ) do |file|
31
+ revisions.each do |revision|
32
+ YAML.dump( [revision.number, revision.changes, revision.author, revision.created_at ] , file )
33
+ file.puts
34
+ end
35
+ end
36
+ end
37
+
38
+ def new_filename_for_old( old_filename )
39
+ basename = File.basename( old_filename, '.*')
40
+ new_extension = '.yaml'
41
+ File.join( File.dirname(old_filename), basename ) + new_extension
42
+ end
43
+
44
+ def upgrade_revisions( directory )
45
+ search = File.join( directory,'content', "*.marshal" )
46
+ Dir[ search ].each do |filename|
47
+ puts "Upgrading #{filename}"
48
+ save_new_revisions( filename, load_old_revisions( filename ))
49
+ File.delete filename
50
+ end
51
+ end
52
+
53
+ def upgrade_textile( filename )
54
+ textile = IO.readlines( filename ).join
55
+ textile.gsub!(/\[\[\s*(.*?)\s*(|:\s*(.*?)\s*)\]\]/) do |m|
56
+ title, page = $1, $3
57
+ page ? "[[ #{title} => #{page} ]]" : "[[ #{title} ]]"
58
+ end
59
+ File.open( filename, 'w' ) { |f| f.puts textile }
60
+ end
61
+
62
+ def upgrade_content( directory )
63
+ search = File.join( directory,'content', "*.textile" )
64
+ Dir[ search ].each do |filename|
65
+ puts "Upgrading #{filename}"
66
+ upgrade_textile( filename )
67
+ end
68
+ end
69
+
70
+ end
data/lib/soks-utils.rb CHANGED
@@ -1,32 +1,76 @@
1
+ # This is a bit like observable, but for events
2
+ module Notify
3
+
4
+ # Will notify in a separate low priority thread
5
+ def watch_for( *events , &action_block )
6
+ self.event_queue.watch_for( events, action_block )
7
+ end
8
+
9
+ # Will notify in the running high priority thread (ie will block response to user)
10
+ def watch_attentively_for( *events, &action_block )
11
+ self.event_queue.watch_attentively_for( events, action_block )
12
+ end
13
+
14
+ def notify( event, *messages)
15
+ self.event_queue.event( event, messages )
16
+ end
17
+
18
+ def event_queue
19
+ @event_queue ||= EventQueue.new
20
+ end
21
+ end
22
+
1
23
  class EventQueue
2
24
 
3
25
  def initialize
4
26
  @queue = Queue.new
5
- Thread.new do
6
- loop do
7
- check_for_events
8
- end
9
- end.priority = -1
27
+ start_thread
10
28
  end
11
29
 
12
30
  def event( event, messages )
31
+ check_thread_ok
13
32
  @queue.enq [ event, messages ]
33
+ notify_attentive_watchers( event, *messages )
14
34
  end
15
35
 
36
+ # Will call the action_block lazily
16
37
  def watch_for( events , action_block )
17
38
  events.each { |event| watchers_for(event) << action_block }
18
39
  end
19
40
 
41
+ # Will call the action_block imediately
42
+ def watch_attentively_for( events, action_block )
43
+ events.each { |event| attentive_watchers_for(event) << action_block }
44
+ end
45
+
20
46
  private
21
47
 
48
+ def check_thread_ok
49
+ start_thread unless @thread && @thread.alive?
50
+ end
51
+
52
+ def start_thread
53
+ @thread = Thread.new do
54
+ loop do
55
+ check_for_events
56
+ end
57
+ end
58
+ @thread.priority = -1
59
+ end
60
+
22
61
  def check_for_events
23
62
  event, messages = @queue.deq
24
63
  notify( event, *messages )
25
64
  end
26
65
 
27
66
  def notify( event, *messages)
28
- watchers_for( event ).each { |action_block|
29
- action_block.call(event, *messages)
67
+ watchers_for( event ).each { |action_block|
68
+ begin
69
+ action_block.call(event, *messages)
70
+ rescue StandardError => err
71
+ $stderr.puts "ERROR #{err}: #{event} - #{messages.join(' ')}"
72
+ err.backtrace.each { |s| $stderr.puts s }
73
+ end
30
74
  }
31
75
  end
32
76
 
@@ -37,31 +81,37 @@ class EventQueue
37
81
  def watchers
38
82
  @watchers ||= {}
39
83
  end
40
- end
41
-
42
- # This is a bit like observable, but for events
43
- module Notify
44
84
 
45
- def watch_for( *events , &action_block )
46
- self.event_queue.watch_for( events, action_block )
85
+ def notify_attentive_watchers( event, *messages )
86
+ attentive_watchers_for( event ).each { |action_block|
87
+ begin
88
+ action_block.call(event, *messages)
89
+ rescue StandardError => err
90
+ $stderr.puts "ERROR #{err}: #{event} - #{messages.join(' ')}"
91
+ err.backtrace.each { |s| $stderr.puts s }
92
+ end
93
+ }
47
94
  end
48
95
 
49
- def notify( event, *messages)
50
- self.event_queue.event( event, messages )
96
+ def attentive_watchers_for( event )
97
+ attentive_watchers[ event ] ||= []
51
98
  end
52
99
 
53
- def event_queue
54
- @event_queue ||= EventQueue.new
100
+ def attentive_watchers
101
+ @attentive_watchers ||= {}
55
102
  end
103
+
56
104
  end
57
105
 
106
+
107
+
58
108
  class String
59
109
  # Return the left bit of a string e.g. "String".left(2) => "St"
60
110
  def left( length ) self.slice( 0, length ) end
61
111
 
62
112
  # Encode the string so it can be used in urls (code coppied from CGI)
63
113
  def url_encode
64
- self.gsub(/([^a-zA-Z0-9_.-]+)/n) do
114
+ self.gsub(/([^a-zA-Z0-9]+)/n) do
65
115
  '%' + $1.unpack('H2' * $1.size).join('%').upcase
66
116
  end.tr(' ', '+')
67
117
  end
@@ -77,4 +127,64 @@ class String
77
127
  def first_lines( lines = 1 )
78
128
  self.split("\n")[0,lines].join("\n")
79
129
  end
80
- end
130
+
131
+ def close_unmatched_html
132
+ start_tags = self.scan(/<(\w+)[^>\/]*?(?!\/)>/)
133
+ end_tags = self.scan(/<\/(\w+)[^>\/]*?>/)
134
+ return self if start_tags.size == end_tags.size
135
+ missing_tags = start_tags - end_tags
136
+ text = self.dup
137
+ missing_tags.each do |tag|
138
+ text << "</#{tag[0]}>"
139
+ end
140
+ text
141
+ end
142
+ end
143
+
144
+ class FiniteUniqueList
145
+ include Enumerable
146
+
147
+ attr_accessor :max_size
148
+
149
+ def initialize( max_size = nil, reverse = false, sort_by = nil )
150
+ @max_size = max_size
151
+ @list = Array.new
152
+ @sort_by = sort_by
153
+ @reverse = reverse
154
+ end
155
+
156
+ def add( item )
157
+ remove( item )
158
+ @list << item
159
+ sort_items
160
+ remove_excess_items
161
+ end
162
+
163
+ def remove( item )
164
+ @list.delete( item )
165
+ end
166
+
167
+ def each
168
+ if @reverse
169
+ @list.reverse_each { |item| yield item }
170
+ else
171
+ @list.each { |item| yield item }
172
+ end
173
+ end
174
+
175
+ def empty?; @list.empty? end
176
+
177
+ private
178
+
179
+ def remove_excess_items
180
+ return unless @max_size
181
+ while @list.size > @max_size
182
+ @list.shift
183
+ end
184
+ end
185
+
186
+ def sort_items
187
+ return unless @sort_by
188
+ @list = @list.sort_by { |item| item.send( @sort_by ) }
189
+ end
190
+ end
data/lib/soks-view.rb CHANGED
@@ -15,13 +15,11 @@ class Links
15
15
  linksto = Array.new
16
16
  @links.each do | pagefrom, pagesto |
17
17
  next if pagefrom == thispage
18
- unless [ "site index", "recent changes to this site" ].include? pagefrom.name.downcase
19
- if pagesto.include? thispage
20
- linksto << pagefrom
21
- end
22
- end
18
+ next if [ "site index", "recent changes to this site" ].include? pagefrom.name.downcase
19
+ next if linksto.include? pagefrom
20
+ linksto << pagefrom if pagesto.include? thispage
23
21
  end
24
- return linksto.uniq.sort
22
+ return linksto.sort
25
23
  end
26
24
  end
27
25
 
@@ -30,8 +28,8 @@ end
30
28
  # the first thing it can, rather than skipping a shorter match to enable
31
29
  # a longer one that starts at a later point in the text
32
30
  class RollingMatch
33
- WORD_REGEX = /^\w*/
34
- SPACE_REGEX = /^\W*/
31
+ WORD_REGEX = /\w*/
32
+ SPACE_REGEX = /\W*/
35
33
  IGNORE_CASE = true
36
34
 
37
35
  def initialize
@@ -117,29 +115,32 @@ if RedCloth::VERSION == '2.0.11'
117
115
 
118
116
  @urlrefs = {}
119
117
  @shelf = []
120
- @internal_links_from_page = [] # NEW
118
+ @internal_links_from_page = [] # NEW
121
119
 
122
- insert_sub_strings text # NEW
123
-
124
- incoming_entities text
125
- ## encode_entities text
126
- ## fix_entities text
127
- clean_white_space text
128
-
129
- get_refs text
120
+ insert_sub_strings text # NEW
121
+
122
+ shelve_divs_and_spans text # NEW
123
+
124
+ clean_white_space text
130
125
 
131
126
  no_textile text
132
127
 
133
128
  pre_list = rip_offtags text # NEW wasteful to duplicate, but reduces overloading
134
129
  hide_textile_links text # NEW
135
130
  hide_textile_image_tags text # NEW
136
- inline_soks_external_link text # NEW
137
131
  inline_soks_bracketed_link text # NEW
132
+ inline_soks_external_link text # NEW
138
133
  inline_soks_automatic_link text # NEW
139
134
  @wiki.links.set_links_from( @page, @internal_links_from_page ) # NEW
140
135
  unhide_textile text # NEW
141
136
  smooth_offtags text, pre_list # NEW wasteful to duplicate
142
137
 
138
+ incoming_entities text
139
+ ## encode_entities text
140
+ ## fix_entities text
141
+
142
+ get_refs text
143
+
143
144
  inline text
144
145
 
145
146
  unless @lite
@@ -153,8 +154,12 @@ if RedCloth::VERSION == '2.0.11'
153
154
  text.gsub!( /x%x%/, '&#38;' )
154
155
  text.gsub!( /<br \/>/, "<br />\n" )
155
156
  text.strip!
157
+
158
+ clean_html text if $SETTINGS[:strict_html_removal]
159
+
156
160
  text
157
161
  end
162
+
158
163
  end
159
164
 
160
165
  if RedCloth::VERSION == '3.0.1'
@@ -173,7 +178,7 @@ if RedCloth::VERSION == '3.0.1'
173
178
  markdown_rules = [:refs_markdown, :block_markdown_setext, :block_markdown_atx, :block_markdown_rule,
174
179
  :block_markdown_bq, :block_markdown_lists,
175
180
  :inline_markdown_reflink, :inline_markdown_link]
176
- soks_rules = [ :hide_textile_links, :hide_textile_image_tags, :inline_soks_external_link, :inline_soks_bracketed_link, :inline_soks_automatic_link, :unhide_textile ]
181
+ soks_rules = [ :hide_textile_links, :hide_textile_image_tags, :inline_soks_bracketed_link, :inline_soks_external_link, :inline_soks_automatic_link, :unhide_textile ]
177
182
  @rules = rules.collect do |rule|
178
183
  case rule
179
184
  when :markdown
@@ -188,7 +193,7 @@ if RedCloth::VERSION == '3.0.1'
188
193
  end.flatten
189
194
 
190
195
  # insert sub pages
191
- insert_sub_strings text
196
+ insert_sub_strings text
192
197
 
193
198
  # standard clean up
194
199
  incoming_entities text
@@ -208,11 +213,81 @@ if RedCloth::VERSION == '3.0.1'
208
213
  text.gsub!( /<\/?notextile>/, '' )
209
214
  text.gsub!( /x%x%/, '&#38;' )
210
215
  text.strip!
216
+
217
+ clean_html text
218
+
211
219
  text
212
220
  end
213
221
  end
214
222
 
215
223
  private
224
+
225
+ def shelve_divs_and_spans( text )
226
+ text.gsub!(/<\/+(div|span).*?>/) { |m| shelve m }
227
+ end
228
+
229
+ ## Dictionary describing allowable HTML
230
+ ## tags and attributes.
231
+ BASIC_TAGS = {
232
+ 'a' => ['href', 'title'],
233
+ 'img' => ['src', 'alt', 'title'],
234
+ 'br' => [],
235
+ 'i' => nil,
236
+ 'u' => nil,
237
+ 'b' => nil,
238
+ 'pre' => nil,
239
+ 'kbd' => nil,
240
+ 'code' => ['lang'],
241
+ 'cite' => nil,
242
+ 'strong' => nil,
243
+ 'em' => nil,
244
+ 'ins' => nil,
245
+ 'sup' => nil,
246
+ 'sub' => nil,
247
+ 'del' => nil,
248
+ 'table' => [ 'class', 'style' ],
249
+ 'tr' => [ 'class', 'style' ],
250
+ 'td' => [ 'class', 'style' ],
251
+ 'th' => [ 'class', 'style' ],
252
+ 'ol' => nil,
253
+ 'ul' => nil,
254
+ 'li' => nil,
255
+ 'p' => [ 'class', 'style' ],
256
+ 'h1' => nil,
257
+ 'h2' => nil,
258
+ 'h3' => nil,
259
+ 'h4' => nil,
260
+ 'h5' => nil,
261
+ 'h6' => nil,
262
+ 'blockquote' => ['cite'],
263
+ 'div' => [ 'class', 'id', 'style' ],
264
+ 'span' => [ 'class', 'id','style'],
265
+ }
266
+
267
+ ## Method which cleans the String of HTML tags
268
+ ## and attributes outside of the allowed list.
269
+ def clean_html( text, tags = BASIC_TAGS )
270
+ text.gsub!( /<(\/*)(\w+)([^>\/]*?)( *\/*)>/ ) do
271
+ raw = $~
272
+ tag = raw[2].downcase
273
+ if tags.has_key? tag
274
+ pcs = [tag]
275
+ tags[tag].each do |prop|
276
+ ['"', "'", ''].each do |q|
277
+ q2 = ( q != '' ? q : '\s' )
278
+ if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i
279
+ pcs << "#{prop}=\"#{$1.gsub('"', '\\"')}\""
280
+ break
281
+ end
282
+ end
283
+ end if tags[tag]
284
+ "<#{raw[1]}#{pcs.join " "}#{raw[4]}>"
285
+ else
286
+ " "
287
+ end
288
+ end
289
+ text
290
+ end
216
291
 
217
292
  def insert_sub_strings( text, count = 0 )
218
293
  return text if count > 5 # Stops us getting locked into a cycle if people mess up the insert
@@ -228,21 +303,21 @@ end
228
303
  end
229
304
 
230
305
  def inline_soks_external_link( text )
231
- text.gsub!(/http:\/\/[^ \n<]+/i) { |m| link m }
232
- text.gsub!(/https:\/\/[^ \n<]+/i) { |m| link m }
233
- text.gsub!(/www.[^ \n<]*/i) { |m| link( "http://#{m}", m) }
306
+ text.gsub!(/http:\/\/[^ \n<]+/i) { |m| link m }
307
+ text.gsub!(/https:\/\/[^ \n<]+/i) { |m| link m }
308
+ text.gsub!(/www\.[^ \n<]*/i) { |m| link( "http://#{m}", m) }
234
309
  text.gsub!(/[A-Za-z0-9.]+?@[A-Za-z0-9.]+/) { |m| link( "mailto:#{m}", m) }
235
310
  end
236
311
 
237
312
  def inline_soks_bracketed_link( text )
238
- text.gsub!(/\[\[([^\]]+)\]\]/) do |m|
239
- title, *pagename = m[2..-3].split(':').map { |t| t.strip }
240
- pagename = pagename.empty? ? title : pagename.join(':')
313
+ text.gsub!(/\[\[\s*(.*?)\s*(|=>\s*(.*?)\s*)\]\]/) do |m|
314
+ title, pagename = $1, $3
315
+ pagename ||= title
241
316
  case pagename
242
- when /^www./i ; link("http://#{pagename}", title )
317
+ when /^www\./i ; link("http://#{pagename}", title )
243
318
  when /[A-Za-z0-9.]+?@[A-Za-z0-9.]+/ ; link("mailto:#{pagename}",title)
244
- when /^http/i ; link(pagename,title)
245
- else ; wiki_link( pagename, title )
319
+ when /^http:\/\//i ; link(pagename,title)
320
+ else ; wiki_link( pagename, title )
246
321
  end
247
322
  end
248
323
  end
@@ -256,17 +331,28 @@ end
256
331
  text.replace( linkedtext )
257
332
  end
258
333
 
259
- def wiki_link( pagename, title, css = nil )
334
+ def wiki_link( pagename, title, css_class = nil )
260
335
  if @wiki.exists? pagename
261
336
  @internal_links_from_page << @wiki.page( pagename )
262
- link(pagename,title, css || '' )
263
- else
264
- link(pagename,title, css || 'missing')
337
+ css_class ||= ''
265
338
  end
339
+ link(url_encode_pagename( pagename ),title, css_class || 'missing')
340
+ end
341
+
342
+ def link( url, title = url, css_class = '' )
343
+ shelve "<a href='#{url}' class='#{css_class}'>#{title}</a>"
266
344
  end
267
345
 
268
- def link( url, title = url, css = '' )
269
- shelve "<a href='#{url}' class='#{css}'>#{title}</a>"
346
+ # This makes sure punctuation can be used in the name of the page
347
+ # by url encoding anything that isn't a letter of a number.
348
+ # The complexity is to check that a command hasn't been embedded inside
349
+ # a pagename (e.g. /edit/home page ) in which case the command should not
350
+ # be encoded.
351
+ def url_encode_pagename( pagename )
352
+ if pagename =~ /\/(\w+)\/(.*)/
353
+ return "/#{$1}/#{$2.url_encode}"
354
+ end
355
+ return pagename.url_encode
270
356
  end
271
357
 
272
358
  def hide_textile_image_tags( text )
@@ -276,7 +362,6 @@ end
276
362
  text
277
363
  end
278
364
 
279
-
280
365
  def hide_textile_links( text )
281
366
  text.gsub!( LINK_RE ) do |match|
282
367
  hide_textile match
@@ -303,13 +388,13 @@ end
303
388
  end
304
389
 
305
390
  class View
306
- include Notify
307
391
  attr_reader :rollingmatch, :links
308
392
 
309
393
  def initialize( wiki, name )
310
394
  @wikiname = name
311
395
  @rollingmatch, @links, @redcloth_cache, @erb_cache = RollingMatch.new, Links.new, Hash.new, Hash.new
312
396
  @wiki = wiki
397
+ wiki.watch_attentively_for( :page_revised ) { |event,page,revision| refresh_redcloth( page ) }
313
398
  end
314
399
 
315
400
  def view( pagename, view = 'view', person = nil )
@@ -325,6 +410,7 @@ class View
325
410
 
326
411
  def find( pagename )
327
412
  return view( pagename ) if @wiki.exists?( pagename )
413
+ view = 'find'
328
414
  search_term = /#{pagename}/i
329
415
  title_results = @wiki.select { |name,page| name=~ search_term }
330
416
  text_results = @wiki.select { |name,page| page.content=~ search_term }
@@ -334,42 +420,30 @@ class View
334
420
  end
335
421
 
336
422
  def revise( pagename, content, person, newpagename = pagename )
337
- if @wiki.exists? pagename
338
- unless newpagename == pagename
339
- mutate( pagename ) { @wiki.revise( pagename, "Content moved to [[#{newpagename}]]", person ) }
340
- mutate( newpagename ) { @wiki.revise( newpagename, "Content moved from [[#{pagename}]]", person ) }
341
- end
423
+ if @wiki.exists?( pagename ) && (newpagename != pagename)
424
+ @wiki.revise( pagename, "#{$MESSAGES[:content_moved_to]} [[#{newpagename}]]", person )
425
+ @wiki.revise( newpagename, "#{$MESSAGES[:content_moved_from]} [[#{pagename}]]", 'AutomaticPageMover' )
426
+ @wiki.revise( newpagename, content, person )
427
+ else
428
+ @wiki.revise( newpagename, content, person )
342
429
  end
343
- mutate( newpagename ) { @wiki.revise( newpagename, content, person ) }
430
+
344
431
  end
345
432
 
346
433
  def move( oldpagename, person, newpagename )
347
434
  unless newpagename == oldpagename
348
- mutate( newpagename ) { @wiki.revise( newpagename, "Content moved from [[#{pagename}]]", person ) }
349
- mutate( newpagename ) { @wiki.revise( newpagename, @wiki.page( oldpagename ).content, person ) }
350
- mutate( oldpagename ) { @wiki.revise( oldpagename, "Content moved to [[#{newpagename}]]", person ) }
435
+ @wiki.revise( newpagename, "#{$MESSAGES[:content_moved_from]} [[#{pagename}]]", 'AutomaticPageMover')
436
+ @wiki.revise( newpagename, @wiki.page( oldpagename ).content, person)
437
+ @wiki.revise( oldpagename, "#{$MESSAGES[:content_moved_to]} [[#{newpagename}]]", person)
351
438
  end
352
439
  end
353
440
 
354
441
  def rollback( pagename, revision, person )
355
- mutate( pagename ) { @wiki.rollback( pagename, revision, person ) }
442
+ @wiki.rollback( pagename, revision, person )
356
443
  end
357
444
 
358
445
  def delete( pagename, person )
359
- mutate( pagename ) { @wiki.revise( pagename, 'Page deleted', person ) }
360
- end
361
-
362
- def mutate( pagename )
363
- didexist = @wiki.exists? pagename
364
- yield
365
- page = @wiki.page( pagename )
366
- clear_redcloth_cache( page )
367
- notify :page_revised, page, page.revisions.last
368
- if page.deleted?
369
- notify :page_deleted, page
370
- elsif !didexist
371
- notify :page_created, page
372
- end
446
+ @wiki.revise( pagename, $MESSAGES[:page_deleted], person )
373
447
  end
374
448
 
375
449
  def refresh_redcloth( page )
@@ -403,7 +477,7 @@ class View
403
477
  def path_for( klass, view ) "#{$SETTINGS[:root_directory]}/views/#{klass}_#{view}.rhtml" end
404
478
 
405
479
  def should_frame?( view )
406
- return true unless ['print','rss'].include? view.downcase
480
+ return true unless $SETTINGS[:dont_frame_templates].include? view.downcase
407
481
  end
408
482
 
409
483
  def frame_erb