para-seo_tools 0.2.1
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.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +178 -0
- data/Rakefile +2 -0
- data/app/.DS_Store +0 -0
- data/app/components/seo_tools_skeleton_component.rb +7 -0
- data/app/controllers/.DS_Store +0 -0
- data/app/controllers/admin/para/seo_tools/skeleton_resources_controller.rb +21 -0
- data/app/decorators/seo_tools_skeleton_component_decorator.rb +3 -0
- data/app/helpers/para/seo_tools/admin/fields_helper.rb +15 -0
- data/app/models/para/seo_tools/page.rb +54 -0
- data/app/views/.DS_Store +0 -0
- data/app/views/admin/.DS_Store +0 -0
- data/app/views/admin/para/seo_tools/pages/_actions.html.haml +5 -0
- data/app/views/admin/para/seo_tools/pages/_filters.html.haml +25 -0
- data/app/views/admin/para/seo_tools/pages/_form.html.haml +13 -0
- data/app/views/admin/para/seo_tools/pages/_table.html.haml +20 -0
- data/app/views/admin/para/seo_tools/skeleton_resources/index.html.haml +4 -0
- data/bin/rails +12 -0
- data/config/locales/para-seo_tools.fr.yml +22 -0
- data/db/migrate/20150601085253_create_seo_tools_pages.rb +14 -0
- data/db/migrate/20160610094032_add_meta_tags_to_seo_tools_pages.rb +12 -0
- data/db/migrate/20160614081242_add_locale_and_remove_meta_tags_list_to_seo_tools_pages.rb +8 -0
- data/lib/generators/para/seo_tools/install/install_generator.rb +24 -0
- data/lib/generators/para/seo_tools/install/templates/initializer.rb +33 -0
- data/lib/generators/para/seo_tools/install/templates/skeleton.rb +37 -0
- data/lib/para/seo_tools/controller.rb +50 -0
- data/lib/para/seo_tools/engine.rb +27 -0
- data/lib/para/seo_tools/meta_tags/renderer.rb +80 -0
- data/lib/para/seo_tools/meta_tags/store.rb +52 -0
- data/lib/para/seo_tools/meta_tags/tags/base.rb +49 -0
- data/lib/para/seo_tools/meta_tags/tags/description.rb +54 -0
- data/lib/para/seo_tools/meta_tags/tags/image.rb +46 -0
- data/lib/para/seo_tools/meta_tags/tags/keywords.rb +20 -0
- data/lib/para/seo_tools/meta_tags/tags/title.rb +48 -0
- data/lib/para/seo_tools/meta_tags/tags/url.rb +13 -0
- data/lib/para/seo_tools/meta_tags/tags.rb +29 -0
- data/lib/para/seo_tools/meta_tags/vendors/base.rb +27 -0
- data/lib/para/seo_tools/meta_tags/vendors/open_graph.rb +23 -0
- data/lib/para/seo_tools/meta_tags/vendors/twitter.rb +23 -0
- data/lib/para/seo_tools/meta_tags/vendors.rb +17 -0
- data/lib/para/seo_tools/meta_tags.rb +12 -0
- data/lib/para/seo_tools/routes.rb +15 -0
- data/lib/para/seo_tools/sitemap.rb +13 -0
- data/lib/para/seo_tools/skeleton/page.rb +71 -0
- data/lib/para/seo_tools/skeleton/site.rb +18 -0
- data/lib/para/seo_tools/skeleton/worker.rb +11 -0
- data/lib/para/seo_tools/skeleton.rb +79 -0
- data/lib/para/seo_tools/version.rb +5 -0
- data/lib/para/seo_tools/view_helpers.rb +9 -0
- data/lib/para/seo_tools.rb +43 -0
- data/lib/tasks/migration.rake +20 -0
- data/lib/tasks/sitemap.rake +17 -0
- data/lib/tasks/skeleton.rake +9 -0
- data/para-seo_tools.gemspec +27 -0
- data/test/fixtures/seo_tools/pages.yml +11 -0
- data/test/models/seo_tools/page_test.rb +7 -0
- metadata +181 -0
@@ -0,0 +1,80 @@
|
|
1
|
+
module Para
|
2
|
+
module SeoTools
|
3
|
+
module MetaTags
|
4
|
+
class Renderer
|
5
|
+
LINE_SEPARATOR = "\n"
|
6
|
+
|
7
|
+
attr_reader :template, :vendors
|
8
|
+
|
9
|
+
delegate :content_tag, :tag, to: :template
|
10
|
+
|
11
|
+
def initialize(template, vendors: nil)
|
12
|
+
@template = template
|
13
|
+
@vendors = vendors
|
14
|
+
end
|
15
|
+
|
16
|
+
def render
|
17
|
+
[
|
18
|
+
charset_tag,
|
19
|
+
title_tag,
|
20
|
+
description_tag,
|
21
|
+
keywords_tag,
|
22
|
+
canonical_tag,
|
23
|
+
vendor_tags
|
24
|
+
].compact.join(LINE_SEPARATOR).html_safe
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def title_tag
|
30
|
+
if (title = meta_tags.title).present?
|
31
|
+
content_tag(:title, title)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def charset_tag
|
36
|
+
tag(:meta, charset: meta_tags.charset)
|
37
|
+
end
|
38
|
+
|
39
|
+
def description_tag
|
40
|
+
if (description = meta_tags.description).present?
|
41
|
+
tag(:meta, name: 'description', content: description)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def keywords_tag
|
46
|
+
if (keywords = meta_tags.keywords).present?
|
47
|
+
tag(:meta, name: 'keywords', content: keywords)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def canonical_tag
|
52
|
+
if (canonical = meta_tags.canonical).present?
|
53
|
+
tag(:link, rel: 'canonical', href: canonical)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def vendor_tags
|
58
|
+
return unless vendors
|
59
|
+
|
60
|
+
vendor_tags = vendors.each_with_object([]) do |vendor_name, tags|
|
61
|
+
vendor_class = MetaTags::Vendors.for(vendor_name)
|
62
|
+
vendor = vendor_class.new(template)
|
63
|
+
|
64
|
+
vendor.tags.each do |tag_name|
|
65
|
+
if (value = meta_tags.send(tag_name)).present?
|
66
|
+
tags << vendor.render(tag_name, value)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
vendor_tags.join(LINE_SEPARATOR)
|
72
|
+
end
|
73
|
+
|
74
|
+
def meta_tags
|
75
|
+
@meta_tags ||= template.controller.meta_tags_store
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Para
|
2
|
+
module SeoTools
|
3
|
+
module MetaTags
|
4
|
+
class Store
|
5
|
+
attr_reader :controller, :defaults
|
6
|
+
|
7
|
+
TAGS = :title, :description, :image, :url, :site_name, :keywords, :type,
|
8
|
+
:site, :card, :charset, :canonical
|
9
|
+
|
10
|
+
attr_writer *TAGS
|
11
|
+
|
12
|
+
def initialize(controller)
|
13
|
+
@controller = controller
|
14
|
+
|
15
|
+
@defaults = if Para::SeoTools.defaults
|
16
|
+
controller.instance_exec(&Para::SeoTools.defaults)
|
17
|
+
else
|
18
|
+
{}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def charset
|
23
|
+
@charset ||= 'utf-8'
|
24
|
+
end
|
25
|
+
|
26
|
+
TAGS.each do |tag_name|
|
27
|
+
define_method(tag_name) do
|
28
|
+
fetch_value_for(tag_name)
|
29
|
+
end unless method_defined?(tag_name)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def fetch_value_for(tag_name)
|
35
|
+
ivar_name = :"@#{ tag_name }"
|
36
|
+
|
37
|
+
if (value = instance_variable_get(ivar_name)).present?
|
38
|
+
value
|
39
|
+
else
|
40
|
+
value = if (processor = MetaTags::Tags[tag_name])
|
41
|
+
processor.new(controller).value
|
42
|
+
end
|
43
|
+
|
44
|
+
value = defaults[tag_name] unless value.present?
|
45
|
+
|
46
|
+
instance_variable_set(ivar_name, value) if value
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Para
|
2
|
+
module SeoTools
|
3
|
+
module MetaTags
|
4
|
+
module Tags
|
5
|
+
class Base
|
6
|
+
attr_accessor :controller
|
7
|
+
|
8
|
+
def initialize(controller)
|
9
|
+
@controller = controller
|
10
|
+
end
|
11
|
+
|
12
|
+
# Overriding this method in subclasses allow to process the meta tag
|
13
|
+
# value before it is rendered, e.g. stripping HTML tags
|
14
|
+
#
|
15
|
+
def self.process(value)
|
16
|
+
value
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def member_action?
|
22
|
+
controller.params.key?(:id)
|
23
|
+
end
|
24
|
+
|
25
|
+
def action_i18n(label)
|
26
|
+
I18n.t(action_key(label), default: '').presence
|
27
|
+
end
|
28
|
+
|
29
|
+
def action_key(label)
|
30
|
+
key = %w(meta_tags controller)
|
31
|
+
key << controller.params[:controller]
|
32
|
+
key << controller.params[:action]
|
33
|
+
key << label
|
34
|
+
key.join(".")
|
35
|
+
end
|
36
|
+
|
37
|
+
# TODO : Define if we'll still support instance meta tags and refactor
|
38
|
+
# accordingly
|
39
|
+
def instance
|
40
|
+
end
|
41
|
+
|
42
|
+
def model_name
|
43
|
+
@model_name ||= controller.class.name.gsub(/Controller$/, '').singularize.underscore
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Para
|
2
|
+
module SeoTools
|
3
|
+
module MetaTags
|
4
|
+
module Tags
|
5
|
+
class Description < Base
|
6
|
+
def value
|
7
|
+
self.class.process(
|
8
|
+
meta_taggable_description || instance_description || action_name
|
9
|
+
)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.process(value)
|
13
|
+
Processor.new.process(value)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def meta_taggable_description
|
19
|
+
instance && instance.meta_tagged? &&
|
20
|
+
instance.meta_tags_list.meta_description.presence
|
21
|
+
end
|
22
|
+
|
23
|
+
def instance_description
|
24
|
+
if instance
|
25
|
+
Para::SeoTools.description_methods.each do |method|
|
26
|
+
if instance.respond_to?(method)
|
27
|
+
if (description = instance.send(method).presence)
|
28
|
+
return description
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
return nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def action_name
|
38
|
+
if (action_name = action_i18n(:description))
|
39
|
+
return action_name
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Processor
|
44
|
+
include ActionView::Helpers::TextHelper
|
45
|
+
|
46
|
+
def process(value)
|
47
|
+
truncate(sanitize(value, tags: []), length: 250)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Para
|
2
|
+
module SeoTools
|
3
|
+
module MetaTags
|
4
|
+
module Tags
|
5
|
+
class Image < Base
|
6
|
+
def value
|
7
|
+
self.class.process(instance_image)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.process(value)
|
11
|
+
url = url_from(value)
|
12
|
+
|
13
|
+
if url.start_with?('http://')
|
14
|
+
url
|
15
|
+
else
|
16
|
+
request = RequestStore.store[:'para.seo_tools.request']
|
17
|
+
URI.join(request.url, url).to_s
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def instance_image
|
24
|
+
if member_action?
|
25
|
+
Para::SeoTools.image_methods.each do |method|
|
26
|
+
if instance.respond_to?(method) && instance.send(method)
|
27
|
+
return instance.send(method).url
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.url_from(value)
|
36
|
+
if Paperclip::Attachment === value
|
37
|
+
value.url(:thumb)
|
38
|
+
else
|
39
|
+
value.to_s
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Para
|
2
|
+
module SeoTools
|
3
|
+
module MetaTags
|
4
|
+
module Tags
|
5
|
+
class Keywords < Base
|
6
|
+
def value
|
7
|
+
meta_taggable_keywords
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def meta_taggable_keywords
|
13
|
+
instance && instance.meta_tagged? &&
|
14
|
+
instance.meta_tags_list.meta_keywords.presence
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Para
|
2
|
+
module SeoTools
|
3
|
+
module MetaTags
|
4
|
+
module Tags
|
5
|
+
class Title < Base
|
6
|
+
def value
|
7
|
+
meta_taggable_title || instance_title || action_name || model_name_translation
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def meta_taggable_title
|
13
|
+
instance && instance.meta_tagged? &&
|
14
|
+
instance.meta_tags_list.meta_title.presence
|
15
|
+
end
|
16
|
+
|
17
|
+
def instance_title
|
18
|
+
if instance
|
19
|
+
Para::SeoTools.title_methods.each do |method|
|
20
|
+
if instance.respond_to?(method) && (title = instance.send(method)).presence
|
21
|
+
return title
|
22
|
+
end
|
23
|
+
end
|
24
|
+
return nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def action_name
|
29
|
+
if (action_name = action_i18n(:title))
|
30
|
+
return action_name
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def model_name_translation
|
35
|
+
return unless model_name
|
36
|
+
model_key = "activerecord.models.#{ model_name }"
|
37
|
+
|
38
|
+
if (translation = I18n.t("#{ model_key }.other", default: "").presence)
|
39
|
+
translation
|
40
|
+
elsif (translation = I18n.t(model_key, default: "").presence)
|
41
|
+
translation.pluralize
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Para
|
2
|
+
module SeoTools
|
3
|
+
module MetaTags
|
4
|
+
module Tags
|
5
|
+
extend ActiveSupport::Autoload
|
6
|
+
|
7
|
+
autoload :Base
|
8
|
+
autoload :Title
|
9
|
+
autoload :Description
|
10
|
+
autoload :Keywords
|
11
|
+
autoload :Image
|
12
|
+
autoload :Url
|
13
|
+
|
14
|
+
def self.[](tag_name)
|
15
|
+
processors[tag_name]
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def self.processors
|
21
|
+
@processors ||= [:title, :description, :keywords, :image, :url].reduce({}) do |hash, tag|
|
22
|
+
hash[tag] = MetaTags::Tags.const_get(tag.to_s.camelize)
|
23
|
+
hash
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Para
|
2
|
+
module SeoTools
|
3
|
+
module MetaTags
|
4
|
+
module Vendors
|
5
|
+
class Base
|
6
|
+
include ActionView::Helpers::TagHelper
|
7
|
+
|
8
|
+
attr_reader :template
|
9
|
+
|
10
|
+
def initialize(template)
|
11
|
+
@template = template
|
12
|
+
end
|
13
|
+
|
14
|
+
def render(key, value)
|
15
|
+
tag(:meta, key_name => attribute_name_for(key), content: value)
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
def attribute_name_for(key)
|
21
|
+
[namespace, key].join(':')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Para
|
2
|
+
module SeoTools
|
3
|
+
module MetaTags
|
4
|
+
module Vendors
|
5
|
+
class OpenGraph < Para::SeoTools::MetaTags::Vendors::Base
|
6
|
+
def tags
|
7
|
+
[:title, :description, :image, :type, :url, :site_name]
|
8
|
+
end
|
9
|
+
|
10
|
+
protected
|
11
|
+
|
12
|
+
def key_name
|
13
|
+
'property'
|
14
|
+
end
|
15
|
+
|
16
|
+
def namespace
|
17
|
+
'og'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Para
|
2
|
+
module SeoTools
|
3
|
+
module MetaTags
|
4
|
+
module Vendors
|
5
|
+
class Twitter < Para::SeoTools::MetaTags::Vendors::Base
|
6
|
+
def tags
|
7
|
+
[:title, :description, :image, :url, :site, :card]
|
8
|
+
end
|
9
|
+
|
10
|
+
protected
|
11
|
+
|
12
|
+
def key_name
|
13
|
+
'name'
|
14
|
+
end
|
15
|
+
|
16
|
+
def namespace
|
17
|
+
'twitter'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Para
|
2
|
+
module SeoTools
|
3
|
+
module MetaTags
|
4
|
+
module Vendors
|
5
|
+
extend ActiveSupport::Autoload
|
6
|
+
|
7
|
+
autoload :Base
|
8
|
+
autoload :OpenGraph
|
9
|
+
autoload :Twitter
|
10
|
+
|
11
|
+
def self.for(vendor_name)
|
12
|
+
const_get(vendor_name.to_s.camelize)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Para
|
2
|
+
module SeoTools
|
3
|
+
class Sitemap
|
4
|
+
def self.build
|
5
|
+
::Sitemap::Generator.instance.load :host => Para::SeoTools.host do
|
6
|
+
Para::SeoTools::Skeleton.site.pages.each do |page|
|
7
|
+
literal page.path, page.sitemap_options
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Para
|
2
|
+
module SeoTools
|
3
|
+
module Skeleton
|
4
|
+
class Page
|
5
|
+
include Rails.application.routes.url_helpers
|
6
|
+
|
7
|
+
attr_reader :name, :resource, :options, :locale
|
8
|
+
|
9
|
+
def initialize(name, path: nil, resource: nil, **options)
|
10
|
+
@name = name
|
11
|
+
@path = path
|
12
|
+
@resource = resource
|
13
|
+
@options = options
|
14
|
+
|
15
|
+
# Fetch locale on page build to allow calling the `page` skeleton
|
16
|
+
# method inside a `I18n.with_locale` block
|
17
|
+
#
|
18
|
+
@locale = options.fetch(:locale, I18n.locale)
|
19
|
+
end
|
20
|
+
|
21
|
+
def identifier
|
22
|
+
@identifier ||= [name, resource.try(:id)].compact.join(':')
|
23
|
+
end
|
24
|
+
|
25
|
+
def display_name
|
26
|
+
@display_name ||= [name, resource.try(:id)].compact.join(' #').humanize
|
27
|
+
end
|
28
|
+
|
29
|
+
def path
|
30
|
+
@path ||= find_route
|
31
|
+
end
|
32
|
+
|
33
|
+
def model
|
34
|
+
@model ||= self.class.model_for(identifier).tap do |page|
|
35
|
+
# Override path (i.e.: slug changed)
|
36
|
+
page.path = path if path.to_s != page.path
|
37
|
+
page.locale = locale
|
38
|
+
# Do not override meta tags if already present
|
39
|
+
page.defaults = options[:defaults] || {}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def sitemap_options
|
44
|
+
[:priority, :change_frequency].each_with_object({}) do |param, hash|
|
45
|
+
hash[param] = options[param] if options.key?(param)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
# Find a route matching the name of the page, passing the resource
|
52
|
+
# for member routes to work out of the box
|
53
|
+
def find_route
|
54
|
+
if respond_to?(:"#{ name }_path")
|
55
|
+
send(:"#{ name }_path", resource)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.model_for(identifier)
|
60
|
+
models[identifier] ||= ::Para::SeoTools::Page.new(identifier: identifier)
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.models
|
64
|
+
@models ||= Para::SeoTools::Page.all.each_with_object({}) do |page, hash|
|
65
|
+
hash[page.identifier] = page
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Para
|
2
|
+
module SeoTools
|
3
|
+
module Skeleton
|
4
|
+
class Site
|
5
|
+
include Rails.application.routes.url_helpers
|
6
|
+
|
7
|
+
def page(name, options = {} , &block)
|
8
|
+
page = Skeleton::Page.new(name, options)
|
9
|
+
pages << page
|
10
|
+
end
|
11
|
+
|
12
|
+
def pages
|
13
|
+
@pages ||= []
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|