Soks 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.txt +5 -4
- data/bin/soks-create-wiki.rb +153 -19
- data/contrib/easyprompt.rb +58 -0
- data/contrib/easyprompt_licence.txt +504 -0
- data/contrib/redcloth-2.0.11.rb +3 -1
- data/lib/authenticators.rb +18 -2
- data/lib/soks-helpers.rb +207 -157
- data/lib/soks-model.rb +131 -114
- data/lib/soks-servlet.rb +54 -35
- data/lib/soks-storage.rb +134 -0
- data/lib/soks-upgrade-0.0.2.rb +70 -0
- data/lib/soks-utils.rb +129 -19
- data/lib/soks-view.rb +136 -62
- data/lib/soks.rb +3 -1
- data/{template → templates/default}/attachment/logo.png +0 -0
- data/templates/default/attachment/logo.tiff +0 -0
- data/templates/default/attachment/newpage.js +41 -0
- data/templates/default/attachment/print_stylesheet.css +7 -0
- data/templates/default/attachment/rss.png +0 -0
- data/{template → templates/default}/attachment/stylesheet.css +44 -17
- data/templates/default/banned_titles.txt +31 -0
- data/templates/default/content/Bug%3A%20In%20a%20list%20of%20links%2C%20the%20last%20link%20is%20sometimes%20not%20linked.textile +10 -0
- data/templates/default/content/Bug%3A%20Symbols%20are%20not%20always%20correctly%20rendered%20in%20html.textile +3 -0
- data/templates/default/content/Bug%3A%20Uploads%20are%20not%20password%20protected.textile +3 -0
- data/templates/default/content/How%20to%20administrate%20this%20wiki.textile +62 -0
- data/templates/default/content/How%20to%20change%20the%20way%20this%20wiki%20looks.textile +30 -0
- data/templates/default/content/How%20to%20export%20a%20site%20from%20this%20wiki.textile +60 -0
- data/{template → templates/default}/content/How%20to%20hack%20soks.textile +3 -2
- data/{template → templates/default}/content/How%20to%20import%20a%20site%20from%20instiki.textile +1 -1
- data/templates/default/content/How%20to%20use%20this%20wiki.textile +27 -0
- data/templates/default/content/List%20of%20changes.textile +35 -0
- data/{template → templates/default}/content/Picture%20of%20a%20pair%20of%20soks.textile +0 -0
- data/{template → templates/default}/content/Soks%20Licence.textile +0 -0
- data/templates/default/content/home%20page.textile +17 -0
- data/templates/default/start.rb +94 -0
- data/templates/default/version.txt +1 -0
- data/{template → templates/default}/views/Page_content.rhtml +0 -0
- data/templates/default/views/Page_edit.rhtml +61 -0
- data/templates/default/views/Page_meta.rhtml +40 -0
- data/templates/default/views/Page_print.rhtml +6 -0
- data/templates/default/views/Page_revisions.rhtml +19 -0
- data/templates/default/views/Page_rss.rhtml +55 -0
- data/{template → templates/default}/views/Page_search_results.rhtml +1 -1
- data/templates/default/views/Page_view.rhtml +4 -0
- data/templates/default/views/UploadPage_edit.rhtml +38 -0
- data/templates/default/views/frame.rhtml +41 -0
- data/templates/default/views/messages.yaml +6 -0
- data/templates/instiki/attachment/header_backdrop.png +0 -0
- data/templates/instiki/attachment/instiki_style_sheet.css +199 -0
- data/templates/instiki/attachment/logo.tiff +0 -0
- data/templates/instiki/attachment/logotext.png +0 -0
- data/templates/instiki/attachment/newpage.js +41 -0
- data/templates/instiki/attachment/rss.png +0 -0
- data/templates/instiki/banned_titles.txt +31 -0
- data/templates/instiki/content/AutomaticSummary.textile +24 -0
- data/templates/instiki/content/How%20to%20export%20a%20site%20from%20this%20wiki.textile +60 -0
- data/templates/instiki/content/How%20to%20hack%20soks.textile +61 -0
- data/templates/instiki/content/How%20to%20import%20a%20site%20from%20instiki.textile +13 -0
- data/{template → templates/instiki}/content/Improving%20the%20style%20of%20this%20wiki.textile +2 -2
- data/templates/instiki/content/Known%20bugs.textile +8 -0
- data/templates/instiki/content/List%20of%20changes.textile +34 -0
- data/templates/instiki/content/Picture%20of%20a%20pair%20of%20soks.textile +1 -0
- data/templates/instiki/content/Pointers%20on%20adjusting%20the%20settings.textile +62 -0
- data/templates/instiki/content/Pointers%20on%20how%20to%20use%20this%20wiki.textile +27 -0
- data/templates/instiki/content/Recent%20Blog%20Entries.textile +3 -0
- data/templates/instiki/content/Recent%20Changes%20to%20This%20Site.textile +48 -0
- data/templates/instiki/content/Site%20Index.textile +16 -0
- data/templates/instiki/content/Soks%20Licence.textile +64 -0
- data/{template → templates/instiki}/content/home%20page.textile +9 -4
- data/templates/instiki/start.rb +85 -0
- data/templates/instiki/version.txt +1 -0
- data/templates/instiki/views/Page_content.rhtml +1 -0
- data/templates/instiki/views/Page_edit.rhtml +8 -0
- data/templates/instiki/views/Page_meta.rhtml +34 -0
- data/templates/instiki/views/Page_print.rhtml +6 -0
- data/templates/instiki/views/Page_revisions.rhtml +17 -0
- data/templates/instiki/views/Page_rss.rhtml +55 -0
- data/templates/instiki/views/Page_search_results.rhtml +18 -0
- data/templates/instiki/views/Page_view.rhtml +2 -0
- data/templates/instiki/views/UploadPage_edit.rhtml +16 -0
- data/templates/instiki/views/frame.rhtml +90 -0
- data/templates/instiki/views/messages.yaml +6 -0
- data/templates/rails/attachment/2colheader.css +77 -0
- data/templates/rails/attachment/basics.css +98 -0
- data/templates/rails/attachment/header_backdrop.png +0 -0
- data/templates/rails/attachment/logo.tiff +0 -0
- data/templates/rails/attachment/logotext.png +0 -0
- data/templates/rails/attachment/newpage.js +41 -0
- data/templates/rails/attachment/rss.png +0 -0
- data/templates/rails/banned_titles.txt +31 -0
- data/templates/rails/content/AutomaticSummary.textile +24 -0
- data/templates/rails/content/How%20to%20export%20a%20site%20from%20this%20wiki.textile +60 -0
- data/templates/rails/content/How%20to%20hack%20soks.textile +61 -0
- data/templates/rails/content/How%20to%20import%20a%20site%20from%20instiki.textile +13 -0
- data/templates/rails/content/Improving%20the%20style%20of%20this%20wiki.textile +30 -0
- data/templates/rails/content/Known%20bugs.textile +8 -0
- data/templates/rails/content/List%20of%20changes.textile +34 -0
- data/templates/rails/content/Picture%20of%20a%20pair%20of%20soks.textile +1 -0
- data/templates/rails/content/Pointers%20on%20adjusting%20the%20settings.textile +62 -0
- data/templates/rails/content/Pointers%20on%20how%20to%20use%20this%20wiki.textile +27 -0
- data/templates/rails/content/Recent%20Blog%20Entries.textile +3 -0
- data/templates/rails/content/Recent%20Changes%20to%20This%20Site.textile +48 -0
- data/templates/rails/content/Site%20Index.textile +16 -0
- data/templates/rails/content/Soks%20Licence.textile +64 -0
- data/templates/rails/content/home%20page.textile +23 -0
- data/templates/rails/start.rb +85 -0
- data/templates/rails/version.txt +1 -0
- data/templates/rails/views/Page_content.rhtml +1 -0
- data/templates/rails/views/Page_edit.rhtml +61 -0
- data/templates/rails/views/Page_meta.rhtml +38 -0
- data/templates/rails/views/Page_print.rhtml +6 -0
- data/templates/rails/views/Page_revisions.rhtml +19 -0
- data/templates/rails/views/Page_rss.rhtml +55 -0
- data/templates/rails/views/Page_search_results.rhtml +19 -0
- data/templates/rails/views/Page_view.rhtml +3 -0
- data/templates/rails/views/UploadPage_edit.rhtml +38 -0
- data/templates/rails/views/frame.rhtml +60 -0
- data/templates/rails/views/messages.yaml +6 -0
- metadata +122 -28
- data/template/content/How%20to%20export%20a%20site%20from%20this%20wiki.textile +0 -5
- data/template/content/Pointers%20on%20adjusting%20the%20settings.textile +0 -39
- data/template/content/Pointers%20on%20how%20to%20use%20this%20wiki.textile +0 -21
- data/template/content/Recent%20Changes%20to%20This%20Site.textile +0 -203
- data/template/start.rb +0 -74
- data/template/views/AttachmentPage_edit.rhtml +0 -36
- data/template/views/ImagePage_edit.rhtml +0 -36
- data/template/views/Page_edit.rhtml +0 -34
- data/template/views/Page_print.rhtml +0 -5
- data/template/views/Page_revisions.rhtml +0 -18
- data/template/views/Page_rss.rhtml +0 -34
- data/template/views/Page_view.rhtml +0 -3
- data/template/views/frame.rhtml +0 -34
data/lib/soks-storage.rb
ADDED
@@ -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
|
-
|
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
|
-
|
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
|
46
|
-
|
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
|
50
|
-
|
96
|
+
def attentive_watchers_for( event )
|
97
|
+
attentive_watchers[ event ] ||= []
|
51
98
|
end
|
52
99
|
|
53
|
-
def
|
54
|
-
@
|
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-
|
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
|
-
|
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
|
-
|
19
|
-
|
20
|
-
|
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.
|
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 =
|
34
|
-
SPACE_REGEX =
|
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
|
-
|
118
|
+
@internal_links_from_page = [] # NEW
|
121
119
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
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%/, '&' )
|
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, :
|
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
|
-
|
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%/, '&' )
|
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)
|
232
|
-
text.gsub!(/https:\/\/[^ \n<]+/i)
|
233
|
-
text.gsub!(/www
|
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!(/\[\[(
|
239
|
-
|
240
|
-
|
313
|
+
text.gsub!(/\[\[\s*(.*?)\s*(|=>\s*(.*?)\s*)\]\]/) do |m|
|
314
|
+
title, pagename = $1, $3
|
315
|
+
pagename ||= title
|
241
316
|
case pagename
|
242
|
-
when /^www
|
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
|
245
|
-
else
|
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,
|
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
|
-
|
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
|
-
|
269
|
-
|
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
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
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
|
-
|
430
|
+
|
344
431
|
end
|
345
432
|
|
346
433
|
def move( oldpagename, person, newpagename )
|
347
434
|
unless newpagename == oldpagename
|
348
|
-
|
349
|
-
|
350
|
-
|
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
|
-
|
442
|
+
@wiki.rollback( pagename, revision, person )
|
356
443
|
end
|
357
444
|
|
358
445
|
def delete( pagename, person )
|
359
|
-
|
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 [
|
480
|
+
return true unless $SETTINGS[:dont_frame_templates].include? view.downcase
|
407
481
|
end
|
408
482
|
|
409
483
|
def frame_erb
|