instiki 0.9.2
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/README +172 -0
- data/app/controllers/wiki.rb +389 -0
- data/app/models/author.rb +4 -0
- data/app/models/chunks/category.rb +31 -0
- data/app/models/chunks/chunk.rb +20 -0
- data/app/models/chunks/engines.rb +38 -0
- data/app/models/chunks/include.rb +29 -0
- data/app/models/chunks/literal.rb +19 -0
- data/app/models/chunks/match.rb +19 -0
- data/app/models/chunks/nowiki.rb +31 -0
- data/app/models/chunks/test.rb +18 -0
- data/app/models/chunks/uri.rb +97 -0
- data/app/models/chunks/wiki.rb +82 -0
- data/app/models/page.rb +86 -0
- data/app/models/page_lock.rb +24 -0
- data/app/models/page_set.rb +64 -0
- data/app/models/revision.rb +90 -0
- data/app/models/web.rb +89 -0
- data/app/models/wiki_content.rb +105 -0
- data/app/models/wiki_service.rb +83 -0
- data/app/models/wiki_words.rb +25 -0
- data/app/views/bottom.rhtml +4 -0
- data/app/views/markdown_help.rhtml +16 -0
- data/app/views/navigation.rhtml +19 -0
- data/app/views/rdoc_help.rhtml +16 -0
- data/app/views/static_style_sheet.rhtml +199 -0
- data/app/views/textile_help.rhtml +28 -0
- data/app/views/top.rhtml +49 -0
- data/app/views/wiki/authors.rhtml +13 -0
- data/app/views/wiki/edit.rhtml +31 -0
- data/app/views/wiki/edit_web.rhtml +138 -0
- data/app/views/wiki/export.rhtml +14 -0
- data/app/views/wiki/feeds.rhtml +10 -0
- data/app/views/wiki/list.rhtml +57 -0
- data/app/views/wiki/locked.rhtml +14 -0
- data/app/views/wiki/login.rhtml +11 -0
- data/app/views/wiki/new.rhtml +27 -0
- data/app/views/wiki/new_system.rhtml +78 -0
- data/app/views/wiki/new_web.rhtml +64 -0
- data/app/views/wiki/page.rhtml +81 -0
- data/app/views/wiki/print.rhtml +16 -0
- data/app/views/wiki/published.rhtml +10 -0
- data/app/views/wiki/recently_revised.rhtml +30 -0
- data/app/views/wiki/revision.rhtml +81 -0
- data/app/views/wiki/rollback.rhtml +31 -0
- data/app/views/wiki/rss_feed.rhtml +22 -0
- data/app/views/wiki/search.rhtml +15 -0
- data/app/views/wiki/tex.rhtml +23 -0
- data/app/views/wiki/tex_web.rhtml +35 -0
- data/app/views/wiki/web_list.rhtml +13 -0
- data/app/views/wiki_words_help.rhtml +8 -0
- data/instiki +67 -0
- data/libraries/action_controller_servlet.rb +177 -0
- data/libraries/diff/diff.rb +475 -0
- data/libraries/erb.rb +490 -0
- data/libraries/madeleine_service.rb +68 -0
- data/libraries/rdocsupport.rb +156 -0
- data/libraries/redcloth_for_tex.rb +869 -0
- data/libraries/view_helper.rb +33 -0
- data/libraries/web_controller_server.rb +81 -0
- metadata +142 -0
@@ -0,0 +1,24 @@
|
|
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
|
+
def lock(time, locked_by)
|
6
|
+
@locked_at, @locked_by = time, locked_by
|
7
|
+
end
|
8
|
+
|
9
|
+
def lock_duration(time)
|
10
|
+
((time - @locked_at) / 60).to_i unless @locked_at.nil?
|
11
|
+
end
|
12
|
+
|
13
|
+
def unlock
|
14
|
+
@locked_at = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def locked?(comparison_time)
|
18
|
+
@locked_at + LOCKING_PERIOD > comparison_time unless @locked_at.nil?
|
19
|
+
end
|
20
|
+
|
21
|
+
def locked_by_link
|
22
|
+
web.make_link(@locked_by)
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# Container for a set of pages with methods for manipulation.
|
2
|
+
class PageSet < Array
|
3
|
+
attr_reader :web
|
4
|
+
|
5
|
+
def initialize(web, pages, accept_proc)
|
6
|
+
@web = web
|
7
|
+
if accept_proc.nil? then
|
8
|
+
super(pages)
|
9
|
+
else
|
10
|
+
super(pages.select { |page| accept_proc[page] })
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def most_recent_revision
|
15
|
+
self.sort_by { |page| [page.created_at] }.reverse.first.created_at
|
16
|
+
end
|
17
|
+
|
18
|
+
def by_name
|
19
|
+
self.sort_by { |page| [page.name] }
|
20
|
+
end
|
21
|
+
|
22
|
+
def by_revision
|
23
|
+
self.sort_by { |page| [page.created_at] }.reverse
|
24
|
+
end
|
25
|
+
|
26
|
+
def pages_that_reference(page_name)
|
27
|
+
self.select { |page| page.wiki_words.include?(page_name) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def pages_authored_by(author)
|
31
|
+
self.select { |page| page.authors.include?(author) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def characters
|
35
|
+
self.inject(0) { |chars,page| chars += page.content.size }
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns all the orphaned pages in this page set. That is,
|
39
|
+
# pages in this set for which there is no reference in the web.
|
40
|
+
# The HomePage and author pages are always assumed to have
|
41
|
+
# references and so cannot be orphans
|
42
|
+
def orphaned_pages
|
43
|
+
references = web.select.wiki_words + ["HomePage"] + web.select.authors
|
44
|
+
self.reject { |page| references.include?(page.name) }
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns all the wiki words in this page set for which
|
48
|
+
# there are no pages in this page set's web
|
49
|
+
def wanted_pages
|
50
|
+
wiki_words - web.select.names
|
51
|
+
end
|
52
|
+
|
53
|
+
def names
|
54
|
+
self.map { |page| page.name }
|
55
|
+
end
|
56
|
+
|
57
|
+
def wiki_words
|
58
|
+
self.inject([]) { |wiki_words, page| wiki_words << page.wiki_words }.flatten.uniq
|
59
|
+
end
|
60
|
+
|
61
|
+
def authors
|
62
|
+
self.inject([]) { |authors, page| authors << page.authors }.flatten.uniq.sort
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
$: << File.dirname(__FILE__) + "../../libraries"
|
2
|
+
|
3
|
+
require "diff/diff"
|
4
|
+
|
5
|
+
require "wiki_content"
|
6
|
+
require "chunks/wiki"
|
7
|
+
|
8
|
+
require "date"
|
9
|
+
require "author"
|
10
|
+
require "page"
|
11
|
+
|
12
|
+
class Revision
|
13
|
+
attr_accessor :page, :number, :content, :created_at, :author
|
14
|
+
|
15
|
+
def initialize(page, number, content, created_at, author)
|
16
|
+
@page, @number, @created_at, @author = page, number, created_at, author
|
17
|
+
self.content = content
|
18
|
+
end
|
19
|
+
|
20
|
+
# Ensure that the wiki content is parsed when ever it is updated.
|
21
|
+
def content=(content)
|
22
|
+
@content = content
|
23
|
+
end
|
24
|
+
|
25
|
+
def created_on
|
26
|
+
Date.new(@created_at.year, @created_at.mon, @created_at.day)
|
27
|
+
end
|
28
|
+
|
29
|
+
def pretty_created_at
|
30
|
+
# Must use DateTime because Time doesn't support %e on at least some platforms
|
31
|
+
DateTime.new(
|
32
|
+
@created_at.year, @created_at.mon, @created_at.day, @created_at.hour, @created_at.min
|
33
|
+
).strftime "%B %e, %Y %H:%M"
|
34
|
+
end
|
35
|
+
|
36
|
+
def next_revision
|
37
|
+
page.revisions[number + 1]
|
38
|
+
end
|
39
|
+
|
40
|
+
def previous_revision
|
41
|
+
number - 1 >= 0 && page.revisions[number - 1]
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns an array of all the WikiWords present in the content of this revision.
|
45
|
+
def wiki_words
|
46
|
+
unless @wiki_words_cache
|
47
|
+
wiki_chunks = display_content.find_chunks(WikiChunk::WikiLink)
|
48
|
+
@wiki_words_cache = wiki_chunks.map { |c| ( c.escaped_text ? nil : c.page_name ) }.compact.uniq
|
49
|
+
end
|
50
|
+
@wiki_words_cache
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns an array of all the WikiWords present in the content of this revision.
|
54
|
+
# that already exists as a page in the web.
|
55
|
+
def existing_pages
|
56
|
+
wiki_words.select { |wiki_word| page.web.pages[wiki_word] }
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns an array of all the WikiWords present in the content of this revision
|
60
|
+
# that *doesn't* already exists as a page in the web.
|
61
|
+
def unexisting_pages
|
62
|
+
wiki_words - existing_pages
|
63
|
+
end
|
64
|
+
|
65
|
+
# Explicit check for new type of display cache with find_chunks method.
|
66
|
+
# Ensures new version works with older snapshots.
|
67
|
+
def display_content
|
68
|
+
unless @display_cache && @display_cache.respond_to?(:find_chunks)
|
69
|
+
@display_cache = WikiContent.new(self)
|
70
|
+
end
|
71
|
+
@display_cache
|
72
|
+
end
|
73
|
+
|
74
|
+
def display_diff
|
75
|
+
previous_revision ? HTMLDiff.diff(previous_revision.display_content, display_content) : display_content
|
76
|
+
end
|
77
|
+
|
78
|
+
def clear_display_cache
|
79
|
+
@display_cache = @published_cache = @wiki_words_cache = nil
|
80
|
+
end
|
81
|
+
|
82
|
+
def display_published
|
83
|
+
@published_cache = WikiContent.new(self, {:mode => :publish}) if @published_cache.nil?
|
84
|
+
@published_cache
|
85
|
+
end
|
86
|
+
|
87
|
+
def display_content_for_export
|
88
|
+
WikiContent.new(self, {:mode => :export} )
|
89
|
+
end
|
90
|
+
end
|
data/app/models/web.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
require "cgi"
|
2
|
+
require "page"
|
3
|
+
require "page_set"
|
4
|
+
require "wiki_words"
|
5
|
+
require "zip/zip"
|
6
|
+
|
7
|
+
class Web
|
8
|
+
attr_accessor :pages, :name, :address, :password
|
9
|
+
attr_accessor :markup, :color, :safe_mode, :additional_style, :published, :brackets_only, :count_pages
|
10
|
+
|
11
|
+
def initialize(name, address, password = nil)
|
12
|
+
@name, @address, @password, @safe_mode = name, address, password, false
|
13
|
+
@pages = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_page(page)
|
17
|
+
@pages[page.name] = page
|
18
|
+
end
|
19
|
+
|
20
|
+
def remove_pages(pages_to_be_removed)
|
21
|
+
pages.delete_if { |page_name, page| pages_to_be_removed.include?(page) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def select(&accept)
|
25
|
+
PageSet.new(self, @pages.values, accept)
|
26
|
+
end
|
27
|
+
|
28
|
+
def revised_on
|
29
|
+
select.most_recent_revision
|
30
|
+
end
|
31
|
+
|
32
|
+
def authors
|
33
|
+
select.authors
|
34
|
+
end
|
35
|
+
|
36
|
+
def categories
|
37
|
+
select.map { |page| page.categories }.flatten.uniq.sort
|
38
|
+
end
|
39
|
+
|
40
|
+
# Create a link for the given page name and link text based
|
41
|
+
# on the render mode in options and whether the page exists
|
42
|
+
# in the this web.
|
43
|
+
def make_link(name, text = nil, options = {})
|
44
|
+
page = pages[name]
|
45
|
+
text = text || WikiWords.separate(name)
|
46
|
+
link = CGI.escape(name)
|
47
|
+
|
48
|
+
case options[:mode]
|
49
|
+
when :export
|
50
|
+
if page then "<a class=\"existingWikiWord\" href=\"#{link}.html\">#{text}</a>"
|
51
|
+
else "<span class=\"newWikiWord\">#{text}</span>" end
|
52
|
+
when :publish
|
53
|
+
if page then "<a class=\"existingWikiWord\" href=\"../show/#{link}\">#{text}</a>"
|
54
|
+
else "<span class=\"newWikiWord\">#{text}</span>" end
|
55
|
+
else
|
56
|
+
if page then "<a class=\"existingWikiWord\" href=\"../show/#{link}\">#{text}</a>"
|
57
|
+
else "<span class=\"newWikiWord\">#{text}<a href=\"../show/#{link}\">?</a></span>" end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
# Clears the display cache for all the pages with references to
|
63
|
+
def refresh_pages_with_references(page_name)
|
64
|
+
select.pages_that_reference(page_name).each { |page|
|
65
|
+
page.revisions.each { |revision| revision.clear_display_cache }
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
def refresh_revisions
|
70
|
+
select.each { |page| page.revisions.each { |revision| revision.clear_display_cache } }
|
71
|
+
end
|
72
|
+
|
73
|
+
# Default values
|
74
|
+
def markup() @markup || :textile end
|
75
|
+
def color() @color || "008B26" end
|
76
|
+
def brackets_only() @brackets_only || false end
|
77
|
+
def count_pages() @count_pages || false end
|
78
|
+
|
79
|
+
private
|
80
|
+
# Returns an array of all the wiki words in any current revision
|
81
|
+
def wiki_words
|
82
|
+
pages.values.inject([]) { |wiki_words, page| wiki_words << page.wiki_words }.flatten.uniq
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns an array of all the page names on this web
|
86
|
+
def page_names
|
87
|
+
pages.keys
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'chunks/engines'
|
3
|
+
require 'chunks/category'
|
4
|
+
require 'chunks/include'
|
5
|
+
require 'chunks/wiki'
|
6
|
+
require 'chunks/literal'
|
7
|
+
require 'chunks/uri'
|
8
|
+
require 'chunks/nowiki'
|
9
|
+
|
10
|
+
# Wiki content is just a string that can process itself with a chain of
|
11
|
+
# actions. The actions can modify wiki content so that certain parts of
|
12
|
+
# it are protected from being rendered by later actions.
|
13
|
+
#
|
14
|
+
# When wiki content is rendered, it can be interrogated to find out
|
15
|
+
# which chunks were rendered. This means things like categories, wiki
|
16
|
+
# links, can be determined.
|
17
|
+
#
|
18
|
+
# Exactly how wiki content is rendered is determined by a number of
|
19
|
+
# settings that are optionally passed in to a constructor. The current
|
20
|
+
# options are:
|
21
|
+
# * :engine
|
22
|
+
# => The structural markup engine to use (Textile, Markdown, RDoc)
|
23
|
+
# * :engine_opts
|
24
|
+
# => A list of options to pass to the markup engines (safe modes, etc)
|
25
|
+
# * :pre_engine_actions
|
26
|
+
# => A list of render actions or chunks to be processed before the
|
27
|
+
# markup engine is applied. By default this is:
|
28
|
+
# Category, Include, URIChunk, WikiChunk::Link, WikiChunk::Word
|
29
|
+
# * :post_engine_actions
|
30
|
+
# => A list of render actions or chunks to apply after the markup
|
31
|
+
# engine. By default these are:
|
32
|
+
# Literal::Pre, Literal::Tags
|
33
|
+
# * :mode
|
34
|
+
# => How should the content be rendered? For normal display (:display),
|
35
|
+
# publishing (:publish) or export (:export)?
|
36
|
+
#
|
37
|
+
# AUTHOR: Mark Reid <mark @ threewordslong . com>
|
38
|
+
# CREATED: 15th May 2004
|
39
|
+
# UPDATED: 22nd May 2004
|
40
|
+
class WikiContent < String
|
41
|
+
|
42
|
+
PRE_ENGINE_ACTIONS = [ NoWiki, Category, Include, URIChunk, WikiChunk::Link, WikiChunk::Word ]
|
43
|
+
POST_ENGINE_ACTIONS = [ Literal::Pre, Literal::Tags ]
|
44
|
+
DEFAULT_OPTS = {
|
45
|
+
:pre_engine_actions => PRE_ENGINE_ACTIONS,
|
46
|
+
:post_engine_actions => POST_ENGINE_ACTIONS,
|
47
|
+
:engine => Engines::Textile,
|
48
|
+
:engine_opts => [],
|
49
|
+
:mode => [:display]
|
50
|
+
}
|
51
|
+
|
52
|
+
attr_reader :web, :options, :rendered
|
53
|
+
|
54
|
+
# Create a new wiki content string from the given one.
|
55
|
+
# The options are explained at the top of this file.
|
56
|
+
def initialize(revision, options = {})
|
57
|
+
@revision = revision
|
58
|
+
@web = @revision.page.web
|
59
|
+
|
60
|
+
# Deep copy of DEFAULT_OPTS to ensure that changes to PRE/POST_ENGINE_ACTIONS stay local
|
61
|
+
@options = Marshal.load(Marshal.dump(DEFAULT_OPTS)).update(options)
|
62
|
+
@options[:engine] = Engines::MAP[@web.markup] || Engines::Textile
|
63
|
+
@options[:engine_opts] = (@web.safe_mode ? [:filter_html, :filter_styles] : [])
|
64
|
+
|
65
|
+
@options[:pre_engine_actions].delete(WikiChunk::Word) if @web.brackets_only
|
66
|
+
|
67
|
+
super(@revision.content)
|
68
|
+
|
69
|
+
begin
|
70
|
+
render!(@options[:pre_engine_actions] + [@options[:engine]] + @options[:post_engine_actions])
|
71
|
+
rescue => e
|
72
|
+
@rendered = e.message
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Call @web.page_link using current options.
|
77
|
+
def page_link(name, text)
|
78
|
+
@web.make_link(name, text, @options)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Find all the chunks of the given types
|
82
|
+
def find_chunks(chunk_type)
|
83
|
+
rendered.select { |chunk| chunk.kind_of?(chunk_type) }
|
84
|
+
end
|
85
|
+
|
86
|
+
# Render this content using the specified actions.
|
87
|
+
def render!(chunk_types)
|
88
|
+
@chunks = []
|
89
|
+
chunk_types.each { |chunk_type| self.apply_type!(chunk_type) }
|
90
|
+
|
91
|
+
@rendered = @chunks.map { |chunk| chunk.unmask(self) }.compact
|
92
|
+
(@chunks - @rendered).each { |chunk| chunk.revert(self) }
|
93
|
+
end
|
94
|
+
|
95
|
+
# Find all the chunks of the given type in this content
|
96
|
+
# Each time the type's pattern is matched, create a new
|
97
|
+
# chunk for it, and replace the occurance of the chunk
|
98
|
+
# in this content with its mask.
|
99
|
+
def apply_type!(chunk_type)
|
100
|
+
self.gsub!( chunk_type.pattern ) do |match|
|
101
|
+
@chunks << chunk_type.new($~)
|
102
|
+
@chunks.last.mask(self)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + "/../../libraries/")
|
2
|
+
|
3
|
+
require "madeleine_service"
|
4
|
+
require "web"
|
5
|
+
require "page"
|
6
|
+
require "author"
|
7
|
+
|
8
|
+
class WikiService < MadeleineService
|
9
|
+
attr_reader :webs, :system
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@webs, @system = {}, {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def setup?
|
16
|
+
!@system.empty?
|
17
|
+
end
|
18
|
+
|
19
|
+
def authenticate(password)
|
20
|
+
password == (@system["password"] || "instiki")
|
21
|
+
end
|
22
|
+
|
23
|
+
def setup(password, web_name, web_address)
|
24
|
+
@system["password"] = password
|
25
|
+
create_web(web_name, web_address)
|
26
|
+
end
|
27
|
+
|
28
|
+
def create_web(name, address, password = nil)
|
29
|
+
@webs[address] = Web.new(name, address, password) unless @webs[address]
|
30
|
+
end
|
31
|
+
|
32
|
+
def update_web(old_address, new_address, name, markup, color, additional_style, safe_mode = false,
|
33
|
+
password = nil, published = false, brackets_only = false, count_pages = false)
|
34
|
+
if old_address != new_address
|
35
|
+
@webs[new_address] = @webs[old_address]
|
36
|
+
@webs.delete(old_address)
|
37
|
+
@webs[new_address].address = new_address
|
38
|
+
end
|
39
|
+
|
40
|
+
web = @webs[new_address]
|
41
|
+
web.refresh_revisions if settings_changed?(web, markup, safe_mode, brackets_only)
|
42
|
+
|
43
|
+
web.name, web.markup, web.color, web.additional_style, web.safe_mode =
|
44
|
+
name, markup, color, additional_style, safe_mode
|
45
|
+
|
46
|
+
web.password, web.published, web.brackets_only, web.count_pages =
|
47
|
+
password, published, brackets_only, count_pages
|
48
|
+
end
|
49
|
+
|
50
|
+
def read_page(web_address, page_name)
|
51
|
+
web = @webs[web_address]
|
52
|
+
web ? web.pages[page_name] : nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def write_page(web_address, page_name, content, written_on, author)
|
56
|
+
page = Page.new(@webs[web_address], page_name, content, written_on, author)
|
57
|
+
@webs[web_address].add_page(page)
|
58
|
+
page
|
59
|
+
end
|
60
|
+
|
61
|
+
def revise_page(web_address, page_name, content, revised_on, author)
|
62
|
+
page = read_page(web_address, page_name)
|
63
|
+
page.revise(content, revised_on, author)
|
64
|
+
page
|
65
|
+
end
|
66
|
+
|
67
|
+
def rollback_page(web_address, page_name, revision_number, created_at, author_id = nil)
|
68
|
+
page = read_page(web_address, page_name)
|
69
|
+
page.rollback(revision_number, created_at, author_id)
|
70
|
+
page
|
71
|
+
end
|
72
|
+
|
73
|
+
def remove_orphaned_pages(web_address)
|
74
|
+
@webs[web_address].remove_pages(@webs[web_address].select.orphaned_pages)
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
def settings_changed?(web, markup, safe_mode, brackets_only)
|
79
|
+
web.markup != markup ||
|
80
|
+
web.safe_mode != safe_mode ||
|
81
|
+
web.brackets_only != brackets_only
|
82
|
+
end
|
83
|
+
end
|