├── spec
├── dummy
│ ├── public
│ │ ├── favicon.ico
│ │ ├── robots.txt
│ │ ├── 422.html
│ │ ├── 404.html
│ │ └── 500.html
│ ├── .rvmrc
│ ├── app
│ │ ├── views
│ │ │ ├── layouts
│ │ │ │ ├── sample.en.erb
│ │ │ │ ├── sample.ru.erb
│ │ │ │ ├── articles.html.erb
│ │ │ │ └── application.html.erb
│ │ │ ├── shared
│ │ │ │ └── _first.html.erb
│ │ │ └── articles
│ │ │ │ └── show.html.erb
│ │ ├── helpers
│ │ │ └── application_helper.rb
│ │ ├── assets
│ │ │ ├── images
│ │ │ │ └── rails.png
│ │ │ ├── stylesheets
│ │ │ │ └── application.css
│ │ │ └── javascripts
│ │ │ │ └── application.js
│ │ ├── controllers
│ │ │ ├── admin
│ │ │ │ └── articles_controller.rb
│ │ │ └── application_controller.rb
│ │ └── models
│ │ │ └── article.rb
│ ├── config
│ │ ├── initializers
│ │ │ ├── puffer_pages.rb
│ │ │ ├── mime_types.rb
│ │ │ ├── backtrace_silencers.rb
│ │ │ ├── session_store.rb
│ │ │ ├── secret_token.rb
│ │ │ ├── wrap_parameters.rb
│ │ │ └── inflections.rb
│ │ ├── environment.rb
│ │ ├── boot.rb
│ │ ├── locales
│ │ │ └── en.yml
│ │ ├── database.yml
│ │ ├── environments
│ │ │ ├── development.rb
│ │ │ ├── pg_test.rb
│ │ │ ├── test.rb
│ │ │ └── production.rb
│ │ ├── routes.rb
│ │ └── application.rb
│ ├── config.ru
│ ├── db
│ │ ├── migrate
│ │ │ ├── 20111117081813_create_articles.rb
│ │ │ ├── 20130118071519_add_translations.rb
│ │ │ ├── 20130118071518_add_locales_to_pages.rb
│ │ │ ├── 20130118071517_add_handler_to_page_parts.rb
│ │ │ ├── 20130118071513_create_layouts.rb
│ │ │ ├── 20130118071514_create_snippets.rb
│ │ │ ├── 20130118071515_create_origins.rb
│ │ │ ├── 20130118071512_create_page_parts.rb
│ │ │ ├── 20130118071511_create_pages.rb
│ │ │ └── 20130118071516_migrate_to_uuid.rb
│ │ └── seeds.rb
│ ├── Rakefile
│ └── script
│ │ └── rails
├── fabricators
│ ├── articles_fabricator.rb
│ ├── origin_fabricator.rb
│ ├── snippets_fabricator.rb
│ ├── page_parts_fabricator.rb
│ ├── pages_fabricator.rb
│ └── layouts_fabricator.rb
├── data
│ ├── broken.json
│ ├── import.json
│ ├── unlocalized.json
│ └── localized.json
├── lib
│ ├── handlers
│ │ ├── base_spec.rb
│ │ └── yaml_spec.rb
│ ├── liquid
│ │ ├── tags
│ │ │ ├── scope_spec.rb
│ │ │ ├── image_spec.rb
│ │ │ ├── partials_spec.rb
│ │ │ ├── include_spec.rb
│ │ │ └── cache_spec.rb
│ │ ├── backend_spec.rb
│ │ ├── interpolation_spec.rb
│ │ └── tags_spec.rb
│ ├── core_spec.rb
│ ├── handlers_spec.rb
│ ├── page_drop_spec.rb
│ ├── rspec
│ │ └── matchers
│ │ │ └── render_page_spec.rb
│ └── pagenator_spec.rb
├── models
│ └── puffer_pages
│ │ ├── page_part_spec.rb
│ │ ├── layout_spec.rb
│ │ ├── snippet_spec.rb
│ │ ├── localable_spec.rb
│ │ └── renderable_spec.rb
├── requests
│ └── origins_requests_spec.rb
├── controllers
│ └── pages_controller_spec.rb
└── spec_helper.rb
├── .rvmrc
├── lib
├── puffer_pages
│ ├── version.rb
│ ├── rspec.rb
│ ├── backends
│ │ ├── controllers
│ │ │ ├── layouts_base.rb
│ │ │ ├── snippets_base.rb
│ │ │ ├── origins_base.rb
│ │ │ └── pages_base.rb
│ │ └── models
│ │ │ ├── layout.rb
│ │ │ ├── snippet.rb
│ │ │ ├── mixins
│ │ │ ├── importable.rb
│ │ │ ├── localable.rb
│ │ │ └── translatable.rb
│ │ │ ├── origin.rb
│ │ │ └── page_part.rb
│ ├── liquid
│ │ ├── tags
│ │ │ ├── helper.rb
│ │ │ ├── javascript.rb
│ │ │ ├── partials.rb
│ │ │ ├── scope.rb
│ │ │ ├── yield.rb
│ │ │ ├── include.rb
│ │ │ ├── render.rb
│ │ │ ├── super.rb
│ │ │ ├── image.rb
│ │ │ ├── array.rb
│ │ │ ├── assets.rb
│ │ │ ├── url.rb
│ │ │ ├── translate.rb
│ │ │ └── cache.rb
│ │ ├── backend.rb
│ │ ├── tracker.rb
│ │ ├── page_drop.rb
│ │ └── file_system.rb
│ ├── helpers.rb
│ ├── handlers
│ │ ├── base.rb
│ │ └── yaml.rb
│ ├── extensions
│ │ ├── core.rb
│ │ ├── renderer.rb
│ │ ├── context.rb
│ │ └── pagenator.rb
│ ├── rspec
│ │ ├── view_rendering.rb
│ │ ├── matchers.rb
│ │ └── matchers
│ │ │ └── render_page.rb
│ ├── backends.rb
│ ├── globalize
│ │ └── migrator.rb
│ ├── renderer.rb
│ ├── engine.rb
│ ├── handlers.rb
│ ├── log_subscriber.rb
│ └── migrations.rb
└── puffer_pages.rb
├── .rspec
├── app
├── components
│ ├── codemirror_component.rb
│ ├── page_parts_component.rb
│ ├── handlers_component.rb
│ ├── render
│ │ └── _tree_page.html.erb
│ ├── handlers
│ │ └── form.html.erb
│ ├── page_parts
│ │ ├── _page_part.html.erb
│ │ └── form.html.erb
│ └── codemirror
│ │ └── form.html.erb
├── models
│ └── puffer_pages
│ │ ├── page.rb
│ │ ├── origin.rb
│ │ ├── layout.rb
│ │ ├── snippet.rb
│ │ └── page_part.rb
├── controllers
│ ├── admin
│ │ ├── pages_controller.rb
│ │ ├── layouts_controller.rb
│ │ ├── origins_controller.rb
│ │ └── snippets_controller.rb
│ └── pages_controller.rb
├── assets
│ ├── stylesheets
│ │ └── puffer
│ │ │ ├── codemirror
│ │ │ ├── ambiance-mobile.css
│ │ │ ├── neat.css
│ │ │ ├── elegant.css
│ │ │ ├── cobalt.css
│ │ │ ├── eclipse.css
│ │ │ ├── night.css
│ │ │ ├── monokai.css
│ │ │ ├── erlang-dark.css
│ │ │ ├── blackboard.css
│ │ │ ├── rubyblue.css
│ │ │ ├── vibrant-ink.css
│ │ │ ├── twilight.css
│ │ │ ├── lesser-dark.css
│ │ │ └── xq-dark.css
│ │ │ └── puffer_pages.css
│ └── javascripts
│ │ └── puffer
│ │ ├── liquid.js
│ │ ├── codemirror
│ │ ├── yaml.js
│ │ └── htmlmixed.js
│ │ ├── matchbrackets.js
│ │ └── multiplex.js
└── helpers
│ └── puffer_pages_helper.rb
├── config
├── routes.rb
└── locales
│ └── en.yml
├── gemfiles
├── Gemfile.rails-3-1
├── Gemfile.rails-3-2
└── Gemfile.rails-head
├── .gitignore
├── db
└── migrate
│ ├── 20130118064524_add_locales_to_pages.rb
│ ├── 20130110144030_add_handler_to_page_parts.rb
│ ├── 20090506102004_create_layouts.rb
│ ├── 20090510121824_create_snippets.rb
│ ├── 20120812100913_create_origins.rb
│ ├── 20090504132337_create_page_parts.rb
│ ├── 20090422092419_create_pages.rb
│ └── 20120924120226_migrate_to_uuid.rb
├── Gemfile
├── .travis.yml
├── Rakefile
├── MIT-LICENSE
├── Guardfile
├── puffer_pages.gemspec
└── README.md
/spec/dummy/public/favicon.ico:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.rvmrc:
--------------------------------------------------------------------------------
1 | rvm use 1.9.3@puffer_pages --create
2 |
--------------------------------------------------------------------------------
/spec/dummy/.rvmrc:
--------------------------------------------------------------------------------
1 | rvm use 1.9.3@puffer_pages --create
2 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/layouts/sample.en.erb:
--------------------------------------------------------------------------------
1 | sample.en.erb
--------------------------------------------------------------------------------
/spec/dummy/app/views/layouts/sample.ru.erb:
--------------------------------------------------------------------------------
1 | sample.ru.erb
--------------------------------------------------------------------------------
/spec/dummy/app/views/shared/_first.html.erb:
--------------------------------------------------------------------------------
1 | shared/first content
--------------------------------------------------------------------------------
/spec/dummy/app/views/articles/show.html.erb:
--------------------------------------------------------------------------------
1 | <%= @article.title %>
--------------------------------------------------------------------------------
/spec/dummy/app/views/layouts/articles.html.erb:
--------------------------------------------------------------------------------
1 | app - <%= yield %> - app
--------------------------------------------------------------------------------
/lib/puffer_pages/version.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | VERSION = "0.5.1"
3 | end
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --colour
2 | #--format documentation
3 | --backtrace
4 | --order random
5 |
--------------------------------------------------------------------------------
/spec/dummy/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/components/codemirror_component.rb:
--------------------------------------------------------------------------------
1 | class CodemirrorComponent < BaseComponent
2 | end
3 |
--------------------------------------------------------------------------------
/app/models/puffer_pages/page.rb:
--------------------------------------------------------------------------------
1 | class PufferPages::Page < PufferPages::Backends::Page
2 | end
3 |
--------------------------------------------------------------------------------
/app/models/puffer_pages/origin.rb:
--------------------------------------------------------------------------------
1 | class PufferPages::Origin < PufferPages::Backends::Origin
2 | end
3 |
--------------------------------------------------------------------------------
/app/controllers/admin/pages_controller.rb:
--------------------------------------------------------------------------------
1 | class Admin::PagesController < PufferPages::PagesBase
2 | unloadable
3 | end
4 |
--------------------------------------------------------------------------------
/app/models/puffer_pages/layout.rb:
--------------------------------------------------------------------------------
1 | class PufferPages::Layout < PufferPages::Backends::Layout
2 | translatable :body
3 | end
4 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | PufferPages::Engine.routes.draw do
2 | match '(*path)' => 'pages#index', :as => 'puffer_page'
3 | end
4 |
--------------------------------------------------------------------------------
/app/controllers/admin/layouts_controller.rb:
--------------------------------------------------------------------------------
1 | class Admin::LayoutsController < PufferPages::LayoutsBase
2 | unloadable
3 | end
4 |
--------------------------------------------------------------------------------
/app/controllers/admin/origins_controller.rb:
--------------------------------------------------------------------------------
1 | class Admin::OriginsController < PufferPages::OriginsBase
2 | unloadable
3 | end
4 |
--------------------------------------------------------------------------------
/app/controllers/admin/snippets_controller.rb:
--------------------------------------------------------------------------------
1 | class Admin::SnippetsController < PufferPages::SnippetsBase
2 | unloadable
3 | end
4 |
--------------------------------------------------------------------------------
/app/models/puffer_pages/snippet.rb:
--------------------------------------------------------------------------------
1 | class PufferPages::Snippet < PufferPages::Backends::Snippet
2 | translatable :body
3 | end
4 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/images/rails.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/puffer/puffer_pages/HEAD/spec/dummy/app/assets/images/rails.png
--------------------------------------------------------------------------------
/app/models/puffer_pages/page_part.rb:
--------------------------------------------------------------------------------
1 | class PufferPages::PagePart < PufferPages::Backends::PagePart
2 | translatable :body
3 | end
4 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | puffer_pages:
3 | inherited_layout: inherited (%{name})
4 | tree_page:
5 | view: view
6 |
--------------------------------------------------------------------------------
/app/components/page_parts_component.rb:
--------------------------------------------------------------------------------
1 | class PagePartsComponent < Puffer::Component::Base
2 | def form
3 | render
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/puffer_pages.rb:
--------------------------------------------------------------------------------
1 | PufferPages.setup do |config|
2 | config.localize = ENV['LOCALIZE'] != 'false'
3 | config.access_token = 'token'
4 | end
5 |
--------------------------------------------------------------------------------
/app/components/handlers_component.rb:
--------------------------------------------------------------------------------
1 | class HandlersComponent < SelectComponent
2 |
3 | private
4 |
5 | def select_options
6 | PufferPages::Handlers.select
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/gemfiles/Gemfile.rails-3-1:
--------------------------------------------------------------------------------
1 | source "http://rubygems.org"
2 |
3 | gemspec path: '../'
4 |
5 | gem 'puffer', github: 'puffer/puffer'
6 | gem 'rails', '~> 3.1.0'
7 | gem 'activeuuid'
8 |
--------------------------------------------------------------------------------
/gemfiles/Gemfile.rails-3-2:
--------------------------------------------------------------------------------
1 | source "http://rubygems.org"
2 |
3 | gemspec path: '../'
4 |
5 | gem 'puffer', github: 'puffer/puffer'
6 | gem 'rails', '~> 3.2.0'
7 | gem 'activeuuid'
8 |
--------------------------------------------------------------------------------
/spec/fabricators/articles_fabricator.rb:
--------------------------------------------------------------------------------
1 | Fabricator(:article) do
2 | title { Forgery::LoremIpsum.word(random: true) }
3 | body { Forgery::LoremIpsum.sentence(random: true) }
4 | end
5 |
--------------------------------------------------------------------------------
/spec/dummy/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require ::File.expand_path('../config/environment', __FILE__)
4 | run Dummy::Application
5 |
--------------------------------------------------------------------------------
/gemfiles/Gemfile.rails-head:
--------------------------------------------------------------------------------
1 | source "http://rubygems.org"
2 |
3 | gemspec path: '../'
4 |
5 | gem 'puffer', github: 'puffer/puffer'
6 | gem 'rails', github: 'rails/rails'
7 | gem 'activeuuid'
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .bundle/
2 | log/*.log
3 | pkg/
4 | spec/dummy/db/*.sqlite3
5 | spec/dummy/log/*.log
6 | spec/dummy/tmp/
7 | *.orig
8 | .idea
9 | tmp/
10 | Gemfile.lock
11 | .DS_Store
12 | .rbx/
13 |
--------------------------------------------------------------------------------
/spec/dummy/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the rails application
2 | require File.expand_path('../application', __FILE__)
3 |
4 | # Initialize the rails application
5 | Dummy::Application.initialize!
6 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/puffer/codemirror/ambiance-mobile.css:
--------------------------------------------------------------------------------
1 | .cm-s-ambiance.CodeMirror {
2 | -webkit-box-shadow: none;
3 | -moz-box-shadow: none;
4 | -o-box-shadow: none;
5 | box-shadow: none;
6 | }
7 |
--------------------------------------------------------------------------------
/spec/fabricators/origin_fabricator.rb:
--------------------------------------------------------------------------------
1 | Fabricator(:origin, class_name: :'puffer_pages/origin') do
2 | name { sequence(:name) { |i| "origin_#{i}" } }
3 | host { 'http://localhost:3000' }
4 | token { SecureRandom.hex }
5 | end
6 |
--------------------------------------------------------------------------------
/spec/dummy/config/boot.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 |
3 | # Set up gems listed in the Gemfile.
4 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__)
5 |
6 | require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
7 |
--------------------------------------------------------------------------------
/spec/dummy/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
2 | #
3 | # To ban all spiders from the entire site uncomment the next two lines:
4 | # User-Agent: *
5 | # Disallow: /
6 |
--------------------------------------------------------------------------------
/spec/data/broken.json:
--------------------------------------------------------------------------------
1 | {
2 | "layouts": [{
3 | "created_at": "2013-12-23T16:23:00Z",
4 | "id": "404DF5860F01492EB470711FE3BE4535",
5 | "name": "application",
6 | "strange_attribute": "Haha, I'm strange!"
7 | }]
8 | }
--------------------------------------------------------------------------------
/db/migrate/20130118064524_add_locales_to_pages.rb:
--------------------------------------------------------------------------------
1 | class AddLocalesToPages < ActiveRecord::Migration
2 | def self.up
3 | add_column :pages, :locales, :text
4 | end
5 |
6 | def self.down
7 | remove_column :pages, :locales
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/spec/dummy/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Sample localization file for English. Add more files in this directory for other locales.
2 | # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
3 |
4 | en:
5 | hello: "Hello world"
6 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 | # Mime::Type.register_alias "text/html", :iphone
6 |
--------------------------------------------------------------------------------
/app/controllers/pages_controller.rb:
--------------------------------------------------------------------------------
1 | class PagesController < ApplicationController
2 | unloadable
3 |
4 | def index
5 | page = PufferPages::Page.find_page(request.path_info)
6 | render puffer_page: page, content_type: page.try(:content_type)
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/app/components/render/_tree_page.html.erb:
--------------------------------------------------------------------------------
1 | <%= tree_page.name %> — <%= "/" + tree_page.location.to_s %>
2 | <%= tree_page.status %>
3 | <%= link_to t('.view'), puffer_pages.puffer_page_path(tree_page.to_location), :target => '_blank' if tree_page.status.published? %>
4 |
--------------------------------------------------------------------------------
/db/migrate/20130110144030_add_handler_to_page_parts.rb:
--------------------------------------------------------------------------------
1 | class AddHandlerToPageParts < ActiveRecord::Migration
2 | def self.up
3 | add_column :page_parts, :handler, :string
4 | end
5 |
6 | def self.down
7 | remove_column :page_parts, :handler
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/lib/puffer_pages/rspec.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Rspec
3 |
4 | end
5 | end
6 |
7 | RSpec.configure do |config|
8 | config.include PufferPages::Rspec::Matchers
9 | end
10 |
11 | RSpec::Rails::ControllerExampleGroup.send :include, PufferPages::Rspec::ViewRendering
12 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20111117081813_create_articles.rb:
--------------------------------------------------------------------------------
1 | class CreateArticles < ActiveRecord::Migration
2 | def change
3 | create_table :articles do |t|
4 | t.string :title
5 | t.string :slug
6 | t.text :body
7 |
8 | t.timestamps
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20130118071519_add_translations.rb:
--------------------------------------------------------------------------------
1 | class AddTranslations < ActiveRecord::Migration
2 | def self.up
3 | PufferPages::Migrations.create_translation_tables!
4 | end
5 |
6 | def self.down
7 | PufferPages::Migrations.drop_translation_table!
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/spec/dummy/Rakefile:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env rake
2 | # Add your own tasks in files placed in lib/tasks ending in .rake,
3 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
4 |
5 | require File.expand_path('../config/application', __FILE__)
6 |
7 | Dummy::Application.load_tasks
8 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "http://rubygems.org"
2 |
3 | gemspec
4 |
5 | gem 'puffer', github: 'puffer/puffer'
6 | gem 'rails'
7 |
8 | group :test do
9 | gem 'guard'
10 | gem 'guard-rspec'
11 | gem 'rb-inotify', require: false
12 | gem 'rb-fsevent', require: false
13 | gem 'rb-fchange', require: false
14 | end
15 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Dummy
5 | <%= stylesheet_link_tag "application" %>
6 | <%= javascript_include_tag "application" %>
7 | <%= csrf_meta_tags %>
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/spec/dummy/script/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3 |
4 | APP_PATH = File.expand_path('../../config/application', __FILE__)
5 | require File.expand_path('../../config/boot', __FILE__)
6 | require 'rails/commands'
7 |
--------------------------------------------------------------------------------
/app/components/handlers/form.html.erb:
--------------------------------------------------------------------------------
1 | <%= component_fields_for @record do |f| %>
2 |
3 | <%= f.label field %><%= f.select field, @options, { include_blank: false }, field.input_options %>
4 |
5 | <%= @record.errors[field.name.to_sym].first %>
6 |
7 |
8 | <% end %>
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/admin/articles_controller.rb:
--------------------------------------------------------------------------------
1 | class Admin::ArticlesController < Puffer::Base
2 |
3 | setup do
4 | group :articles
5 | end
6 |
7 | index do
8 | field :title
9 | field :slug
10 | field :body
11 | end
12 |
13 | form do
14 | field :title
15 | field :body
16 | end
17 |
18 | end
19 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20130118071518_add_locales_to_pages.rb:
--------------------------------------------------------------------------------
1 | # This migration comes from puffer_pages (originally 20130118064524)
2 | class AddLocalesToPages < ActiveRecord::Migration
3 | def self.up
4 | add_column :pages, :locales, :text
5 | end
6 |
7 | def self.down
8 | remove_column :pages, :locales
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | rvm:
2 | - 1.9.3
3 | - 2.0.0
4 | - rbx-19mode
5 |
6 | gemfile:
7 | - Gemfile
8 | - gemfiles/Gemfile.rails-3-1
9 | - gemfiles/Gemfile.rails-3-2
10 | - gemfiles/Gemfile.rails-head
11 |
12 | env:
13 | - LOCALIZE=true
14 | - LOCALIZE=false
15 |
16 | matrix:
17 | allow_failures:
18 | - gemfile: gemfiles/Gemfile.rails-head
--------------------------------------------------------------------------------
/db/migrate/20090506102004_create_layouts.rb:
--------------------------------------------------------------------------------
1 | class CreateLayouts < ActiveRecord::Migration
2 | def self.up
3 | create_table :layouts do |t|
4 | t.string :name
5 | t.text :body
6 |
7 | t.timestamps
8 | end
9 |
10 | add_index :layouts, :name
11 | end
12 |
13 | def self.down
14 | drop_table :layouts
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/article.rb:
--------------------------------------------------------------------------------
1 | class Article < ActiveRecord::Base
2 |
3 | validates :title, :presence => true
4 | validates :slug, :presence => true, :uniqueness => true
5 |
6 | before_validation :sluggify
7 |
8 | def sluggify
9 | self.slug = title.parameterize
10 | end
11 |
12 | def to_param
13 | slug
14 | end
15 |
16 | end
17 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20130118071517_add_handler_to_page_parts.rb:
--------------------------------------------------------------------------------
1 | # This migration comes from puffer_pages (originally 20130110144030)
2 | class AddHandlerToPageParts < ActiveRecord::Migration
3 | def self.up
4 | add_column :page_parts, :handler, :string
5 | end
6 |
7 | def self.down
8 | remove_column :page_parts, :handler
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/spec/lib/handlers/base_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe PufferPages::Handlers::Base do
4 | subject { described_class.new(:html) }
5 | let(:page_part) { Fabricate :main, body: 'just {{type}}' }
6 |
7 | context do
8 | specify { subject.process(page_part, drops: { type: 'plain html' }).should == 'just plain html' }
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/db/migrate/20090510121824_create_snippets.rb:
--------------------------------------------------------------------------------
1 | class CreateSnippets < ActiveRecord::Migration
2 | def self.up
3 | create_table :snippets do |t|
4 | t.string :name
5 | t.text :body
6 |
7 | t.timestamps
8 | end
9 |
10 | add_index :snippets, :name
11 | end
12 |
13 | def self.down
14 | drop_table :snippets
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/puffer_pages/backends/controllers/layouts_base.rb:
--------------------------------------------------------------------------------
1 | class PufferPages::LayoutsBase < Puffer::Base
2 | setup do
3 | group :pages
4 | model_name :'puffer_pages/layout'
5 | end
6 |
7 | index do
8 | field :name
9 | end
10 |
11 | form do
12 | field :name
13 | field :body, type: :codemirror, mode: 'text/x-liquid-html'
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/db/migrate/20120812100913_create_origins.rb:
--------------------------------------------------------------------------------
1 | class CreateOrigins < ActiveRecord::Migration
2 | def self.up
3 | create_table :origins do |t|
4 | t.string :name
5 | t.string :host
6 | t.string :path
7 | t.string :token
8 |
9 | t.timestamps
10 | end
11 | end
12 |
13 | def self.down
14 | drop_table :origins
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/puffer_pages/backends/controllers/snippets_base.rb:
--------------------------------------------------------------------------------
1 | class PufferPages::SnippetsBase < Puffer::Base
2 | setup do
3 | group :pages
4 | model_name :'puffer_pages/snippet'
5 | end
6 |
7 | index do
8 | field :name
9 | end
10 |
11 | form do
12 | field :name
13 | field :body, type: :codemirror, mode: 'text/x-liquid-html'
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/spec/dummy/db/seeds.rb:
--------------------------------------------------------------------------------
1 | # This file should contain all the record creation needed to seed the database with its default values.
2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
3 | #
4 | # Examples:
5 | #
6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
7 | # Mayor.create(name: 'Emanuel', city: cities.first)
8 |
--------------------------------------------------------------------------------
/spec/models/puffer_pages/page_part_spec.rb:
--------------------------------------------------------------------------------
1 | # encoding: UTF-8
2 | require 'spec_helper'
3 |
4 | describe PufferPages::PagePart do
5 | it { should be_a(PufferPages::Backends::Mixins::Renderable) }
6 |
7 | describe "validations" do
8 | it { should validate_presence_of(:name) }
9 |
10 | describe do
11 | it { should validate_uniqueness_of(:name) }
12 | end
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/lib/puffer_pages/liquid/tags/helper.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Liquid
3 | module Tags
4 |
5 | class Helper < ::Liquid::Tag
6 | def render(context)
7 | context.registers[:tracker].register("<%= #{@tag_name} %>")
8 | end
9 | end
10 |
11 | end
12 | end
13 | end
14 |
15 | Liquid::Template.register_tag('csrf_meta_tags', PufferPages::Liquid::Tags::Helper)
16 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/stylesheets/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll automatically include all the stylesheets available in this directory
3 | * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
4 | * the top of the compiled file, but it's generally better to create a new file per style scope.
5 | * = require_self
6 | * = require_tree .
7 | */
--------------------------------------------------------------------------------
/app/components/page_parts/_page_part.html.erb:
--------------------------------------------------------------------------------
1 | <%= form.fields_for field.name, page_part, :child_index => child_index do |page_part_builder| %>
2 | <% field.children.each do |field| %>
3 | <%= field.render :form, parent_controller, page_part_builder.object, :builder => page_part_builder %>
4 | <% end %>
5 | <%= page_part_builder.hidden_field :id, :data => { :acts => 'id' } if page_part_builder.object.persisted? %>
6 | <% end %>
7 |
--------------------------------------------------------------------------------
/db/migrate/20090504132337_create_page_parts.rb:
--------------------------------------------------------------------------------
1 | class CreatePageParts < ActiveRecord::Migration
2 | def self.up
3 | create_table :page_parts do |t|
4 | t.string :name
5 | t.text :body
6 | t.integer :page_id
7 |
8 | t.timestamps
9 | end
10 |
11 | add_index :page_parts, :name
12 | add_index :page_parts, :page_id
13 | end
14 |
15 | def self.down
16 | drop_table :page_parts
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/lib/puffer_pages/liquid/tags/javascript.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Liquid
3 | module Tags
4 |
5 | class Javascript < ::Liquid::Block
6 | def render(context)
7 | context.registers[:tracker].register("<%= javascript_tag do %>#{super}<% end %>")
8 | end
9 | end
10 |
11 | end
12 | end
13 | end
14 |
15 | Liquid::Template.register_tag('javascript', PufferPages::Liquid::Tags::Javascript)
16 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20130118071513_create_layouts.rb:
--------------------------------------------------------------------------------
1 | # This migration comes from puffer_pages (originally 20090506102004)
2 | class CreateLayouts < ActiveRecord::Migration
3 | def self.up
4 | create_table :layouts do |t|
5 | t.string :name
6 | t.text :body
7 |
8 | t.timestamps
9 | end
10 |
11 | add_index :layouts, :name
12 | end
13 |
14 | def self.down
15 | drop_table :layouts
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | protect_from_forgery
3 |
4 | around_filter :locale_context
5 |
6 | def locale_context &block
7 | params[:locale].present? ? I18n.with_locale(params[:locale], &block) : block.call
8 | end
9 |
10 | def has_puffer_access? namespace
11 | true
12 | end
13 |
14 | def current_puffer_user
15 | nil
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20130118071514_create_snippets.rb:
--------------------------------------------------------------------------------
1 | # This migration comes from puffer_pages (originally 20090510121824)
2 | class CreateSnippets < ActiveRecord::Migration
3 | def self.up
4 | create_table :snippets do |t|
5 | t.string :name
6 | t.text :body
7 |
8 | t.timestamps
9 | end
10 |
11 | add_index :snippets, :name
12 | end
13 |
14 | def self.down
15 | drop_table :snippets
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/javascripts/application.js:
--------------------------------------------------------------------------------
1 | // This is a manifest file that'll be compiled into including all the files listed below.
2 | // Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
3 | // be included in the compiled file accessible from http://example.com/assets/application.js
4 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
5 | // the compiled file.
6 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20130118071515_create_origins.rb:
--------------------------------------------------------------------------------
1 | # This migration comes from puffer_pages (originally 20120812100913)
2 | class CreateOrigins < ActiveRecord::Migration
3 | def self.up
4 | create_table :origins do |t|
5 | t.string :name
6 | t.string :host
7 | t.string :path
8 | t.string :token
9 |
10 | t.timestamps
11 | end
12 | end
13 |
14 | def self.down
15 | drop_table :origins
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/lib/puffer_pages/liquid/backend.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Liquid
3 | class Backend
4 | include ::I18n::Backend::Simple::Implementation
5 | include ::I18n::Backend::Pluralization
6 |
7 | def load_translations; end
8 | def store_translations(*args); end
9 | def initialized?; true; end
10 |
11 | def translations
12 | Contextuality.page_translations || {}
13 | end
14 |
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Dummy::Application.config.session_store :cookie_store, key: '_dummy_session'
4 |
5 | # Use the database for sessions instead of the cookie-based default,
6 | # which shouldn't be used to store highly confidential information
7 | # (create the session table with "rails generate session_migration")
8 | # Dummy::Application.config.session_store :active_record_store
9 |
--------------------------------------------------------------------------------
/spec/models/puffer_pages/layout_spec.rb:
--------------------------------------------------------------------------------
1 | # encoding: UTF-8
2 | require 'spec_helper'
3 |
4 | describe PufferPages::Layout do
5 | describe "validations" do
6 | it { should validate_presence_of(:name) }
7 | it { should validate_uniqueness_of(:name)}
8 | end
9 |
10 | describe "#find_layout" do
11 | let!(:layout) { Fabricate :layout, :name => 'main' }
12 |
13 | specify { PufferPages::Layout.find_layout('main').should == layout}
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/spec/models/puffer_pages/snippet_spec.rb:
--------------------------------------------------------------------------------
1 | # encoding: UTF-8
2 | require 'spec_helper'
3 |
4 | describe PufferPages::Snippet do
5 | describe "validations" do
6 | it { should validate_presence_of(:name) }
7 | it { should validate_uniqueness_of(:name) }
8 | end
9 |
10 | describe "#find_snippet" do
11 | let!(:snippet) { Fabricate :snippet, :name => 'main' }
12 |
13 | specify { PufferPages::Snippet.find_snippet('main').should == snippet}
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/lib/puffer_pages/helpers.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Helpers
3 | def puffer_pages_context
4 | drops = assigns.each_with_object({}) do |(key, value), result|
5 | result[key] = value if value.respond_to?(:to_liquid)
6 | end
7 | registers = assigns.each_with_object({}) do |(key, value), result|
8 | result[key] = value
9 | end
10 |
11 | { drops: drops, registers: registers.merge!(:controller => controller) }
12 | end
13 | end
14 | end
--------------------------------------------------------------------------------
/spec/lib/liquid/tags/scope_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe PufferPages::Liquid::Tags::Scope do
4 | let(:klass) do
5 | Class.new do
6 | include PufferPages::Backends::Mixins::Renderable
7 |
8 | def render *args
9 | render_template *args
10 | end
11 | end
12 | end
13 | let(:template) do
14 | klass.new
15 | end
16 |
17 | specify { template.render("{% scope foo: 'hello' %}{{ foo }}{% endscope %}").should == 'hello' }
18 | end
19 |
--------------------------------------------------------------------------------
/lib/puffer_pages/handlers/base.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Handlers
3 | class Base
4 | attr_reader :type
5 |
6 | def initialize type
7 | @type = type
8 | end
9 |
10 | def process renderable, context = nil
11 | renderable.render context
12 | end
13 |
14 | def codemirror_mode
15 | 'text/x-liquid-html'
16 | end
17 | end
18 | end
19 | end
20 |
21 | PufferPages::Handlers.register PufferPages::Handlers::Base, :html
22 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20130118071512_create_page_parts.rb:
--------------------------------------------------------------------------------
1 | # This migration comes from puffer_pages (originally 20090504132337)
2 | class CreatePageParts < ActiveRecord::Migration
3 | def self.up
4 | create_table :page_parts do |t|
5 | t.string :name
6 | t.text :body
7 | t.integer :page_id
8 |
9 | t.timestamps
10 | end
11 |
12 | add_index :page_parts, :name
13 | add_index :page_parts, :page_id
14 | end
15 |
16 | def self.down
17 | drop_table :page_parts
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/secret_token.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 | # Make sure the secret is at least 30 characters and all random,
6 | # no regular words or you'll be exposed to dictionary attacks.
7 | Dummy::Application.config.secret_token = '8cf6d9f23949d20957da43a2b5b54c77d84a52592120f14e54969949efef0508ad8cb263ce3949a78ebff63b3143b237e26bc2d147609f831dc57204cecd2561'
8 |
--------------------------------------------------------------------------------
/spec/lib/liquid/tags/image_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe PufferPages::Liquid::Tags::Image do
4 | describe 'snippet' do
5 | let!(:root) { Fabricate :root }
6 |
7 | specify { root.render("{% image 'i.png' %}").should == "<%= image_tag 'i.png', {} %>" }
8 | specify { root.render("{% image 'i.png', alt:'txt' %}").should == "<%= image_tag 'i.png', {\"alt\"=>\"txt\"} %>" }
9 | specify { root.render("{% image 'i.png', alt:var %}", var: 'txt').should == "<%= image_tag 'i.png', {\"alt\"=>\"txt\"} %>" }
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/spec/fabricators/snippets_fabricator.rb:
--------------------------------------------------------------------------------
1 | Fabricator(:snippet, class_name: :'puffer_pages/snippet') do
2 | name { Forgery::LoremIpsum.word(random: true) }
3 | body { Forgery::LoremIpsum.sentence(random: true) }
4 | if PufferPages.localize
5 | body_translations do
6 | (I18n.available_locales - [I18n.locale]).each_with_object({}) do |locale, result|
7 | result[locale] = Forgery::LoremIpsum.sentence(random: true)
8 | end
9 | end
10 | end
11 | end
12 |
13 | Fabricator(:custom, from: :snippet) do
14 | name { "custom" }
15 | end
16 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 | #
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json]
9 | end
10 |
11 | # Disable root element in JSON by default.
12 | ActiveSupport.on_load(:active_record) do
13 | self.include_root_in_json = false
14 | end
15 |
--------------------------------------------------------------------------------
/spec/lib/core_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe 'Core' do
4 |
5 | describe 'arrange' do
6 |
7 | it 'should generate proper hash' do
8 | @root = Fabricate :page, :layout_name => 'foo_layout'
9 | @foo = Fabricate :page, :slug => 'foo', :parent => @root
10 | @bar = Fabricate :page, :slug => 'bar', :parent => @foo
11 | @baz = Fabricate :page, :slug => 'baz', :parent => @root
12 |
13 | @root.reload.self_and_descendants.all.arranged.should == @root.reload.self_and_descendants.arrange
14 | end
15 |
16 | end
17 |
18 | end
19 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/puffer/codemirror/neat.css:
--------------------------------------------------------------------------------
1 | .cm-s-neat span.cm-comment { color: #a86; }
2 | .cm-s-neat span.cm-keyword { line-height: 1em; font-weight: bold; color: blue; }
3 | .cm-s-neat span.cm-string { color: #a22; }
4 | .cm-s-neat span.cm-builtin { line-height: 1em; font-weight: bold; color: #077; }
5 | .cm-s-neat span.cm-special { line-height: 1em; font-weight: bold; color: #0aa; }
6 | .cm-s-neat span.cm-variable { color: black; }
7 | .cm-s-neat span.cm-number, .cm-s-neat span.cm-atom { color: #3a3; }
8 | .cm-s-neat span.cm-meta {color: #555;}
9 | .cm-s-neat span.cm-link { color: #3a3; }
10 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format
4 | # (all these examples are active by default):
5 | # ActiveSupport::Inflector.inflections do |inflect|
6 | # inflect.plural /^(ox)$/i, '\1en'
7 | # inflect.singular /^(ox)en/i, '\1'
8 | # inflect.irregular 'person', 'people'
9 | # inflect.uncountable %w( fish sheep )
10 | # end
11 | #
12 | # These inflection rules are supported but not enabled by default:
13 | # ActiveSupport::Inflector.inflections do |inflect|
14 | # inflect.acronym 'RESTful'
15 | # end
16 |
--------------------------------------------------------------------------------
/lib/puffer_pages/liquid/tracker.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Liquid
3 | class Tracker
4 |
5 | def initialize
6 | @ids = []
7 | end
8 |
9 | def register content
10 | @ids << Digest::MD5.hexdigest(SecureRandom.uuid)
11 | content.gsub(/<%/, "<#{@ids.last}%").gsub(/%>/, "%#{@ids.last}>")
12 | end
13 |
14 | def cleanup content
15 | ids = @ids.join('|')
16 | content = content.gsub(/<%/, "<%").gsub(/%>/, "%>")# unless PufferPages.allow_erb
17 | content.gsub(/<(#{ids})%/, "<%").gsub(/%(#{ids})>/, "%>")
18 | end
19 |
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/lib/puffer_pages/extensions/core.rb:
--------------------------------------------------------------------------------
1 | Array.class_eval do
2 | def arranged
3 | arranged = ActiveSupport::OrderedHash.new
4 | insertion_points = [arranged]
5 | depth = 0
6 | each do |node|
7 | next if node.depth > depth && insertion_points.last.keys.last && node.parent_id != insertion_points.last.keys.last.id
8 | insertion_points.push insertion_points.last.values.last if node.depth > depth
9 | (depth - node.depth).times { insertion_points.pop } if node.depth < depth
10 | insertion_points.last.merge! node => ActiveSupport::OrderedHash.new
11 | depth = node.depth
12 | end
13 | arranged
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/spec/fabricators/page_parts_fabricator.rb:
--------------------------------------------------------------------------------
1 | Fabricator(:page_part, class_name: :'puffer_pages/page_part') do
2 | name { Forgery::LoremIpsum.word }
3 | body { |attrs| "PagePart: `#{attrs[:name]}`" }
4 | if PufferPages.localize
5 | body_translations do
6 | (I18n.available_locales - [I18n.locale]).each_with_object({}) do |locale, result|
7 | result[locale] = Forgery::LoremIpsum.sentence(random: true)
8 | end
9 | end
10 | end
11 | end
12 |
13 | Fabricator(:main, from: :page_part) do
14 | name { PufferPages.primary_page_part_name }
15 | end
16 |
17 | Fabricator(:sidebar, from: :page_part) do
18 | name { 'sidebar' }
19 | end
20 |
--------------------------------------------------------------------------------
/spec/fabricators/pages_fabricator.rb:
--------------------------------------------------------------------------------
1 | Fabricator(:page, class_name: :'puffer_pages/page') do
2 | name { Forgery::LoremIpsum.sentence(random: true) }
3 | slug ''
4 | status {'published'}
5 |
6 | after_build do |page|
7 | page.page_parts.each do |page_part|
8 | if page_part.body == "PagePart: `#{page_part.name}`"
9 | page_part.body = "PagePart: `#{page_part.name}`, Page: `#{page.location}`"
10 | end
11 | end
12 | end
13 | end
14 |
15 | Fabricator(:root, from: :page) do
16 | slug ''
17 | layout_name 'application'
18 | end
19 |
20 | Fabricator(:foo_root, from: :page) do
21 | slug ''
22 | layout_name 'foo_layout'
23 | end
24 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/puffer/codemirror/elegant.css:
--------------------------------------------------------------------------------
1 | .cm-s-elegant span.cm-number, .cm-s-elegant span.cm-string, .cm-s-elegant span.cm-atom {color: #762;}
2 | .cm-s-elegant span.cm-comment {color: #262; font-style: italic; line-height: 1em;}
3 | .cm-s-elegant span.cm-meta {color: #555; font-style: italic; line-height: 1em;}
4 | .cm-s-elegant span.cm-variable {color: black;}
5 | .cm-s-elegant span.cm-variable-2 {color: #b11;}
6 | .cm-s-elegant span.cm-qualifier {color: #555;}
7 | .cm-s-elegant span.cm-keyword {color: #730;}
8 | .cm-s-elegant span.cm-builtin {color: #30a;}
9 | .cm-s-elegant span.cm-error {background-color: #fdd;}
10 | .cm-s-elegant span.cm-link {color: #762;}
11 |
--------------------------------------------------------------------------------
/db/migrate/20090422092419_create_pages.rb:
--------------------------------------------------------------------------------
1 | class CreatePages < ActiveRecord::Migration
2 | def self.up
3 | create_table :pages do |t|
4 | t.string :name
5 | t.string :slug
6 | t.string :location
7 | t.text :title
8 | t.text :description
9 | t.text :keywords
10 | t.string :layout_name
11 | t.string :status
12 |
13 | t.integer :parent_id
14 | t.integer :lft
15 | t.integer :rgt
16 | t.integer :depth, :default => 0
17 |
18 | t.timestamps
19 | end
20 |
21 | add_index :pages, :slug
22 | add_index :pages, :location
23 | end
24 |
25 | def self.down
26 | drop_table :pages
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/lib/puffer_pages/rspec/view_rendering.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Rspec
3 | module ViewRendering
4 | extend ActiveSupport::Concern
5 |
6 | included do
7 | before do
8 | unless render_views?
9 | dummy_page = PufferPages::Page.new
10 | dummy_page.stub(:inherited_layout) { '' }
11 | dummy_page.stub(:dummy_page?) { true }
12 | controller.stub(:_puffer_page_for) do |location, scope|
13 | dummy_page.stub(:location) { PufferPages::Page::normalize_path(location) }
14 | dummy_page
15 | end
16 | end
17 | end
18 | end
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # encoding: UTF-8
2 | require 'rubygems'
3 | begin
4 | require 'bundler/setup'
5 | rescue LoadError
6 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7 | end
8 |
9 | require 'rake'
10 | require 'rdoc/task'
11 |
12 | require 'rspec/core'
13 | require 'rspec/core/rake_task'
14 |
15 | RSpec::Core::RakeTask.new(:spec)
16 |
17 | task :default => :spec
18 |
19 | Rake::RDocTask.new(:rdoc) do |rdoc|
20 | rdoc.rdoc_dir = 'rdoc'
21 | rdoc.title = 'Puffer Pages'
22 | rdoc.options << '--line-numbers' << '--inline-source'
23 | rdoc.rdoc_files.include('README.rdoc')
24 | rdoc.rdoc_files.include('lib/**/*.rb')
25 | end
26 |
27 | require "bundler/gem_tasks"
28 |
--------------------------------------------------------------------------------
/lib/puffer_pages/liquid/tags/partials.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Liquid
3 | module Tags
4 |
5 | class Partials < Include
6 |
7 | private
8 |
9 | def _read_template_from_file_system(context)
10 | file_system = context.registers[:file_system] || Liquid::Template.file_system
11 | template_name = "#{@tag_name.pluralize}/#{context[@template_name]}"
12 |
13 | file_system.read_template_file(template_name, context)
14 | end
15 | end
16 |
17 | end
18 | end
19 | end
20 |
21 | Liquid::Template.register_tag('snippet', PufferPages::Liquid::Tags::Partials)
22 | Liquid::Template.register_tag('layout', PufferPages::Liquid::Tags::Partials)
23 |
--------------------------------------------------------------------------------
/lib/puffer_pages/handlers/yaml.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Handlers
3 | class Yaml < Base
4 | def process renderable, context = nil
5 | renderable.self_and_ancestors.where(handler: 'yaml').reverse.each_with_object({}) do |renderable, result|
6 | load_arguments = [renderable.render(context)]
7 | load_arguments.push renderable.name if YAML.method(:load).arity == -2
8 | hash = YAML.load *load_arguments
9 | result.deep_merge! hash
10 | end
11 | end
12 |
13 | def codemirror_mode
14 | 'text/x-liquid-yaml'
15 | end
16 | end
17 | end
18 | end
19 |
20 | PufferPages::Handlers.register PufferPages::Handlers::Yaml, :yaml
21 |
--------------------------------------------------------------------------------
/lib/puffer_pages/backends.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Backends
3 | autoload :Snippet, 'puffer_pages/backends/models/snippet'
4 | autoload :Layout, 'puffer_pages/backends/models/layout'
5 | autoload :Page, 'puffer_pages/backends/models/page'
6 | autoload :PagePart, 'puffer_pages/backends/models/page_part'
7 | autoload :Origin, 'puffer_pages/backends/models/origin'
8 |
9 | module Mixins
10 | autoload :Renderable, 'puffer_pages/backends/models/mixins/renderable'
11 | autoload :Importable, 'puffer_pages/backends/models/mixins/importable'
12 | autoload :Translatable, 'puffer_pages/backends/models/mixins/translatable'
13 | autoload :Localable, 'puffer_pages/backends/models/mixins/localable'
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20130118071511_create_pages.rb:
--------------------------------------------------------------------------------
1 | # This migration comes from puffer_pages (originally 20090422092419)
2 | class CreatePages < ActiveRecord::Migration
3 | def self.up
4 | create_table :pages do |t|
5 | t.string :name
6 | t.string :slug
7 | t.string :location
8 | t.text :title
9 | t.text :description
10 | t.text :keywords
11 | t.string :layout_name
12 | t.string :status
13 |
14 | t.integer :parent_id
15 | t.integer :lft
16 | t.integer :rgt
17 | t.integer :depth, :default => 0
18 |
19 | t.timestamps
20 | end
21 |
22 | add_index :pages, :slug
23 | add_index :pages, :location
24 | end
25 |
26 | def self.down
27 | drop_table :pages
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/lib/puffer_pages/globalize/migrator.rb:
--------------------------------------------------------------------------------
1 | require 'globalize'
2 |
3 | module PufferPages
4 | module Globalize
5 | class Migrator < ::Globalize::ActiveRecord::Migration::Migrator
6 | def create_translation_table
7 | connection.create_table(translations_table_name, id: false) do |t|
8 | t.uuid :id, primary_key: true
9 | t.uuid "#{table_name.sub(/^#{table_name_prefix}/, '').singularize}_id"
10 | t.string :locale
11 | fields.each do |name, options|
12 | if options.is_a? Hash
13 | t.column name, options.delete(:type), options
14 | else
15 | t.column name, options
16 | end
17 | end
18 | t.timestamps
19 | end
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/app/helpers/puffer_pages_helper.rb:
--------------------------------------------------------------------------------
1 | module PufferPagesHelper
2 | unloadable
3 |
4 | def possible_layouts
5 | inherited_layout + (application_layouts + puffer_layouts).uniq.sort
6 | end
7 |
8 | def application_layouts
9 | Dir.glob("#{view_paths.first}/layouts/[^_]*").flatten.map {|path| File.basename(path).gsub(/\..*$/, '')}.uniq
10 | end
11 |
12 | def puffer_layouts
13 | PufferPages::Layout.order(:name).all.map(&:name)
14 | end
15 |
16 | def inherited_layout
17 | record.inherited_layout_name && !record.root? ? [[t('puffer_pages.inherited_layout', :name => record.inherited_layout_name), '']] : []
18 | end
19 |
20 | def possible_statuses
21 | PufferPages::Page.statuses
22 | end
23 |
24 | def tree_page
25 | render :partial => 'tree_page', :object => record
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/spec/fabricators/layouts_fabricator.rb:
--------------------------------------------------------------------------------
1 | Fabricator(:layout, class_name: :'puffer_pages/layout') do
2 | name { Forgery::LoremIpsum.word(random: true) }
3 | body { Forgery::LoremIpsum.sentence(random: true) }
4 | if PufferPages.localize
5 | body_translations do
6 | (I18n.available_locales - [I18n.locale]).each_with_object({}) do |locale, result|
7 | result[locale] = Forgery::LoremIpsum.sentence(random: true)
8 | end
9 | end
10 | end
11 | end
12 |
13 | Fabricator(:application, from: :layout) do
14 | name { "application" }
15 | end
16 |
17 | Fabricator(:foo_layout, from: :layout) do
18 | name { "foo_layout" }
19 | body { "foo_layout {{self.slug}}" }
20 | end
21 |
22 | Fabricator(:bar_layout, from: :layout) do
23 | name { "bar_layout" }
24 | body { "bar_layout {{self.slug}}" }
25 | end
26 |
--------------------------------------------------------------------------------
/lib/puffer_pages/renderer.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | class Renderer < ActionView::TemplateRenderer
3 | def render(context, options)
4 | @view = context
5 | super
6 | end
7 |
8 | def determine_template(options)
9 | @view.assign(@view.assigns.merge!('puffer_page' => options[:puffer_page]))
10 |
11 | super
12 | rescue ActionView::MissingTemplate
13 | options[:text] = ''
14 | super
15 | end
16 |
17 | def find_layout(layout, keys)
18 | layout = "<%= render(:inline => @puffer_page.render(puffer_pages_context),
19 | :layout => @puffer_page.layout_for_render) %>"
20 |
21 | handler = ActionView::Template.handler_for_extension("erb")
22 | ActionView::Template.new(layout, "puffer pages layout wrapper", handler, :locals => keys)
23 | end
24 | end
25 | end
--------------------------------------------------------------------------------
/spec/dummy/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
17 |
18 |
19 |
20 |
21 |
22 |
The change you wanted was rejected.
23 |
Maybe you tried to change something you didn't have access to.
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/lib/puffer_pages/liquid/tags/scope.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Liquid
3 | module Tags
4 |
5 | class Scope < ::Liquid::Block
6 | def initialize(tag_name, markup, tokens)
7 | @attributes = {}
8 | markup.scan(::Liquid::TagAttributes) do |key, value|
9 | @attributes[key] = value
10 | end
11 |
12 | super
13 | end
14 |
15 | def render(context)
16 | context.stack do
17 | @attributes.each do |key, value|
18 | context[key] = context[value]
19 | end
20 |
21 | super
22 | end
23 | end
24 | end
25 |
26 | end
27 | end
28 | end
29 |
30 | Liquid::Template.register_tag('scope', PufferPages::Liquid::Tags::Scope)
31 | Liquid::Template.register_tag('context', PufferPages::Liquid::Tags::Scope)
32 |
--------------------------------------------------------------------------------
/spec/dummy/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
17 |
18 |
19 |
20 |
21 |
22 |
The page you were looking for doesn't exist.
23 |
You may have mistyped the address or the page may have moved.
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/spec/dummy/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
17 |
18 |
19 |
20 |
21 |
22 |
We're sorry, but something went wrong.
23 |
We've been notified about this issue and we'll take a look at it shortly.
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/lib/puffer_pages/liquid/tags/yield.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Liquid
3 | module Tags
4 |
5 | class Yield < ::Liquid::Tag
6 | Syntax = /^(#{::Liquid::QuotedFragment})/
7 |
8 | def initialize(tag_name, markup, tokens)
9 | if markup =~ Syntax
10 | @name = $1
11 | elsif markup.blank?
12 | @name = nil
13 | else
14 | raise SyntaxError.new("Syntax Error in 'yield' - Valid syntax: yield [content_name]")
15 | end
16 |
17 | super
18 | end
19 |
20 | def render(context)
21 | context.registers[:tracker].register(@name ?
22 | "<%= yield :'#{@name}' %>" :
23 | "<%= yield %>")
24 | end
25 | end
26 |
27 | end
28 | end
29 | end
30 |
31 | Liquid::Template.register_tag('yield', PufferPages::Liquid::Tags::Yield)
32 |
--------------------------------------------------------------------------------
/lib/puffer_pages/engine.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | class Engine < Rails::Engine
3 | engine_name :puffer_pages
4 |
5 | config.autoload_paths << File.join(root, 'lib')
6 | config.puffer_pages = PufferPages.config
7 |
8 | initializer 'puffer_pages.install_i18n_backend', after: 'build_middleware_stack' do
9 | if PufferPages.install_i18n_backend
10 | I18n.backend = I18n::Backend::Chain.new(PufferPages.i18n_backend, I18n.backend)
11 | end
12 | end
13 |
14 | initializer 'puffer_pages.initialize_cache', after: 'initialize_cache' do
15 | PufferPages.cache_store = config.puffer_pages.cache_store || ::Rails.cache
16 | PufferPages.config.perform_caching = config.action_controller.perform_caching
17 | end
18 |
19 | ActiveSupport.on_load(:action_view) do
20 | ActionView::Base.send :include, PufferPages::Helpers
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/puffer_pages/handlers.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Handlers
3 | class HandlerMissing < Exception; end
4 |
5 | def self.handlers
6 | @handlers ||= HashWithIndifferentAccess.new
7 | end
8 |
9 | def self.register klass, *types
10 | handlers.merge! Hash[types.flatten.map { |type| [type, klass.new(type)] }]
11 | end
12 |
13 | def self.process type, *args
14 | raise HandlerMissing.new("Can not find handler for '#{type}' type") unless handlers[type]
15 | handlers[type].process(*args)
16 | end
17 |
18 | def self.select
19 | handlers.values.map { |handler| [
20 | I18n.t("puffer_pages.handlers.#{handler.type}"),
21 | handler.type,
22 | { 'data-codemirror-mode' => handler.codemirror_mode }
23 | ] }
24 | end
25 | end
26 | end
27 |
28 | require 'puffer_pages/handlers/base'
29 | require 'puffer_pages/handlers/yaml'
30 |
--------------------------------------------------------------------------------
/lib/puffer_pages/backends/controllers/origins_base.rb:
--------------------------------------------------------------------------------
1 | class PufferPages::OriginsBase < Puffer::Base
2 | skip_before_filter :require_puffer_user, only: :export
3 |
4 | member do
5 | get :import
6 | end
7 |
8 | collection do
9 | get :export, display: false
10 | end
11 |
12 | def import
13 | @record = resource.member
14 | @record.import!
15 | redirect_to admin_origins_path
16 | end
17 |
18 | def export
19 | if params[:token] == PufferPages.access_token
20 | render json: resource.model.export_json
21 | else
22 | render nothing: true, status: 401
23 | end
24 | end
25 |
26 | setup do
27 | group :pages
28 | model_name :'puffer_pages/origin'
29 | end
30 |
31 | index do
32 | field :name
33 | field :uri
34 | field :token
35 | end
36 |
37 | form do
38 | field :name
39 | field :host
40 | field :path
41 | field :token
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/lib/puffer_pages/backends/models/layout.rb:
--------------------------------------------------------------------------------
1 | class PufferPages::Backends::Layout < ActiveRecord::Base
2 | include ActiveUUID::UUID
3 | include PufferPages::Backends::Mixins::Renderable
4 | include PufferPages::Backends::Mixins::Importable
5 | include PufferPages::Backends::Mixins::Translatable
6 | self.abstract_class = true
7 | self.table_name = :layouts
8 |
9 | attr_protected
10 |
11 | validates_presence_of :name
12 | validates_uniqueness_of :name
13 |
14 | def self.find_layout(name)
15 | where(:name => name).first
16 | end
17 |
18 | def render *args
19 | _, context = normalize_render_options *args
20 | render_template body, context, additional_render_options
21 | end
22 |
23 | def additional_render_options
24 | { environment: { processed: self } }
25 | end
26 |
27 | def i18n_scope
28 | [:layouts, name.to_sym]
29 | end
30 |
31 | def i18n_defaults
32 | []
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/lib/puffer_pages/backends/models/snippet.rb:
--------------------------------------------------------------------------------
1 | class PufferPages::Backends::Snippet < ActiveRecord::Base
2 | include ActiveUUID::UUID
3 | include PufferPages::Backends::Mixins::Renderable
4 | include PufferPages::Backends::Mixins::Importable
5 | include PufferPages::Backends::Mixins::Translatable
6 | self.abstract_class = true
7 | self.table_name = :snippets
8 |
9 | attr_protected
10 |
11 | validates_presence_of :name
12 | validates_uniqueness_of :name
13 |
14 | def self.find_snippet(name)
15 | where(:name => name).first
16 | end
17 |
18 | def render *args
19 | _, context = normalize_render_options *args
20 | render_template body, context, additional_render_options
21 | end
22 |
23 | def additional_render_options
24 | { environment: { processed: self } }
25 | end
26 |
27 | def i18n_scope
28 | [:snippets, name.to_sym]
29 | end
30 |
31 | def i18n_defaults
32 | []
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/spec/dummy/config/database.yml:
--------------------------------------------------------------------------------
1 | # SQLite version 3.x
2 | # gem install sqlite3-ruby (not necessary on OS X Leopard)
3 | development:
4 | adapter: sqlite3
5 | database: db/development.sqlite3
6 | pool: 5
7 | timeout: 5000
8 |
9 | # development:
10 | # adapter: mysql2
11 | # database: puffer_pages_development
12 | # usename: root
13 | # encoding: utf8
14 |
15 | # Warning: The database defined as "test" will be erased and
16 | # re-generated from your development database when you run "rake".
17 | # Do not set this db to the same as development or production.
18 | test:
19 | adapter: sqlite3
20 | database: ":memory:"
21 | pool: 5
22 | timeout: 5000
23 |
24 | pg_test:
25 | adapter: postgresql
26 | hostname: localhost
27 | database: puffer_pages
28 | schema_search_path: public
29 | encoding: utf8
30 |
31 | production:
32 | adapter: sqlite3
33 | database: db/development.sqlite3
34 | pool: 5
35 | timeout: 5000
36 |
--------------------------------------------------------------------------------
/spec/lib/liquid/backend_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe PufferPages::Liquid::Backend do
4 | let(:backend) { described_class.new }
5 | let(:translations) { { en: { hello: 'PufferPages world' } } }
6 |
7 | specify { backend.translations == {} }
8 |
9 | specify do
10 | contextualize(page_translations: translations) do
11 | backend.translations
12 | end.should == translations
13 | end
14 |
15 | specify do
16 | contextualize(page_translations: translations) do
17 | backend.translate(:en, 'hello')
18 | end.should == 'PufferPages world'
19 | end
20 |
21 | context 'backend installation' do
22 | context 'fallbacks' do
23 | specify do
24 | contextualize(page_translations: translations) do
25 | I18n.with_locale :ru do
26 | I18n.translate('hello')
27 | end
28 | end.should == 'PufferPages world'
29 | end
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/lib/puffer_pages/extensions/renderer.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Extensions
3 | module Renderer
4 | extend ActiveSupport::Concern
5 |
6 | included do
7 | alias_method_chain :render, :puffer_pages
8 | end
9 |
10 | def render_with_puffer_pages(context, options)
11 | if options.key?(:puffer_page) && options[:puffer_page].is_a?(PufferPages::Page)
12 | render_puffer_page(context, options)
13 | else
14 | render_without_puffer_pages(context, options)
15 | end
16 | end
17 |
18 | def render_puffer_page(context, options)
19 | _puffer_page_renderer.render(context, options)
20 | end
21 |
22 | private
23 |
24 | def _puffer_page_renderer #:nodoc:
25 | @_puffer_page_renderer ||= PufferPages::Renderer.new(@lookup_context)
26 | end
27 | end
28 | end
29 | end
30 |
31 | ActionView::Renderer.send :include, PufferPages::Extensions::Renderer
--------------------------------------------------------------------------------
/lib/puffer_pages/log_subscriber.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | class LogSubscriber < ActiveSupport::LogSubscriber
3 | def render_page event
4 | message = " PufferPages: rendered page /#{event.payload[:subject].location} #{duration(event)}"
5 | info message
6 | end
7 |
8 | def render_page_part event
9 | message = " PufferPages: rendered page_part #{event.payload[:subject].name} #{duration(event)}"
10 | debug message
11 | end
12 |
13 | def render_layout event
14 | message = " PufferPages: rendered layout #{event.payload[:subject].name} #{duration(event)}"
15 | debug message
16 | end
17 |
18 | def render_snippet event
19 | message = " PufferPages: rendered snippet #{event.payload[:subject].name} #{duration(event)}"
20 | debug message
21 | end
22 |
23 | def duration event
24 | '(%.1fms)' % event.duration
25 | end
26 | end
27 | end
28 |
29 | PufferPages::LogSubscriber.attach_to :puffer_pages
30 |
--------------------------------------------------------------------------------
/app/assets/javascripts/puffer/liquid.js:
--------------------------------------------------------------------------------
1 | CodeMirror.defineMode("text/x-liquid-html", function(config) {
2 | return CodeMirror.multiplexingMode(
3 | CodeMirror.getMode(config, "text/html"),
4 | {
5 | open: "{{", close: "}}",
6 | mode: CodeMirror.getMode(config, "text/x-liquid-variable"),
7 | delimStyle: "tag"
8 | },
9 | {
10 | open: "{%", close: "%}",
11 | mode: CodeMirror.getMode(config, "text/x-liquid-tag"),
12 | delimStyle: "tag"
13 | }
14 | );
15 | });
16 |
17 | CodeMirror.defineMode("text/x-liquid-yaml", function(config) {
18 | return CodeMirror.multiplexingMode(
19 | CodeMirror.getMode(config, "text/x-yaml"),
20 | {
21 | open: "{{", close: "}}",
22 | mode: CodeMirror.getMode(config, "text/x-liquid-variable"),
23 | delimStyle: "tag"
24 | },
25 | {
26 | open: "{%", close: "%}",
27 | mode: CodeMirror.getMode(config, "text/x-liquid-tag"),
28 | delimStyle: "tag"
29 | }
30 | );
31 | });
32 |
--------------------------------------------------------------------------------
/lib/puffer_pages/extensions/context.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Extensions
3 | module Liquid
4 | module Context
5 | extend ActiveSupport::Concern
6 |
7 | included do
8 | alias_method_chain :resolve, :interpolation
9 | end
10 |
11 | def resolve_with_interpolation key
12 | if key.is_a? Symbol
13 | scope = @scopes.detect { |s| s.key? key }
14 | scope ||= @environments.detect { |s| s.key? key }
15 | scope[key] if scope
16 | else
17 | resolved = resolve_without_interpolation key
18 | if resolved.is_a?(String) && key =~ /^"(.*)"$/
19 | resolved.gsub!(/\#\{(.*?)\}/) do
20 | ::Liquid::Variable.new($1).render(self)
21 | end
22 | end
23 | resolved
24 | end
25 | end
26 | end
27 | end
28 | end
29 | end
30 |
31 | Liquid::Context.send :include, PufferPages::Extensions::Liquid::Context
32 |
--------------------------------------------------------------------------------
/lib/puffer_pages/rspec/matchers.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Rspec
3 | module Matchers
4 |
5 | end
6 | end
7 | end
8 |
9 | require 'puffer_pages/rspec/matchers/render_page'
10 |
11 | RSpec::Rails::ControllerExampleGroup.class_eval do
12 | def puffer_pages_render
13 | @puffer_pages_render
14 | end
15 |
16 | def self.included_with_puffer_pages base
17 | base.class_eval do
18 | around(:each) do |example|
19 | @puffer_pages_render = {}
20 | ActiveSupport::Notifications.subscribe('render_page.puffer_pages') do |name, start, finish, id, payload|
21 | @puffer_pages_render[payload[:subject]] ||= []
22 | @puffer_pages_render[payload[:subject]].push payload
23 | end
24 | example.run
25 | ActiveSupport::Notifications.unsubscribe('render_page.puffer_pages')
26 | end
27 | end
28 | included_without_puffer_pages base
29 | end
30 |
31 | singleton_class.alias_method_chain :included, :puffer_pages
32 | end
33 |
--------------------------------------------------------------------------------
/lib/puffer_pages/liquid/tags/include.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Liquid
3 | module Tags
4 |
5 | class Include < ::Liquid::Include
6 | def render(context)
7 | source = _read_template_from_file_system(context)
8 | variable = context[@variable_name || @template_name[1..-2]]
9 |
10 | context.stack do
11 | @attributes.each do |key, value|
12 | context[key] = context[value]
13 | end
14 |
15 | if variable.is_a?(Array)
16 | variable.collect do |variable|
17 | context[@template_name[1..-2]] = variable
18 | source.render(context)
19 | end
20 | else
21 | context[@template_name[1..-2]] = variable
22 | source.render(context)
23 | end
24 | end
25 | end
26 | end
27 |
28 | end
29 | end
30 | end
31 |
32 | Liquid::Template.register_tag('include', PufferPages::Liquid::Tags::Include)
33 |
--------------------------------------------------------------------------------
/lib/puffer_pages/liquid/tags/render.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Liquid
3 | module Tags
4 |
5 | class Render < ::Liquid::Tag
6 | Syntax = /^(#{::Liquid::QuotedFragment})/
7 |
8 | def initialize(tag_name, markup, tokens)
9 | if markup =~ Syntax
10 | @path = $1
11 | else
12 | raise SyntaxError.new("Syntax Error in 'render' - Valid syntax: render path")
13 | end
14 |
15 | super
16 | end
17 |
18 | def render(context)
19 | path = context[@path]
20 | context.registers[:tracker].register("<%=
21 | old_formats = formats
22 | begin
23 | self.formats = old_formats | [:html]
24 | render '#{path}'
25 | ensure
26 | self.formats = old_formats
27 | end
28 | %>")
29 | end
30 | end
31 |
32 | end
33 | end
34 | end
35 |
36 | Liquid::Template.register_tag('render', PufferPages::Liquid::Tags::Render)
37 |
--------------------------------------------------------------------------------
/lib/puffer_pages/liquid/tags/super.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Liquid
3 | module Tags
4 |
5 | class Super < ::Liquid::Include
6 | def initialize(tag_name, markup, tokens)
7 | @attributes = {}
8 | markup.scan(::Liquid::TagAttributes) do |key, value|
9 | @attributes[key] = value
10 | end
11 | end
12 |
13 | def render(context)
14 | source = _read_template_from_file_system(context)
15 |
16 | context.stack do
17 | @attributes.each do |key, value|
18 | context[key] = context[value]
19 | end
20 |
21 | source.render(context)
22 | end
23 | end
24 |
25 | private
26 | def _read_template_from_file_system(context)
27 | file_system = context.registers[:file_system] || Liquid::Template.file_system
28 | file_system.read_template_file(:super, context)
29 | end
30 | end
31 |
32 | end
33 | end
34 | end
35 |
36 | Liquid::Template.register_tag('super', PufferPages::Liquid::Tags::Super)
37 |
--------------------------------------------------------------------------------
/lib/puffer_pages/liquid/tags/image.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Liquid
3 | module Tags
4 |
5 | class Image < ::Liquid::Tag
6 | Syntax = /^(#{::Liquid::QuotedFragment})/
7 |
8 | def initialize(tag_name, markup, tokens)
9 | if markup =~ Syntax
10 | @path = $1
11 | else
12 | raise SyntaxError.new("Syntax Error in 'image' - Valid syntax: image '/path/to/image'")
13 | end
14 |
15 | @attributes = {}
16 | markup.scan(::Liquid::TagAttributes) do |key, value|
17 | @attributes[key] = value
18 | end
19 |
20 | super
21 | end
22 |
23 | def render(context)
24 | attributes = {}
25 | @attributes.each do |key, value|
26 | attributes[key] = context[value]
27 | end
28 |
29 | context.registers[:tracker].register("<%= image_tag #{@path}, #{attributes} %>")
30 | end
31 | end
32 |
33 | end
34 | end
35 | end
36 |
37 | Liquid::Template.register_tag('image', PufferPages::Liquid::Tags::Image)
38 |
--------------------------------------------------------------------------------
/spec/lib/liquid/interpolation_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe 'Interpolation' do
4 | let!(:root) { Fabricate :root }
5 |
6 | context 'double quotes' do
7 | let!(:layout) { Fabricate :application, body: '{% assign var = "#{foo} interpolation" %}{{ var }}' }
8 | specify { root.render('foo' => 'bar').should == 'bar interpolation' }
9 | end
10 |
11 | context 'single quotes' do
12 | let!(:layout) { Fabricate :application, body: '{% assign var = \'#{foo} interpolation\' %}{{ var }}' }
13 | specify { root.render('foo' => 'bar').should == '#{foo} interpolation' }
14 | end
15 |
16 | context 'filters' do
17 | let!(:layout) { Fabricate :application, body: '{% assign var = "#{foo | capitalize} interpolation" %}{{ var }}' }
18 | specify { root.render('foo' => 'bar').should == 'Bar interpolation' }
19 | end
20 |
21 | context 'filters with parameters' do
22 | let!(:layout) { Fabricate :application, body: '{% assign var = "#{foo | minus: 1} interpolation" %}{{ var }}' }
23 | specify { root.render('foo' => 4).should == '3 interpolation' }
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2011 YOURNAME
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/lib/puffer_pages/liquid/tags/array.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Liquid
3 | module Tags
4 |
5 | class Array < ::Liquid::Tag
6 | Syntax = /^(#{::Liquid::VariableSignature}+)\s*=\s*(.*)\s*/
7 |
8 | def initialize(tag_name, markup, tokens)
9 | if markup =~ Syntax
10 | @variable_name = $1
11 | @items = variables_from_string($2)
12 | else
13 | raise SyntaxError.new("Syntax Error in 'array' - Valid syntax: array array_name = item[, item ...]")
14 | end
15 |
16 | super
17 | end
18 |
19 | def render(context)
20 | context[@variable_name] = @items.map { |item| context[item] }
21 | ''
22 | end
23 |
24 | private
25 |
26 | def variables_from_string(markup)
27 | markup.split(',').map do |var|
28 | var.strip =~ /\s*(#{::Liquid::QuotedFragment})\s*/
29 | $1 ? $1 : nil
30 | end.compact
31 | end
32 |
33 | end
34 |
35 | end
36 | end
37 | end
38 |
39 | Liquid::Template.register_tag('array', PufferPages::Liquid::Tags::Array)
40 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/puffer/codemirror/cobalt.css:
--------------------------------------------------------------------------------
1 | .cm-s-cobalt.CodeMirror { background: #002240; color: white; }
2 | .cm-s-cobalt div.CodeMirror-selected { background: #b36539 !important; }
3 | .cm-s-cobalt .CodeMirror-gutters { background: #002240; border-right: 1px solid #aaa; }
4 | .cm-s-cobalt .CodeMirror-linenumber { color: #d0d0d0; }
5 | .cm-s-cobalt .CodeMirror-cursor { border-left: 1px solid white !important; }
6 |
7 | .cm-s-cobalt span.cm-comment { color: #08f; }
8 | .cm-s-cobalt span.cm-atom { color: #845dc4; }
9 | .cm-s-cobalt span.cm-number, .cm-s-cobalt span.cm-attribute { color: #ff80e1; }
10 | .cm-s-cobalt span.cm-keyword { color: #ffee80; }
11 | .cm-s-cobalt span.cm-string { color: #3ad900; }
12 | .cm-s-cobalt span.cm-meta { color: #ff9d00; }
13 | .cm-s-cobalt span.cm-variable-2, .cm-s-cobalt span.cm-tag { color: #9effff; }
14 | .cm-s-cobalt span.cm-variable-3, .cm-s-cobalt span.cm-def { color: white; }
15 | .cm-s-cobalt span.cm-error { color: #9d1e15; }
16 | .cm-s-cobalt span.cm-bracket { color: #d8d8d8; }
17 | .cm-s-cobalt span.cm-builtin, .cm-s-cobalt span.cm-special { color: #ff9e59; }
18 | .cm-s-cobalt span.cm-link { color: #845dc4; }
19 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/puffer/codemirror/eclipse.css:
--------------------------------------------------------------------------------
1 | .cm-s-eclipse span.cm-meta {color: #FF1717;}
2 | .cm-s-eclipse span.cm-keyword { line-height: 1em; font-weight: bold; color: #7F0055; }
3 | .cm-s-eclipse span.cm-atom {color: #219;}
4 | .cm-s-eclipse span.cm-number {color: #164;}
5 | .cm-s-eclipse span.cm-def {color: #00f;}
6 | .cm-s-eclipse span.cm-variable {color: black;}
7 | .cm-s-eclipse span.cm-variable-2 {color: #0000C0;}
8 | .cm-s-eclipse span.cm-variable-3 {color: #0000C0;}
9 | .cm-s-eclipse span.cm-property {color: black;}
10 | .cm-s-eclipse span.cm-operator {color: black;}
11 | .cm-s-eclipse span.cm-comment {color: #3F7F5F;}
12 | .cm-s-eclipse span.cm-string {color: #2A00FF;}
13 | .cm-s-eclipse span.cm-string-2 {color: #f50;}
14 | .cm-s-eclipse span.cm-error {color: #f00;}
15 | .cm-s-eclipse span.cm-qualifier {color: #555;}
16 | .cm-s-eclipse span.cm-builtin {color: #30a;}
17 | .cm-s-eclipse span.cm-bracket {color: #cc7;}
18 | .cm-s-eclipse span.cm-tag {color: #170;}
19 | .cm-s-eclipse span.cm-attribute {color: #00c;}
20 | .cm-s-eclipse span.cm-link {color: #219;}
21 |
22 | .cm-s-eclipse .CodeMirror-matchingbracket {
23 | border:1px solid grey;
24 | color:black !important;;
25 | }
26 |
--------------------------------------------------------------------------------
/Guardfile:
--------------------------------------------------------------------------------
1 | # A sample Guardfile
2 | # More info at https://github.com/guard/guard#readme
3 |
4 | guard :rspec do
5 | watch(%r{^spec/.+_spec\.rb$})
6 | watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7 | watch('spec/spec_helper.rb') { "spec" }
8 |
9 | # Rails example
10 | watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
11 | watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
12 | watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
13 | watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
14 | watch('config/routes.rb') { "spec/routing" }
15 | watch('app/controllers/application_controller.rb') { "spec/controllers" }
16 |
17 | # Capybara features specs
18 | watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/features/#{m[1]}_spec.rb" }
19 |
20 | # Turnip features and steps
21 | watch(%r{^spec/acceptance/(.+)\.feature$})
22 | watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
23 | end
24 |
25 |
--------------------------------------------------------------------------------
/lib/puffer_pages/migrations.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Migrations
3 | def self.create_translation_tables! options = {}
4 | unless PufferPages.localize
5 | puts "WARN: Translation tables creation skip. Set `PufferPages.localize = true` to perform it"
6 | return
7 | end
8 | options = options.reverse_merge migrate_data: true
9 |
10 | [PufferPages::PagePart, PufferPages::Layout, PufferPages::Snippet].each do |model|
11 | model.create_translation_table!({
12 | body: { type: :text }
13 | }, options)
14 | puts "-- Created translation table for #{model} with #{options}"
15 | end
16 | end
17 |
18 | def self.drop_translation_tables! options = {}
19 | unless PufferPages.localize
20 | puts "WARN: Translation tables dropping skip. Set `PufferPages.localize = true` to perform it"
21 | return
22 | end
23 | options = options.reverse_merge migrate_data: true
24 |
25 | [PufferPages::PagePart, PufferPages::Layout, PufferPages::Snippet].each do |model|
26 | model.drop_translation_table! options
27 | puts "-- Dropped translation table for #{model} with #{options}"
28 | end
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/puffer/codemirror/night.css:
--------------------------------------------------------------------------------
1 | /* Loosely based on the Midnight Textmate theme */
2 |
3 | .cm-s-night.CodeMirror { background: #0a001f; color: #f8f8f8; }
4 | .cm-s-night div.CodeMirror-selected { background: #447 !important; }
5 | .cm-s-night .CodeMirror-gutters { background: #0a001f; border-right: 1px solid #aaa; }
6 | .cm-s-night .CodeMirror-linenumber { color: #f8f8f8; }
7 | .cm-s-night .CodeMirror-cursor { border-left: 1px solid white !important; }
8 |
9 | .cm-s-night span.cm-comment { color: #6900a1; }
10 | .cm-s-night span.cm-atom { color: #845dc4; }
11 | .cm-s-night span.cm-number, .cm-s-night span.cm-attribute { color: #ffd500; }
12 | .cm-s-night span.cm-keyword { color: #599eff; }
13 | .cm-s-night span.cm-string { color: #37f14a; }
14 | .cm-s-night span.cm-meta { color: #7678e2; }
15 | .cm-s-night span.cm-variable-2, .cm-s-night span.cm-tag { color: #99b2ff; }
16 | .cm-s-night span.cm-variable-3, .cm-s-night span.cm-def { color: white; }
17 | .cm-s-night span.cm-error { color: #9d1e15; }
18 | .cm-s-night span.cm-bracket { color: #8da6ce; }
19 | .cm-s-night span.cm-comment { color: #6900a1; }
20 | .cm-s-night span.cm-builtin, .cm-s-night span.cm-special { color: #ff9e59; }
21 | .cm-s-night span.cm-link { color: #845dc4; }
22 |
--------------------------------------------------------------------------------
/spec/lib/handlers_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe PufferPages::Handlers do
4 | before(:all) { @handlers = described_class.handlers }
5 | after(:all) { described_class.instance_variable_set(:@handlers, @handlers) }
6 |
7 | before { subject.instance_variable_set(:@handlers, nil) }
8 |
9 | context do
10 | let(:klass) do
11 | Class.new do
12 | attr_accessor :type
13 | def initialize type
14 | @type = type
15 | end
16 |
17 | def process *args
18 | "processed #{@type}, #{args.first}"
19 | end
20 |
21 | def codemirror_mode
22 | 'mode'
23 | end
24 | end
25 | end
26 | before { subject.register klass, :html, :json }
27 |
28 | specify { subject.handlers[:html].should be_a klass }
29 | specify { subject.handlers[:json].should be_a klass }
30 | specify { subject.select.should == [
31 | ['translation missing: en.puffer_pages.handlers.html', :html, {"data-codemirror-mode" => "mode"}],
32 | ['translation missing: en.puffer_pages.handlers.json', :json, {"data-codemirror-mode" => "mode"}]
33 | ] }
34 | specify { subject.process('html', 'object').should == 'processed html, object' }
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/spec/requests/origins_requests_spec.rb:
--------------------------------------------------------------------------------
1 | require "spec_helper"
2 |
3 | describe "Origins controller" do
4 | let(:token) { "token" }
5 |
6 | describe "#import" do
7 | let(:json) { File.new('spec/data/import.json').read }
8 | let(:origin) { Fabricate :origin }
9 |
10 | before { FakeWeb.register_uri :get, origin.uri, :body => json, :content_length => json.length }
11 | before { get "/admin/origins/#{origin.id}/import" }
12 |
13 | specify { PufferPages::Layout.count.should == 2 }
14 | specify { PufferPages::Page.count.should == 1 }
15 | specify { PufferPages::Snippet.count.should == 1 }
16 | specify { PufferPages::PagePart.count.should == 2 }
17 | end
18 |
19 | describe "#export" do
20 | context "with a valid token" do
21 | before { get "/admin/origins/export", :token => "token" }
22 |
23 | subject { ActiveSupport::JSON.decode response.body }
24 |
25 | %w(layouts pages snippets).each do |key|
26 | it { should have_key(key) }
27 | end
28 | end
29 |
30 | context "with an invalid token" do
31 | before { get "/admin/origins/export", :token => "bad" }
32 |
33 | specify { response.body.should be_blank }
34 | specify { response.status.should == 401 }
35 | end
36 | end
37 | end
--------------------------------------------------------------------------------
/app/assets/stylesheets/puffer/codemirror/monokai.css:
--------------------------------------------------------------------------------
1 | /* Based on Sublime Text's Monokai theme */
2 |
3 | .cm-s-monokai.CodeMirror {background: #272822; color: #f8f8f2;}
4 | .cm-s-monokai div.CodeMirror-selected {background: #49483E !important;}
5 | .cm-s-monokai .CodeMirror-gutters {background: #272822; border-right: 0px;}
6 | .cm-s-monokai .CodeMirror-linenumber {color: #d0d0d0;}
7 | .cm-s-monokai .CodeMirror-cursor {border-left: 1px solid #f8f8f0 !important;}
8 |
9 | .cm-s-monokai span.cm-comment {color: #75715e;}
10 | .cm-s-monokai span.cm-atom {color: #ae81ff;}
11 | .cm-s-monokai span.cm-number {color: #ae81ff;}
12 |
13 | .cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute {color: #a6e22e;}
14 | .cm-s-monokai span.cm-keyword {color: #f92672;}
15 | .cm-s-monokai span.cm-string {color: #e6db74;}
16 |
17 | .cm-s-monokai span.cm-variable {color: #a6e22e;}
18 | .cm-s-monokai span.cm-variable-2 {color: #9effff;}
19 | .cm-s-monokai span.cm-def {color: #fd971f;}
20 | .cm-s-monokai span.cm-error {background: #f92672; color: #f8f8f0;}
21 | .cm-s-monokai span.cm-bracket {color: #f8f8f2;}
22 | .cm-s-monokai span.cm-tag {color: #f92672;}
23 | .cm-s-monokai span.cm-link {color: #ae81ff;}
24 |
25 | .cm-s-monokai .CodeMirror-matchingbracket {
26 | text-decoration: underline;
27 | color: white !important;
28 | }
29 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/puffer/codemirror/erlang-dark.css:
--------------------------------------------------------------------------------
1 | .cm-s-erlang-dark.CodeMirror { background: #002240; color: white; }
2 | .cm-s-erlang-dark div.CodeMirror-selected { background: #b36539 !important; }
3 | .cm-s-erlang-dark .CodeMirror-gutters { background: #002240; border-right: 1px solid #aaa; }
4 | .cm-s-erlang-dark .CodeMirror-linenumber { color: #d0d0d0; }
5 | .cm-s-erlang-dark .CodeMirror-cursor { border-left: 1px solid white !important; }
6 |
7 | .cm-s-erlang-dark span.cm-atom { color: #845dc4; }
8 | .cm-s-erlang-dark span.cm-attribute { color: #ff80e1; }
9 | .cm-s-erlang-dark span.cm-bracket { color: #ff9d00; }
10 | .cm-s-erlang-dark span.cm-builtin { color: #eeaaaa; }
11 | .cm-s-erlang-dark span.cm-comment { color: #7777ff; }
12 | .cm-s-erlang-dark span.cm-def { color: #ee77aa; }
13 | .cm-s-erlang-dark span.cm-error { color: #9d1e15; }
14 | .cm-s-erlang-dark span.cm-keyword { color: #ffee80; }
15 | .cm-s-erlang-dark span.cm-meta { color: #50fefe; }
16 | .cm-s-erlang-dark span.cm-number { color: #ffd0d0; }
17 | .cm-s-erlang-dark span.cm-operator { color: #dd1111; }
18 | .cm-s-erlang-dark span.cm-string { color: #3ad900; }
19 | .cm-s-erlang-dark span.cm-tag { color: #9effff; }
20 | .cm-s-erlang-dark span.cm-variable { color: #50fe50; }
21 | .cm-s-erlang-dark span.cm-variable-2 { color: #ee00ee; }
22 |
--------------------------------------------------------------------------------
/spec/lib/handlers/yaml_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe PufferPages::Handlers::Yaml do
4 | subject { described_class.new(:yaml) }
5 |
6 | let!(:root) { Fabricate :root, name: 'root', page_parts: [ancestors.last] }
7 | let!(:first) { Fabricate :page, slug: 'first', parent: root, page_parts: [ancestors.first] }
8 | let!(:second) { Fabricate :page, slug: 'second', parent: first, page_parts: [page_part] }
9 | let(:page_part) {
10 | Fabricate.build :main, handler: 'yaml', body: YAML.dump(config: { value: 42, array: [1, 2], hash: { foo: 1 } })
11 | }
12 | let(:ancestors) {
13 | [
14 | Fabricate.build(:main, handler: 'yaml', body: YAML.dump(config: { array: [4, 5], hash: { bar: 2 } })),
15 | Fabricate.build(:main, handler: 'yaml', body: YAML.dump(config: { value: 33, hash: { bar: 1, baz: 3 } }))
16 | ]
17 | }
18 |
19 | context do
20 | specify { subject.process(page_part).should ==
21 | { config: { value: 42, array: [1, 2], hash: { foo: 1, bar: 2, baz: 3 } } } }
22 | end
23 |
24 | context 'error yaml' do
25 | let(:page_part) {
26 | Fabricate.build :main, handler: 'yaml', body: " key: value\n key:value\n key:value"
27 | }
28 |
29 | specify do
30 | expect { subject.process(page_part).should }.
31 | to raise_exception Psych::SyntaxError
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/puffer/codemirror/blackboard.css:
--------------------------------------------------------------------------------
1 | /* Port of TextMate's Blackboard theme */
2 |
3 | .cm-s-blackboard.CodeMirror { background: #0C1021; color: #F8F8F8; }
4 | .cm-s-blackboard .CodeMirror-selected { background: #253B76 !important; }
5 | .cm-s-blackboard .CodeMirror-gutters { background: #0C1021; border-right: 0; }
6 | .cm-s-blackboard .CodeMirror-linenumber { color: #888; }
7 | .cm-s-blackboard .CodeMirror-cursor { border-left: 1px solid #A7A7A7 !important; }
8 |
9 | .cm-s-blackboard .cm-keyword { color: #FBDE2D; }
10 | .cm-s-blackboard .cm-atom { color: #D8FA3C; }
11 | .cm-s-blackboard .cm-number { color: #D8FA3C; }
12 | .cm-s-blackboard .cm-def { color: #8DA6CE; }
13 | .cm-s-blackboard .cm-variable { color: #FF6400; }
14 | .cm-s-blackboard .cm-operator { color: #FBDE2D;}
15 | .cm-s-blackboard .cm-comment { color: #AEAEAE; }
16 | .cm-s-blackboard .cm-string { color: #61CE3C; }
17 | .cm-s-blackboard .cm-string-2 { color: #61CE3C; }
18 | .cm-s-blackboard .cm-meta { color: #D8FA3C; }
19 | .cm-s-blackboard .cm-error { background: #9D1E15; color: #F8F8F8; }
20 | .cm-s-blackboard .cm-builtin { color: #8DA6CE; }
21 | .cm-s-blackboard .cm-tag { color: #8DA6CE; }
22 | .cm-s-blackboard .cm-attribute { color: #8DA6CE; }
23 | .cm-s-blackboard .cm-header { color: #FF6400; }
24 | .cm-s-blackboard .cm-hr { color: #AEAEAE; }
25 | .cm-s-blackboard .cm-link { color: #8DA6CE; }
26 |
--------------------------------------------------------------------------------
/lib/puffer_pages/backends/models/mixins/importable.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Backends
3 | module Mixins
4 | module Importable
5 | extend ActiveSupport::Concern
6 |
7 | module ClassMethods
8 | def export_json
9 | all
10 | end
11 |
12 | def import_destroy
13 | destroy_all
14 | end
15 |
16 | def import_json json
17 | data = json.is_a?(String) ? ActiveSupport::JSON.decode(json) : json.map(&:stringify_keys!)
18 |
19 | import_destroy
20 |
21 | data.each do |attributes|
22 | associations = attributes.keys.each_with_object({}) do |attribute, hsh|
23 | if scoped.reflect_on_association(attribute.to_sym)
24 | hsh[attribute] = attributes.delete(attribute)
25 | end
26 | end
27 |
28 | attributes = attributes.with_indifferent_access
29 | record = scoped.create!(attributes) do |record|
30 | record.id = attributes[:id] if attributes[:id].present?
31 | end
32 |
33 | associations.each do |association, attributes|
34 | record.send(association).import_json(attributes)
35 | end
36 | end
37 | end
38 | end
39 | end
40 | end
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/puffer/codemirror/rubyblue.css:
--------------------------------------------------------------------------------
1 | .cm-s-rubyblue { font:13px/1.4em Trebuchet, Verdana, sans-serif; } /* - customized editor font - */
2 |
3 | .cm-s-rubyblue.CodeMirror { background: #112435; color: white; }
4 | .cm-s-rubyblue div.CodeMirror-selected { background: #38566F !important; }
5 | .cm-s-rubyblue .CodeMirror-gutters { background: #1F4661; border-right: 7px solid #3E7087; }
6 | .cm-s-rubyblue .CodeMirror-linenumber { color: white; }
7 | .cm-s-rubyblue .CodeMirror-cursor { border-left: 1px solid white !important; }
8 |
9 | .cm-s-rubyblue span.cm-comment { color: #999; font-style:italic; line-height: 1em; }
10 | .cm-s-rubyblue span.cm-atom { color: #F4C20B; }
11 | .cm-s-rubyblue span.cm-number, .cm-s-rubyblue span.cm-attribute { color: #82C6E0; }
12 | .cm-s-rubyblue span.cm-keyword { color: #F0F; }
13 | .cm-s-rubyblue span.cm-string { color: #F08047; }
14 | .cm-s-rubyblue span.cm-meta { color: #F0F; }
15 | .cm-s-rubyblue span.cm-variable-2, .cm-s-rubyblue span.cm-tag { color: #7BD827; }
16 | .cm-s-rubyblue span.cm-variable-3, .cm-s-rubyblue span.cm-def { color: white; }
17 | .cm-s-rubyblue span.cm-error { color: #AF2018; }
18 | .cm-s-rubyblue span.cm-bracket { color: #F0F; }
19 | .cm-s-rubyblue span.cm-link { color: #F4C20B; }
20 | .cm-s-rubyblue span.CodeMirror-matchingbracket { color:#F0F !important; }
21 | .cm-s-rubyblue span.cm-builtin, .cm-s-rubyblue span.cm-special { color: #FF9D00; }
22 |
--------------------------------------------------------------------------------
/lib/puffer_pages/liquid/tags/assets.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Liquid
3 | module Tags
4 |
5 | class Assets < ::Liquid::Tag
6 | Syntax = /^(#{::Liquid::QuotedFragment}+)/
7 |
8 | def initialize(tag_name, markup, tokens)
9 | if markup =~ Syntax
10 | @paths = variables_from_string(markup)
11 | else
12 | raise SyntaxError.new("Syntax Error in '#{tag_name}' - Valid syntax: #{tag_name} path [, path, path ...]")
13 | end
14 |
15 | super
16 | end
17 |
18 | def render(context)
19 | paths = @paths.map {|path| "'#{context[path]}'" }.join(', ')
20 |
21 | erb = case @tag_name
22 | when 'javascripts'
23 | "<%= javascript_include_tag #{paths} %>"
24 | when 'stylesheets'
25 | "<%= stylesheet_link_tag #{paths} %>"
26 | end
27 |
28 | context.registers[:tracker].register(erb)
29 | end
30 |
31 | private
32 |
33 | def variables_from_string(markup)
34 | markup.split(',').map do |var|
35 | var.strip =~ /\s*(#{::Liquid::QuotedFragment})\s*/
36 | $1 ? $1 : nil
37 | end.compact
38 | end
39 |
40 | end
41 |
42 | end
43 | end
44 | end
45 |
46 | Liquid::Template.register_tag('javascripts', PufferPages::Liquid::Tags::Assets)
47 | Liquid::Template.register_tag('stylesheets', PufferPages::Liquid::Tags::Assets)
48 |
--------------------------------------------------------------------------------
/spec/controllers/pages_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe PagesController do
4 |
5 | before { @routes = PufferPages::Engine.routes }
6 |
7 | describe "GET index" do
8 | render_views
9 |
10 | let!(:layout) { Fabricate :application }
11 | let!(:root) { Fabricate :root }
12 | let!(:first) { Fabricate :page, slug: 'first', parent: root }
13 | let!(:second) { Fabricate :page, slug: 'second.css', parent: first }
14 |
15 | describe "proper page rendering" do
16 | it { should render_page root }
17 | specify do
18 | get :index, path: 'first'
19 | response.should render_page first
20 | response.should be_ok
21 | end
22 | specify do
23 | get :index, path: 'first/second.css'
24 | response.should render_page second
25 | response.content_type.should == 'text/css'
26 | response.should be_ok
27 | end
28 | specify do
29 | expect { get :index, path: 'first/second' }.to raise_error PufferPages::MissedPage
30 | response.should_not render_page
31 | response.should_not be_not_found
32 | end
33 | end
34 |
35 | describe "layout loaded from filesystem" do
36 | let!(:root) { Fabricate :root, layout_name: 'sample' }
37 |
38 | context do
39 | specify do
40 | get :index
41 | response.should render_template 'layouts/sample'
42 | end
43 | end
44 | end
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/app/components/page_parts/form.html.erb:
--------------------------------------------------------------------------------
1 | <%= component_fields_for @record do |f| %>
2 |
3 |
4 | <% @record.send(field.name).each_with_index do |page_part| %>
5 | -
6 | <%= link_to "##{page_part.class.to_s.underscore}_#{page_part.id}" do %>
7 | <%= page_part.name %>
8 | <% if page_part.errors[:name].present? %>
9 |
10 | <%= page_part.errors[:name].first %>
11 |
12 | <% end %>
13 | <% end %>
14 |
15 | <% end %>
16 |
17 | <% @record.send(field.name).each do |page_part| -%>
18 | - " class="rui-tabs-panel" data-name="<%= page_part.name %>" data-entity-model="<%= page_part.class.model_name.underscore %>" data-entity-id="<%= page_part.id if page_part.persisted? %>">
19 | <%= render 'page_part', :form => f, :page_part => page_part, :child_index => nil %>
20 |
21 | <% end -%>
22 |
23 | <% end %>
24 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/puffer/codemirror/vibrant-ink.css:
--------------------------------------------------------------------------------
1 | /* Taken from the popular Visual Studio Vibrant Ink Schema */
2 |
3 | .cm-s-vibrant-ink.CodeMirror { background: black; color: white; }
4 | .cm-s-vibrant-ink .CodeMirror-selected { background: #35493c !important; }
5 |
6 | .cm-s-vibrant-ink .CodeMirror-gutters { background: #002240; border-right: 1px solid #aaa; }
7 | .cm-s-vibrant-ink .CodeMirror-linenumber { color: #d0d0d0; }
8 | .cm-s-vibrant-ink .CodeMirror-cursor { border-left: 1px solid white !important; }
9 |
10 | .cm-s-vibrant-ink .cm-keyword { color: #CC7832; }
11 | .cm-s-vibrant-ink .cm-atom { color: #FC0; }
12 | .cm-s-vibrant-ink .cm-number { color: #FFEE98; }
13 | .cm-s-vibrant-ink .cm-def { color: #8DA6CE; }
14 | .cm-s-vibrant-ink span.cm-variable-2, .cm-s-cobalt span.cm-tag { color: #FFC66D }
15 | .cm-s-vibrant-ink span.cm-variable-3, .cm-s-cobalt span.cm-def { color: #FFC66D }
16 | .cm-s-vibrant-ink .cm-operator { color: #888; }
17 | .cm-s-vibrant-ink .cm-comment { color: gray; font-weight: bold; }
18 | .cm-s-vibrant-ink .cm-string { color: #A5C25C }
19 | .cm-s-vibrant-ink .cm-string-2 { color: red }
20 | .cm-s-vibrant-ink .cm-meta { color: #D8FA3C; }
21 | .cm-s-vibrant-ink .cm-error { border-bottom: 1px solid red; }
22 | .cm-s-vibrant-ink .cm-builtin { color: #8DA6CE; }
23 | .cm-s-vibrant-ink .cm-tag { color: #8DA6CE; }
24 | .cm-s-vibrant-ink .cm-attribute { color: #8DA6CE; }
25 | .cm-s-vibrant-ink .cm-header { color: #FF6400; }
26 | .cm-s-vibrant-ink .cm-hr { color: #AEAEAE; }
27 | .cm-s-vibrant-ink .cm-link { color: blue; }
28 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/puffer/codemirror/twilight.css:
--------------------------------------------------------------------------------
1 | .cm-s-twilight.CodeMirror { background: #141414; color: #f7f7f7; } /**/
2 | .cm-s-twilight .CodeMirror-selected { background: #323232 !important; } /**/
3 |
4 | .cm-s-twilight .CodeMirror-gutters { background: #222; border-right: 1px solid #aaa; }
5 | .cm-s-twilight .CodeMirror-linenumber { color: #aaa; }
6 | .cm-s-twilight .CodeMirror-cursor { border-left: 1px solid white !important; }
7 |
8 | .cm-s-twilight .cm-keyword { color: #f9ee98; } /**/
9 | .cm-s-twilight .cm-atom { color: #FC0; }
10 | .cm-s-twilight .cm-number { color: #ca7841; } /**/
11 | .cm-s-twilight .cm-def { color: #8DA6CE; }
12 | .cm-s-twilight span.cm-variable-2, .cm-s-twilight span.cm-tag { color: #607392; } /**/
13 | .cm-s-twilight span.cm-variable-3, .cm-s-twilight span.cm-def { color: #607392; } /**/
14 | .cm-s-twilight .cm-operator { color: #cda869; } /**/
15 | .cm-s-twilight .cm-comment { color:#777; font-style:italic; font-weight:normal; } /**/
16 | .cm-s-twilight .cm-string { color:#8f9d6a; font-style:italic; } /**/
17 | .cm-s-twilight .cm-string-2 { color:#bd6b18 } /*?*/
18 | .cm-s-twilight .cm-meta { background-color:#141414; color:#f7f7f7; } /*?*/
19 | .cm-s-twilight .cm-error { border-bottom: 1px solid red; }
20 | .cm-s-twilight .cm-builtin { color: #cda869; } /*?*/
21 | .cm-s-twilight .cm-tag { color: #997643; } /**/
22 | .cm-s-twilight .cm-attribute { color: #d6bb6d; } /*?*/
23 | .cm-s-twilight .cm-header { color: #FF6400; }
24 | .cm-s-twilight .cm-hr { color: #AEAEAE; }
25 | .cm-s-twilight .cm-link { color:#ad9361; font-style:italic; text-decoration:none; } /**/
26 |
27 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Log error messages when you accidentally call methods on nil.
10 | config.whiny_nils = true
11 |
12 | # Show full error reports and disable caching
13 | config.consider_all_requests_local = true
14 | config.action_controller.perform_caching = false
15 |
16 | # Don't care if the mailer can't send
17 | config.action_mailer.raise_delivery_errors = false
18 |
19 | # Print deprecation notices to the Rails logger
20 | config.active_support.deprecation = :log
21 |
22 | # Only use best-standards-support built into browsers
23 | config.action_dispatch.best_standards_support = :builtin
24 |
25 | # Raise exception on mass assignment protection for Active Record models
26 | # config.active_record.mass_assignment_sanitizer = :strict
27 |
28 | # Log the query plan for queries taking more than this (works
29 | # with SQLite, MySQL, and PostgreSQL)
30 | # config.active_record.auto_explain_threshold_in_seconds = 0.5
31 |
32 | # Do not compress assets
33 | config.assets.compress = false
34 |
35 | # Expands the lines which load the assets
36 | config.assets.debug = false
37 |
38 | config.puffer_pages.raise_errors = true
39 | end
40 |
--------------------------------------------------------------------------------
/spec/lib/liquid/tags/partials_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe PufferPages::Liquid::Tags::Partials do
4 | describe 'snippet' do
5 | let!(:root) { Fabricate :root }
6 | let!(:custom) { Fabricate :custom }
7 |
8 | specify { root.render("{% snippet 'custom' %}").should == custom.body }
9 | specify { root.render("{% assign snippet = 'custom' %}{% snippet snippet %}").should == custom.body }
10 |
11 | context do
12 | let!(:custom) { Fabricate :custom, body: "{{ custom }}" }
13 | specify { root.render("{% snippet 'custom' with 'hello' %}").should == 'hello' }
14 | end
15 |
16 | context do
17 | let!(:custom) { Fabricate :custom, body: "{{ variable }}" }
18 | specify { root.render("{% snippet 'custom', variable: 'hello' %}").should == 'hello' }
19 | end
20 | end
21 |
22 | describe 'layout' do
23 | let!(:root) { Fabricate :root }
24 | let!(:application) { Fabricate :application }
25 |
26 | specify { root.render("{% layout 'application' %}").should == application.body }
27 | specify { root.render("{% assign layout = 'application' %}{% layout layout %}").should == application.body }
28 |
29 | context do
30 | let!(:application) { Fabricate :application, body: "{{ application }}" }
31 | specify { root.render("{% layout 'application' with 'hello' %}").should == 'hello' }
32 | end
33 |
34 | context do
35 | let!(:application) { Fabricate :application, body: "{{ variable }}" }
36 | specify { root.render("{% layout 'application', variable: 'hello' %}").should == 'hello' }
37 | end
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | # Configure Rails Envinronment
2 | ENV["RAILS_ENV"] ||= "test"
3 |
4 | require File.expand_path("../dummy/config/environment.rb", __FILE__)
5 | require "rails/test_help"
6 | require "rspec/rails"
7 | require "puffer_pages/rspec"
8 |
9 | Bundler.require :development
10 |
11 | ActionMailer::Base.delivery_method = :test
12 | ActionMailer::Base.perform_deliveries = true
13 | ActionMailer::Base.default_url_options[:host] = "test.com"
14 |
15 | Rails.backtrace_cleaner.remove_silencers!
16 |
17 | # ActiveRecord::Base.logger = Logger.new(STDOUT)
18 | # Run any available migration
19 | ActiveRecord::Migrator.migrate File.expand_path("../dummy/db/migrate/", __FILE__)
20 |
21 | if ActiveRecord::Base.connection.respond_to? :schema_cache
22 | ActiveRecord::Base.connection.schema_cache.clear!
23 | end
24 |
25 | # Load support files
26 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
27 | Dir["#{File.dirname(__FILE__)}/fabricators/**/*.rb"].each { |f| require f }
28 |
29 | RSpec.configure do |config|
30 | # Remove this line if you don't want RSpec's should and should_not
31 | # methods or matchers
32 | require 'rspec/expectations'
33 | config.include RSpec::Matchers
34 |
35 | # == Mock Framework
36 | config.mock_with :rspec
37 |
38 | config.use_transactional_fixtures = false
39 |
40 | config.before(:suite) do
41 | DatabaseCleaner.clean_with(:truncation)
42 | DatabaseCleaner.strategy = :transaction
43 | end
44 |
45 | config.before(:each) do
46 | DatabaseCleaner.start
47 | end
48 |
49 | config.after(:each) do
50 | DatabaseCleaner.clean
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/lib/puffer_pages/backends/models/origin.rb:
--------------------------------------------------------------------------------
1 | class PufferPages::Backends::Origin < ActiveRecord::Base
2 | self.abstract_class = true
3 | self.table_name = :origins
4 |
5 | attr_protected
6 |
7 | validates_presence_of :name, :host, :token
8 | validates_uniqueness_of :name
9 |
10 | def path
11 | read_attribute(:path).presence || PufferPages.export_path
12 | end
13 |
14 | def uri
15 | parsed_host.merge URI::Generic.build(:path => path, :query => { :token => token }.to_query)
16 | end
17 |
18 | def import!
19 | import_json import_data
20 | end
21 |
22 | def import_data
23 | Net::HTTP.get(uri)
24 | rescue
25 | raise PufferPages::ImportFailed.new("Could not connect to `#{name}` (#{uri})")
26 | end
27 |
28 | def import_json json
29 | data = json.is_a?(String) ? ActiveSupport::JSON.decode(json) : json
30 | ActiveRecord::Base.transaction do
31 | %w(layouts snippets pages).each do |table|
32 | klass = "puffer_pages/#{table}".classify.constantize
33 | klass.import_json data[table]
34 | end
35 | end
36 | end
37 |
38 | def self.export_json
39 | %w(layouts snippets pages).each_with_object({}) do |table, result|
40 | klass = "puffer_pages/#{table}".classify.constantize
41 | result[table] = klass.export_json
42 | end.as_json.to_json
43 | end
44 |
45 | private
46 |
47 | def parsed_host
48 | URI.parse([scheme, real_host].join('://'))
49 | end
50 |
51 | def real_host
52 | host.gsub(/\Ahttps?\:\/\//, '')
53 | end
54 |
55 | def scheme
56 | match = host.match(/\A(https?)\:\/\//)
57 | match ? match[1] : 'http'
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/spec/lib/liquid/tags/include_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe PufferPages::Liquid::Tags::Include do
4 | describe 'include page_part' do
5 | let!(:root) { Fabricate :root, page_parts: [main, sidebar] }
6 | let!(:main) { Fabricate :main }
7 | let!(:sidebar) { Fabricate :sidebar }
8 |
9 | specify { root.render("{% include '#{PufferPages.primary_page_part_name}' %}").should == main.body }
10 | specify { root.render("{% assign sb = 'sidebar' %}{% include sb %}").should == sidebar.body }
11 | end
12 |
13 | describe 'include snippet' do
14 | let!(:root) { Fabricate :root }
15 | let!(:custom) { Fabricate :custom }
16 |
17 | specify { root.render("{% include 'snippets/custom' %}").should == custom.body }
18 | specify { root.render("{% assign snippet = 'snippets/custom' %}{% include snippet %}").should == custom.body }
19 |
20 | context do
21 | let!(:custom) { Fabricate :custom, body: "{{ variable }}" }
22 | specify { root.render("{% include 'snippets/custom', variable: 'hello' %}").should == 'hello' }
23 | end
24 | end
25 |
26 | describe 'include layout' do
27 | let!(:root) { Fabricate :root }
28 | let!(:application) { Fabricate :application }
29 |
30 | specify { root.render("{% include 'layouts/application' %}").should == application.body }
31 | specify { root.render("{% assign layout = 'layouts/application' %}{% include layout %}").should == application.body }
32 |
33 | context do
34 | let!(:application) { Fabricate :application, body: "{{ variable }}" }
35 | specify { root.render("{% include 'layouts/application', variable: 'hello' %}").should == 'hello' }
36 | end
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/lib/puffer_pages/backends/controllers/pages_base.rb:
--------------------------------------------------------------------------------
1 | class PufferPages::PagesBase < Puffer::TreeBase
2 | helper :puffer_pages
3 |
4 | setup do
5 | group :pages
6 | model_name :'puffer_pages/page'
7 | end
8 |
9 | tree do
10 | #field :name, :render => :tree_page
11 | field :name, render: -> { render :partial => 'tree_page', :object => record }
12 | end
13 |
14 | def new
15 | @record = resource.new_member
16 | if !@record.inherited_page_part(PufferPages.primary_page_part_name)
17 | @record.page_parts.build :name => PufferPages.primary_page_part_name
18 | end
19 | respond_with @record
20 | end
21 |
22 | index do
23 | field :name
24 | field :slug
25 | field :layout_name
26 | field :status
27 | end
28 |
29 | filter do
30 | field :name
31 | field :slug
32 | field :layout_name
33 | field :locales
34 | field 'page_parts.name'
35 | field 'page_parts.body'
36 | end
37 |
38 | form do
39 | field :parent_id, type: :hidden
40 | field :name
41 | field :slug
42 | field :layout_name, select: :possible_layouts, include_blank: false
43 | field :status, select: :possible_statuses, include_blank: false
44 | field :page_parts, type: :page_parts do
45 | field :handler, type: :handlers, include_blank: false,
46 | html: { 'data-codemirror-mode-select' => true }
47 | field :body, type: :codemirror, input_only: true, mode: 'text/x-liquid-html'
48 | field :name, type: :hidden, html: { data: { acts: 'name' } }
49 | field :_destroy, type: :hidden, html: { data: { acts: 'destroy' } }
50 | end
51 | field :locales, type: :codemirror, mode: 'text/x-yaml'
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/puffer_pages.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | $:.push File.expand_path("../lib", __FILE__)
3 | require "puffer_pages/version"
4 |
5 | Gem::Specification.new do |s|
6 | s.name = "puffer_pages"
7 | s.version = PufferPages::VERSION
8 | s.authors = ["pyromaniac"]
9 | s.email = ["kinwizard@gmail.com"]
10 | s.homepage = "http://github.com/puffer/puffer_pages"
11 | s.summary = %q{Content Management System}
12 | s.description = %q{Puffer pages is integratable rails CMS with puffer admin interface}
13 |
14 | s.rubyforge_project = "puffer_pages"
15 |
16 | s.files = `git ls-files`.split("\n")
17 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19 | s.require_paths = ["lib"]
20 |
21 | # specify any dependencies here; for example:
22 | s.add_runtime_dependency "rails", ">= 3.1"
23 | s.add_runtime_dependency "puffer"
24 | s.add_runtime_dependency "liquid"
25 | s.add_runtime_dependency "nested_set"
26 | s.add_runtime_dependency "activeuuid", ">= 0.4.0"
27 | s.add_runtime_dependency "contextuality"
28 |
29 | s.add_development_dependency "bcrypt-ruby"
30 | s.add_development_dependency "sqlite3"
31 | s.add_development_dependency "pg"
32 | s.add_development_dependency "mysql2"
33 | s.add_development_dependency "globalize3"
34 | s.add_development_dependency "rspec-rails"
35 | s.add_development_dependency "shoulda"
36 | s.add_development_dependency "database_cleaner"
37 | s.add_development_dependency "forgery"
38 | s.add_development_dependency "fabrication"
39 | s.add_development_dependency "fakeweb"
40 | s.add_development_dependency "timecop"
41 | end
42 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/pg_test.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Configure static asset server for tests with Cache-Control for performance
11 | config.serve_static_assets = true
12 | config.static_cache_control = "public, max-age=3600"
13 |
14 | # Log error messages when you accidentally call methods on nil
15 | config.whiny_nils = true
16 |
17 | # Show full error reports and disable caching
18 | config.consider_all_requests_local = true
19 | config.action_controller.perform_caching = false
20 |
21 | # Raise exceptions instead of rendering exception templates
22 | config.action_dispatch.show_exceptions = false
23 |
24 | # Disable request forgery protection in test environment
25 | config.action_controller.allow_forgery_protection = false
26 |
27 | # Tell Action Mailer not to deliver emails to the real world.
28 | # The :test delivery method accumulates sent emails in the
29 | # ActionMailer::Base.deliveries array.
30 | config.action_mailer.delivery_method = :test
31 |
32 | # Raise exception on mass assignment protection for Active Record models
33 | # config.active_record.mass_assignment_sanitizer = :strict
34 |
35 | # Print deprecation notices to the stderr
36 | config.active_support.deprecation = :stderr
37 |
38 | config.puffer_pages.raise_errors = true
39 | end
40 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Configure static asset server for tests with Cache-Control for performance
11 | config.serve_static_assets = true
12 | config.static_cache_control = "public, max-age=3600"
13 |
14 | # Log error messages when you accidentally call methods on nil
15 | config.whiny_nils = true
16 |
17 | # Show full error reports and disable caching
18 | config.consider_all_requests_local = true
19 | config.action_controller.perform_caching = false
20 |
21 | # Raise exceptions instead of rendering exception templates
22 | config.action_dispatch.show_exceptions = false
23 |
24 | # Disable request forgery protection in test environment
25 | config.action_controller.allow_forgery_protection = false
26 |
27 | # Tell Action Mailer not to deliver emails to the real world.
28 | # The :test delivery method accumulates sent emails in the
29 | # ActionMailer::Base.deliveries array.
30 | config.action_mailer.delivery_method = :test
31 |
32 | # Raise exception on mass assignment protection for Active Record models
33 | # config.active_record.mass_assignment_sanitizer = :strict
34 |
35 | # Print deprecation notices to the stderr
36 | config.active_support.deprecation = :stderr
37 |
38 | config.puffer_pages.raise_errors = true
39 | end
40 |
--------------------------------------------------------------------------------
/app/components/codemirror/form.html.erb:
--------------------------------------------------------------------------------
1 | <%= component_fields_for @record do |f| %>
2 | <% unless field.options[:input_only] %>
3 |
4 | <%= f.label field %>
5 |
6 | <%= @record.errors[field.name.to_sym].first %>
7 |
8 |
9 | <% end %>
10 | <% if @record.respond_to? "#{field}_translations" %>
11 |
12 |
13 | <% I18n.available_locales.sort_by { |locale| locale == I18n.default_locale ? 0 : 1 }.each do |locale| %>
14 | -
15 | <%= link_to locale, "##{@record.class.to_s.underscore}_#{@record.new_record? ? 'new_tab_panel_index' : @record.id}_#{field}_translations_#{locale}" %>
16 |
17 | <% end %>
18 |
19 | <%= f.fields_for "#{field}_translations" do |fields| %>
20 | <% I18n.available_locales.sort_by { |locale| locale == I18n.default_locale ? 0 : 1 }.each do |locale| %>
21 | - " class="rui-tabs-panel">
22 | <%= fields.text_area locale, field.input_options.merge(data: { codemirror: { mode: field.options[:mode] || 'text/html' } }, value: @record.send("#{field}_translations")[locale]) %>
23 |
24 | <% end %>
25 | <% end %>
26 |
27 | <% else %>
28 |
29 | <%= f.text_area field, field.input_options.merge(data: { codemirror: { mode: field.options[:mode] || 'text/html' } }) %>
30 |
31 | <% end %>
32 | <% end %>
33 |
--------------------------------------------------------------------------------
/lib/puffer_pages/liquid/page_drop.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Liquid
3 | class PageDrop < ::Liquid::Drop
4 |
5 | delegate *(PufferPages::Page.statuses.map {|s| "#{s}?"} << {:to => :page})
6 | delegate :id, :slug, :created_at, :updated_at, :to => :page
7 |
8 | def initialize page
9 | @page = page
10 | end
11 |
12 | def name
13 | @context ? page.render(page.name, @context) : page.name
14 | end
15 |
16 | %w(parent root ancestors self_and_ancestors children self_and_children siblings
17 | self_and_siblings descendants, self_and_descendants).each do |attribute|
18 | define_method attribute do
19 | instance_variable_get("@#{attribute}") ||
20 | instance_variable_set("@#{attribute}", page.send(attribute))
21 | end
22 | end
23 |
24 | def path
25 | controller.puffer_pages.puffer_page_path page.to_location
26 | end
27 |
28 | def url
29 | controller.puffer_pages.puffer_page_url page.to_location
30 | end
31 |
32 | def current?
33 | current_page && page == current_page
34 | end
35 |
36 | def ancestor?
37 | current_page && page.is_ancestor_of?(current_page)
38 | end
39 |
40 | def == other
41 | page == other.send(:page)
42 | end
43 |
44 | def before_method name
45 | page_part = page.inherited_page_part(name)
46 | page_part.handle(@context) if page_part && @context
47 | end
48 |
49 | private
50 | attr_reader :page
51 |
52 | def current_page
53 | @current_page ||= @context.registers[:page] if @context
54 | end
55 |
56 | def controller
57 | @controller ||= @context.registers[:controller] if @context
58 | end
59 | end
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/spec/data/import.json:
--------------------------------------------------------------------------------
1 | {
2 | "layouts": [
3 | {
4 | "body":"Lorem ipsum dolor sit amet, consectetuer adipiscing elit.",
5 | "created_at":"2012-10-10T12:30:09Z",
6 | "id":"2822BCAA21D24B3187C76C6C41E685F2",
7 | "name":"layout",
8 | "updated_at":"2012-10-10T12:30:09Z"
9 | },
10 | {
11 | "body":"Lorem ipsum dolor sit amet, consectetuer adipiscing elit.",
12 | "created_at":"2012-10-10T12:30:09Z","id":"520ACC91563B4F75816770C99038D67D",
13 | "name":"layout1","updated_at":"2012-10-10T12:30:09Z"
14 | }
15 | ],
16 | "snippets": [
17 | {
18 | "body":"Lorem ipsum dolor sit amet, consectetuer adipiscing elit.",
19 | "created_at":"2012-10-10T12:30:09Z",
20 | "id":"64AE0A360FD14E70AA0F9FB33C66D75B",
21 | "name":"lorem",
22 | "updated_at":"2012-10-10T12:30:09Z"
23 | }
24 | ],
25 | "pages": [
26 | {
27 | "created_at":"2012-10-10T12:30:09Z",
28 | "id":"A080B50AF20B4F59B7008977201F1C1F",
29 | "layout_name":"layout",
30 | "name":"Lorem ipsum dolor sit amet, consectetuer adipiscing elit.",
31 | "parent_id":null,
32 | "slug":null,
33 | "status":"published",
34 | "updated_at":"2012-10-10T12:30:09Z",
35 | "page_parts":[
36 | {
37 | "body":null,
38 | "created_at":"2012-10-10T12:30:09Z",
39 | "id":"4B3086FFEBD4491F9D2401F324D49CBF",
40 | "name":"body",
41 | "page_id":"A080B50AF20B4F59B7008977201F1C1F",
42 | "updated_at":"2012-10-10T12:30:09Z"
43 | },
44 | {
45 | "body":null,
46 | "created_at":"2012-10-10T12:30:09Z",
47 | "id":"F6423DC0AD074C9B979F6E90293817E4",
48 | "name":"main",
49 | "page_id":"A080B50AF20B4F59B7008977201F1C1F",
50 | "updated_at":"2012-10-10T12:30:09Z"
51 | }
52 | ]
53 | }
54 | ]
55 | }
--------------------------------------------------------------------------------
/lib/puffer_pages/liquid/tags/url.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Liquid
3 | module Tags
4 |
5 | class Url < ::Liquid::Tag
6 | Syntax = /^(#{::Liquid::VariableSignature}+)\s*(.*)\s*/
7 |
8 | def initialize(tag_name, markup, tokens)
9 | if markup =~ Syntax
10 |
11 | @helper_name, @arguments, @attributes = $1, [], {}
12 |
13 | @arguments = $2.split(',')
14 | attributes = @arguments.pop if @arguments.last && @arguments.last.strip =~ /(#{::Liquid::TagAttributes})/
15 |
16 | @arguments = @arguments.map do |var|
17 | var.strip =~ /(#{::Liquid::QuotedFragment})/
18 | $1 ? $1 : nil
19 | end.compact
20 |
21 | attributes.scan(::Liquid::TagAttributes) do |key, value|
22 | @attributes[key] = value
23 | end if attributes.present?
24 | else
25 | raise SyntaxError.new("Error in tag '#{tag_name}' - Valid syntax: #{tag_name} helper_name [object, object...] [, option:value option:value...]")
26 | end
27 |
28 | super
29 | end
30 |
31 | def render(context)
32 | key = context[@key]
33 | arguments = @arguments.map do |argument|
34 | argument = context[argument]
35 | argument.to_param if argument.is_a?(::Liquid::Drop)
36 | argument
37 | end
38 | attributes = @attributes.each_with_object({}) do |(name, value), result|
39 | result[name.to_sym] = context[value]
40 | end
41 | attributes.merge(:path_only => true) if @tag_name == 'path'
42 |
43 | context.registers[:controller].send("#{@helper_name}_#{@tag_name}", *arguments, attributes)
44 | end
45 |
46 | end
47 |
48 | end
49 | end
50 | end
51 |
52 | Liquid::Template.register_tag('path', PufferPages::Liquid::Tags::Url)
53 | Liquid::Template.register_tag('url', PufferPages::Liquid::Tags::Url)
54 |
--------------------------------------------------------------------------------
/lib/puffer_pages/backends/models/mixins/localable.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Backends
3 | module Mixins
4 | module Localable
5 | extend ActiveSupport::Concern
6 |
7 | included do
8 | serialize :locales, Locales
9 |
10 | validate do
11 | errors[:locales] = locales.error unless locales.valid?
12 | end
13 | end
14 |
15 | def locales
16 | value = read_attribute(:locales)
17 | value.is_a?(Locales) ? value : Locales.new(value)
18 | end
19 |
20 | class Locales < ActiveSupport::HashWithIndifferentAccess
21 | def initialize source
22 | update (source.presence || {}).stringify_keys
23 | super()
24 | end
25 |
26 | def valid?
27 | !error
28 | end
29 |
30 | def error
31 | translations
32 | rescue ::SyntaxError => e
33 | e.message
34 | else
35 | nil
36 | end
37 |
38 | def translations
39 | @translations ||= Hash[map do |(locale, yaml)|
40 | load_arguments = [yaml]
41 | load_arguments.push "<#{locale}>" if YAML.method(:load).arity == -2
42 | result = YAML.load(*load_arguments).presence || {}
43 | raise ::SyntaxError.new("(<#{locale}>): Locale should be a hash") unless result.is_a?(Hash)
44 | [locale.to_sym, result.deep_symbolize_keys]
45 | end]
46 | end
47 |
48 | class << self
49 | def dump value
50 | value = value.is_a?(Locales) ? value : Locales.new(value)
51 | value ? YAML.dump(value.to_hash) : nil
52 | end
53 |
54 | def load value
55 | return unless value
56 | value = YAML.load(value) if value.is_a?(String)
57 | value.is_a?(Locales) ? value : Locales.new(value)
58 | end
59 | end
60 | end
61 | end
62 | end
63 | end
64 | end
65 |
--------------------------------------------------------------------------------
/lib/puffer_pages/backends/models/page_part.rb:
--------------------------------------------------------------------------------
1 | class PufferPages::Backends::PagePart < ActiveRecord::Base
2 | include ActiveUUID::UUID
3 | include PufferPages::Backends::Mixins::Renderable
4 | include PufferPages::Backends::Mixins::Importable
5 | include PufferPages::Backends::Mixins::Translatable
6 | self.abstract_class = true
7 | self.table_name = :page_parts
8 |
9 | attr_protected
10 |
11 | default_scope ->{ includes :translations } if PufferPages.localize
12 |
13 | validates_presence_of :name
14 | validates_uniqueness_of :name, :scope => :page_id
15 |
16 | belongs_to :page, :class_name => '::PufferPages::Page', :inverse_of => :page_parts
17 |
18 | before_validation :defaultize_attributes
19 | def defaultize_attributes
20 | self.handler ||= 'html'
21 | end
22 |
23 | def main?
24 | name == PufferPages.primary_page_part_name
25 | end
26 |
27 | def parent
28 | ancestors.first
29 | end
30 |
31 | def ancestors
32 | page.ancestors_page_parts.where(name: name)
33 | end
34 |
35 | def self_and_ancestors
36 | page.self_and_ancestors_page_parts.where(name: name)
37 | end
38 |
39 | def render *args
40 | _, context = normalize_render_options *args
41 | render_template body, context, additional_render_options
42 | end
43 |
44 | def handle *args
45 | _, context = normalize_render_options *args
46 | PufferPages::Handlers.process handler || 'html', self, context
47 | end
48 |
49 | def additional_render_options
50 | { environment: { processed: self } }
51 | end
52 |
53 | def i18n_scope
54 | i18n_scope_for page.segments, :page_parts, name
55 | end
56 |
57 | def i18n_defaults
58 | page.segments.inject([]) do |memo, element|
59 | memo.push (memo.last || []).dup.push(element)
60 | end.unshift([]).inject([]) do |memo, segments|
61 | memo.unshift i18n_scope_for(segments)
62 | memo.unshift i18n_scope_for(segments, :page_parts, name)
63 | end
64 | end
65 |
66 | private
67 |
68 | def i18n_scope_for *segments
69 | [:pages, *segments.flatten.map(&:to_sym)]
70 | end
71 | end
72 |
--------------------------------------------------------------------------------
/lib/puffer_pages/liquid/tags/translate.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Liquid
3 | module Tags
4 |
5 | class Translate < ::Liquid::Tag
6 | Syntax = /^(#{::Liquid::QuotedFragment})/
7 |
8 | def initialize(tag_name, markup, tokens)
9 | if markup =~ Syntax
10 | @key = $1
11 | else
12 | raise SyntaxError.new("Syntax Error in 'translate' - Valid syntax: translate key")
13 | end
14 |
15 | @options = {}
16 | markup.scan(::Liquid::TagAttributes) do |key, value|
17 | @options[key.to_sym] = value
18 | end
19 |
20 | super
21 | end
22 |
23 | def render(context)
24 | key = context[@key]
25 | options = @options.each_with_object({}) do |(name, value), result|
26 | result[name] = context[value] unless I18n::RESERVED_KEYS.include?(name)
27 | end
28 | processed = context[:processed]
29 |
30 | if options[:count].is_a?(String)
31 | begin
32 | options[:count] = (options[:count] =~ /\./ ? Float(options[:count]) : Integer(options[:count]))
33 | rescue ArgumentError
34 | end
35 | end
36 |
37 | if processed && key.first == '.'
38 | I18n.translate i18n_key(processed, key.last(-1)),
39 | options.merge!(:default => i18n_defaults(processed, key.last(-1)))
40 | else
41 | I18n.translate key, options
42 | end
43 | end
44 |
45 | def i18n_key(processed, key)
46 | array_to_key processed.i18n_scope, key
47 | end
48 |
49 | def i18n_defaults(processed, key)
50 | processed.i18n_defaults.map { |default| array_to_key default, key }
51 | end
52 |
53 | def array_to_key *array
54 | array.flatten.map { |segment| segment.to_s.gsub(?., ?/) }.join(?.).to_sym
55 | end
56 | end
57 |
58 | end
59 | end
60 | end
61 |
62 | Liquid::Template.register_tag('translate', PufferPages::Liquid::Tags::Translate)
63 | Liquid::Template.register_tag('t', PufferPages::Liquid::Tags::Translate)
64 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/puffer/puffer_pages.css:
--------------------------------------------------------------------------------
1 | //= require puffer/codemirror
2 | //= require_tree ./codemirror
3 | //= require_self
4 |
5 | .cm-translation {
6 | color: #a11;
7 | border-bottom: 1px dashed #a11;
8 | }
9 |
10 | .cm-s-default {
11 | background: #fff;
12 | }
13 |
14 | .cm-liquid-tag {
15 | color: #0089b7;
16 | }
17 |
18 | .cm-liquid-variable {
19 | color: #0089b7;
20 | }
21 |
22 | .cm-tab {
23 | background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAMCAYAAAAkuj5RAAAAAXNSR0IArs4c6QAAAGFJREFUSMft1LsRQFAQheHPowAKoACx3IgEKtaEHujDjORSgWTH/ZOdnZOcM/sgk/kFFWY0qV8foQwS4MKBCS3qR6ixBJvElOobYAtivseIE120FaowJPN75GMu8j/LfMwNjh4HUpwg4LUAAAAASUVORK5CYII=);
24 | background-position: right;
25 | background-repeat: no-repeat;
26 | }
27 |
28 | div.CodeMirror span.CodeMirror-matchingbracket {
29 | color: #e80000;
30 | }
31 |
32 | .handler {
33 | padding: 5px 10px;
34 | }
35 |
36 | .rui-tabs li {
37 | margin-bottom: 0;
38 | }
39 |
40 | .rui-tabs .rui-tabs-panel {
41 | padding: 0;
42 | border-left: 1px solid #ccc;
43 | border-right: 1px solid #ccc;
44 | height: 100%;
45 | }
46 |
47 | .rui-tabs .locales {
48 | border: 0;
49 | }
50 |
51 | .rui-tabs .locales .rui-tabs-panel {
52 | border: 0;
53 | }
54 |
55 | .codemirror_wrapper {
56 | border: 1px solid #CCC;
57 | }
58 |
59 | .rui-tabs-panel .codemirror_wrapper {
60 | border: 0;
61 | }
62 |
63 | *[data-fullscreen='true'] {
64 | background-color: #fff !important;
65 | display: block !important;
66 | position: fixed !important;
67 | top: 0 !important;
68 | left: 0 !important;
69 | width: 100% !important;
70 | height: 100% !important;
71 | z-index: 9999 !important;
72 | margin: 0 !important;
73 | padding: 0 !important;
74 | }
75 |
76 | *[data-fullscreen='true'] .codemirror_wrapper {
77 | height: 100% !important;
78 | }
79 |
80 | *[data-fullscreen='true'] .CodeMirror {
81 | height: 100% !important;
82 | }
83 |
84 | *[data-fullscreen='true'] .rui-tabs {
85 | height: 100% !important;
86 | }
87 |
88 | *[data-fullscreen='true'] .rui-tabs-panel {
89 | height: 100% !important;
90 | height: -webkit-calc(100% - 28px) !important;
91 | height: calc(100% - 28px) !important;
92 | }
--------------------------------------------------------------------------------
/app/assets/stylesheets/puffer/codemirror/lesser-dark.css:
--------------------------------------------------------------------------------
1 | /*
2 | http://lesscss.org/ dark theme
3 | Ported to CodeMirror by Peter Kroon
4 | */
5 | .cm-s-lesser-dark {
6 | line-height: 1.3em;
7 | }
8 | .cm-s-lesser-dark {
9 | font-family: 'Bitstream Vera Sans Mono', 'DejaVu Sans Mono', 'Monaco', Courier, monospace !important;
10 | }
11 |
12 | .cm-s-lesser-dark.CodeMirror { background: #262626; color: #EBEFE7; text-shadow: 0 -1px 1px #262626; }
13 | .cm-s-lesser-dark div.CodeMirror-selected {background: #45443B !important;} /* 33322B*/
14 | .cm-s-lesser-dark .CodeMirror-cursor { border-left: 1px solid white !important; }
15 | .cm-s-lesser-dark pre { padding: 0 8px; }/*editable code holder*/
16 |
17 | div.CodeMirror span.CodeMirror-matchingbracket { color: #7EFC7E; }/*65FC65*/
18 |
19 | .cm-s-lesser-dark .CodeMirror-gutters { background: #262626; border-right:1px solid #aaa; }
20 | .cm-s-lesser-dark .CodeMirror-linenumber { color: #777; }
21 |
22 | .cm-s-lesser-dark span.cm-keyword { color: #599eff; }
23 | .cm-s-lesser-dark span.cm-atom { color: #C2B470; }
24 | .cm-s-lesser-dark span.cm-number { color: #B35E4D; }
25 | .cm-s-lesser-dark span.cm-def {color: white;}
26 | .cm-s-lesser-dark span.cm-variable { color:#D9BF8C; }
27 | .cm-s-lesser-dark span.cm-variable-2 { color: #669199; }
28 | .cm-s-lesser-dark span.cm-variable-3 { color: white; }
29 | .cm-s-lesser-dark span.cm-property {color: #92A75C;}
30 | .cm-s-lesser-dark span.cm-operator {color: #92A75C;}
31 | .cm-s-lesser-dark span.cm-comment { color: #666; }
32 | .cm-s-lesser-dark span.cm-string { color: #BCD279; }
33 | .cm-s-lesser-dark span.cm-string-2 {color: #f50;}
34 | .cm-s-lesser-dark span.cm-meta { color: #738C73; }
35 | .cm-s-lesser-dark span.cm-error { color: #9d1e15; }
36 | .cm-s-lesser-dark span.cm-qualifier {color: #555;}
37 | .cm-s-lesser-dark span.cm-builtin { color: #ff9e59; }
38 | .cm-s-lesser-dark span.cm-bracket { color: #EBEFE7; }
39 | .cm-s-lesser-dark span.cm-tag { color: #669199; }
40 | .cm-s-lesser-dark span.cm-attribute {color: #00c;}
41 | .cm-s-lesser-dark span.cm-header {color: #a0a;}
42 | .cm-s-lesser-dark span.cm-quote {color: #090;}
43 | .cm-s-lesser-dark span.cm-hr {color: #999;}
44 | .cm-s-lesser-dark span.cm-link {color: #00c;}
45 |
--------------------------------------------------------------------------------
/spec/models/puffer_pages/localable_spec.rb:
--------------------------------------------------------------------------------
1 | # encoding: UTF-8
2 | require 'spec_helper'
3 |
4 | describe PufferPages::Backends::Mixins::Localable do
5 | let(:locales_class) { PufferPages::Backends::Mixins::Localable::Locales }
6 |
7 | context do
8 | let!(:page) { Fabricate.build :root }
9 | specify { page.locales.should == {} }
10 | specify { page.locales.should be_a locales_class }
11 | end
12 |
13 | context do
14 | let!(:page) { Fabricate :root }
15 | specify { page.locales.should == {} }
16 | specify { page.reload.locales.should == {} }
17 | specify { page.locales.should be_a locales_class }
18 | specify { page.reload.locales.should be_a locales_class }
19 | end
20 |
21 | context do
22 | let!(:page) { Fabricate :root, locales: { en: 'hello: world' } }
23 | specify { page.locales.should == { 'en' => 'hello: world' } }
24 | specify { page.reload.locales.should == { 'en' => 'hello: world' } }
25 | specify { page.locales.should be_a locales_class }
26 | specify { page.reload.locales.should be_a locales_class }
27 | end
28 |
29 | context do
30 | let!(:page) { Fabricate.build :root, locales: { en: 'Hello' } }
31 | specify { page.should be_invalid }
32 | specify { page.tap { |page| page.valid? }.errors[:locales].first.should == page.locales.error }
33 | end
34 | end
35 |
36 | describe PufferPages::Backends::Mixins::Localable::Locales do
37 | let(:valid) {
38 | described_class.new(
39 | en: YAML.dump(hello: 'World', bye: 'Hell'),
40 | de: YAML.dump('key' => 'value')
41 | )
42 | }
43 | let(:invalid) {
44 | described_class.new(
45 | en: YAML.dump('hello')
46 | )
47 | }
48 | describe '#translations' do
49 | specify { valid.translations.should == { en: { hello: 'World', bye: 'Hell' }, de: { key: 'value' } } }
50 | specify { expect { invalid.translations }.to raise_exception SyntaxError }
51 | end
52 |
53 | describe '#valid?' do
54 | specify { valid.should be_valid }
55 | specify { invalid.should_not be_valid }
56 | end
57 |
58 | describe '#valid?' do
59 | specify { valid.error.should be_false }
60 | specify { invalid.error.should =~ /Locale should be a hash/ }
61 | end
62 | end
63 |
--------------------------------------------------------------------------------
/lib/puffer_pages/backends/models/mixins/translatable.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Backends
3 | module Mixins
4 | module Translatable
5 | extend ActiveSupport::Concern
6 |
7 | module ClassMethods
8 | def translatable *fields
9 | if PufferPages.localize
10 | translates *fields, fallbacks_for_empty_translations: true
11 | translation_class.send(:include, ActiveUUID::UUID)
12 |
13 | def self.globalize_migrator
14 | @globalize_migrator ||= PufferPages::Globalize::Migrator.new(self)
15 | end
16 |
17 | fields.each do |field|
18 | define_method "#{field}_translations" do
19 | result = translations.each_with_object(HashWithIndifferentAccess.new) do |translation, result|
20 | result[translation.locale] = translation.send(field)
21 | end
22 | globalize.stash.keys.each_with_object(result) do |locale, result|
23 | result[locale] = globalize.stash.read(locale, field) if globalize.stash.contains?(locale, field)
24 | end
25 | end
26 |
27 | define_method "#{field}_translations=" do |value|
28 | value.each do |(locale, value)|
29 | write_attribute(field, value, locale: locale)
30 | end
31 | end
32 | end
33 |
34 | define_method :serializable_hash_with_translations do |options = nil|
35 | options ||= {}
36 | except = Array.wrap(options[:except])
37 | options[:except] = except +
38 | self.class.translated_attribute_names.map(&:to_s)
39 | methods = Array.wrap(options[:methods])
40 | options[:methods] = methods +
41 | self.class.translated_attribute_names.map { |name| "#{name}_translations" }
42 | serializable_hash_without_translations options
43 | end
44 |
45 | alias_method_chain :serializable_hash, :translations
46 | end
47 | end
48 | end
49 | end
50 | end
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/spec/dummy/config/routes.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.routes.draw do
2 |
3 | namespace :admin do
4 | resources :pages
5 | resources :layouts
6 | resources :snippets
7 | resources :origins
8 | resources :articles
9 | end
10 |
11 | mount PufferPages::Engine => '/'
12 |
13 | # The priority is based upon order of creation:
14 | # first created -> highest priority.
15 |
16 | # Sample of regular route:
17 | # match 'products/:id' => 'catalog#view'
18 | # Keep in mind you can assign values other than :controller and :action
19 |
20 | # Sample of named route:
21 | # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase
22 | # This route can be invoked with purchase_url(:id => product.id)
23 |
24 | # Sample resource route (maps HTTP verbs to controller actions automatically):
25 | # resources :products
26 |
27 | # Sample resource route with options:
28 | # resources :products do
29 | # member do
30 | # get 'short'
31 | # post 'toggle'
32 | # end
33 | #
34 | # collection do
35 | # get 'sold'
36 | # end
37 | # end
38 |
39 | # Sample resource route with sub-resources:
40 | # resources :products do
41 | # resources :comments, :sales
42 | # resource :seller
43 | # end
44 |
45 | # Sample resource route with more complex sub-resources
46 | # resources :products do
47 | # resources :comments
48 | # resources :sales do
49 | # get 'recent', :on => :collection
50 | # end
51 | # end
52 |
53 | # Sample resource route within a namespace:
54 | # namespace :admin do
55 | # # Directs /admin/products/* to Admin::ProductsController
56 | # # (app/controllers/admin/products_controller.rb)
57 | # resources :products
58 | # end
59 |
60 | # You can have the root of your site routed with "root"
61 | # just remember to delete public/index.html.
62 | # root :to => 'welcome#index'
63 |
64 | # See how all your routes lay out with "rake routes"
65 |
66 | # This is a legacy wild controller route that's not recommended for RESTful applications.
67 | # Note: This route will make all actions in every controller accessible via GET requests.
68 | # match ':controller(/:action(/:id(.:format)))'
69 | end
70 |
--------------------------------------------------------------------------------
/lib/puffer_pages/liquid/file_system.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Liquid
3 | class FileSystem < ::Liquid::BlankFileSystem
4 |
5 | def read_template_file template_path, context
6 | source = case template_type(template_path)
7 | when :snippet then
8 | template_path = template_path.gsub(/^snippets\//, '')
9 | snippet = snippet(template_path)
10 | raise ::Liquid::FileSystemError,
11 | "No such snippet '#{template_path}' found" unless snippet
12 | snippet
13 | when :layout then
14 | template_path = template_path.gsub(/^layouts\//, '')
15 | layout = layout(template_path)
16 | raise ::Liquid::FileSystemError,
17 | "No such layout '#{template_path}' found" unless layout
18 | layout
19 | when :super then
20 | page_part = context[:processed]
21 | raise ::Liquid::FileSystemError,
22 | "Can not render super page_part outside the page_part context" unless page_part.is_a?(PufferPages::PagePart)
23 | parent_part = page_part.parent
24 | raise ::Liquid::FileSystemError,
25 | "No super page_part found for #{page_part.name}" unless parent_part
26 | parent_part
27 | when :page_part then
28 | page_part = context.registers[:page].inherited_page_part(template_path)
29 | raise ::Liquid::FileSystemError,
30 | "No such page_part '#{template_path}' found for current page" unless page_part
31 | page_part
32 | end
33 |
34 | source.respond_to?(:render) ? source : ::Liquid::Template.parse(source)
35 | end
36 |
37 | def template_type template_path
38 | return :super if template_path == :super
39 | return :layout if template_path.start_with? 'layouts/'
40 | return :snippet if template_path.start_with? 'snippets/'
41 | return :page_part
42 | end
43 |
44 | def snippet name
45 | @snippets_cache ||= {}
46 | @snippets_cache[name] ||= PufferPages::Snippet.find_snippet(name)
47 | end
48 |
49 | def layout name
50 | @layouts_cache ||= {}
51 | @layouts_cache[name] ||= PufferPages::Layout.find_layout(name)
52 | end
53 |
54 | end
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/puffer/codemirror/xq-dark.css:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2011 by MarkLogic Corporation
3 | Author: Mike Brevoort
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 | */
23 | .cm-s-xq-dark.CodeMirror { background: #0a001f; color: #f8f8f8; }
24 | .cm-s-xq-dark span.CodeMirror-selected { background: #a8f !important; }
25 | .cm-s-xq-dark .CodeMirror-gutters { background: #0a001f; border-right: 1px solid #aaa; }
26 | .cm-s-xq-dark .CodeMirror-linenumber { color: #f8f8f8; }
27 | .cm-s-xq-dark .CodeMirror-cursor { border-left: 1px solid white !important; }
28 |
29 | .cm-s-xq-dark span.cm-keyword {color: #FFBD40;}
30 | .cm-s-xq-dark span.cm-atom {color: #6C8CD5;}
31 | .cm-s-xq-dark span.cm-number {color: #164;}
32 | .cm-s-xq-dark span.cm-def {color: #FFF; text-decoration:underline;}
33 | .cm-s-xq-dark span.cm-variable {color: #FFF;}
34 | .cm-s-xq-dark span.cm-variable-2 {color: #EEE;}
35 | .cm-s-xq-dark span.cm-variable-3 {color: #DDD;}
36 | .cm-s-xq-dark span.cm-property {}
37 | .cm-s-xq-dark span.cm-operator {}
38 | .cm-s-xq-dark span.cm-comment {color: gray;}
39 | .cm-s-xq-dark span.cm-string {color: #9FEE00;}
40 | .cm-s-xq-dark span.cm-meta {color: yellow;}
41 | .cm-s-xq-dark span.cm-error {color: #f00;}
42 | .cm-s-xq-dark span.cm-qualifier {color: #FFF700;}
43 | .cm-s-xq-dark span.cm-builtin {color: #30a;}
44 | .cm-s-xq-dark span.cm-bracket {color: #cc7;}
45 | .cm-s-xq-dark span.cm-tag {color: #FFBD40;}
46 | .cm-s-xq-dark span.cm-attribute {color: #FFF700;}
47 |
--------------------------------------------------------------------------------
/lib/puffer_pages/liquid/tags/cache.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Liquid
3 | module Tags
4 |
5 | class Cache < ::Liquid::Block
6 | TIME_FORMATS = {
7 | /\A([0-9\.]+)s?\Z/ => 1,
8 | /\A([0-9\.]+)m\Z/ => 60,
9 | /\A([0-9\.]+)h\Z/ => 60*60,
10 | /\A([0-9\.]+)d\Z/ => 60*60*24
11 | }
12 |
13 | def initialize(tag_name, markup, tokens)
14 | arguments = markup.split(?,)
15 | options = arguments.pop if arguments.last && arguments.last.strip =~ /(#{::Liquid::TagAttributes})/
16 |
17 | @arguments = arguments.map do |var|
18 | var.strip =~ /(#{::Liquid::QuotedFragment})/
19 | $1 ? $1 : nil
20 | end.compact
21 |
22 | @options = {}
23 | options.scan(::Liquid::TagAttributes) do |key, value|
24 | @options[key] = value
25 | end if options
26 |
27 | super
28 | end
29 |
30 | def render(context)
31 | arguments = @arguments.map { |value| context[value] }
32 |
33 | options = @options.each_with_object({}) do |(key, value), result|
34 | result[key] = context[value]
35 | end
36 | options = {
37 | expires_in: expires_in(options['expires_in'])
38 | }.reject { |k, v| v.nil? }
39 |
40 | key = cache_key arguments.unshift(context[:processed])
41 |
42 | if cache?
43 | context.registers[:tracker].register(cache_store.fetch(key, options) do
44 | context.registers[:tracker].cleanup super
45 | end)
46 | else
47 | super
48 | end
49 | end
50 |
51 | def expires_in expiration
52 | fragments = expiration.to_s.split(' ').map(&:strip)
53 | times = fragments.map do |fragment|
54 | TIME_FORMATS.inject(nil) do |result, (format, multiplier)|
55 | break result if result
56 | match = fragment.match(format)
57 | (match[1].to_f * multiplier).round if match
58 | end
59 | end
60 | times.sum if times.present? && times.all?
61 | end
62 |
63 | def cache_key key
64 | ActiveSupport::Cache.expand_cache_key key.unshift('puffer_pages_cache')
65 | end
66 |
67 | def cache_store
68 | PufferPages.cache_store
69 | end
70 |
71 | def cache?
72 | PufferPages.config.perform_caching && cache_store
73 | end
74 | end
75 |
76 | end
77 | end
78 | end
79 |
80 | Liquid::Template.register_tag('cache', PufferPages::Liquid::Tags::Cache)
81 |
--------------------------------------------------------------------------------
/lib/puffer_pages/extensions/pagenator.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Extensions
3 | module Pagenator # There is no error in module name
4 | extend ActiveSupport::Concern
5 |
6 | included do
7 | class_attribute :_puffer_pages_options
8 | self._puffer_pages_options = {:pagenated => false}
9 | end
10 |
11 | module ClassMethods
12 | def inherited(klass)
13 | super
14 | klass._puffer_pages_options = _puffer_pages_options.dup
15 | end
16 |
17 | def puffer_pages options = {}
18 | _puffer_pages_options[:pagenated] = true
19 | _puffer_pages_options[:only] = Array.wrap(options[:only]).map(&:to_s).presence
20 | _puffer_pages_options[:except] = Array.wrap(options[:except]).map(&:to_s).presence
21 | _puffer_pages_options[:scope] = options[:scope]
22 | end
23 | end
24 |
25 | def _normalize_options options
26 | super
27 | if (options.key?(:puffer_page) || _puffer_pages_action?) && options[:puffer_page] != false
28 | scope = options[:puffer_scope].presence || _puffer_pages_options[:scope].presence
29 | page = options.values_at(:puffer_page, :partial, :action, :file).delete_if(&:blank?).first
30 | options[:puffer_page] = _puffer_pages_template(page, scope)
31 | options[:layout] = 'puffer_page'
32 | end
33 | end
34 |
35 | private
36 |
37 | def _puffer_pages_action?
38 | if only = _puffer_pages_options[:only]
39 | only.include?(action_name)
40 | elsif except = _puffer_pages_options[:except]
41 | !except.include?(action_name)
42 | else
43 | _puffer_pages_options[:pagenated]
44 | end
45 | end
46 |
47 | def _puffer_pages_template suggest, scope = nil
48 | return suggest if suggest.is_a?(PufferPages::Page)
49 |
50 | scope = case scope
51 | when Proc
52 | scope.call(self)
53 | when String, Symbol
54 | send scope
55 | else
56 | scope
57 | end
58 |
59 | _puffer_page_for suggest, scope
60 | end
61 |
62 | def _puffer_page_for location, scope = nil
63 | location = location.presence || request.path_info
64 | formats = lookup_context.formats
65 | page = PufferPages::Page.controller_scope(scope).find_view_page(location, formats: formats)
66 | raise PufferPages::MissedPage.new(location, formats) unless page
67 | raise PufferPages::DraftPage.new(location, formats) if page.draft?
68 | page
69 | end
70 |
71 | end
72 | end
73 | end
74 |
75 | ActionController::Base.send :include, PufferPages::Extensions::Pagenator
76 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb
3 |
4 | # Code is not reloaded between requests
5 | config.cache_classes = true
6 |
7 | # Full error reports are disabled and caching is turned on
8 | config.consider_all_requests_local = false
9 | config.action_controller.perform_caching = true
10 |
11 | # Disable Rails's static asset server (Apache or nginx will already do this)
12 | config.serve_static_assets = false
13 |
14 | # Compress JavaScripts and CSS
15 | config.assets.compress = true
16 |
17 | # Don't fallback to assets pipeline if a precompiled asset is missed
18 | config.assets.compile = false
19 |
20 | # Generate digests for assets URLs
21 | config.assets.digest = true
22 |
23 | # Defaults to Rails.root.join("public/assets")
24 | # config.assets.manifest = YOUR_PATH
25 |
26 | # Specifies the header that your server uses for sending files
27 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
28 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
29 |
30 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
31 | # config.force_ssl = true
32 |
33 | # See everything in the log (default is :info)
34 | # config.log_level = :debug
35 |
36 | # Prepend all log lines with the following tags
37 | # config.log_tags = [ :subdomain, :uuid ]
38 |
39 | # Use a different logger for distributed setups
40 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
41 |
42 | # Use a different cache store in production
43 | # config.cache_store = :mem_cache_store
44 |
45 | # Enable serving of images, stylesheets, and JavaScripts from an asset server
46 | # config.action_controller.asset_host = "http://assets.example.com"
47 |
48 | # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
49 | # config.assets.precompile += %w( search.js )
50 |
51 | # Disable delivery errors, bad email addresses will be ignored
52 | # config.action_mailer.raise_delivery_errors = false
53 |
54 | # Enable threaded mode
55 | # config.threadsafe!
56 |
57 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
58 | # the I18n.default_locale when a translation can not be found)
59 | config.i18n.fallbacks = true
60 |
61 | # Send deprecation notices to registered listeners
62 | config.active_support.deprecation = :notify
63 |
64 | # Log the query plan for queries taking more than this (works
65 | # with SQLite, MySQL, and PostgreSQL)
66 | # config.active_record.auto_explain_threshold_in_seconds = 0.5
67 | end
68 |
--------------------------------------------------------------------------------
/spec/dummy/config/application.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../boot', __FILE__)
2 |
3 | require 'rails/all'
4 |
5 | Bundler.require :development
6 |
7 | require "puffer_pages"
8 |
9 | module Dummy
10 | class Application < Rails::Application
11 | # Settings in config/environments/* take precedence over those specified here.
12 | # Application configuration should go into files in config/initializers
13 | # -- all .rb files in that directory are automatically loaded.
14 |
15 | # Custom directories with classes and modules you want to be autoloadable.
16 | # config.autoload_paths += %W(#{config.root}/extras)
17 |
18 | # Only load the plugins named here, in the order given (default is alphabetical).
19 | # :all can be used as a placeholder for all plugins not explicitly named.
20 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
21 |
22 | # Activate observers that should always be running.
23 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
24 |
25 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
26 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
27 | # config.time_zone = 'Central Time (US & Canada)'
28 |
29 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
30 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
31 | config.i18n.default_locale = :en
32 | config.i18n.available_locales = [:ru, :en, :cn]
33 | config.i18n.fallbacks = true
34 |
35 | # Configure the default encoding used in templates for Ruby 1.9.
36 | config.encoding = "utf-8"
37 |
38 | # Configure sensitive parameters which will be filtered from the log file.
39 | config.filter_parameters += [:password]
40 |
41 | # Use SQL instead of Active Record's schema dumper when creating the database.
42 | # This is necessary if your schema can't be completely dumped by the schema dumper,
43 | # like if you have constraints or database-specific column types
44 | # config.active_record.schema_format = :sql
45 |
46 | # Enforce whitelist mode for mass assignment.
47 | # This will create an empty whitelist of attributes available for mass-assignment for all models
48 | # in your app. As such, your models will need to explicitly whitelist or blacklist accessible
49 | # parameters by using an attr_accessible or attr_protected declaration.
50 | # config.active_record.whitelist_attributes = true
51 |
52 | # Enable the asset pipeline
53 | config.assets.enabled = true
54 |
55 | # Version of your assets, change this if you want to expire all your assets
56 | config.assets.version = '1.0'
57 |
58 | config.puffer_pages.raise_errors = true
59 | end
60 | end
61 |
--------------------------------------------------------------------------------
/app/assets/javascripts/puffer/codemirror/yaml.js:
--------------------------------------------------------------------------------
1 | CodeMirror.defineMode("yaml", function() {
2 |
3 | var cons = ['true', 'false', 'on', 'off', 'yes', 'no'];
4 | var keywordRegex = new RegExp("\\b(("+cons.join(")|(")+"))$", 'i');
5 |
6 | return {
7 | token: function(stream, state) {
8 | var ch = stream.peek();
9 | var esc = state.escaped;
10 | state.escaped = false;
11 | /* comments */
12 | if (ch == "#") { stream.skipToEnd(); return "comment"; }
13 | if (state.literal && stream.indentation() > state.keyCol) {
14 | stream.skipToEnd(); return "string";
15 | } else if (state.literal) { state.literal = false; }
16 | if (stream.sol()) {
17 | state.keyCol = 0;
18 | state.pair = false;
19 | state.pairStart = false;
20 | /* document start */
21 | if(stream.match(/---/)) { return "def"; }
22 | /* document end */
23 | if (stream.match(/\.\.\./)) { return "def"; }
24 | /* array list item */
25 | if (stream.match(/\s*-\s+/)) { return 'meta'; }
26 | }
27 | /* pairs (associative arrays) -> key */
28 | if (!state.pair && stream.match(/^\s*([a-z0-9\._-])+(?=\s*:)/i)) {
29 | state.pair = true;
30 | state.keyCol = stream.indentation();
31 | return "atom";
32 | }
33 | if (state.pair && stream.match(/^:\s*/)) { state.pairStart = true; return 'meta'; }
34 |
35 | /* inline pairs/lists */
36 | if (stream.match(/^(\{|\}|\[|\])/)) {
37 | if (ch == '{')
38 | state.inlinePairs++;
39 | else if (ch == '}')
40 | state.inlinePairs--;
41 | else if (ch == '[')
42 | state.inlineList++;
43 | else
44 | state.inlineList--;
45 | return 'meta';
46 | }
47 |
48 | /* list seperator */
49 | if (state.inlineList > 0 && !esc && ch == ',') {
50 | stream.next();
51 | return 'meta';
52 | }
53 | /* pairs seperator */
54 | if (state.inlinePairs > 0 && !esc && ch == ',') {
55 | state.keyCol = 0;
56 | state.pair = false;
57 | state.pairStart = false;
58 | stream.next();
59 | return 'meta';
60 | }
61 |
62 | /* start of value of a pair */
63 | if (state.pairStart) {
64 | /* block literals */
65 | if (stream.match(/^\s*(\||\>)\s*/)) { state.literal = true; return 'meta'; };
66 | /* references */
67 | if (stream.match(/^\s*(\&|\*)[a-z0-9\._-]+\b/i)) { return 'variable-2'; }
68 | /* numbers */
69 | if (state.inlinePairs == 0 && stream.match(/^\s*-?[0-9\.\,]+\s?$/)) { return 'number'; }
70 | if (state.inlinePairs > 0 && stream.match(/^\s*-?[0-9\.\,]+\s?(?=(,|}))/)) { return 'number'; }
71 | /* keywords */
72 | if (stream.match(keywordRegex)) { return 'keyword'; }
73 | }
74 |
75 | /* nothing found, continue */
76 | state.pairStart = false;
77 | state.escaped = (ch == '\\');
78 | stream.next();
79 | return null;
80 | },
81 | startState: function() {
82 | return {
83 | pair: false,
84 | pairStart: false,
85 | keyCol: 0,
86 | inlinePairs: 0,
87 | inlineList: 0,
88 | literal: false,
89 | escaped: false
90 | };
91 | }
92 | };
93 | });
94 |
95 | CodeMirror.defineMIME("text/x-yaml", "yaml");
96 |
--------------------------------------------------------------------------------
/app/assets/javascripts/puffer/matchbrackets.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"};
3 | function findMatchingBracket(cm) {
4 | var cur = cm.getCursor(), line = cm.getLineHandle(cur.line), pos = cur.ch - 1;
5 | var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];
6 | if (!match) return null;
7 | var forward = match.charAt(1) == ">", d = forward ? 1 : -1;
8 | var style = cm.getTokenAt({line: cur.line, ch: pos + 1}).type;
9 |
10 | var stack = [line.text.charAt(pos)], re = /[(){}[\]]/;
11 | function scan(line, lineNo, start) {
12 | if (!line.text) return;
13 | var pos = forward ? 0 : line.text.length - 1, end = forward ? line.text.length : -1;
14 | if (start != null) pos = start + d;
15 | for (; pos != end; pos += d) {
16 | var ch = line.text.charAt(pos);
17 | if (re.test(ch) && cm.getTokenAt({line: lineNo, ch: pos + 1}).type == style) {
18 | var match = matching[ch];
19 | if (match.charAt(1) == ">" == forward) stack.push(ch);
20 | else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false};
21 | else if (!stack.length) return {pos: pos, match: true};
22 | }
23 | }
24 | }
25 | for (var i = cur.line, found, e = forward ? Math.min(i + 100, cm.lineCount()) : Math.max(-1, i - 100); i != e; i+=d) {
26 | if (i == cur.line) found = scan(line, i, pos);
27 | else found = scan(cm.getLineHandle(i), i);
28 | if (found) break;
29 | }
30 | return {from: {line: cur.line, ch: pos}, to: found && {line: i, ch: found.pos}, match: found && found.match};
31 | }
32 |
33 | function matchBrackets(cm, autoclear) {
34 | var found = findMatchingBracket(cm);
35 | if (!found) return;
36 | var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
37 | var one = cm.markText(found.from, {line: found.from.line, ch: found.from.ch + 1},
38 | {className: style});
39 | var two = found.to && cm.markText(found.to, {line: found.to.line, ch: found.to.ch + 1},
40 | {className: style});
41 | var clear = function() {
42 | cm.operation(function() { one.clear(); two && two.clear(); });
43 | };
44 | if (autoclear) setTimeout(clear, 800);
45 | else return clear;
46 | }
47 |
48 | var currentlyHighlighted = null;
49 | function doMatchBrackets(cm) {
50 | cm.operation(function() {
51 | if (currentlyHighlighted) {currentlyHighlighted(); currentlyHighlighted = null;}
52 | if (!cm.somethingSelected()) currentlyHighlighted = matchBrackets(cm, false);
53 | });
54 | }
55 |
56 | CodeMirror.defineOption("matchBrackets", false, function(cm, val) {
57 | if (val) cm.on("cursorActivity", doMatchBrackets);
58 | else cm.off("cursorActivity", doMatchBrackets);
59 | });
60 |
61 | CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);});
62 | CodeMirror.defineExtension("findMatchingBracket", function(){return findMatchingBracket(this);});
63 | })();
64 |
--------------------------------------------------------------------------------
/spec/lib/page_drop_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe PufferPages::Liquid::PageDrop do
4 |
5 | def render_layout layout, current_page, page = nil
6 | current_page.render(layout, other: page)
7 | end
8 |
9 | context do
10 | let(:hash) { { 'hello' => '{{ self.name }}' } }
11 |
12 | let!(:root) { Fabricate :root, name: 'root' }
13 | let!(:first) { Fabricate :page, slug: 'first', parent: root }
14 | let!(:second) { Fabricate :page, slug: 'second', parent: first, page_parts: [main, sidebar] }
15 | let!(:css) { Fabricate :page, slug: 'page.css', parent: first }
16 | let!(:main) { Fabricate(:main) }
17 | let!(:sidebar) { Fabricate(:sidebar, handler: 'yaml', body: YAML.dump(hash)) }
18 |
19 | before do
20 | root.reload
21 | first.reload
22 | second.reload
23 | end
24 |
25 | specify { render_layout('{{ self.parent.name }}', second).should == first.name }
26 | specify { render_layout('{{ self.root.name }}', second).should == 'root' }
27 |
28 | specify { render_layout('{{ other.current? }}', first, first).should == 'true' }
29 | specify { render_layout('{{ other.current? }}', first, root).should == 'false' }
30 | specify { render_layout('{{ other.current? }}', first, second).should == 'false' }
31 |
32 | specify { render_layout('{{ other.ancestor? }}', first, first).should == 'false' }
33 | specify { render_layout('{{ other.ancestor? }}', first, root).should == 'true' }
34 | specify { render_layout('{{ other.ancestor? }}', first, second).should == 'false' }
35 |
36 | specify { render_layout('{% if self == other %}equal{% else %}not equal{% endif %}', first, first).should == 'equal' }
37 | specify { render_layout('{% if self == other %}equal{% else %}not equal{% endif %}', first, root).should == 'not equal' }
38 | specify { render_layout('{% if self == other %}equal{% else %}not equal{% endif %}', first, second).should == 'not equal' }
39 |
40 | specify { render_layout('{{ self.body }}', second).should == main.body }
41 | specify { render_layout('{{ self.sidebar.hello }}', second).should == second.name }
42 |
43 | context do
44 | include RSpec::Rails::RequestExampleGroup
45 |
46 | context 'url helpers' do
47 | let!(:application) { Fabricate :application, :body => '{{ self.path }} {{ self.url }}' }
48 |
49 | specify do
50 | get "/#{second.location}"
51 | response.body.should == '/first/second http://www.example.com/first/second'
52 | end
53 | end
54 |
55 | context 'render tag' do
56 | let!(:application) { Fabricate :application, :body => "{% render 'shared/first' %}" }
57 |
58 | specify do
59 | get "/#{second.location}"
60 | response.body.should == 'shared/first content'
61 | end
62 | end
63 |
64 | context 'render tag with other format' do
65 | let!(:application) { Fabricate :application, :body => "{% render 'shared/first' %}" }
66 |
67 | specify do
68 | get "/#{css.location}"
69 | response.body.should == 'shared/first content'
70 | end
71 | end
72 | end
73 | end
74 |
75 | end
76 |
--------------------------------------------------------------------------------
/spec/lib/rspec/matchers/render_page_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe 'Specs' do
4 | describe '#render_page' do
5 | include RSpec::Rails::ControllerExampleGroup
6 |
7 | let!(:layout) { Fabricate :application }
8 | let!(:root) { Fabricate :root }
9 | let!(:first) { Fabricate :page, slug: 'first', parent: root }
10 | let!(:second) { Fabricate :page, slug: 'second', parent: first }
11 |
12 | context 'no page specified' do
13 | context do
14 | controller do
15 | def index
16 | @page = PufferPages::Page.root
17 | render puffer_page: @page
18 | end
19 | end
20 |
21 | it { should render_page }
22 | specify do
23 | get :index
24 | response.should render_page
25 | end
26 | end
27 |
28 | context do
29 | controller do
30 | def index
31 | render nothing: true
32 | end
33 | end
34 |
35 | it { should_not render_page }
36 | specify do
37 | get :index
38 | response.should_not render_page
39 | end
40 | end
41 | end
42 |
43 | context 'with page instance' do
44 | controller do
45 | def index
46 | @page = PufferPages::Page.root
47 | render puffer_page: @page
48 | end
49 |
50 | def show
51 | @page = PufferPages::Page.find_page 'first'
52 | render puffer_page: @page
53 | end
54 | end
55 |
56 | it { should render_page root }
57 | it { should_not render_page first }
58 | it { should_not render_page second }
59 | specify do
60 | get :index
61 | response.should render_page root
62 | end
63 | specify do
64 | get :index
65 | response.should_not render_page first
66 | end
67 | specify do
68 | get :index
69 | response.should_not render_page second
70 | end
71 | specify do
72 | get :show, id: 42
73 | response.should_not render_page root
74 | end
75 | specify do
76 | get :show, id: 42
77 | response.should render_page first
78 | end
79 | specify do
80 | get :show, id: 42
81 | response.should_not render_page second
82 | end
83 | end
84 |
85 | context 'with drops' do
86 | controller do
87 | def index
88 | page = PufferPages::Page.root
89 | @count = 42
90 | @string = 'hello'
91 | @object = Object.new
92 | render puffer_page: page
93 | end
94 | end
95 |
96 | it { should render_page }
97 | it { should render_page.with_drops }
98 | it { should render_page.with_drops('count') }
99 | it { should render_page.with_drops('string' => 'hello') }
100 | it { should render_page.with_drops('string', 'count' => 42) }
101 | it { should render_page.with_drops { |drops| drops.keys.include?('string') } }
102 | it { should render_page.with_drops { |drops| drops['count'] > 40 } }
103 | it { should render_page.with_drops('count') { |drops| !drops.key?('object') } }
104 | it { should_not render_page.with_drops('object') }
105 | end
106 | end
107 | end
108 |
--------------------------------------------------------------------------------
/app/assets/javascripts/puffer/codemirror/htmlmixed.js:
--------------------------------------------------------------------------------
1 | CodeMirror.defineMode("htmlmixed", function(config) {
2 | var htmlMode = CodeMirror.getMode(config, {name: "xml", htmlMode: true});
3 | var jsMode = CodeMirror.getMode(config, "javascript");
4 | var cssMode = CodeMirror.getMode(config, "css");
5 |
6 | function html(stream, state) {
7 | var style = htmlMode.token(stream, state.htmlState);
8 | if (/(?:^|\s)tag(?:\s|$)/.test(style) && stream.current() == ">" && state.htmlState.context) {
9 | if (/^script$/i.test(state.htmlState.context.tagName)) {
10 | state.token = javascript;
11 | state.localState = jsMode.startState(htmlMode.indent(state.htmlState, ""));
12 | }
13 | else if (/^style$/i.test(state.htmlState.context.tagName)) {
14 | state.token = css;
15 | state.localState = cssMode.startState(htmlMode.indent(state.htmlState, ""));
16 | }
17 | }
18 | return style;
19 | }
20 | function maybeBackup(stream, pat, style) {
21 | var cur = stream.current();
22 | var close = cur.search(pat), m;
23 | if (close > -1) stream.backUp(cur.length - close);
24 | else if (m = cur.match(/<\/?$/)) {
25 | stream.backUp(cur.length);
26 | if (!stream.match(pat, false)) stream.match(cur[0]);
27 | }
28 | return style;
29 | }
30 | function javascript(stream, state) {
31 | if (stream.match(/^<\/\s*script\s*>/i, false)) {
32 | state.token = html;
33 | state.localState = null;
34 | return html(stream, state);
35 | }
36 | return maybeBackup(stream, /<\/\s*script\s*>/,
37 | jsMode.token(stream, state.localState));
38 | }
39 | function css(stream, state) {
40 | if (stream.match(/^<\/\s*style\s*>/i, false)) {
41 | state.token = html;
42 | state.localState = null;
43 | return html(stream, state);
44 | }
45 | return maybeBackup(stream, /<\/\s*style\s*>/,
46 | cssMode.token(stream, state.localState));
47 | }
48 |
49 | return {
50 | startState: function() {
51 | var state = htmlMode.startState();
52 | return {token: html, localState: null, mode: "html", htmlState: state};
53 | },
54 |
55 | copyState: function(state) {
56 | if (state.localState)
57 | var local = CodeMirror.copyState(state.token == css ? cssMode : jsMode, state.localState);
58 | return {token: state.token, localState: local, mode: state.mode,
59 | htmlState: CodeMirror.copyState(htmlMode, state.htmlState)};
60 | },
61 |
62 | token: function(stream, state) {
63 | return state.token(stream, state);
64 | },
65 |
66 | indent: function(state, textAfter) {
67 | if (state.token == html || /^\s*<\//.test(textAfter))
68 | return htmlMode.indent(state.htmlState, textAfter);
69 | else if (state.token == javascript)
70 | return jsMode.indent(state.localState, textAfter);
71 | else
72 | return cssMode.indent(state.localState, textAfter);
73 | },
74 |
75 | electricChars: "/{}:",
76 |
77 | innerMode: function(state) {
78 | var mode = state.token == html ? htmlMode : state.token == javascript ? jsMode : cssMode;
79 | return {state: state.localState || state.htmlState, mode: mode};
80 | }
81 | };
82 | }, "xml", "javascript", "css");
83 |
84 | CodeMirror.defineMIME("text/html", "htmlmixed");
85 |
--------------------------------------------------------------------------------
/spec/data/unlocalized.json:
--------------------------------------------------------------------------------
1 | {
2 | "layouts": [{
3 | "created_at": "2013-12-23T16:23:00Z",
4 | "id": "404DF5860F01492EB470711FE3BE4535",
5 | "name": "application",
6 | "updated_at": "2013-12-23T16:23:00Z",
7 | "body": "Nulla suscipit ligula in lacus."
8 | }],
9 | "snippets": [{
10 | "created_at": "2013-12-23T16:23:00Z",
11 | "id": "C647D698E0D64A99BC8AA0C0A0F0BAEF",
12 | "name": "lacus",
13 | "updated_at": "2013-12-23T16:23:00Z",
14 | "body": "Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nulla dapibus dolor vel est."
15 | }],
16 | "pages": [{
17 | "created_at": "2013-12-23T16:23:00Z",
18 | "id": "55E458FCA2BB4BA0B95AE99ACDDAF094",
19 | "layout_name": "application",
20 | "locales": {},
21 | "name": "Nunc nisl.",
22 | "parent_id": null,
23 | "slug": null,
24 | "status": "published",
25 | "updated_at": "2013-12-23T16:23:00Z",
26 | "page_parts": [{
27 | "created_at": "2013-12-23T16:23:00Z",
28 | "handler": "html",
29 | "id": "A0C54DBB446D4727903A2B6FA5D4C9E2",
30 | "name": "body",
31 | "page_id": "55E458FCA2BB4BA0B95AE99ACDDAF094",
32 | "updated_at": "2013-12-23T16:23:00Z",
33 | "body": "PagePart: `body`, Page: ``"
34 | }, {
35 | "created_at": "2013-12-23T16:23:00Z",
36 | "handler": "html",
37 | "id": "EA1DE54674D1430894134AA9FDB603F3",
38 | "name": "sidebar",
39 | "page_id": "55E458FCA2BB4BA0B95AE99ACDDAF094",
40 | "updated_at": "2013-12-23T16:23:00Z",
41 | "body": "PagePart: `sidebar`, Page: ``"
42 | }]
43 | }, {
44 | "created_at": "2013-12-23T16:23:00Z",
45 | "id": "58C3B16335C242E8B231E2B634D1B7EA",
46 | "layout_name": null,
47 | "locales": {},
48 | "name": "Morbi non quam nec dui luctus rutrum.",
49 | "parent_id": "55E458FCA2BB4BA0B95AE99ACDDAF094",
50 | "slug": "first",
51 | "status": "published",
52 | "updated_at": "2013-12-23T16:23:00Z",
53 | "page_parts": [{
54 | "created_at": "2013-12-23T16:23:00Z",
55 | "handler": "html",
56 | "id": "A0BC46E472FC45CB8C57F068685DEA3D",
57 | "name": "body",
58 | "page_id": "58C3B16335C242E8B231E2B634D1B7EA",
59 | "updated_at": "2013-12-23T16:23:00Z",
60 | "body": "PagePart: `body`, Page: `first`"
61 | }]
62 | }, {
63 | "created_at": "2013-12-23T16:23:00Z",
64 | "id": "C8D5E74CE293403DAB5FBB5A89BDE6C5",
65 | "layout_name": null,
66 | "locales": {},
67 | "name": "Donec dapibus.",
68 | "parent_id": "58C3B16335C242E8B231E2B634D1B7EA",
69 | "slug": "second",
70 | "status": "published",
71 | "updated_at": "2013-12-23T16:23:00Z",
72 | "page_parts": [{
73 | "created_at": "2013-12-23T16:23:00Z",
74 | "handler": "html",
75 | "id": "504F931E6E8C495699BA5D4C9D1FAD06",
76 | "name": "sidebar",
77 | "page_id": "C8D5E74CE293403DAB5FBB5A89BDE6C5",
78 | "updated_at": "2013-12-23T16:23:00Z",
79 | "body": "PagePart: `sidebar`, Page: `first/second`"
80 | }]
81 | }]
82 | }
--------------------------------------------------------------------------------
/spec/lib/liquid/tags_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe 'Tags' do
4 |
5 | def render_page(page, drops = {})
6 | page.render({ self: page }.merge(drops))
7 | end
8 |
9 | describe 'stylesheets' do
10 |
11 | it 'should render stylesheets with proper params' do
12 | @page = Fabricate :page, :layout_name => 'foo_layout'
13 | @layout = Fabricate :layout, :name => 'foo_layout', :body => "{% assign st = 'styles' %}{% stylesheets 'reset', st %}"
14 | render_page(@page).should == "<%= stylesheet_link_tag 'reset', 'styles' %>"
15 | end
16 |
17 | end
18 |
19 | describe 'javascripts' do
20 |
21 | it 'should render javascripts with proper params' do
22 | @page = Fabricate :page, :layout_name => 'foo_layout'
23 | @layout = Fabricate :layout, :name => 'foo_layout', :body => "{% assign ctrl = 'controls' %}{% javascripts 'prototype', ctrl %}"
24 | render_page(@page).should == "<%= javascript_include_tag 'prototype', 'controls' %>"
25 | end
26 |
27 | end
28 |
29 | describe 'javascript' do
30 |
31 | it 'should render javascript tag' do
32 | @page = Fabricate :page, :layout_name => 'foo_layout'
33 | @layout = Fabricate :layout, :name => 'foo_layout', :body => "{% javascript %}\nvar i = \"\";\ni = 2;\n{% endjavascript %}"
34 | render_page(@page).should == "<%= javascript_tag do %>\nvar i = \"\";\ni = 2;\n<% end %>"
35 | end
36 |
37 | end
38 |
39 | describe 'super' do
40 | let!(:root){
41 | Fabricate :page, :layout_name => 'foo', :page_parts => [
42 | Fabricate(:page_part, :name => 'sidebar', :body => "root sidebar {{var}}")
43 | ]
44 | }
45 | let!(:page){
46 | Fabricate :page, :slug => 'page', :parent => root, :page_parts => [
47 | Fabricate(:page_part, :name => 'sidebar', :body => "wrap {% super var:'hello' %} sidebar")
48 | ]
49 | }
50 | let!(:page2){
51 | Fabricate :page, :slug => 'page2', :parent => page, :page_parts => [
52 | Fabricate(:page_part, :name => 'sidebar', :body => "wrap2 {% super %} sidebar")
53 | ]
54 | }
55 |
56 | specify{page.render("{% include 'sidebar' %}").should == "wrap root sidebar hello sidebar"}
57 | specify{page2.render("{% include 'sidebar' %}").should == "wrap2 wrap root sidebar hello sidebar sidebar"}
58 | end
59 |
60 | describe 'array' do
61 | subject{Liquid::Template.parse("{% array arr = 'one', 2, var %}{{arr[0]}} {{arr[1]}} {{arr[2]}}")}
62 | specify{subject.render('var' => 'three').should == 'one 2 three'}
63 | end
64 |
65 | context 'url helpers' do
66 | include RSpec::Rails::ControllerExampleGroup
67 |
68 | controller{}
69 |
70 | def render template, variables = {}
71 | Liquid::Template.parse(template).render!(variables.stringify_keys, {:registers => {:controller => controller}})
72 | end
73 |
74 | specify { render("{% url admin_pages %}").should == 'http://test.host/admin/pages' }
75 | specify { render("{% path admin_page 10 %}").should == '/admin/pages/10' }
76 | specify { render("{% path admin_pages key: 'value' %}").should == '/admin/pages?key=value' }
77 | specify { render("{% path admin_page 'haha' %}").should == '/admin/pages/haha' }
78 | specify { render("{% path admin_page var %}", { var: 'foo' }).should == '/admin/pages/foo' }
79 | specify { render("{% path admin_page 10, key: value %}", { value: 'hello' }).should == '/admin/pages/10?key=hello' }
80 | end
81 | end
82 |
--------------------------------------------------------------------------------
/lib/puffer_pages/rspec/matchers/render_page.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | module Rspec
3 | module Matchers
4 | class RenderPage < ::RSpec::Matchers::BuiltIn::BaseMatcher
5 | attr_reader :scope, :page, :drops
6 |
7 | def initialize scope, page = nil
8 | @scope = scope
9 | @page = page
10 | @page = PufferPages::Page.find_page(page) if page.is_a?(String)
11 | @drops ||= { values: {}, names: [], manual: [] }
12 | end
13 |
14 | def matches? controller_or_request
15 | scope.get :index if controller_or_request.is_a?(ActionController::Base)
16 |
17 | rendered_page && (!with_page? || page_conformity) && (!with_drops? || drops_conformity)
18 | end
19 |
20 | def with_drops *drops, &block
21 | @drops[:values].merge! drops.extract_options!
22 | @drops[:names].concat(drops.flatten).uniq!
23 | @drops[:manual].push block if block
24 | self
25 | end
26 |
27 | def failure_message_for_should
28 | message = ''
29 | message << "expected action to render #{page_message}\n"
30 | message << "with drops #{drops_message}\n" if with_drops?
31 | if with_drops? && !drops_conformity
32 | rendered_drops = scope.puffer_pages_render[rendered_page].first[:drops]
33 | message << "but available drops are: #{PP.pp rendered_drops, ''}"
34 | else
35 | message << "but #{rendered_page ? "`/#{rendered_page.location}`" : 'nothing'} was rendered"
36 | end
37 | message
38 | end
39 |
40 | def failure_message_for_should_not
41 | "expected action not to render #{page_message} but `/#{rendered_page.location}` was rendered"
42 | end
43 |
44 | def description
45 | "render page #{page_message}"
46 | end
47 |
48 | private
49 |
50 | def page_message
51 | page ? "page `/#{page.location}`" : 'any page'
52 | end
53 |
54 | def drops_message
55 | [drops[:names], drops[:values]].delete_if(&:blank?).map(&:inspect).join ?,
56 | end
57 |
58 | def rendered_page
59 | @rendered_page ||= scope.puffer_pages_render.keys.first
60 | end
61 |
62 | def with_page?
63 | !!page
64 | end
65 |
66 | def page_conformity
67 | scope.puffer_pages_render[rendered_page] &&
68 | scope.puffer_pages_render[rendered_page].count == 1 &&
69 | proper_page_rendered
70 | end
71 |
72 | def proper_page_rendered
73 | if rendered_page.respond_to?(:dummy_page?) && rendered_page.dummy_page?
74 | rendered_page.location == page.location
75 | else
76 | rendered_page == page
77 | end
78 | end
79 |
80 | def with_drops?
81 | drops.values.any?(&:present?)
82 | end
83 |
84 | def drops_conformity
85 | rendered_drops = scope.puffer_pages_render[rendered_page].first[:drops]
86 |
87 | rendered_drops.keys | drops[:names] == rendered_drops.keys &&
88 | drops[:values].all? { |(name, value)| rendered_drops[name] == value } &&
89 | drops[:manual].all? { |block| block.call(rendered_drops) }
90 | end
91 | end
92 |
93 | def render_page page = nil
94 | RenderPage.new self, page
95 | end
96 | end
97 | end
98 | end
--------------------------------------------------------------------------------
/lib/puffer_pages.rb:
--------------------------------------------------------------------------------
1 | module PufferPages
2 | include ActiveSupport::Configurable
3 |
4 | autoload :SnippetsBase, 'puffer_pages/backends/controllers/snippets_base'
5 | autoload :LayoutsBase, 'puffer_pages/backends/controllers/layouts_base'
6 | autoload :PagesBase, 'puffer_pages/backends/controllers/pages_base'
7 | autoload :OriginsBase, 'puffer_pages/backends/controllers/origins_base'
8 |
9 | class PufferPagesError < StandardError
10 | end
11 |
12 | class RenderError < PufferPagesError
13 | def initialize location, formats = []
14 | @location, @formats = location, formats
15 | end
16 |
17 | def with_formats
18 | if @formats.present?
19 | "with formats `#{@formats.join('`, `')}`"
20 | else
21 | "with any format"
22 | end
23 | end
24 |
25 | def reason
26 | end
27 |
28 | def message
29 | "PufferPages can`t render page `#{@location}` #{with_formats} #{reason}"
30 | end
31 | end
32 |
33 | class DraftPage < RenderError
34 | def reason
35 | "because it`s draft"
36 | end
37 | end
38 |
39 | class MissedPage < RenderError
40 | def reason
41 | "because it`s missed"
42 | end
43 | end
44 |
45 | class ImportFailed < PufferPagesError
46 | end
47 |
48 | mattr_accessor :primary_page_part_name
49 | self.primary_page_part_name = 'body'
50 |
51 | mattr_accessor :single_section_page_path
52 | self.single_section_page_path = false
53 |
54 | mattr_accessor :localize
55 | self.localize = false
56 |
57 | mattr_accessor :access_token
58 | self.access_token = nil
59 |
60 | mattr_accessor :export_path
61 | self.export_path = '/admin/origins/export'
62 |
63 | mattr_accessor :install_i18n_backend
64 | self.install_i18n_backend = true
65 |
66 | config.raise_errors = false
67 |
68 | module ConfigMethods
69 | def setup
70 | yield self
71 | end
72 |
73 | def i18n_backend
74 | @i18n_backend ||= PufferPages::Liquid::Backend.new
75 | end
76 |
77 | def cache_store
78 | config.cache_store
79 | end
80 |
81 | def cache_store= store
82 | config.cache_store = ActiveSupport::Cache.lookup_store(store)
83 | end
84 | end
85 |
86 | extend ConfigMethods
87 | end
88 |
89 | require 'puffer'
90 | require 'liquid'
91 | require 'uuidtools'
92 | require 'activeuuid'
93 | require 'nested_set'
94 | require 'contextuality'
95 |
96 | require 'puffer_pages/helpers'
97 | require 'puffer_pages/engine'
98 | require 'puffer_pages/backends'
99 | require 'puffer_pages/handlers'
100 | require 'puffer_pages/migrations'
101 | require 'puffer_pages/log_subscriber'
102 | require 'puffer_pages/extensions/core'
103 | require 'puffer_pages/extensions/context'
104 | require 'puffer_pages/extensions/renderer'
105 | require 'puffer_pages/extensions/pagenator'
106 |
107 | require 'puffer_pages/liquid/tags/url'
108 | require 'puffer_pages/liquid/tags/cache'
109 | require 'puffer_pages/liquid/tags/yield'
110 | require 'puffer_pages/liquid/tags/array'
111 | require 'puffer_pages/liquid/tags/assets'
112 | require 'puffer_pages/liquid/tags/image'
113 | require 'puffer_pages/liquid/tags/helper'
114 | require 'puffer_pages/liquid/tags/render'
115 | require 'puffer_pages/liquid/tags/scope'
116 | require 'puffer_pages/liquid/tags/super'
117 | require 'puffer_pages/liquid/tags/include'
118 | require 'puffer_pages/liquid/tags/partials'
119 | require 'puffer_pages/liquid/tags/translate'
120 | require 'puffer_pages/liquid/tags/javascript'
121 |
--------------------------------------------------------------------------------
/spec/models/puffer_pages/renderable_spec.rb:
--------------------------------------------------------------------------------
1 | # encoding: UTF-8
2 | require 'spec_helper'
3 |
4 | describe PufferPages::Backends::Mixins::Renderable do
5 | let(:klass) do
6 | Class.new do
7 | include PufferPages::Backends::Mixins::Renderable
8 | end
9 | end
10 | subject { klass.new }
11 |
12 | def merge *args
13 | subject.send(:merge_context, *args)
14 | end
15 |
16 | describe "#normalize_render_options" do
17 | let(:string) { 'Hello ^^' }
18 | let(:object) { Object.new }
19 | let(:hash) { { a: 1 } }
20 | let(:another_hash) { { b: 2 } }
21 | let(:context) { ::Liquid::Context.new }
22 |
23 | def normalize *args
24 | subject.send(:normalize_render_options, *args)
25 | end
26 |
27 | specify { normalize(string, context, hash).should == [string, merge(context, hash)] }
28 | specify { normalize(object, context, hash).should == [object, merge(context, hash)] }
29 | specify { normalize(context, hash).should == [nil, merge(context, hash)] }
30 | specify { normalize(string, hash, another_hash).should == [string, merge(hash, another_hash)] }
31 | specify { normalize(hash, another_hash).should == [nil, merge(hash, another_hash)] }
32 | specify { normalize(string, context).should == [string, merge(context, {})] }
33 | specify { normalize(string, hash).should == [string, merge(hash, {})] }
34 | specify { normalize(context).should == [nil, context] }
35 | specify { normalize(hash).should == [nil, merge(hash, {})] }
36 | specify { expect { normalize(string, context, hash, 42).should }.to raise_error ArgumentError }
37 | end
38 |
39 | describe "#merge_context" do
40 | let(:hash) { { a: 1 } }
41 | let(:another_hash) { { registers: { b: 2 }, environment: { c: 3 } } }
42 | let(:context) { ::Liquid::Context.new }
43 |
44 | specify { merge(hash, {}).should == { drops: { 'a' => 1 }, environment: {}, registers: {} } }
45 | specify { merge(another_hash, {}).should == { drops: {}, environment: { c: 3 }, registers: { b: 2 } } }
46 | specify { merge(hash, another_hash).should == { drops: { 'a' => 1 }, environment: { c: 3 }, registers: { b: 2 } } }
47 | specify { merge(context, {}).should == context }
48 | specify { merge(context, hash)['a'].should == 1 }
49 | specify { merge(context, hash).registers.should == {} }
50 | specify { merge(context, another_hash).registers.should == { b: 2 } }
51 | end
52 |
53 | describe "#normalize_context_options" do
54 | let(:hash1) { { a: 1 } }
55 | let(:hash2) { { b: 2 } }
56 | let(:hash3) { { c: 3 } }
57 |
58 | def normalize *args
59 | subject.send(:normalize_context_options, *args)
60 | end
61 |
62 | specify { normalize(foo: hash1).should == { drops: { 'foo' => hash1 }, environment: {}, registers: {} } }
63 | specify { normalize(drops: { 'a' => 1 }, environment: hash2, registers: hash3).should ==
64 | { drops: { 'a' => 1 }, environment: hash2, registers: hash3 } }
65 | specify { normalize(environment: hash1, foo: hash2).should ==
66 | { drops: { 'environment' => hash1, 'foo' => hash2 }, environment: {}, registers: {} } }
67 | end
68 |
69 | describe "#render_template" do
70 | def render *args
71 | subject.send(:render_template, *args)
72 | end
73 |
74 | context 'with hash context' do
75 | specify { render("{{ hello }}", hello: 'Hello').should == 'Hello' }
76 | end
77 |
78 | context 'with object context' do
79 | let(:liquid_context) { ::Liquid::Context.new('hello' => 'Hello') }
80 |
81 | specify { render("{{ hello }}", liquid_context).should == 'Hello' }
82 | end
83 | end
84 | end
85 |
--------------------------------------------------------------------------------
/app/assets/javascripts/puffer/multiplex.js:
--------------------------------------------------------------------------------
1 | CodeMirror.multiplexingMode = function(outer /*, others */) {
2 | // Others should be {open, close, mode [, delimStyle]} objects
3 | var others = Array.prototype.slice.call(arguments, 1);
4 | var n_others = others.length;
5 |
6 | function indexOf(string, pattern, from) {
7 | if (typeof pattern == "string") return string.indexOf(pattern, from);
8 | var m = pattern.exec(from ? string.slice(from) : string);
9 | return m ? m.index + from : -1;
10 | }
11 |
12 | return {
13 | startState: function() {
14 | return {
15 | outer: CodeMirror.startState(outer),
16 | innerActive: null,
17 | inner: null
18 | };
19 | },
20 |
21 | copyState: function(state) {
22 | return {
23 | outer: CodeMirror.copyState(outer, state.outer),
24 | innerActive: state.innerActive,
25 | inner: state.innerActive && CodeMirror.copyState(state.innerActive.mode, state.inner)
26 | };
27 | },
28 |
29 | token: function(stream, state) {
30 | if (!state.innerActive) {
31 | var cutOff = Infinity, oldContent = stream.string;
32 | for (var i = 0; i < n_others; ++i) {
33 | var other = others[i];
34 | var found = indexOf(oldContent, other.open, stream.pos);
35 | if (found == stream.pos) {
36 | stream.match(other.open);
37 | state.innerActive = other;
38 | state.inner = CodeMirror.startState(other.mode, outer.indent ? outer.indent(state.outer, "") : 0);
39 | return other.delimStyle;
40 | } else if (found != -1 && found < cutOff) {
41 | cutOff = found;
42 | }
43 | }
44 | if (cutOff != Infinity) stream.string = oldContent.slice(0, cutOff);
45 | var outerToken = outer.token(stream, state.outer);
46 | if (cutOff != Infinity) stream.string = oldContent;
47 | return outerToken;
48 | } else {
49 | var curInner = state.innerActive, oldContent = stream.string;
50 | var found = indexOf(oldContent, curInner.close, stream.pos);
51 | if (found == stream.pos) {
52 | stream.match(curInner.close);
53 | state.innerActive = state.inner = null;
54 | return curInner.delimStyle;
55 | }
56 | if (found > -1) stream.string = oldContent.slice(0, found);
57 | var innerToken = curInner.mode.token(stream, state.inner);
58 | if (found > -1) stream.string = oldContent;
59 | var cur = stream.current(), found = cur.indexOf(curInner.close);
60 | if (found > -1) stream.backUp(cur.length - found);
61 | return innerToken;
62 | }
63 | },
64 |
65 | indent: function(state, textAfter) {
66 | var mode = state.innerActive ? state.innerActive.mode : outer;
67 | if (!mode.indent) return CodeMirror.Pass;
68 | return mode.indent(state.innerActive ? state.inner : state.outer, textAfter);
69 | },
70 |
71 | blankLine: function(state) {
72 | var mode = state.innerActive ? state.innerActive.mode : outer;
73 | if (mode.blankLine) {
74 | mode.blankLine(state.innerActive ? state.inner : state.outer);
75 | }
76 | if (!state.innerActive) {
77 | for (var i = 0; i < n_others; ++i) {
78 | var other = others[i];
79 | if (other.open === "\n") {
80 | state.innerActive = other;
81 | state.inner = CodeMirror.startState(other.mode, mode.indent ? mode.indent(state.outer, "") : 0);
82 | }
83 | }
84 | } else if (mode.close === "\n") {
85 | state.innerActive = state.inner = null;
86 | }
87 | },
88 |
89 | electricChars: outer.electricChars,
90 |
91 | innerMode: function(state) {
92 | return state.inner ? {state: state.inner, mode: state.innerActive.mode} : {state: state.outer, mode: outer};
93 | }
94 | };
95 | };
96 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://codeclimate.com/github/puffer/puffer_pages)
2 | [](http://travis-ci.org/puffer/puffer_pages)
3 |
4 | # PufferPages is lightweight but powerful rails >= 3.1 CMS
5 |
6 | Interface of PufferPages based on [puffer](https://github.com/puffer/puffer)
7 |
8 | ## Keyfeatures
9 |
10 | * Full rails integration. PufferPages is part of rails and you can different features related to pages in rails application directly
11 | * Flexibility. Puffer designed to be as flexible as possible, so you can create your own functionality easily.
12 | * Layouts. You can use rails layouts for pages and you can use pages as action layouts!
13 |
14 | ## Installation
15 |
16 | You can instal puffer as a gem:
17 | gem install puffer_pages
18 | Or in Gemfile:
19 | gem "puffer_pages"
20 |
21 | Next step is:
22 | rake puffer_pages:install:migrations
23 | This will install PufferPages config file in your initializers, some css/js, controllers and migrations
24 | rake db:migrate
25 |
26 | Nex step - adding routes:
27 |
28 | mount PufferPages::Engine => '/'
29 |
30 |
31 | To start working with admin interface, you need to add some routes like:
32 |
33 | namespace :admin do
34 | resources :pages
35 | resources :layouts
36 | resources :snippets
37 | resources :origins
38 | end
39 |
40 |
41 | ## Introduction
42 | PufferPages is radiant-like cms, so it has layouts, snippets and pages.
43 | PufferPages use liquid as template language.
44 |
45 | ## Pages
46 | Pages - tree-based structure of site.
47 | Every page has one or more page parts.
48 |
49 | ## PageParts
50 | Page_parts are the same as content_for block content in rails. You can insert current page page_patrs at layout.
51 | Also, page_parts are inheritable. It means, that if root has page_part named `sidebar`, all its children will have the same page_part until this page_part will be redefined.
52 | Every page part must have main page part, named by default `body`. You can configure main page part name in config/initializers/puffer_pages.rb
53 |
54 | ## Layouts
55 | Layout is page canvas, so you can draw page parts on it.
56 | You can use layouts from database or rails applcation layouts for pages.
57 |
58 | ### Rails application layouts
59 | For application layout page_part body will be inserted instead of SUDDENLY! <%= yield %>
60 | For yield with no params specified puffer will use page part with default page_part name.
61 |
62 | So, main page part is action view and other are partials. So easy.
63 |
64 | ## [Liquid](http://github.com/tobi/liquid/)
65 |
66 | ### Variables
67 | This variables accessible from every page:
68 |
69 | * self - current page reference.
70 | {{ self.name }}
71 | self is an instance of page drop. View [this](https://github.com/puffer/puffer_pages/blob/master/lib/puffer_pages/liquid/page_drop.rb) to find list of possible page drop methods
72 |
73 | ### include
74 | `include` is standart liquid tag with puffer data model 'file_system'
75 |
76 | #### for page_parts
77 | Use include tag for current page page_parts inclusion:
78 | {% include 'page_part_name' %}
79 |
80 | #### for snippets
81 | To include snippet use this path form:
82 | {% include 'snippets/snippet_name' %}
83 |
84 | Usage example:
85 |
86 | {% include 'sidebar' %} # this will render 'sidebar' page_part
87 | {% assign navigation = 'snippets/navigation' %}
88 | {% include navigation %} # this will render 'navigation' snippet
89 |
90 |
91 | ### stylesheets, javascripts
92 | {% stylesheets path [, path, path ...] %}
93 | Both tags syntax is equal
94 | Tags renders rail`s stylesheet_link_tag or javascript_include_tag.
95 |
96 | Usage example:
97 |
98 | {% assign ctrl = 'controls' %}
99 | {% javascripts 'prototype', ctrl %}
100 |
101 |
102 |
--------------------------------------------------------------------------------
/spec/lib/liquid/tags/cache_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe PufferPages::Liquid::Tags::Cache do
4 | describe '#expires_in' do
5 | subject { PufferPages::Liquid::Tags::Cache.allocate }
6 |
7 | specify { subject.expires_in('10').should == 10.seconds }
8 | specify { subject.expires_in('10s').should == 10.seconds }
9 | specify { subject.expires_in('10m').should == 10.minutes }
10 | specify { subject.expires_in('10h').should == 10.hours }
11 | specify { subject.expires_in('10h 10m 10s').should == 10.hours + 10.minutes + 10.seconds }
12 | specify { subject.expires_in(nil).should be_nil }
13 | specify { subject.expires_in('').should be_nil }
14 | specify { subject.expires_in('10blabla').should be_nil }
15 | specify { subject.expires_in('10blabla 10m').should be_nil }
16 | end
17 |
18 | describe '#render' do
19 |
20 | def cache_key *args
21 | ActiveSupport::Cache.expand_cache_key args.unshift('puffer_pages_cache')
22 | end
23 |
24 | let!(:root) { Fabricate :root }
25 | before { PufferPages.cache_store = :memory_store }
26 | before { PufferPages.config.stub(:perform_caching) { true } }
27 |
28 | context 'simple caching' do
29 | let(:entry) { ActiveSupport::Cache::Entry.new 'Bye' }
30 |
31 | specify do
32 | PufferPages.config.stub(:perform_caching) { false }
33 | PufferPages.cache_store.should_not_receive(:write)
34 | PufferPages.cache_store.should_not_receive(:read_entry)
35 | root.render("{% cache %}Hello{% endcache %}").should == 'Hello'
36 | end
37 |
38 | specify do
39 | PufferPages.cache_store.should_receive(:write).with(
40 | cache_key(root), 'Hello', {}
41 | ).and_call_original
42 | root.render("{% cache %}Hello{% endcache %}").should == 'Hello'
43 | end
44 |
45 | specify do
46 | PufferPages.cache_store.stub(:read_entry).with(
47 | cache_key(root), {}
48 | ) { entry }
49 | root.render("{% cache %}Hello{% endcache %}").should == entry.value
50 | end
51 | end
52 |
53 | context 'additional params' do
54 | specify do
55 | PufferPages.cache_store.should_receive(:write).with(
56 | cache_key(root), 'Hello', { expires_in: 60 }
57 | ).and_call_original
58 | root.render("{% cache expires_in: '1m' %}Hello{% endcache %}")
59 | end
60 |
61 | specify do
62 | PufferPages.cache_store.should_receive(:write).with(
63 | cache_key(root, 'additional_key'), 'Hello', { expires_in: 60 }
64 | ).and_call_original
65 | root.render("{% cache 'additional_key', expires_in: '1m' %}Hello{% endcache %}")
66 | end
67 |
68 | specify do
69 | PufferPages.cache_store.should_receive(:write).with(
70 | cache_key(root, 'key1', 'key2'), 'Hello', {}
71 | ).and_call_original
72 | root.render("{% cache 'key1', 'key2', expires_in: '' %}Hello{% endcache %}")
73 | end
74 |
75 | specify do
76 | PufferPages.cache_store.should_receive(:write).with(
77 | cache_key(root, 'additional_key'), 'Hello', { expires_in: 3600 }
78 | ).and_call_original
79 | root.render(
80 | "{% cache key, expires_in: expire %}Hello{% endcache %}",
81 | { expire: '1h', key: 'additional_key' }
82 | )
83 | end
84 | end
85 |
86 | context 'proper cache key' do
87 | let!(:custom) { Fabricate :custom, body: "{% cache %}Hello{% endcache %}" }
88 |
89 | specify do
90 | PufferPages.cache_store.should_receive(:write).with(
91 | cache_key(custom), 'Hello', {}
92 | ).and_call_original
93 | root.render("{% snippet 'custom' %}")
94 | end
95 | end
96 |
97 | context 'erb tracker cleanup' do
98 | specify do
99 | root.render("{% cache %}{% yield %}{% endcache %}").should == '<%= yield %>' # write
100 | root.render("{% cache %}{% yield %}{% endcache %}").should == '<%= yield %>' # read
101 | end
102 | end
103 | end
104 | end
105 |
--------------------------------------------------------------------------------
/db/migrate/20120924120226_migrate_to_uuid.rb:
--------------------------------------------------------------------------------
1 | class PreviousPage < ActiveRecord::Base
2 | acts_as_nested_set
3 | has_many :page_parts, :class_name => '::PreviousPagePart', :inverse_of => :page, :foreign_key => :page_id
4 | def import_attributes
5 | attributes.except *%w(id parent_id lft rgt depth location title description keywords)
6 | end
7 | end
8 |
9 | class PreviousPagePart < ActiveRecord::Base
10 | belongs_to :page, :class_name => '::PreviousPage', :inverse_of => :page_parts
11 | def import_attributes
12 | attributes.except *%w(id page_id)
13 | end
14 | end
15 |
16 | class PreviousLayout < ActiveRecord::Base
17 | def import_attributes
18 | attributes.except *%w(id)
19 | end
20 | end
21 |
22 | class PreviousSnippet < ActiveRecord::Base
23 | def import_attributes
24 | attributes.except *%w(id)
25 | end
26 | end
27 |
28 | class MigrateToUuid < ActiveRecord::Migration
29 | def up
30 | [:pages, :page_parts, :layouts, :snippets].each do |table|
31 | ActiveRecord::Base.connection.indexes(table).each do |index|
32 | remove_index table, :name => index.name
33 | end
34 | rename_table table, :"previous_#{table}"
35 | end
36 |
37 | create_table :pages, :id => false do |t|
38 | t.uuid :id, :primary_key => true
39 | t.string :name
40 | t.string :slug
41 | t.string :layout_name
42 | t.string :status
43 | t.uuid :parent_id
44 | t.integer :lft
45 | t.integer :rgt
46 | t.integer :depth, :default => 0
47 | t.string :location
48 | t.timestamps
49 | end
50 |
51 | create_table :page_parts, :id => false do |t|
52 | t.uuid :id, :primary_key => true
53 | t.string :name
54 | t.text :body
55 | t.uuid :page_id
56 | t.timestamps
57 | end
58 |
59 | create_table :layouts, :id => false do |t|
60 | t.uuid :id, :primary_key => true
61 | t.string :name
62 | t.text :body
63 | t.timestamps
64 | end
65 |
66 | create_table :snippets, :id => false do |t|
67 | t.uuid :id, :primary_key => true
68 | t.string :name
69 | t.text :body
70 | t.timestamps
71 | end
72 |
73 | add_index :pages, :slug
74 | add_index :pages, :location
75 |
76 | add_index :page_parts, :name
77 | add_index :page_parts, :page_id
78 |
79 | add_index :layouts, :name
80 |
81 | add_index :snippets, :name
82 |
83 | [PufferPages::Page, PufferPages::PagePart, PufferPages::Layout, PufferPages::Snippet,
84 | PreviousPage, PreviousPagePart, PreviousLayout, PreviousSnippet].each do |model|
85 | model.reset_column_information
86 | end
87 |
88 | puts "\nMigrating data"
89 | PufferPages::Page.transaction do
90 | import_child_pages PreviousPage.roots
91 | PreviousLayout.all.each { |layout| PufferPages::Layout.create!(layout.import_attributes); print '.' }
92 | PreviousSnippet.all.each { |snippet| PufferPages::Snippet.create!(snippet.import_attributes); print '.' }
93 | end
94 | puts "\n"
95 |
96 | [:pages, :page_parts, :layouts, :snippets].each do |table|
97 | drop_table :"previous_#{table}"
98 | end
99 | end
100 |
101 | def import_child_pages pages, new_parent = nil
102 | pages.each do |page|
103 | new_page = (new_parent ? new_parent.children : PufferPages::Page).create! page.import_attributes
104 | new_page.page_parts = page.page_parts.map do |page_part|
105 | print '.'
106 | PufferPages::PagePart.create! page_part.import_attributes
107 | end
108 | print '.'
109 | %w(title keywords description).each do |attribute|
110 | print '.'
111 | page_part = new_page.page_parts.detect { |page_part| page_part.name == attribute }
112 | if page_part
113 | page_part.update_attributes body: "#{page_part.body}\n--#{attribute} value--\n#{page.send(attribute)}"
114 | else
115 | new_page.page_parts.create! name: attribute, body: page.send(attribute)
116 | end
117 | end
118 | page.save
119 | import_child_pages page.children, new_page unless page.leaf?
120 | end
121 | end
122 |
123 | def down
124 | raise IrreversibleMigration
125 | end
126 | end
127 |
--------------------------------------------------------------------------------
/spec/dummy/db/migrate/20130118071516_migrate_to_uuid.rb:
--------------------------------------------------------------------------------
1 | # This migration comes from puffer_pages (originally 20120924120226)
2 | class PreviousPage < ActiveRecord::Base
3 | acts_as_nested_set
4 | has_many :page_parts, :class_name => '::PreviousPagePart', :inverse_of => :page, :foreign_key => :page_id
5 | def import_attributes
6 | attributes.except *%w(id parent_id lft rgt depth location title description keywords)
7 | end
8 | end
9 |
10 | class PreviousPagePart < ActiveRecord::Base
11 | belongs_to :page, :class_name => '::PreviousPage', :inverse_of => :page_parts
12 | def import_attributes
13 | attributes.except *%w(id page_id)
14 | end
15 | end
16 |
17 | class PreviousLayout < ActiveRecord::Base
18 | def import_attributes
19 | attributes.except *%w(id)
20 | end
21 | end
22 |
23 | class PreviousSnippet < ActiveRecord::Base
24 | def import_attributes
25 | attributes.except *%w(id)
26 | end
27 | end
28 |
29 | class MigrateToUuid < ActiveRecord::Migration
30 | def up
31 | [:pages, :page_parts, :layouts, :snippets].each do |table|
32 | ActiveRecord::Base.connection.indexes(table).each do |index|
33 | remove_index table, :name => index.name
34 | end
35 | rename_table table, :"previous_#{table}"
36 | end
37 |
38 | create_table :pages, :id => false do |t|
39 | t.uuid :id, :primary_key => true
40 | t.string :name
41 | t.string :slug
42 | t.string :layout_name
43 | t.string :status
44 | t.uuid :parent_id
45 | t.integer :lft
46 | t.integer :rgt
47 | t.integer :depth, :default => 0
48 | t.string :location
49 | t.timestamps
50 | end
51 |
52 | create_table :page_parts, :id => false do |t|
53 | t.uuid :id, :primary_key => true
54 | t.string :name
55 | t.text :body
56 | t.uuid :page_id
57 | t.timestamps
58 | end
59 |
60 | create_table :layouts, :id => false do |t|
61 | t.uuid :id, :primary_key => true
62 | t.string :name
63 | t.text :body
64 | t.timestamps
65 | end
66 |
67 | create_table :snippets, :id => false do |t|
68 | t.uuid :id, :primary_key => true
69 | t.string :name
70 | t.text :body
71 | t.timestamps
72 | end
73 |
74 | add_index :pages, :slug
75 | add_index :pages, :location
76 |
77 | add_index :page_parts, :name
78 | add_index :page_parts, :page_id
79 |
80 | add_index :layouts, :name
81 |
82 | add_index :snippets, :name
83 |
84 | [PufferPages::Page, PufferPages::PagePart, PufferPages::Layout, PufferPages::Snippet,
85 | PreviousPage, PreviousPagePart, PreviousLayout, PreviousSnippet].each do |model|
86 | model.reset_column_information
87 | end
88 |
89 | puts "\nMigrating data"
90 | PufferPages::Page.transaction do
91 | import_child_pages PreviousPage.roots
92 | PreviousLayout.all.each { |layout| PufferPages::Layout.create!(layout.import_attributes); print '.' }
93 | PreviousSnippet.all.each { |snippet| PufferPages::Snippet.create!(snippet.import_attributes); print '.' }
94 | end
95 | puts "\n"
96 |
97 | [:pages, :page_parts, :layouts, :snippets].each do |table|
98 | drop_table :"previous_#{table}"
99 | end
100 | end
101 |
102 | def import_child_pages pages, new_parent = nil
103 | pages.each do |page|
104 | new_page = (new_parent ? new_parent.children : PufferPages::Page).create! page.import_attributes
105 | new_page.page_parts = page.page_parts.map do |page_part|
106 | print '.'
107 | PufferPages::PagePart.create! page_part.import_attributes
108 | end
109 | print '.'
110 | %w(title keywords description).each do |attribute|
111 | print '.'
112 | page_part = new_page.page_parts.detect { |page_part| page_part.name == attribute }
113 | if page_part
114 | page_part.update_attributes body: "#{page_part.body}\n--#{attribute} value--\n#{page.send(attribute)}"
115 | else
116 | new_page.page_parts.create! name: attribute, body: page.send(attribute)
117 | end
118 | end
119 | page.save
120 | import_child_pages page.children, new_page unless page.leaf?
121 | end
122 | end
123 |
124 | def down
125 | raise IrreversibleMigration
126 | end
127 | end
128 |
--------------------------------------------------------------------------------
/spec/lib/pagenator_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe PufferPages::Extensions::Pagenator do
4 | context 'controller' do
5 | include RSpec::Rails::ControllerExampleGroup
6 | render_views
7 |
8 | let!(:foo_layout){Fabricate :foo_layout}
9 | let!(:root){Fabricate :foo_root}
10 | let!(:anonymous){Fabricate :page, :slug => 'anonymous', :name => 'foo', :parent => root}
11 | let!(:foo){Fabricate :page, :slug => 'foo', :parent => root}
12 | let!(:bar){Fabricate :page, :slug => 'bar', :parent => foo}
13 |
14 | let!(:bar_layout){Fabricate :bar_layout}
15 | let!(:root2){Fabricate :foo_root, :layout_name => 'bar_layout'}
16 |
17 | let!(:named){Fabricate :page, :slug => 'named', :name => 'foo', :parent => root}
18 | let!(:named2){Fabricate :page, :slug => 'named', :name => 'bar', :parent => root2}
19 |
20 | context do
21 | controller do
22 | puffer_pages
23 | def index; end
24 | end
25 |
26 | specify do
27 | get :index
28 | response.body.should == 'foo_layout anonymous'
29 | end
30 | end
31 |
32 | context do
33 | controller do
34 | puffer_pages :only => :index
35 | def index; end
36 | end
37 |
38 | specify do
39 | get :index
40 | response.body.should == 'foo_layout anonymous'
41 | end
42 | end
43 |
44 | context do
45 | controller do
46 | puffer_pages :except => :show
47 | def index; end
48 | end
49 |
50 | specify do
51 | get :index
52 | response.body.should == 'foo_layout anonymous'
53 | end
54 | end
55 |
56 | context do
57 | controller do
58 | puffer_pages :only => [:show]
59 | def index; end
60 | end
61 |
62 | specify{expect{get :index}.to raise_error}
63 | end
64 |
65 | context do
66 | controller do
67 | puffer_pages :except => [:index]
68 | def index; end
69 | end
70 |
71 | specify{expect{get :index}.to raise_error}
72 | end
73 |
74 | context do
75 | controller do
76 | puffer_pages
77 | def index
78 | render 'foo'
79 | end
80 | end
81 |
82 | specify do
83 | get :index
84 | response.body.should == 'foo_layout foo'
85 | end
86 | end
87 |
88 | context do
89 | controller do
90 | puffer_pages
91 | def index
92 | render PufferPages::Page.where(:slug => 'foo').first
93 | end
94 | end
95 |
96 | specify do
97 | get :index
98 | response.body.should == 'foo_layout foo'
99 | end
100 | end
101 |
102 | context do
103 | controller do
104 | puffer_pages
105 | def index
106 | render 'foo/bar'
107 | end
108 | end
109 |
110 | specify do
111 | get :index
112 | response.body.should == 'foo_layout bar'
113 | end
114 | end
115 |
116 | context do
117 | controller do
118 | puffer_pages
119 | def index
120 | render 'foo/bar'
121 | end
122 | end
123 |
124 | specify do
125 | get :index
126 | response.body.should == 'foo_layout bar'
127 | end
128 | end
129 |
130 | context do
131 | controller do
132 | puffer_pages :scope => {:name => 'foo'}
133 | def index
134 | render 'named'
135 | end
136 | end
137 |
138 | specify do
139 | get :index
140 | response.body.should == 'foo_layout named'
141 | end
142 | end
143 |
144 | context do
145 | controller do
146 | puffer_pages :scope => lambda {|conroller|
147 | {:name => 'bar'}
148 | }
149 | def index
150 | render 'named'
151 | end
152 | end
153 |
154 | specify do
155 | get :index
156 | response.body.should == 'bar_layout named'
157 | end
158 | end
159 |
160 | context do
161 | controller do
162 | puffer_pages scope: :puffer_scope
163 | def index
164 | render 'named'
165 | end
166 |
167 | def puffer_scope
168 | {name: 'bar'}
169 | end
170 | end
171 |
172 | specify do
173 | get :index
174 | response.body.should == 'bar_layout named'
175 | end
176 | end
177 | end
178 | end
--------------------------------------------------------------------------------
/spec/data/localized.json:
--------------------------------------------------------------------------------
1 | {
2 | "layouts": [{
3 | "created_at": "2013-12-23T16:23:00Z",
4 | "id": "404DF5860F01492EB470711FE3BE4535",
5 | "name": "application",
6 | "updated_at": "2013-12-23T16:23:00Z",
7 | "body_translations": {
8 | "en": "Nulla suscipit ligula in lacus.",
9 | "ru": "Morbi ut odio.",
10 | "cn": "Donec ut mauris eget massa tempor convallis."
11 | }
12 | }],
13 | "snippets": [{
14 | "created_at": "2013-12-23T16:23:00Z",
15 | "id": "C647D698E0D64A99BC8AA0C0A0F0BAEF",
16 | "name": "lacus",
17 | "updated_at": "2013-12-23T16:23:00Z",
18 | "body_translations": {
19 | "en": "Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nulla dapibus dolor vel est.",
20 | "ru": "Curabitur gravida nisi at nibh.",
21 | "cn": "Nam nulla."
22 | }
23 | }],
24 | "pages": [{
25 | "created_at": "2013-12-23T16:23:00Z",
26 | "id": "55E458FCA2BB4BA0B95AE99ACDDAF094",
27 | "layout_name": "application",
28 | "locales": {},
29 | "name": "Nunc nisl.",
30 | "parent_id": null,
31 | "slug": null,
32 | "status": "published",
33 | "updated_at": "2013-12-23T16:23:00Z",
34 | "page_parts": [{
35 | "created_at": "2013-12-23T16:23:00Z",
36 | "handler": "html",
37 | "id": "A0C54DBB446D4727903A2B6FA5D4C9E2",
38 | "name": "body",
39 | "page_id": "55E458FCA2BB4BA0B95AE99ACDDAF094",
40 | "updated_at": "2013-12-23T16:23:00Z",
41 | "body_translations": {
42 | "en": "PagePart: `body`, Page: ``",
43 | "ru": "Nulla neque libero, convallis eget, eleifend luctus, ultricies eu, nibh.",
44 | "cn": "Fusce posuere felis sed lacus."
45 | }
46 | }, {
47 | "created_at": "2013-12-23T16:23:00Z",
48 | "handler": "html",
49 | "id": "EA1DE54674D1430894134AA9FDB603F3",
50 | "name": "sidebar",
51 | "page_id": "55E458FCA2BB4BA0B95AE99ACDDAF094",
52 | "updated_at": "2013-12-23T16:23:00Z",
53 | "body_translations": {
54 | "en": "PagePart: `sidebar`, Page: ``",
55 | "ru": "Mauris enim leo, rhoncus sed, vestibulum sit amet, cursus id, turpis.",
56 | "cn": "Vivamus vel nulla eget eros elementum pellentesque."
57 | }
58 | }]
59 | }, {
60 | "created_at": "2013-12-23T16:23:00Z",
61 | "id": "58C3B16335C242E8B231E2B634D1B7EA",
62 | "layout_name": null,
63 | "locales": {},
64 | "name": "Morbi non quam nec dui luctus rutrum.",
65 | "parent_id": "55E458FCA2BB4BA0B95AE99ACDDAF094",
66 | "slug": "first",
67 | "status": "published",
68 | "updated_at": "2013-12-23T16:23:00Z",
69 | "page_parts": [{
70 | "created_at": "2013-12-23T16:23:00Z",
71 | "handler": "html",
72 | "id": "A0BC46E472FC45CB8C57F068685DEA3D",
73 | "name": "body",
74 | "page_id": "58C3B16335C242E8B231E2B634D1B7EA",
75 | "updated_at": "2013-12-23T16:23:00Z",
76 | "body_translations": {
77 | "en": "PagePart: `body`, Page: `first`",
78 | "ru": "Nam ultrices, libero non mattis pulvinar, nulla pede ullamcorper augue, a suscipit nulla elit ac nulla.",
79 | "cn": "Etiam justo."
80 | }
81 | }]
82 | }, {
83 | "created_at": "2013-12-23T16:23:00Z",
84 | "id": "C8D5E74CE293403DAB5FBB5A89BDE6C5",
85 | "layout_name": null,
86 | "locales": {},
87 | "name": "Donec dapibus.",
88 | "parent_id": "58C3B16335C242E8B231E2B634D1B7EA",
89 | "slug": "second",
90 | "status": "published",
91 | "updated_at": "2013-12-23T16:23:00Z",
92 | "page_parts": [{
93 | "created_at": "2013-12-23T16:23:00Z",
94 | "handler": "html",
95 | "id": "504F931E6E8C495699BA5D4C9D1FAD06",
96 | "name": "sidebar",
97 | "page_id": "C8D5E74CE293403DAB5FBB5A89BDE6C5",
98 | "updated_at": "2013-12-23T16:23:00Z",
99 | "body_translations": {
100 | "en": "PagePart: `sidebar`, Page: `first/second`",
101 | "ru": "Vivamus in felis eu sapien cursus vestibulum.",
102 | "cn": "Aenean sit amet justo."
103 | }
104 | }]
105 | }]
106 | }
--------------------------------------------------------------------------------