cortex-reaver 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +25 -0
- data/README +48 -0
- data/bin/console +11 -0
- data/bin/cortex_reaver +110 -0
- data/bin/import.rb +78 -0
- data/lib/cortex_reaver.rb +114 -0
- data/lib/cortex_reaver/config.rb +21 -0
- data/lib/cortex_reaver/controller/comment.rb +113 -0
- data/lib/cortex_reaver/controller/config.rb +14 -0
- data/lib/cortex_reaver/controller/journal.rb +40 -0
- data/lib/cortex_reaver/controller/main.rb +56 -0
- data/lib/cortex_reaver/controller/page.rb +34 -0
- data/lib/cortex_reaver/controller/photograph.rb +45 -0
- data/lib/cortex_reaver/controller/project.rb +40 -0
- data/lib/cortex_reaver/controller/tag.rb +67 -0
- data/lib/cortex_reaver/controller/user.rb +71 -0
- data/lib/cortex_reaver/helper/activity.rb +9 -0
- data/lib/cortex_reaver/helper/attachments.rb +139 -0
- data/lib/cortex_reaver/helper/auth.rb +45 -0
- data/lib/cortex_reaver/helper/canonical.rb +55 -0
- data/lib/cortex_reaver/helper/crud.rb +317 -0
- data/lib/cortex_reaver/helper/date.rb +29 -0
- data/lib/cortex_reaver/helper/error.rb +14 -0
- data/lib/cortex_reaver/helper/feeds.rb +88 -0
- data/lib/cortex_reaver/helper/form.rb +114 -0
- data/lib/cortex_reaver/helper/navigation.rb +142 -0
- data/lib/cortex_reaver/helper/photographs.rb +25 -0
- data/lib/cortex_reaver/helper/tags.rb +47 -0
- data/lib/cortex_reaver/helper/workflow.rb +27 -0
- data/lib/cortex_reaver/migrations/001_users.rb +24 -0
- data/lib/cortex_reaver/migrations/002_pages.rb +29 -0
- data/lib/cortex_reaver/migrations/003_journals.rb +26 -0
- data/lib/cortex_reaver/migrations/004_photographs.rb +24 -0
- data/lib/cortex_reaver/migrations/005_projects.rb +27 -0
- data/lib/cortex_reaver/migrations/006_tags.rb +64 -0
- data/lib/cortex_reaver/migrations/007_comments.rb +40 -0
- data/lib/cortex_reaver/migrations/008_config.rb +23 -0
- data/lib/cortex_reaver/model/comment.rb +109 -0
- data/lib/cortex_reaver/model/journal.rb +53 -0
- data/lib/cortex_reaver/model/page.rb +87 -0
- data/lib/cortex_reaver/model/photograph.rb +133 -0
- data/lib/cortex_reaver/model/project.rb +49 -0
- data/lib/cortex_reaver/model/tag.rb +72 -0
- data/lib/cortex_reaver/model/user.rb +147 -0
- data/lib/cortex_reaver/public/css/admin.css +45 -0
- data/lib/cortex_reaver/public/css/custom.css +0 -0
- data/lib/cortex_reaver/public/css/form.css +51 -0
- data/lib/cortex_reaver/public/css/main.css +325 -0
- data/lib/cortex_reaver/public/css/photo.css +113 -0
- data/lib/cortex_reaver/public/css/ramaze_error.css +90 -0
- data/lib/cortex_reaver/public/css/text.css +25 -0
- data/lib/cortex_reaver/public/dispatch.fcgi +11 -0
- data/lib/cortex_reaver/public/images/CortexReaver.gif +0 -0
- data/lib/cortex_reaver/public/images/atom-xml-icon.png +0 -0
- data/lib/cortex_reaver/public/images/body.png +0 -0
- data/lib/cortex_reaver/public/images/border_bottom.png +0 -0
- data/lib/cortex_reaver/public/images/border_bottom_left.png +0 -0
- data/lib/cortex_reaver/public/images/border_bottom_right.png +0 -0
- data/lib/cortex_reaver/public/images/border_left.png +0 -0
- data/lib/cortex_reaver/public/images/border_right.png +0 -0
- data/lib/cortex_reaver/public/images/border_top.png +0 -0
- data/lib/cortex_reaver/public/images/border_top_left.png +0 -0
- data/lib/cortex_reaver/public/images/border_top_right.png +0 -0
- data/lib/cortex_reaver/public/images/comment.gif +0 -0
- data/lib/cortex_reaver/public/images/dark_trans.png +0 -0
- data/lib/cortex_reaver/public/images/delete.gif +0 -0
- data/lib/cortex_reaver/public/images/edit.gif +0 -0
- data/lib/cortex_reaver/public/images/header.png +0 -0
- data/lib/cortex_reaver/public/images/header.xcf +0 -0
- data/lib/cortex_reaver/public/images/header_background.png +0 -0
- data/lib/cortex_reaver/public/images/parent.gif +0 -0
- data/lib/cortex_reaver/public/images/rss-xml-icon.png +0 -0
- data/lib/cortex_reaver/public/images/sections.png +0 -0
- data/lib/cortex_reaver/public/images/sections_highlight.png +0 -0
- data/lib/cortex_reaver/public/js/admin.js +36 -0
- data/lib/cortex_reaver/public/js/cookie.js +27 -0
- data/lib/cortex_reaver/public/js/jquery.js +32 -0
- data/lib/cortex_reaver/public/js/photo.js +33 -0
- data/lib/cortex_reaver/snippets/array.rb +7 -0
- data/lib/cortex_reaver/snippets/ramaze/dispatcher/file.rb +37 -0
- data/lib/cortex_reaver/support/attachments.rb +235 -0
- data/lib/cortex_reaver/support/cached_rendering.rb +79 -0
- data/lib/cortex_reaver/support/canonical.rb +107 -0
- data/lib/cortex_reaver/support/comments.rb +69 -0
- data/lib/cortex_reaver/support/pagination.rb +38 -0
- data/lib/cortex_reaver/support/renderer.rb +196 -0
- data/lib/cortex_reaver/support/sequenceable.rb +248 -0
- data/lib/cortex_reaver/support/tags.rb +108 -0
- data/lib/cortex_reaver/support/timestamps.rb +33 -0
- data/lib/cortex_reaver/version.rb +8 -0
- data/lib/cortex_reaver/view/adminbox.rhtml +56 -0
- data/lib/cortex_reaver/view/blank_layout.rhtml +46 -0
- data/lib/cortex_reaver/view/comments/comment.rhtml +34 -0
- data/lib/cortex_reaver/view/comments/form.rhtml +25 -0
- data/lib/cortex_reaver/view/comments/list.rhtml +5 -0
- data/lib/cortex_reaver/view/comments/post_form.rhtml +36 -0
- data/lib/cortex_reaver/view/config/form.rhtml +10 -0
- data/lib/cortex_reaver/view/error.rhtml +72 -0
- data/lib/cortex_reaver/view/journals/form.rhtml +12 -0
- data/lib/cortex_reaver/view/journals/journal.rhtml +39 -0
- data/lib/cortex_reaver/view/journals/list.rhtml +33 -0
- data/lib/cortex_reaver/view/journals/short.rhtml +3 -0
- data/lib/cortex_reaver/view/journals/show.rhtml +5 -0
- data/lib/cortex_reaver/view/pages/form.rhtml +12 -0
- data/lib/cortex_reaver/view/pages/list.rhtml +26 -0
- data/lib/cortex_reaver/view/pages/show.rhtml +12 -0
- data/lib/cortex_reaver/view/photographs/atom_fragment.rhtml +82 -0
- data/lib/cortex_reaver/view/photographs/form.rhtml +19 -0
- data/lib/cortex_reaver/view/photographs/grid.rhtml +36 -0
- data/lib/cortex_reaver/view/photographs/list.rhtml +9 -0
- data/lib/cortex_reaver/view/photographs/short.rhtml +3 -0
- data/lib/cortex_reaver/view/photographs/show.rhtml +114 -0
- data/lib/cortex_reaver/view/photographs/sidebar.rhtml +7 -0
- data/lib/cortex_reaver/view/projects/form.rhtml +13 -0
- data/lib/cortex_reaver/view/projects/list.rhtml +27 -0
- data/lib/cortex_reaver/view/projects/show.rhtml +38 -0
- data/lib/cortex_reaver/view/tags/form.rhtml +9 -0
- data/lib/cortex_reaver/view/tags/list.rhtml +28 -0
- data/lib/cortex_reaver/view/tags/show.rhtml +51 -0
- data/lib/cortex_reaver/view/text_layout.rhtml +78 -0
- data/lib/cortex_reaver/view/users/form.rhtml +16 -0
- data/lib/cortex_reaver/view/users/list.rhtml +38 -0
- data/lib/cortex_reaver/view/users/login.rhtml +13 -0
- data/lib/cortex_reaver/view/users/register.rhtml +13 -0
- data/lib/cortex_reaver/view/users/show.rhtml +37 -0
- data/lib/cortex_reaver/view/users/user.rhtml +35 -0
- data/lib/proto/cortex_reaver.yaml +28 -0
- metadata +285 -0
@@ -0,0 +1,79 @@
|
|
1
|
+
module CortexReaver
|
2
|
+
module Model
|
3
|
+
|
4
|
+
# On save, calls a special rendering method on configured attributes, and
|
5
|
+
# saves the results to their cache.
|
6
|
+
module CachedRendering
|
7
|
+
require 'ostruct'
|
8
|
+
|
9
|
+
def self.included(base)
|
10
|
+
base.class_eval do
|
11
|
+
# Before save, render all changed caching fields
|
12
|
+
before_save(:render_to_cache) do
|
13
|
+
# Get changed fields to render
|
14
|
+
if new?
|
15
|
+
changed = columns.map { |c| c.to_sym }
|
16
|
+
else
|
17
|
+
changed = changed_columns.map { |c| c.to_sym }
|
18
|
+
end
|
19
|
+
fields = render_fields.select do |k, v|
|
20
|
+
changed.include? k.to_sym
|
21
|
+
end
|
22
|
+
|
23
|
+
fields.each do |name, field|
|
24
|
+
# Render and cache
|
25
|
+
self[field.to] = self.send(field.with, self[name])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Refreshes all records with cached fields.
|
30
|
+
def self.refresh_render_caches
|
31
|
+
# TODO: inefficient, but Model.each breaks Sequel in validation
|
32
|
+
# "commands out of sync"
|
33
|
+
all.each do |record|
|
34
|
+
# Mark all caching columns as changed, so the before_save hook
|
35
|
+
# processes them.
|
36
|
+
record.skip_timestamp_update = true
|
37
|
+
render_fields.keys.each do |column|
|
38
|
+
record.changed_columns << column
|
39
|
+
end
|
40
|
+
record.save
|
41
|
+
end
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
|
45
|
+
# Assigns a field to cache
|
46
|
+
#
|
47
|
+
# render :body, :with => 'wikify', :to => 'cached_body'
|
48
|
+
#
|
49
|
+
# ... calls #wikify on the value of self.body, and stores the result
|
50
|
+
# in self.cached_body. :to defaults to the field name with _cache
|
51
|
+
# appended. :with defaults to :render.
|
52
|
+
def self.render(field, params = {})
|
53
|
+
# Assign parameters
|
54
|
+
params = {
|
55
|
+
:to => (field.to_s + '_cache').to_sym,
|
56
|
+
:with => :render
|
57
|
+
}.merge!(params)
|
58
|
+
|
59
|
+
# Store field
|
60
|
+
render_fields[field] = OpenStruct.new(params)
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.render_fields
|
64
|
+
@render_fields ||= {}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Default renderer
|
70
|
+
def render(value)
|
71
|
+
value
|
72
|
+
end
|
73
|
+
|
74
|
+
def render_fields
|
75
|
+
self.class.render_fields
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module CortexReaver
|
2
|
+
module Model
|
3
|
+
# Supports canonical, url-safe identifiers for records, inferred from other
|
4
|
+
# fields.
|
5
|
+
module Canonical
|
6
|
+
|
7
|
+
# The canonical name attribute
|
8
|
+
CANONICAL_NAME_ATTR = :name
|
9
|
+
# The attribute we infer the canonical name from, if not set.
|
10
|
+
CANONICAL_INFERENCE_ATTR = :title
|
11
|
+
|
12
|
+
def self.included(base)
|
13
|
+
base.class_eval do
|
14
|
+
# Canonical names which cannot be reserved.
|
15
|
+
def self.reserved_canonical_names
|
16
|
+
@reserved_canonical_names ||= []
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.reserved_canonical_names=(names)
|
20
|
+
@reserved_canonical_names = names
|
21
|
+
end
|
22
|
+
|
23
|
+
# Canonicalize a string. Optionally, ignore conflicts with the record
|
24
|
+
# with id.
|
25
|
+
def self.canonicalize(string, id = nil)
|
26
|
+
# Lower case, remove special chars, and replace with hyphens.
|
27
|
+
proper = string.downcase.gsub(/[^a-z0-9_]/, '-').squeeze('-')[0..250].sub(/-$/, '')
|
28
|
+
|
29
|
+
# If proper is blank, just return it at this point.
|
30
|
+
if proper.blank?
|
31
|
+
return proper
|
32
|
+
end
|
33
|
+
|
34
|
+
# Numeric suffix to append
|
35
|
+
suffix = nil
|
36
|
+
|
37
|
+
if proper != filter(:id => id).map(canonical_name_attr).first
|
38
|
+
# We don't already have this name.
|
39
|
+
|
40
|
+
similar = []
|
41
|
+
|
42
|
+
if filter(canonical_name_attr => proper).limit(1).count > 0
|
43
|
+
similar << proper
|
44
|
+
# This name already exists, and it's not ours!
|
45
|
+
similar += filter(canonical_name_attr.like(/^#{proper}\-[0-9]+$/)).map(canonical_name_attr)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Check for reserved names
|
49
|
+
reserved_canonical_names.each do |name|
|
50
|
+
if name =~ /^#{proper}(-\d+)?$/
|
51
|
+
similar << name
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Find possible conflicting names from actions on this model's controller.
|
56
|
+
# if self.respond_to? :url and controller = Ramaze::Controller.at(self.url)
|
57
|
+
# similar += controller.action_methods.select do |action|
|
58
|
+
# action =~ /^#{proper}(-\d+)?$/
|
59
|
+
# end
|
60
|
+
# end
|
61
|
+
|
62
|
+
# Extract numeric suffices
|
63
|
+
suffices = {}
|
64
|
+
similar.each do |name|
|
65
|
+
suffices[name[/\d$/].to_i] = true
|
66
|
+
end
|
67
|
+
|
68
|
+
# Compute suffix
|
69
|
+
unless suffices.empty?
|
70
|
+
i = 1
|
71
|
+
while suffices.include? i
|
72
|
+
i += 1
|
73
|
+
end
|
74
|
+
suffix = i
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
if suffix
|
79
|
+
proper + '-' + suffix.to_s
|
80
|
+
else
|
81
|
+
proper
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Sets the attribute we infer the canonical name from to attr, or
|
86
|
+
# gets that attr if nil.
|
87
|
+
def self.canonical_inference_attr(attr = nil)
|
88
|
+
if attr
|
89
|
+
@canonical_inference_attr = attr.to_sym
|
90
|
+
else
|
91
|
+
@canonical_inference_attr || CANONICAL_INFERENCE_ATTR
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Sets the canonical name attribute to attr. Returns it if nil.
|
96
|
+
def self.canonical_name_attr(attr = nil)
|
97
|
+
if attr
|
98
|
+
@canonical_name_attr = attr.to_sym
|
99
|
+
else
|
100
|
+
@canonical_name_attr || CANONICAL_NAME_ATTR
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module CortexReaver
|
2
|
+
module Model
|
3
|
+
# Support methods for comments on models
|
4
|
+
module Comments
|
5
|
+
def self.included(base)
|
6
|
+
base.class_eval do
|
7
|
+
# When we delete a model that has comments, remove the comments too.
|
8
|
+
before_delete(:drop_comments) do
|
9
|
+
comments = self.comments
|
10
|
+
remove_all_comments
|
11
|
+
comments.each do |comment|
|
12
|
+
comment.destroy
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Refresh all comment counts
|
17
|
+
def self.refresh_comment_counts
|
18
|
+
all.each do |model|
|
19
|
+
model.refresh_comment_count
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Recalculates the number of comments on this record (and all comments
|
26
|
+
# below it, recursively) and saves those values. Returns the comment
|
27
|
+
# count on this record.
|
28
|
+
def refresh_comment_count
|
29
|
+
count = 0
|
30
|
+
comments.each do |comment|
|
31
|
+
# Recalculate for sub-comments and sum.
|
32
|
+
count += comment.refresh_comment_count + 1
|
33
|
+
end
|
34
|
+
self[:comment_count] = count
|
35
|
+
self.skip_timestamp_update = true
|
36
|
+
|
37
|
+
# Save and return
|
38
|
+
self.save
|
39
|
+
self[:comment_count]
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the parent of a given comment. Caches, pass true to refresh.
|
43
|
+
def parent(refresh = false)
|
44
|
+
if refresh or @parent_cache.nil?
|
45
|
+
[:comment, :journal, :photograph, :project, :page].each do |p|
|
46
|
+
if self.respond_to?(p) and parent = self.send(p)
|
47
|
+
# We found an applicable parent.
|
48
|
+
@parent_cache = parent
|
49
|
+
return parent
|
50
|
+
end
|
51
|
+
end
|
52
|
+
# We didn't find any parent
|
53
|
+
nil
|
54
|
+
else
|
55
|
+
@parent_cache
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns the top-level parent of a given comment.
|
60
|
+
def root_parent
|
61
|
+
if parent
|
62
|
+
parent.root_parent
|
63
|
+
else
|
64
|
+
self
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module CortexReaver
|
2
|
+
module Model
|
3
|
+
# Defines class-level accessors for page size, order attribute, etc.
|
4
|
+
module Pagination
|
5
|
+
DEFAULT_SIZE = 16
|
6
|
+
DEFAULT_ORDER = 'created_on'
|
7
|
+
DEFAULT_REVERSE = false
|
8
|
+
|
9
|
+
def self.included(base)
|
10
|
+
base.class_eval do
|
11
|
+
class << self
|
12
|
+
attr_accessor :page_size, :page_order, :page_reverse
|
13
|
+
end
|
14
|
+
|
15
|
+
@page_size ||= CortexReaver::Model::Pagination::DEFAULT_SIZE
|
16
|
+
@page_order ||= CortexReaver::Model::Pagination::DEFAULT_ORDER
|
17
|
+
@page_reverse ||= CortexReaver::Model::Pagination::DEFAULT_REVERSE
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns a paginated dataset at page number. Optionally, filters on
|
21
|
+
# dataset instead of the whole model.
|
22
|
+
def page(number, dataset = self.dataset)
|
23
|
+
if reverse
|
24
|
+
dataset = dataset.reverse
|
25
|
+
end
|
26
|
+
|
27
|
+
dataset.order(@order).paginate(number, @page_size)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns the page number for this model. Optionally, filters on dataset
|
32
|
+
# rather than the whole model.
|
33
|
+
def page_number(dataset = self.class.dataset)
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,196 @@
|
|
1
|
+
module CortexReaver
|
2
|
+
module Model
|
3
|
+
# Some common rendering methods, wrapped up for your convenience. Use in your model
|
4
|
+
# with something like:
|
5
|
+
#
|
6
|
+
# render :body, :with => :render_comment
|
7
|
+
#
|
8
|
+
# See CortexReaver::Model::CachedRendering for more details.
|
9
|
+
module Renderer
|
10
|
+
require 'bluecloth'
|
11
|
+
require 'hpricot'
|
12
|
+
require 'syntax'
|
13
|
+
|
14
|
+
# Elements to allow in sanitized HTML.
|
15
|
+
ELEMENTS = [
|
16
|
+
'a', 'b', 'blockquote', 'br', 'code', 'dd', 'dl', 'dt', 'em', 'i', 'li',
|
17
|
+
'ol', 'p', 'pre', 'small', 'strike', 'strong', 'sub', 'sup', 'u', 'ul'
|
18
|
+
]
|
19
|
+
|
20
|
+
# Attributes to allow in sanitized HTML elements.
|
21
|
+
ATTRIBUTES = {
|
22
|
+
'a' => ['href', 'title'],
|
23
|
+
'pre' => ['class']
|
24
|
+
}
|
25
|
+
|
26
|
+
# Attributes to add to sanitized HTML elements.
|
27
|
+
ADD_ATTRIBUTES = {
|
28
|
+
'a' => {'rel' => 'nofollow'}
|
29
|
+
}
|
30
|
+
|
31
|
+
# Attributes that should be checked for valid protocols.
|
32
|
+
PROTOCOL_ATTRIBUTES = {'a' => ['href']}
|
33
|
+
|
34
|
+
# Valid protocols.
|
35
|
+
PROTOCOLS = ['ftp', 'http', 'https', 'mailto']
|
36
|
+
|
37
|
+
|
38
|
+
# Renders plain text and html to html.
|
39
|
+
def bluecloth(text)
|
40
|
+
return text if text.nil?
|
41
|
+
|
42
|
+
BlueCloth::new(text).to_html
|
43
|
+
end
|
44
|
+
|
45
|
+
# Replace <% and %> to prevent Erubis injection.
|
46
|
+
def erubis_filter(text)
|
47
|
+
return text if text.nil?
|
48
|
+
|
49
|
+
t = text.dup
|
50
|
+
t.gsub!('<%', '<%')
|
51
|
+
t.gsub!('%>', '%&rt;')
|
52
|
+
t
|
53
|
+
end
|
54
|
+
|
55
|
+
# Macro substitutions
|
56
|
+
#
|
57
|
+
# Expands [[type:resource][name]] macros. Right now, resource is just an attachment.
|
58
|
+
# Included types are:
|
59
|
+
#
|
60
|
+
# url: returns the URL to an attachment
|
61
|
+
# image: returns an image tag
|
62
|
+
# link: returns a link to an attachment
|
63
|
+
#
|
64
|
+
# The default action is a link, so
|
65
|
+
#
|
66
|
+
# [[foo.jpg]] => <a href="/data/.../foo.jpg">foo.jpg</a>
|
67
|
+
def macro(text)
|
68
|
+
return text if text.nil?
|
69
|
+
|
70
|
+
copy = text.dup
|
71
|
+
|
72
|
+
# Links
|
73
|
+
#
|
74
|
+
# Example [[image:foo.png][name]]
|
75
|
+
# 1. the link type prefix image:
|
76
|
+
# 2. the link type, sans-colon image
|
77
|
+
# 3. the link itself foo.png
|
78
|
+
# 4. the second half of the link [name]
|
79
|
+
# 5. the name name
|
80
|
+
copy.gsub!(/\[\[(([^\]]+):)?([^\]]+)(\]\[([^\]]+))?\]\]/) do |match|
|
81
|
+
prefix = $2
|
82
|
+
path = $3
|
83
|
+
name = $5
|
84
|
+
|
85
|
+
# Find the link target
|
86
|
+
target = attachment(path)
|
87
|
+
|
88
|
+
if target.exists?
|
89
|
+
# Name of the link
|
90
|
+
name ||= path
|
91
|
+
|
92
|
+
# Create link to this target
|
93
|
+
case prefix
|
94
|
+
when 'image'
|
95
|
+
# Create an inline image
|
96
|
+
"<img src=\"#{target.public_path}\" alt=\"#{name.gsub('"', '"')}\" title=\"#{name.gsub('"', '"')}\" />"
|
97
|
+
when 'url'
|
98
|
+
# Create a URL
|
99
|
+
target.public_path
|
100
|
+
else
|
101
|
+
# Create a full link
|
102
|
+
"<a href=\"#{target.public_path}\">#{Rack::Utils.escape_html(name).gsub(/#([{@$]@?)/, '#\1')}</a>"
|
103
|
+
end
|
104
|
+
else
|
105
|
+
# Don't create a link
|
106
|
+
match
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
copy
|
111
|
+
end
|
112
|
+
|
113
|
+
def syntax_highlight(text)
|
114
|
+
return text if text.nil?
|
115
|
+
|
116
|
+
h = Hpricot(text)
|
117
|
+
|
118
|
+
h.search('cr:code').replace do |code|
|
119
|
+
code[:lang]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Stolen wholesale from Ryan's Thoth (http://github.com/rgrove/thoth/)
|
124
|
+
# Who adapted it from http://rid.onkulo.us/archives/14-sanitizing-html-with-ruby-and-hpricot
|
125
|
+
def sanitize_html(html)
|
126
|
+
return html if html.nil?
|
127
|
+
|
128
|
+
h = Hpricot(html)
|
129
|
+
|
130
|
+
h.search('*').each do |el|
|
131
|
+
if el.elem?
|
132
|
+
tag = el.name.downcase
|
133
|
+
|
134
|
+
if ELEMENTS.include?(tag)
|
135
|
+
if ATTRIBUTES.has_key?(tag)
|
136
|
+
# Delete any attribute that isn't in the whitelist for this
|
137
|
+
# particular element.
|
138
|
+
el.raw_attributes.delete_if do |key, val|
|
139
|
+
!ATTRIBUTES[tag].include?(key.downcase)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Check applicable attributes for valid protocols.
|
143
|
+
if PROTOCOL_ATTRIBUTES.has_key?(tag)
|
144
|
+
el.raw_attributes.delete_if do |key, val|
|
145
|
+
PROTOCOL_ATTRIBUTES[tag].include?(key.downcase) &&
|
146
|
+
(!(val.downcase =~ /^([^:]+)\:/) || !PROTOCOLS.include?($1))
|
147
|
+
end
|
148
|
+
end
|
149
|
+
else
|
150
|
+
# Delete all attributes from elements with no whitelisted
|
151
|
+
# attributes.
|
152
|
+
el.raw_attributes = {}
|
153
|
+
end
|
154
|
+
|
155
|
+
# Add required attributes.
|
156
|
+
if ADD_ATTRIBUTES.has_key?(tag)
|
157
|
+
el.raw_attributes.merge!(ADD_ATTRIBUTES[tag])
|
158
|
+
end
|
159
|
+
else
|
160
|
+
# Delete any element that isn't in the whitelist.
|
161
|
+
el.parent.replace_child(el, el.children)
|
162
|
+
end
|
163
|
+
elsif el.comment?
|
164
|
+
# Delete all comments, since it's possible to make IE execute JS
|
165
|
+
# within conditional comments.
|
166
|
+
el.swap('')
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
h.to_s
|
171
|
+
end
|
172
|
+
|
173
|
+
# Default renderer
|
174
|
+
def render(text)
|
175
|
+
bluecloth(
|
176
|
+
macro(
|
177
|
+
erubis_filter(
|
178
|
+
text
|
179
|
+
)
|
180
|
+
)
|
181
|
+
) # (((Feeling) LISPish yet)?)
|
182
|
+
end
|
183
|
+
|
184
|
+
# Comments render
|
185
|
+
def render_comment(text)
|
186
|
+
bluecloth(
|
187
|
+
erubis_filter(
|
188
|
+
sanitize_html(
|
189
|
+
text
|
190
|
+
)
|
191
|
+
)
|
192
|
+
)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|