├── .ruby-gemset ├── .ruby-version ├── .rspec ├── lib ├── thesis │ ├── version.rb │ ├── railtie.rb │ ├── routing │ │ ├── routes.rb │ │ └── route_constraint.rb │ ├── exceptions.rb │ ├── engine.rb │ ├── colorizer.rb │ ├── controllers │ │ ├── controller_helpers.rb │ │ └── thesis_controller.rb │ └── models │ │ ├── page_content.rb │ │ └── page.rb ├── tasks │ └── thesis_tasks.rake ├── thesis.rb └── generators │ └── thesis │ └── install │ ├── templates │ ├── migrations │ │ ├── thesis_create_page.rb │ │ └── thesis_create_page_content.rb │ └── page_templates │ │ ├── default.html.slim │ │ ├── default.html.haml │ │ └── default.html.erb │ └── install_generator.rb ├── config └── routes.rb ├── .travis.yml ├── app └── assets │ ├── stylesheets │ ├── thesis │ │ ├── base │ │ │ ├── bourbon │ │ │ │ ├── settings │ │ │ │ │ ├── _px-to-em.scss │ │ │ │ │ ├── _asset-pipeline.scss │ │ │ │ │ └── _prefixer.scss │ │ │ │ ├── css3 │ │ │ │ │ ├── _appearance.scss │ │ │ │ │ ├── _user-select.scss │ │ │ │ │ ├── _calc.scss │ │ │ │ │ ├── _hyphens.scss │ │ │ │ │ ├── _box-sizing.scss │ │ │ │ │ ├── _filter.scss │ │ │ │ │ ├── _placeholder.scss │ │ │ │ │ ├── _perspective.scss │ │ │ │ │ ├── _backface-visibility.scss │ │ │ │ │ ├── _image-rendering.scss │ │ │ │ │ ├── _font-feature-settings.scss │ │ │ │ │ ├── _hidpi-media-query.scss │ │ │ │ │ ├── _transform.scss │ │ │ │ │ ├── _border-radius.scss │ │ │ │ │ ├── _font-face.scss │ │ │ │ │ ├── _keyframes.scss │ │ │ │ │ ├── _columns.scss │ │ │ │ │ ├── _linear-gradient.scss │ │ │ │ │ ├── _background-image.scss │ │ │ │ │ ├── _radial-gradient.scss │ │ │ │ │ ├── _animation.scss │ │ │ │ │ ├── _background.scss │ │ │ │ │ ├── _border-image.scss │ │ │ │ │ ├── _transition.scss │ │ │ │ │ └── _flex-box.scss │ │ │ │ ├── functions │ │ │ │ │ ├── _golden-ratio.scss │ │ │ │ │ ├── _strip-units.scss │ │ │ │ │ ├── _tint-shade.scss │ │ │ │ │ ├── _assign.scss │ │ │ │ │ ├── _px-to-rem.scss │ │ │ │ │ ├── _px-to-em.scss │ │ │ │ │ ├── _grid-width.scss │ │ │ │ │ ├── _color-lightness.scss │ │ │ │ │ ├── _unpack.scss │ │ │ │ │ ├── _transition-property-name.scss │ │ │ │ │ ├── _flex-grid.scss │ │ │ │ │ └── _modular-scale.scss │ │ │ │ ├── addons │ │ │ │ │ ├── _ellipsis.scss │ │ │ │ │ ├── _hide-text.scss │ │ │ │ │ ├── _word-wrap.scss │ │ │ │ │ ├── _font-family.scss │ │ │ │ │ ├── _size.scss │ │ │ │ │ ├── _clearfix.scss │ │ │ │ │ ├── _position.scss │ │ │ │ │ ├── _retina-image.scss │ │ │ │ │ ├── _prefixer.scss │ │ │ │ │ ├── _timing-functions.scss │ │ │ │ │ ├── _triangle.scss │ │ │ │ │ ├── _directional-values.scss │ │ │ │ │ ├── _html5-input-types.scss │ │ │ │ │ └── _button.scss │ │ │ │ ├── helpers │ │ │ │ │ ├── _shape-size-stripper.scss │ │ │ │ │ ├── _is-num.scss │ │ │ │ │ ├── _gradient-positions-parser.scss │ │ │ │ │ ├── _radial-positions-parser.scss │ │ │ │ │ ├── _convert-units.scss │ │ │ │ │ ├── _linear-angle-parser.scss │ │ │ │ │ ├── _render-gradients.scss │ │ │ │ │ ├── _linear-side-corner-parser.scss │ │ │ │ │ ├── _linear-gradient-parser.scss │ │ │ │ │ ├── _str-to-num.scss │ │ │ │ │ ├── _radial-gradient-parser.scss │ │ │ │ │ ├── _radial-arg-parser.scss │ │ │ │ │ └── _linear-positions-parser.scss │ │ │ │ ├── _bourbon-deprecated-upcoming.scss │ │ │ │ └── _bourbon.scss │ │ │ ├── _base.sass │ │ │ ├── _colors.sass │ │ │ ├── _fonts.sass │ │ │ ├── _forms.scss │ │ │ └── _keyframes.sass │ │ ├── _mixins │ │ │ ├── _barber-pole.sass │ │ │ └── editor.sass │ │ └── _thesis.sass │ └── thesis.sass │ └── javascripts │ ├── thesis.js │ └── thesis │ ├── utilities.coffee │ ├── thesis.coffee │ └── rangy-core.js ├── Rakefile ├── spec ├── factories │ ├── page_contents.rb │ └── pages.rb ├── thesis │ ├── routing │ │ └── thesis_routing_spec.rb │ ├── models │ │ ├── page_content_spec.rb │ │ └── page_spec.rb │ └── controllers │ │ └── thesis_controller_spec.rb └── spec_helper.rb ├── Gemfile ├── .gitignore ├── thesis.gemspec ├── LICENSE.txt └── README.md /.ruby-gemset: -------------------------------------------------------------------------------- 1 | thesis 2 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-2.1.5 2 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --colour --format Fuubar 2 | -------------------------------------------------------------------------------- /lib/thesis/version.rb: -------------------------------------------------------------------------------- 1 | module Thesis 2 | VERSION = "0.1.3" 3 | end 4 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | thesis_routes 3 | end -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.0.0 4 | - 1.9.3 5 | script: bundle exec rspec 6 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/settings/_px-to-em.scss: -------------------------------------------------------------------------------- 1 | $em-base: 16px !default; 2 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/settings/_asset-pipeline.scss: -------------------------------------------------------------------------------- 1 | $asset-pipeline: false !default; 2 | -------------------------------------------------------------------------------- /lib/tasks/thesis_tasks.rake: -------------------------------------------------------------------------------- 1 | # desc "Explaining what the task does" 2 | # task :thesis do 3 | # # Task goes here 4 | # end 5 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require 'rspec/core/rake_task' 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task :default => :spec 7 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/_base.sass: -------------------------------------------------------------------------------- 1 | @import "colors" 2 | @import "fonts" 3 | @import "../_mixins/**/*" 4 | @import "keyframes" 5 | @import "forms" -------------------------------------------------------------------------------- /app/assets/javascripts/thesis.js: -------------------------------------------------------------------------------- 1 | //= require ./thesis/utilities 2 | //= require ./thesis/rangy-core 3 | //= require ./thesis/hallo 4 | //= require ./thesis/thesis -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis.sass: -------------------------------------------------------------------------------- 1 | @import "thesis/base/bourbon/bourbon" 2 | @import "thesis/base/base" 3 | @import "thesis/_mixins/**/*" 4 | @import "thesis/thesis" -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/css3/_appearance.scss: -------------------------------------------------------------------------------- 1 | @mixin appearance ($value) { 2 | @include prefixer(appearance, $value, webkit moz ms o spec); 3 | } 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/css3/_user-select.scss: -------------------------------------------------------------------------------- 1 | @mixin user-select($arg: none) { 2 | @include prefixer(user-select, $arg, webkit moz ms spec); 3 | } 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/_colors.sass: -------------------------------------------------------------------------------- 1 | $gray : #A0A1A6 2 | $orange : #F88834 3 | $pink : #CB006C 4 | $green : #76A642 5 | $blue : #1BB6EC 6 | $yellow : #f0c600 -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/functions/_golden-ratio.scss: -------------------------------------------------------------------------------- 1 | @function golden-ratio($value, $increment) { 2 | @return modular-scale($value, $increment, $golden) 3 | } 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/css3/_calc.scss: -------------------------------------------------------------------------------- 1 | @mixin calc($property, $value) { 2 | #{$property}: -webkit-calc(#{$value}); 3 | #{$property}: calc(#{$value}); 4 | } 5 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/css3/_hyphens.scss: -------------------------------------------------------------------------------- 1 | @mixin hyphens($hyphenation: none) { 2 | // none | manual | auto 3 | @include prefixer(hyphens, $hyphenation, webkit moz ms spec); 4 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/css3/_box-sizing.scss: -------------------------------------------------------------------------------- 1 | @mixin box-sizing ($box) { 2 | // content-box | border-box | inherit 3 | @include prefixer(box-sizing, $box, webkit moz spec); 4 | } 5 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/functions/_strip-units.scss: -------------------------------------------------------------------------------- 1 | // Srtips the units from a value. e.g. 12px -> 12 2 | 3 | @function strip-units($val) { 4 | @return ($val / ($val * 0 + 1)); 5 | } 6 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/css3/_filter.scss: -------------------------------------------------------------------------------- 1 | @mixin filter($function: none) { 2 | // [Some Header

A little paragraph.

" 5 | content_type "html" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/factories/pages.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :page, class: Thesis::Page do 3 | name "Some Page" 4 | slug "/some-page" 5 | title "An Awesome Page" 6 | description "A description goes here" 7 | template "default" 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/_fonts.sass: -------------------------------------------------------------------------------- 1 | // RALEWAY 2 | @import url(//fonts.googleapis.com/css?family=Raleway:400,300,700) 3 | $raleway: "Raleway", sans-serif 4 | 5 | // FONT AWESOME 6 | @import url(//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/css/font-awesome.min.css) -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/css3/_placeholder.scss: -------------------------------------------------------------------------------- 1 | @mixin placeholder { 2 | $placeholders: ":-webkit-input" ":-moz" "-moz" "-ms-input"; 3 | @each $placeholder in $placeholders { 4 | &:#{$placeholder}-placeholder { 5 | @content; 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /lib/thesis/railtie.rb: -------------------------------------------------------------------------------- 1 | require 'thesis' 2 | require 'rails' 3 | 4 | module Thesis 5 | class Railtie < Rails::Railtie 6 | railtie_name :thesis 7 | 8 | rake_tasks do 9 | # load File.join(File.dirname(__FILE__), "tasks/db.rake") # Load rake tasks here 10 | end 11 | end 12 | end -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in thesis.gemspec 4 | gemspec 5 | gem "rspec", "~> 2.14.1" 6 | gem "factory_girl", "~> 4.0" 7 | gem "fuubar", "~> 1.0" # Prettier progress bars 8 | gem "database_cleaner" # Clean database between test runs 9 | gem "sqlite3" 10 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/css3/_perspective.scss: -------------------------------------------------------------------------------- 1 | @mixin perspective($depth: none) { 2 | // none | 3 | @include prefixer(perspective, $depth, webkit moz spec); 4 | } 5 | 6 | @mixin perspective-origin($value: 50% 50%) { 7 | @include prefixer(perspective-origin, $value, webkit moz spec); 8 | } 9 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/functions/_tint-shade.scss: -------------------------------------------------------------------------------- 1 | // Add percentage of white to a color 2 | @function tint($color, $percent){ 3 | @return mix(white, $color, $percent); 4 | } 5 | 6 | // Add percentage of black to a color 7 | @function shade($color, $percent){ 8 | @return mix(black, $color, $percent); 9 | } 10 | -------------------------------------------------------------------------------- /lib/thesis.rb: -------------------------------------------------------------------------------- 1 | require "thesis/version" 2 | require "thesis/exceptions" 3 | require "thesis/controllers/controller_helpers" 4 | require "thesis/routing/routes" 5 | require "thesis/routing/route_constraint" 6 | require "thesis/models/page" 7 | require "thesis/models/page_content" 8 | require "thesis/engine" 9 | 10 | module Thesis 11 | end 12 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/settings/_prefixer.scss: -------------------------------------------------------------------------------- 1 | // Variable settings for /addons/prefixer.scss 2 | $prefix-for-webkit: true !default; 3 | $prefix-for-mozilla: true !default; 4 | $prefix-for-microsoft: true !default; 5 | $prefix-for-opera: true !default; 6 | $prefix-for-spec: true !default; // required for keyframe mixin 7 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/functions/_assign.scss: -------------------------------------------------------------------------------- 1 | @function assign-inputs($inputs, $pseudo: null) { 2 | $list : (); 3 | 4 | @each $input in $inputs { 5 | $input: unquote($input); 6 | $input: if($pseudo, $input + ":" + $pseudo, $input); 7 | $list: append($list, $input, comma); 8 | } 9 | 10 | @return $list; 11 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/css3/_backface-visibility.scss: -------------------------------------------------------------------------------- 1 | //************************************************************************// 2 | // Backface-visibility mixin 3 | //************************************************************************// 4 | @mixin backface-visibility($visibility) { 5 | @include prefixer(backface-visibility, $visibility, webkit spec); 6 | } 7 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/addons/_font-family.scss: -------------------------------------------------------------------------------- 1 | $georgia: Georgia, Cambria, "Times New Roman", Times, serif; 2 | $helvetica: "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif; 3 | $lucida-grande: "Lucida Grande", Tahoma, Verdana, Arial, sans-serif; 4 | $monospace: "Bitstream Vera Sans Mono", Consolas, Courier, monospace; 5 | $verdana: Verdana, Geneva, sans-serif; 6 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/helpers/_shape-size-stripper.scss: -------------------------------------------------------------------------------- 1 | @function _shape-size-stripper($shape-size) { 2 | $shape-size-spec: null; 3 | @each $value in $shape-size { 4 | @if ($value == "cover") or ($value == "contain") { 5 | $value: null; 6 | } 7 | $shape-size-spec: "#{$shape-size-spec} #{$value}"; 8 | } 9 | @return $shape-size-spec; 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | .bundle/ 19 | log/*.log 20 | pkg/ 21 | spec/dummy/db/*.sqlite3 22 | spec/dummy/log/*.log 23 | spec/dummy/tmp/ 24 | spec/dummy/.sass-cache 25 | .DS_Store 26 | -------------------------------------------------------------------------------- /app/assets/javascripts/thesis/utilities.coffee: -------------------------------------------------------------------------------- 1 | class @Utilities 2 | 3 | # settimeout 4 | @delay: (ms, func) -> setTimeout func, ms 5 | 6 | # smoothscroll 7 | @scroll_to: (area, modifier, callback) -> 8 | modifier = modifier || 0 9 | go_to_position = $(area).offset().top 10 | $("html,body").stop() 11 | $("html,body").animate 12 | scrollTop: go_to_position + modifier 13 | , "slow", callback -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/_mixins/_barber-pole.sass: -------------------------------------------------------------------------------- 1 | @mixin barber_pole($color:$gray, $intensity:0.25, $speed:.7s) 2 | @include animation(barber_pole $speed linear infinite) 3 | @include background-image(linear-gradient(-45deg, rgba(255,255,255, $intensity) 25%,transparent 25%,transparent 50%,rgba(255,255,255, $intensity) 50%,rgba(255,255,255, $intensity) 75%,transparent 75%)) 4 | background-color : $color 5 | background-size : 30px 30px -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/helpers/_is-num.scss: -------------------------------------------------------------------------------- 1 | //************************************************************************// 2 | // Helper for linear-gradient-parser 3 | //************************************************************************// 4 | @function _is-num($char) { 5 | $values: '0' '1' '2' '3' '4' '5' '6' '7' '8' '9' 0 1 2 3 4 5 6 7 8 9; 6 | $index: index($values, $char); 7 | @return if($index, true, false); 8 | } 9 | -------------------------------------------------------------------------------- /lib/thesis/routing/routes.rb: -------------------------------------------------------------------------------- 1 | module ActionDispatch::Routing 2 | class Mapper 3 | def thesis_routes 4 | put "thesis/update_page_content" => "thesis/thesis#update_page_content" 5 | post "thesis/create_page" => "thesis/thesis#create_page" 6 | delete "thesis/delete_page" => "thesis/thesis#delete_page" 7 | 8 | get "*slug" => 'thesis/thesis#show', constraints: ::Thesis::RouteConstraint.new 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/addons/_size.scss: -------------------------------------------------------------------------------- 1 | @mixin size($size) { 2 | $height: nth($size, 1); 3 | $width: $height; 4 | 5 | @if length($size) > 1 { 6 | $height: nth($size, 2); 7 | } 8 | 9 | @if $height == auto or (type-of($height) == number and not unitless($height)) { 10 | height: $height; 11 | } 12 | 13 | @if $width == auto or (type-of($width) == number and not unitless($width)) { 14 | width: $width; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/css3/_image-rendering.scss: -------------------------------------------------------------------------------- 1 | @mixin image-rendering ($mode:auto) { 2 | 3 | @if ($mode == crisp-edges) { 4 | -ms-interpolation-mode: nearest-neighbor; // IE8+ 5 | image-rendering: -moz-crisp-edges; 6 | image-rendering: -o-crisp-edges; 7 | image-rendering: -webkit-optimize-contrast; 8 | image-rendering: crisp-edges; 9 | } 10 | 11 | @else { 12 | image-rendering: $mode; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/functions/_px-to-rem.scss: -------------------------------------------------------------------------------- 1 | // Convert pixels to rems 2 | // eg. for a relational value of 12px write rem(12) 3 | // Assumes $em-base is the font-size of 4 | 5 | @function rem($pxval) { 6 | @if not unitless($pxval) { 7 | $pxval: strip-units($pxval); 8 | } 9 | 10 | $base: $em-base; 11 | @if not unitless($base) { 12 | $base: strip-units($base); 13 | } 14 | @return ($pxval / $base) * 1rem; 15 | } 16 | -------------------------------------------------------------------------------- /lib/thesis/routing/route_constraint.rb: -------------------------------------------------------------------------------- 1 | module Thesis 2 | class RouteConstraint 3 | def matches?(request) 4 | slug = request.path.to_s.sub(/(\/)+$/,'') 5 | return false if excluded_path?(slug) 6 | Page.where(slug: slug).size > 0 7 | end 8 | 9 | def excluded_path?(path) 10 | excluded_extensions = [ :jpg, :jpeg, :png, :css, :js, :ico, :pdf ] 11 | excluded_extensions.any?{ |ext| path.ends_with?(".#{ext}") } 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/functions/_px-to-em.scss: -------------------------------------------------------------------------------- 1 | // Convert pixels to ems 2 | // eg. for a relational value of 12px write em(12) when the parent is 16px 3 | // if the parent is another value say 24px write em(12, 24) 4 | 5 | @function em($pxval, $base: $em-base) { 6 | @if not unitless($pxval) { 7 | $pxval: strip-units($pxval); 8 | } 9 | @if not unitless($base) { 10 | $base: strip-units($base); 11 | } 12 | @return ($pxval / $base) * 1em; 13 | } 14 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/_bourbon-deprecated-upcoming.scss: -------------------------------------------------------------------------------- 1 | //************************************************************************// 2 | // These mixins/functions are deprecated 3 | // They will be removed in the next MAJOR version release 4 | //************************************************************************// 5 | @mixin inline-block { 6 | display: inline-block; 7 | @warn "inline-block mixin is deprecated and will be removed in the next major version release"; 8 | } 9 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/functions/_grid-width.scss: -------------------------------------------------------------------------------- 1 | @function grid-width($n) { 2 | @return $n * $gw-column + ($n - 1) * $gw-gutter; 3 | } 4 | 5 | // The $gw-column and $gw-gutter variables must be defined in your base stylesheet to properly use the grid-width function. 6 | // 7 | // $gw-column: 100px; // Column Width 8 | // $gw-gutter: 40px; // Gutter Width 9 | // 10 | // div { 11 | // width: grid-width(4); // returns 520px; 12 | // margin-left: $gw-gutter; // returns 40px; 13 | // } 14 | -------------------------------------------------------------------------------- /lib/thesis/exceptions.rb: -------------------------------------------------------------------------------- 1 | module Thesis 2 | # A generic Thesis exception 3 | class Error < StandardError; end 4 | 5 | # When a required method is not implemented 6 | class RequiredMethodNotImplemented < StandardError; end 7 | 8 | # ActiveRecord is the only ORM that works with this currently 9 | class ActiveRecordRequired < StandardError; end 10 | 11 | # 404s 12 | # class PageNotFound < ActionController::RoutingError; end 13 | 14 | # No template specified 15 | class PageRequiresTemplate < StandardError; end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/css3/_font-feature-settings.scss: -------------------------------------------------------------------------------- 1 | // Font feature settings mixin and property default. 2 | // Examples: @include font-feature-settings("liga"); 3 | // @include font-feature-settings("lnum" false); 4 | // @include font-feature-settings("pnum" 1, "kern" 0); 5 | // @include font-feature-settings("ss01", "ss02"); 6 | 7 | @mixin font-feature-settings($settings...) { 8 | @if length($settings) == 0 { $settings: none; } 9 | @include prefixer(font-feature-settings, $settings, webkit moz ms spec); 10 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/css3/_hidpi-media-query.scss: -------------------------------------------------------------------------------- 1 | // HiDPI mixin. Default value set to 1.3 to target Google Nexus 7 (http://bjango.com/articles/min-device-pixel-ratio/) 2 | @mixin hidpi($ratio: 1.3) { 3 | @media only screen and (-webkit-min-device-pixel-ratio: $ratio), 4 | only screen and (min--moz-device-pixel-ratio: $ratio), 5 | only screen and (-o-min-device-pixel-ratio: #{$ratio}/1), 6 | only screen and (min-resolution: #{round($ratio*96)}dpi), 7 | only screen and (min-resolution: #{$ratio}dppx) { 8 | @content; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/functions/_color-lightness.scss: -------------------------------------------------------------------------------- 1 | // Programatically determines whether a color is light or dark 2 | // Returns a boolean 3 | // More details here http://robots.thoughtbot.com/closer-look-color-lightness 4 | 5 | @function is-light($hex-color) { 6 | $-local-red: red(rgba($hex-color, 1.0)); 7 | $-local-green: green(rgba($hex-color, 1.0)); 8 | $-local-blue: blue(rgba($hex-color, 1.0)); 9 | 10 | $-local-lightness: ($-local-red * 0.2126 + $-local-green * 0.7152 + $-local-blue * 0.0722) / 255; 11 | 12 | @return $-local-lightness > .6; 13 | } 14 | -------------------------------------------------------------------------------- /lib/thesis/engine.rb: -------------------------------------------------------------------------------- 1 | module Thesis 2 | class Engine < ::Rails::Engine 3 | # isolate_namespace Thesis # We're accessing the application controller, so we can't do this. 4 | initializer 'thesis.action_controller' do |app| 5 | require "thesis/controllers/controller_helpers" 6 | require "thesis/controllers/thesis_controller" 7 | 8 | ActiveSupport.on_load(:action_controller) do 9 | include ::Thesis::ControllerHelpers 10 | helper_method :current_page, :root_pages, :page_is_editable?, :thesis_editor 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/_mixins/editor.sass: -------------------------------------------------------------------------------- 1 | @mixin editor_icons($icons) 2 | @each $class, $color in $icons 3 | &.#{$class} > i 4 | background-color: $color 5 | &:hover 6 | background-color: darken($color, 8%) 7 | @include transition(background-color .3s ease-in-out) 8 | 9 | @mixin curve_buttons($limit) 10 | @for $i from 1 to $limit 11 | &:nth-child(#{$i}) 12 | @include transform(rotate(- (($limit - $i) * 3.14)+deg)) 13 | @include transform-origin(100% 100%) 14 | $right: (($limit - $i) * $limit) * ($limit + 1 - $i) / 3 15 | right: $right+px -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/helpers/_gradient-positions-parser.scss: -------------------------------------------------------------------------------- 1 | @function _gradient-positions-parser($gradient-type, $gradient-positions) { 2 | @if $gradient-positions 3 | and ($gradient-type == linear) 4 | and (type-of($gradient-positions) != color) { 5 | $gradient-positions: _linear-positions-parser($gradient-positions); 6 | } 7 | @else if $gradient-positions 8 | and ($gradient-type == radial) 9 | and (type-of($gradient-positions) != color) { 10 | $gradient-positions: _radial-positions-parser($gradient-positions); 11 | } 12 | @return $gradient-positions; 13 | } 14 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/css3/_transform.scss: -------------------------------------------------------------------------------- 1 | @mixin transform($property: none) { 2 | // none | 3 | @include prefixer(transform, $property, webkit moz ms o spec); 4 | } 5 | 6 | @mixin transform-origin($axes: 50%) { 7 | // x-axis - left | center | right | length | % 8 | // y-axis - top | center | bottom | length | % 9 | // z-axis - length 10 | @include prefixer(transform-origin, $axes, webkit moz ms o spec); 11 | } 12 | 13 | @mixin transform-style ($style: flat) { 14 | @include prefixer(transform-style, $style, webkit moz ms o spec); 15 | } 16 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/functions/_unpack.scss: -------------------------------------------------------------------------------- 1 | // Convert shorthand to the 4-value syntax 2 | 3 | @function unpack($shorthand) { 4 | @if length($shorthand) == 1 { 5 | @return nth($shorthand, 1) nth($shorthand, 1) nth($shorthand, 1) nth($shorthand, 1); 6 | } 7 | @else if length($shorthand) == 2 { 8 | @return nth($shorthand, 1) nth($shorthand, 2) nth($shorthand, 1) nth($shorthand, 2); 9 | } 10 | @else if length($shorthand) == 3 { 11 | @return nth($shorthand, 1) nth($shorthand, 2) nth($shorthand, 3) nth($shorthand, 2); 12 | } 13 | @else { 14 | @return $shorthand; 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/helpers/_radial-positions-parser.scss: -------------------------------------------------------------------------------- 1 | @function _radial-positions-parser($gradient-pos) { 2 | $shape-size: nth($gradient-pos, 1); 3 | $pos: nth($gradient-pos, 2); 4 | $shape-size-spec: _shape-size-stripper($shape-size); 5 | 6 | $pre-spec: unquote(if($pos, "#{$pos}, ", null)) 7 | unquote(if($shape-size, "#{$shape-size},", null)); 8 | $pos-spec: if($pos, "at #{$pos}", null); 9 | 10 | $spec: "#{$shape-size-spec} #{$pos-spec}"; 11 | 12 | // Add comma 13 | @if ($spec != ' ') { 14 | $spec: "#{$spec}," 15 | } 16 | 17 | @return $pre-spec $spec; 18 | } 19 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/addons/_clearfix.scss: -------------------------------------------------------------------------------- 1 | // Modern micro clearfix provides an easy way to contain floats without adding additional markup. 2 | // 3 | // Example usage: 4 | // 5 | // // Contain all floats within .wrapper 6 | // .wrapper { 7 | // @include clearfix; 8 | // .content, 9 | // .sidebar { 10 | // float : left; 11 | // } 12 | // } 13 | 14 | @mixin clearfix { 15 | &:after { 16 | content:""; 17 | display:table; 18 | clear:both; 19 | } 20 | } 21 | 22 | // Acknowledgements 23 | // Beat *that* clearfix: [Thierry Koblentz](http://www.css-101.org/articles/clearfix/latest-new-clearfix-so-far.php) 24 | -------------------------------------------------------------------------------- /lib/generators/thesis/install/templates/migrations/thesis_create_page.rb: -------------------------------------------------------------------------------- 1 | class ThesisCreatePage < ActiveRecord::Migration 2 | def self.up 3 | # Please note: if you change this (in the thesis gem) please 4 | # update the /spec/spec_helper.rb to match. 5 | 6 | create_table :pages do |t| 7 | t.integer :parent_id 8 | t.string :name 9 | t.string :slug 10 | t.string :title 11 | t.string :description 12 | t.integer :sort_order, default: 0, null: false 13 | t.string :template, default: "default", null: false 14 | t.timestamps 15 | end 16 | end 17 | 18 | def self.down 19 | drop_table :pages 20 | end 21 | end -------------------------------------------------------------------------------- /lib/generators/thesis/install/templates/migrations/thesis_create_page_content.rb: -------------------------------------------------------------------------------- 1 | class ThesisCreatePageContent < ActiveRecord::Migration 2 | def self.up 3 | # Please note: if you change this (in the thesis gem) please 4 | # update the /spec/spec_helper.rb to match. 5 | 6 | create_table :page_contents do |t| 7 | t.integer :page_id, null: false 8 | t.string :name, null: false 9 | t.text :content, default: "Edit This Content Area" 10 | t.string :content_type, default: :html 11 | t.timestamps 12 | end 13 | end 14 | 15 | def self.down 16 | drop_table :page_contents 17 | end 18 | end -------------------------------------------------------------------------------- /spec/thesis/routing/thesis_routing_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Thesis::RouteConstraint do 4 | subject { Thesis::RouteConstraint.new } 5 | let(:page) { create :page, template: "default" } 6 | 7 | describe ".matches?" do 8 | context "proper route" do 9 | it "returns true" do 10 | req = Struct.new(:path).new(page.slug) 11 | expect(subject.matches?(req)).to be_true 12 | end 13 | end 14 | 15 | context "invalid route" do 16 | it "returns false" do 17 | req = Struct.new(:path).new("invalid") 18 | expect(subject.matches?(req)).to be_false 19 | end 20 | end 21 | end 22 | end 23 | 24 | -------------------------------------------------------------------------------- /lib/thesis/colorizer.rb: -------------------------------------------------------------------------------- 1 | class String 2 | # colorization 3 | def colorize(color_code) 4 | "\e[#{color_code}m#{self}\e[0m" 5 | end 6 | 7 | def red 8 | colorize 31 9 | end 10 | 11 | def green 12 | colorize 32 13 | end 14 | 15 | def yellow 16 | colorize 33 17 | end 18 | 19 | def pink 20 | colorize 35 21 | end 22 | 23 | def gray 24 | colorize 37 25 | end 26 | 27 | def brown 28 | colorize 33 29 | end 30 | 31 | def blue 32 | colorize 34 33 | end 34 | 35 | def magenta 36 | colorize 35 37 | end 38 | 39 | def cyan 40 | colorize 36 41 | end 42 | 43 | def bold 44 | colorize 1 45 | end 46 | 47 | def blink 48 | colorize 5 49 | end 50 | 51 | end -------------------------------------------------------------------------------- /lib/thesis/controllers/controller_helpers.rb: -------------------------------------------------------------------------------- 1 | module Thesis 2 | module ControllerHelpers 3 | def current_page 4 | @current_page ||= begin 5 | p = Page.where(slug: current_slug).first_or_initialize 6 | p.name = current_slug.to_s.split("/").last.to_s.humanize 7 | p.save! 8 | p.editable = page_is_editable?(p) 9 | p 10 | end 11 | end 12 | 13 | def current_slug 14 | request.fullpath.sub(/(\/)+$/,'').presence || "/" 15 | end 16 | 17 | def root_pages 18 | @root_pages ||= Page.where(parent_id: nil).order("sort_order ASC") 19 | end 20 | 21 | def thesis_editor 22 | current_page.editable ? "
".html_safe : "" 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/helpers/_convert-units.scss: -------------------------------------------------------------------------------- 1 | //************************************************************************// 2 | // Helper function for str-to-num fn. 3 | // Source: http://sassmeister.com/gist/9647408 4 | //************************************************************************// 5 | @function _convert-units($number, $unit) { 6 | $strings: 'px' 'cm' 'mm' '%' 'ch' 'pica' 'in' 'em' 'rem' 'pt' 'pc' 'ex' 'vw' 'vh' 'vmin' 'vmax', 'deg', 'rad', 'grad', 'turn'; 7 | $units: 1px 1cm 1mm 1% 1ch 1pica 1in 1em 1rem 1pt 1pc 1ex 1vw 1vh 1vmin 1vmax, 1deg, 1rad, 1grad, 1turn; 8 | $index: index($strings, $unit); 9 | 10 | @if not $index { 11 | @warn "Unknown unit `#{$unit}`."; 12 | @return false; 13 | } 14 | @return $number * nth($units, $index); 15 | } 16 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/functions/_transition-property-name.scss: -------------------------------------------------------------------------------- 1 | // Return vendor-prefixed property names if appropriate 2 | // Example: transition-property-names((transform, color, background), moz) -> -moz-transform, color, background 3 | //************************************************************************// 4 | @function transition-property-names($props, $vendor: false) { 5 | $new-props: (); 6 | 7 | @each $prop in $props { 8 | $new-props: append($new-props, transition-property-name($prop, $vendor), comma); 9 | } 10 | 11 | @return $new-props; 12 | } 13 | 14 | @function transition-property-name($prop, $vendor: false) { 15 | // put other properties that need to be prefixed here aswell 16 | @if $vendor and $prop == transform { 17 | @return unquote('-'+$vendor+'-'+$prop); 18 | } 19 | @else { 20 | @return $prop; 21 | } 22 | } -------------------------------------------------------------------------------- /lib/generators/thesis/install/templates/page_templates/default.html.slim: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= current_page.title 5 | meta content=current_page.content("Description", :text) type="description" 6 | 7 | = stylesheet_link_tag "application", :media => "all" 8 | = javascript_include_tag "application" 9 | = csrf_meta_tags 10 | body 11 | = thesis_editor 12 | 13 | header 14 | h1= current_page.title 15 | 16 | nav 17 | ul 18 | - root_pages.each do |p| 19 | li= link_to p.name, p.path 20 | 21 | article 22 | .main-image= current_page.content("Main Image", :image) 23 | .content= current_page.content("Main Content", :html) 24 | 25 | aside 26 | = current_page.content("Sidebar Content", :html) 27 | 28 | footer 29 | p= current_page.content("Footer Content", :text) 30 | -------------------------------------------------------------------------------- /lib/generators/thesis/install/templates/page_templates/default.html.haml: -------------------------------------------------------------------------------- 1 | !!! 2 | %html 3 | %head 4 | %title= current_page.title 5 | %meta{ content: current_page.content("Description", :text), type: "description" } 6 | 7 | = stylesheet_link_tag "application", :media => "all" 8 | = javascript_include_tag "application" 9 | = csrf_meta_tags 10 | %body 11 | = thesis_editor 12 | 13 | %header 14 | %h1= current_page.title 15 | 16 | %nav 17 | %ul 18 | - root_pages.each do |p| 19 | %li= link_to p.name, p.path 20 | 21 | %article 22 | .main-image= current_page.content("Main Image", :image) 23 | .content= current_page.content("Main Content", :html) 24 | 25 | %aside 26 | = current_page.content("Sidebar Content", :html) 27 | 28 | %footer 29 | %p= current_page.content("Footer Content", :text) -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/addons/_position.scss: -------------------------------------------------------------------------------- 1 | @mixin position ($position: relative, $coordinates: null null null null) { 2 | 3 | @if type-of($position) == list { 4 | $coordinates: $position; 5 | $position: relative; 6 | } 7 | 8 | $coordinates: unpack($coordinates); 9 | 10 | $top: nth($coordinates, 1); 11 | $right: nth($coordinates, 2); 12 | $bottom: nth($coordinates, 3); 13 | $left: nth($coordinates, 4); 14 | 15 | position: $position; 16 | 17 | @if ($top and $top == auto) or (type-of($top) == number) { 18 | top: $top; 19 | } 20 | 21 | @if ($right and $right == auto) or (type-of($right) == number) { 22 | right: $right; 23 | } 24 | 25 | @if ($bottom and $bottom == auto) or (type-of($bottom) == number) { 26 | bottom: $bottom; 27 | } 28 | 29 | @if ($left and $left == auto) or (type-of($left) == number) { 30 | left: $left; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/helpers/_linear-angle-parser.scss: -------------------------------------------------------------------------------- 1 | // Private function for linear-gradient-parser 2 | @function _linear-angle-parser($image, $first-val, $prefix, $suffix) { 3 | $offset: null; 4 | $unit-short: str-slice($first-val, str-length($first-val) - 2, str-length($first-val)); 5 | $unit-long: str-slice($first-val, str-length($first-val) - 3, str-length($first-val)); 6 | 7 | @if ($unit-long == "grad") or 8 | ($unit-long == "turn") { 9 | $offset: if($unit-long == "grad", -100grad * 3, -0.75turn); 10 | } 11 | 12 | @else if ($unit-short == "deg") or 13 | ($unit-short == "rad") { 14 | $offset: if($unit-short == "deg", -90 * 3, 1.6rad); 15 | } 16 | 17 | @if $offset { 18 | $num: _str-to-num($first-val); 19 | 20 | @return ( 21 | webkit-image: -webkit- + $prefix + ($offset - $num) + $suffix, 22 | spec-image: $image 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/css3/_border-radius.scss: -------------------------------------------------------------------------------- 1 | //************************************************************************// 2 | // Shorthand Border-radius mixins 3 | //************************************************************************// 4 | @mixin border-top-radius($radii) { 5 | @include prefixer(border-top-left-radius, $radii, spec); 6 | @include prefixer(border-top-right-radius, $radii, spec); 7 | } 8 | 9 | @mixin border-bottom-radius($radii) { 10 | @include prefixer(border-bottom-left-radius, $radii, spec); 11 | @include prefixer(border-bottom-right-radius, $radii, spec); 12 | } 13 | 14 | @mixin border-left-radius($radii) { 15 | @include prefixer(border-top-left-radius, $radii, spec); 16 | @include prefixer(border-bottom-left-radius, $radii, spec); 17 | } 18 | 19 | @mixin border-right-radius($radii) { 20 | @include prefixer(border-top-right-radius, $radii, spec); 21 | @include prefixer(border-bottom-right-radius, $radii, spec); 22 | } 23 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/helpers/_render-gradients.scss: -------------------------------------------------------------------------------- 1 | // User for linear and radial gradients within background-image or border-image properties 2 | 3 | @function _render-gradients($gradient-positions, $gradients, $gradient-type, $vendor: false) { 4 | $pre-spec: null; 5 | $spec: null; 6 | $vendor-gradients: null; 7 | @if $gradient-type == linear { 8 | @if $gradient-positions { 9 | $pre-spec: nth($gradient-positions, 1); 10 | $spec: nth($gradient-positions, 2); 11 | } 12 | } 13 | @else if $gradient-type == radial { 14 | $pre-spec: nth($gradient-positions, 1); 15 | $spec: nth($gradient-positions, 2); 16 | } 17 | 18 | @if $vendor { 19 | $vendor-gradients: -#{$vendor}-#{$gradient-type}-gradient(#{$pre-spec} $gradients); 20 | } 21 | @else if $vendor == false { 22 | $vendor-gradients: "#{$gradient-type}-gradient(#{$spec} #{$gradients})"; 23 | $vendor-gradients: unquote($vendor-gradients); 24 | } 25 | @return $vendor-gradients; 26 | } 27 | -------------------------------------------------------------------------------- /thesis.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'thesis/version' 5 | 6 | Gem::Specification.new do |gem| 7 | gem.name = "thesis" 8 | gem.version = Thesis::VERSION 9 | gem.authors = ["ClearSight Studio"] 10 | gem.email = ["contact@clearsightstudio.com"] 11 | gem.description = %q{Thesis: A Rails CMS that doesn't hijack your development workflow.} 12 | gem.summary = %q{Thesis: A Rails CMS that doesn't hijack your development workflow.} 13 | gem.homepage = "https://github.com/clearsightstudio/thesis" 14 | 15 | gem.required_ruby_version = '>= 2.0.0' 16 | 17 | gem.files = Dir["{lib,app,config}/**/*"] + ["README.md", "LICENSE.txt"] 18 | gem.require_paths = ["lib", "app"] 19 | gem.test_files = Dir["spec/**/*"] 20 | 21 | gem.add_dependency "rails", "~> 4.0" 22 | gem.add_dependency "sass", ">= 3.3" 23 | gem.add_dependency "sass-rails", ">= 5.0" 24 | end 25 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/addons/_retina-image.scss: -------------------------------------------------------------------------------- 1 | @mixin retina-image($filename, $background-size, $extension: png, $retina-filename: null, $retina-suffix: _2x, $asset-pipeline: $asset-pipeline) { 2 | @if $asset-pipeline { 3 | background-image: image-url("#{$filename}.#{$extension}"); 4 | } 5 | @else { 6 | background-image: url("#{$filename}.#{$extension}"); 7 | } 8 | 9 | @include hidpi { 10 | @if $asset-pipeline { 11 | @if $retina-filename { 12 | background-image: image-url("#{$retina-filename}.#{$extension}"); 13 | } 14 | @else { 15 | background-image: image-url("#{$filename}#{$retina-suffix}.#{$extension}"); 16 | } 17 | } 18 | 19 | @else { 20 | @if $retina-filename { 21 | background-image: url("#{$retina-filename}.#{$extension}"); 22 | } 23 | @else { 24 | background-image: url("#{$filename}#{$retina-suffix}.#{$extension}"); 25 | } 26 | } 27 | 28 | background-size: $background-size; 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /spec/thesis/models/page_content_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Thesis::PageContent do 4 | let(:page) { create(:page) } 5 | let(:page_content) { create(:page_content, attributes.merge(page: page)) } 6 | 7 | describe "#render" do 8 | subject { page_content.render(editable: true) } 9 | 10 | context "when the content type is 'html'" do 11 | let(:attributes) {{ content_type: 'html' }} 12 | 13 | it { should match /thesis-content-html/ } 14 | it { should match page_content.content } 15 | end 16 | 17 | context "when the content type is 'text'" do 18 | let(:attributes) {{ content_type: "text" }} 19 | 20 | it { should match /thesis-content-text/ } 21 | it { should match page_content.content } 22 | end 23 | 24 | context "when the content type is 'image'" do 25 | let(:attributes) {{ content_type: "image" }} 26 | 27 | it { should match /thesis-content-image/ } 28 | it { should match /img/ } 29 | it { should match page_content.content } 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/helpers/_linear-side-corner-parser.scss: -------------------------------------------------------------------------------- 1 | // Private function for linear-gradient-parser 2 | @function _linear-side-corner-parser($image, $first-val, $prefix, $suffix, $has-multiple-vals) { 3 | $val-1: str-slice($first-val, 0, $has-multiple-vals - 1 ); 4 | $val-2: str-slice($first-val, $has-multiple-vals + 1, str-length($first-val)); 5 | $val-3: null; 6 | $has-val-3: str-index($val-2, " "); 7 | 8 | @if $has-val-3 { 9 | $val-3: str-slice($val-2, $has-val-3 + 1, str-length($val-2)); 10 | $val-2: str-slice($val-2, 0, $has-val-3 - 1); 11 | } 12 | 13 | $pos: _position-flipper($val-1) _position-flipper($val-2) _position-flipper($val-3); 14 | $pos: unquote($pos + ""); 15 | 16 | // Use old spec for webkit 17 | @if $val-1 == "to" { 18 | @return ( 19 | webkit-image: -webkit- + $prefix + $pos + $suffix, 20 | spec-image: $image 21 | ); 22 | } 23 | 24 | // Bring the code up to spec 25 | @else { 26 | @return ( 27 | webkit-image: -webkit- + $image, 28 | spec-image: $prefix + "to " + $pos + $suffix 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 ClearSight Studio 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/css3/_font-face.scss: -------------------------------------------------------------------------------- 1 | // Order of the includes matters, and it is: normal, bold, italic, bold+italic. 2 | 3 | @mixin font-face($font-family, $file-path, $weight: normal, $style: normal, $asset-pipeline: $asset-pipeline) { 4 | @font-face { 5 | font-family: $font-family; 6 | font-weight: $weight; 7 | font-style: $style; 8 | 9 | @if $asset-pipeline == true { 10 | src: font-url('#{$file-path}.eot'); 11 | src: font-url('#{$file-path}.eot?#iefix') format('embedded-opentype'), 12 | font-url('#{$file-path}.woff') format('woff'), 13 | font-url('#{$file-path}.ttf') format('truetype'), 14 | font-url('#{$file-path}.svg##{$font-family}') format('svg'); 15 | } @else { 16 | src: url('#{$file-path}.eot'); 17 | src: url('#{$file-path}.eot?#iefix') format('embedded-opentype'), 18 | url('#{$file-path}.woff') format('woff'), 19 | url('#{$file-path}.ttf') format('truetype'), 20 | url('#{$file-path}.svg##{$font-family}') format('svg'); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/generators/thesis/install/templates/page_templates/default.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= current_page.title %> 5 | " /> 6 | 7 | <%= stylesheet_link_tag "application", :media => "all" %> 8 | <%= javascript_include_tag "application" %> 9 | <%= csrf_meta_tags %> 10 | 11 | 12 | <%= thesis_editor %> 13 |
14 |

<%= current_page.title %>

15 |
16 | 17 | 24 | 25 |
26 |
<%= current_page.content("Main Image", :image) %>
27 |
<%= current_page.content("Main Content", :html) %>
28 |
29 | 30 | 33 | 34 |
35 |

<%= current_page.content("Footer Content", :text) %>

36 |
37 | 38 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/helpers/_linear-gradient-parser.scss: -------------------------------------------------------------------------------- 1 | @function _linear-gradient-parser($image) { 2 | $image: unquote($image); 3 | $gradients: (); 4 | $start: str-index($image, "("); 5 | $end: str-index($image, ","); 6 | $first-val: str-slice($image, $start + 1, $end - 1); 7 | 8 | $prefix: str-slice($image, 0, $start); 9 | $suffix: str-slice($image, $end, str-length($image)); 10 | 11 | $has-multiple-vals: str-index($first-val, " "); 12 | $has-single-position: unquote(_position-flipper($first-val) + ""); 13 | $has-angle: _is-num(str-slice($first-val, 0, 0)); 14 | 15 | @if $has-multiple-vals { 16 | $gradients: _linear-side-corner-parser($image, $first-val, $prefix, $suffix, $has-multiple-vals); 17 | } 18 | 19 | @else if $has-single-position != "" { 20 | $pos: unquote($has-single-position + ""); 21 | 22 | $gradients: ( 23 | webkit-image: -webkit- + $image, 24 | spec-image: $prefix + "to " + $pos + $suffix 25 | ); 26 | } 27 | 28 | @else if $has-angle { 29 | // Rotate degree for webkit 30 | $gradients: _linear-angle-parser($image, $first-val, $prefix, $suffix); 31 | } 32 | 33 | @else { 34 | $gradients: ( 35 | webkit-image: -webkit- + $image, 36 | spec-image: $image 37 | ); 38 | } 39 | 40 | @return $gradients; 41 | } 42 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/css3/_keyframes.scss: -------------------------------------------------------------------------------- 1 | // Adds keyframes blocks for supported prefixes, removing redundant prefixes in the block's content 2 | @mixin keyframes($name) { 3 | $original-prefix-for-webkit: $prefix-for-webkit; 4 | $original-prefix-for-mozilla: $prefix-for-mozilla; 5 | $original-prefix-for-microsoft: $prefix-for-microsoft; 6 | $original-prefix-for-opera: $prefix-for-opera; 7 | $original-prefix-for-spec: $prefix-for-spec; 8 | 9 | @if $original-prefix-for-webkit { 10 | @include disable-prefix-for-all(); 11 | $prefix-for-webkit: true !global; 12 | @-webkit-keyframes #{$name} { 13 | @content; 14 | } 15 | } 16 | @if $original-prefix-for-mozilla { 17 | @include disable-prefix-for-all(); 18 | $prefix-for-mozilla: true !global; 19 | @-moz-keyframes #{$name} { 20 | @content; 21 | } 22 | } 23 | 24 | $prefix-for-webkit: $original-prefix-for-webkit !global; 25 | $prefix-for-mozilla: $original-prefix-for-mozilla !global; 26 | $prefix-for-microsoft: $original-prefix-for-microsoft !global; 27 | $prefix-for-opera: $original-prefix-for-opera !global; 28 | $prefix-for-spec: $original-prefix-for-spec !global; 29 | 30 | @if $original-prefix-for-spec { 31 | @keyframes #{$name} { 32 | @content; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/addons/_prefixer.scss: -------------------------------------------------------------------------------- 1 | //************************************************************************// 2 | // Example: @include prefixer(border-radius, $radii, webkit ms spec); 3 | //************************************************************************// 4 | // Variables located in /settings/_prefixer.scss 5 | 6 | @mixin prefixer ($property, $value, $prefixes) { 7 | @each $prefix in $prefixes { 8 | @if $prefix == webkit { 9 | @if $prefix-for-webkit { 10 | -webkit-#{$property}: $value; 11 | } 12 | } 13 | @else if $prefix == moz { 14 | @if $prefix-for-mozilla { 15 | -moz-#{$property}: $value; 16 | } 17 | } 18 | @else if $prefix == ms { 19 | @if $prefix-for-microsoft { 20 | -ms-#{$property}: $value; 21 | } 22 | } 23 | @else if $prefix == o { 24 | @if $prefix-for-opera { 25 | -o-#{$property}: $value; 26 | } 27 | } 28 | @else if $prefix == spec { 29 | @if $prefix-for-spec { 30 | #{$property}: $value; 31 | } 32 | } 33 | @else { 34 | @warn "Unrecognized prefix: #{$prefix}"; 35 | } 36 | } 37 | } 38 | 39 | @mixin disable-prefix-for-all() { 40 | $prefix-for-webkit: false !global; 41 | $prefix-for-mozilla: false !global; 42 | $prefix-for-microsoft: false !global; 43 | $prefix-for-opera: false !global; 44 | $prefix-for-spec: false !global; 45 | } 46 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/css3/_columns.scss: -------------------------------------------------------------------------------- 1 | @mixin columns($arg: auto) { 2 | // || 3 | @include prefixer(columns, $arg, webkit moz spec); 4 | } 5 | 6 | @mixin column-count($int: auto) { 7 | // auto || integer 8 | @include prefixer(column-count, $int, webkit moz spec); 9 | } 10 | 11 | @mixin column-gap($length: normal) { 12 | // normal || length 13 | @include prefixer(column-gap, $length, webkit moz spec); 14 | } 15 | 16 | @mixin column-fill($arg: auto) { 17 | // auto || length 18 | @include prefixer(column-fill, $arg, webkit moz spec); 19 | } 20 | 21 | @mixin column-rule($arg) { 22 | // || || 23 | @include prefixer(column-rule, $arg, webkit moz spec); 24 | } 25 | 26 | @mixin column-rule-color($color) { 27 | @include prefixer(column-rule-color, $color, webkit moz spec); 28 | } 29 | 30 | @mixin column-rule-style($style: none) { 31 | // none | hidden | dashed | dotted | double | groove | inset | inset | outset | ridge | solid 32 | @include prefixer(column-rule-style, $style, webkit moz spec); 33 | } 34 | 35 | @mixin column-rule-width ($width: none) { 36 | @include prefixer(column-rule-width, $width, webkit moz spec); 37 | } 38 | 39 | @mixin column-span($arg: none) { 40 | // none || all 41 | @include prefixer(column-span, $arg, webkit moz spec); 42 | } 43 | 44 | @mixin column-width($length: auto) { 45 | // auto || length 46 | @include prefixer(column-width, $length, webkit moz spec); 47 | } 48 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/helpers/_str-to-num.scss: -------------------------------------------------------------------------------- 1 | //************************************************************************// 2 | // Helper function for linear/radial-gradient-parsers. 3 | // Source: http://sassmeister.com/gist/9647408 4 | //************************************************************************// 5 | @function _str-to-num($string) { 6 | // Matrices 7 | $strings: '0' '1' '2' '3' '4' '5' '6' '7' '8' '9'; 8 | $numbers: 0 1 2 3 4 5 6 7 8 9; 9 | 10 | // Result 11 | $result: 0; 12 | $divider: 0; 13 | $minus: false; 14 | 15 | // Looping through all characters 16 | @for $i from 1 through str-length($string) { 17 | $character: str-slice($string, $i, $i); 18 | $index: index($strings, $character); 19 | 20 | @if $character == '-' { 21 | $minus: true; 22 | } 23 | 24 | @else if $character == '.' { 25 | $divider: 1; 26 | } 27 | 28 | @else { 29 | @if not $index { 30 | $result: if($minus, $result * -1, $result); 31 | @return _convert-units($result, str-slice($string, $i)); 32 | } 33 | 34 | $number: nth($numbers, $index); 35 | 36 | @if $divider == 0 { 37 | $result: $result * 10; 38 | } 39 | 40 | @else { 41 | // Move the decimal dot to the left 42 | $divider: $divider * 10; 43 | $number: $number / $divider; 44 | } 45 | 46 | $result: $result + $number; 47 | } 48 | } 49 | @return if($minus, $result * -1, $result); 50 | } 51 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/css3/_linear-gradient.scss: -------------------------------------------------------------------------------- 1 | @mixin linear-gradient($pos, $G1, $G2: null, 2 | $G3: null, $G4: null, 3 | $G5: null, $G6: null, 4 | $G7: null, $G8: null, 5 | $G9: null, $G10: null, 6 | $fallback: null) { 7 | // Detect what type of value exists in $pos 8 | $pos-type: type-of(nth($pos, 1)); 9 | $pos-spec: null; 10 | $pos-degree: null; 11 | 12 | // If $pos is missing from mixin, reassign vars and add default position 13 | @if ($pos-type == color) or (nth($pos, 1) == "transparent") { 14 | $G10: $G9; $G9: $G8; $G8: $G7; $G7: $G6; $G6: $G5; 15 | $G5: $G4; $G4: $G3; $G3: $G2; $G2: $G1; $G1: $pos; 16 | $pos: null; 17 | } 18 | 19 | @if $pos { 20 | $positions: _linear-positions-parser($pos); 21 | $pos-degree: nth($positions, 1); 22 | $pos-spec: nth($positions, 2); 23 | } 24 | 25 | $full: $G1, $G2, $G3, $G4, $G5, $G6, $G7, $G8, $G9, $G10; 26 | 27 | // Set $G1 as the default fallback color 28 | $fallback-color: nth($G1, 1); 29 | 30 | // If $fallback is a color use that color as the fallback color 31 | @if (type-of($fallback) == color) or ($fallback == "transparent") { 32 | $fallback-color: $fallback; 33 | } 34 | 35 | background-color: $fallback-color; 36 | background-image: -webkit-linear-gradient($pos-degree $full); // Safari 5.1+, Chrome 37 | background-image: unquote("linear-gradient(#{$pos-spec}#{$full})"); 38 | } 39 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/helpers/_radial-gradient-parser.scss: -------------------------------------------------------------------------------- 1 | @function _radial-gradient-parser($image) { 2 | $image: unquote($image); 3 | $gradients: (); 4 | $start: str-index($image, "("); 5 | $end: str-index($image, ","); 6 | $first-val: str-slice($image, $start + 1, $end - 1); 7 | 8 | $prefix: str-slice($image, 0, $start); 9 | $suffix: str-slice($image, $end, str-length($image)); 10 | 11 | $is-spec-syntax: str-index($first-val, "at"); 12 | 13 | @if $is-spec-syntax and $is-spec-syntax > 1 { 14 | $keyword: str-slice($first-val, 1, $is-spec-syntax - 2); 15 | $pos: str-slice($first-val, $is-spec-syntax + 3, str-length($first-val)); 16 | $pos: append($pos, $keyword, comma); 17 | 18 | $gradients: ( 19 | webkit-image: -webkit- + $prefix + $pos + $suffix, 20 | spec-image: $image 21 | ) 22 | } 23 | 24 | @else if $is-spec-syntax == 1 { 25 | $pos: str-slice($first-val, $is-spec-syntax + 3, str-length($first-val)); 26 | 27 | $gradients: ( 28 | webkit-image: -webkit- + $prefix + $pos + $suffix, 29 | spec-image: $image 30 | ) 31 | } 32 | 33 | @else if str-index($image, "cover") or str-index($image, "contain") { 34 | @warn "Radial-gradient needs to be updated to conform to latest spec."; 35 | 36 | $gradients: ( 37 | webkit-image: null, 38 | spec-image: $image 39 | ) 40 | } 41 | 42 | @else { 43 | $gradients: ( 44 | webkit-image: -webkit- + $image, 45 | spec-image: $image 46 | ) 47 | } 48 | 49 | @return $gradients; 50 | } 51 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/css3/_background-image.scss: -------------------------------------------------------------------------------- 1 | //************************************************************************// 2 | // Background-image property for adding multiple background images with 3 | // gradients, or for stringing multiple gradients together. 4 | //************************************************************************// 5 | 6 | @mixin background-image($images...) { 7 | $webkit-images: (); 8 | $spec-images: (); 9 | 10 | @each $image in $images { 11 | $webkit-image: (); 12 | $spec-image: (); 13 | 14 | @if (type-of($image) == string) { 15 | $url-str: str-slice($image, 0, 3); 16 | $gradient-type: str-slice($image, 0, 6); 17 | 18 | @if $url-str == "url" { 19 | $webkit-image: $image; 20 | $spec-image: $image; 21 | } 22 | 23 | @else if $gradient-type == "linear" { 24 | $gradients: _linear-gradient-parser($image); 25 | $webkit-image: map-get($gradients, webkit-image); 26 | $spec-image: map-get($gradients, spec-image); 27 | } 28 | 29 | @else if $gradient-type == "radial" { 30 | $gradients: _radial-gradient-parser($image); 31 | $webkit-image: map-get($gradients, webkit-image); 32 | $spec-image: map-get($gradients, spec-image); 33 | } 34 | } 35 | 36 | $webkit-images: append($webkit-images, $webkit-image, comma); 37 | $spec-images: append($spec-images, $spec-image, comma); 38 | } 39 | 40 | background-image: $webkit-images; 41 | background-image: $spec-images; 42 | } 43 | -------------------------------------------------------------------------------- /spec/thesis/models/page_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Thesis::Page do 4 | subject(:page) { create(:page) } 5 | 6 | describe "#update_slug" do 7 | let(:page) { build(:page, slug: "/original-slug") } 8 | 9 | it "updates the page's slug attribute, based on the page name" do 10 | page.name = "New Name" 11 | page.update_slug 12 | expect(page.slug).to eq "/new-name" 13 | end 14 | end 15 | 16 | describe "#update_subpage_slugs" do 17 | let!(:parent) { create(:page, name: "Parent") } 18 | let!(:subpage) { create(:page, name: "Subpage", parent_id: parent.id) } 19 | 20 | before { parent.reload } 21 | 22 | it "fixes the subpage's slug attributes when the parent's name is updated" do 23 | parent.name = "New Parent Name" 24 | parent.save 25 | 26 | expect(subpage.reload.slug).to eq "/new-parent-name/subpage" 27 | end 28 | end 29 | 30 | describe "#content" do 31 | it "creates a Thesis::PageContent record if one does not exist" do 32 | page.content("nonexistent-content-block") 33 | content = Thesis::PageContent.first 34 | expect(content.name).to eq "nonexistent-content-block" 35 | expect(content.content_type).to eq "html" 36 | end 37 | 38 | it "returns a string of content" do 39 | result = page.content("nonexistent-content-block") 40 | expect(result).to be_a String 41 | end 42 | end 43 | 44 | describe "#path" do 45 | it "is an alias for #slug" do 46 | expect(page.path).to eq page.slug 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/css3/_radial-gradient.scss: -------------------------------------------------------------------------------- 1 | // Requires Sass 3.1+ 2 | @mixin radial-gradient($G1, $G2, 3 | $G3: null, $G4: null, 4 | $G5: null, $G6: null, 5 | $G7: null, $G8: null, 6 | $G9: null, $G10: null, 7 | $pos: null, 8 | $shape-size: null, 9 | $fallback: null) { 10 | 11 | $data: _radial-arg-parser($G1, $G2, $pos, $shape-size); 12 | $G1: nth($data, 1); 13 | $G2: nth($data, 2); 14 | $pos: nth($data, 3); 15 | $shape-size: nth($data, 4); 16 | 17 | $full: $G1, $G2, $G3, $G4, $G5, $G6, $G7, $G8, $G9, $G10; 18 | 19 | // Strip deprecated cover/contain for spec 20 | $shape-size-spec: _shape-size-stripper($shape-size); 21 | 22 | // Set $G1 as the default fallback color 23 | $first-color: nth($full, 1); 24 | $fallback-color: nth($first-color, 1); 25 | 26 | @if (type-of($fallback) == color) or ($fallback == "transparent") { 27 | $fallback-color: $fallback; 28 | } 29 | 30 | // Add Commas and spaces 31 | $shape-size: if($shape-size, '#{$shape-size}, ', null); 32 | $pos: if($pos, '#{$pos}, ', null); 33 | $pos-spec: if($pos, 'at #{$pos}', null); 34 | $shape-size-spec: if(($shape-size-spec != ' ') and ($pos == null), '#{$shape-size-spec}, ', '#{$shape-size-spec} '); 35 | 36 | background-color: $fallback-color; 37 | background-image: -webkit-radial-gradient(unquote(#{$pos}#{$shape-size}#{$full})); 38 | background-image: unquote("radial-gradient(#{$shape-size-spec}#{$pos-spec}#{$full})"); 39 | } 40 | -------------------------------------------------------------------------------- /lib/thesis/models/page_content.rb: -------------------------------------------------------------------------------- 1 | module Thesis 2 | class PageContent < ActiveRecord::Base 3 | self.table_name = "page_contents" 4 | 5 | belongs_to :page 6 | validates :page_id, presence: true 7 | 8 | def render(args={}) 9 | args[:editable] ? render_editable : render_content 10 | end 11 | 12 | def render_editable 13 | case self.content_type.to_sym 14 | when :html then render_html_editable 15 | when :text then render_plain_text_editable 16 | when :image then render_image_editable 17 | else render_html_editable 18 | end 19 | end 20 | 21 | def render_content 22 | case self.content_type.to_sym 23 | when :image then render_image_tag 24 | else self.content.to_s.html_safe 25 | end 26 | end 27 | 28 | protected 29 | 30 | def render_html_editable 31 | ( 32 | "" + 33 | "#{self.content}" + 34 | "" 35 | ).html_safe 36 | end 37 | 38 | def render_plain_text_editable 39 | ( 40 | "" + 41 | "#{self.content}" + 42 | "" 43 | ).html_safe 44 | end 45 | 46 | def render_image_editable 47 | ( 48 | "" + 49 | render_image_tag + 50 | "" 51 | ).html_safe 52 | end 53 | 54 | def render_image_tag 55 | "".html_safe 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/functions/_flex-grid.scss: -------------------------------------------------------------------------------- 1 | // Flexible grid 2 | @function flex-grid($columns, $container-columns: $fg-max-columns) { 3 | $width: $columns * $fg-column + ($columns - 1) * $fg-gutter; 4 | $container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter; 5 | @return percentage($width / $container-width); 6 | } 7 | 8 | // Flexible gutter 9 | @function flex-gutter($container-columns: $fg-max-columns, $gutter: $fg-gutter) { 10 | $container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter; 11 | @return percentage($gutter / $container-width); 12 | } 13 | 14 | // The $fg-column, $fg-gutter and $fg-max-columns variables must be defined in your base stylesheet to properly use the flex-grid function. 15 | // This function takes the fluid grid equation (target / context = result) and uses columns to help define each. 16 | // 17 | // The calculation presumes that your column structure will be missing the last gutter: 18 | // 19 | // -- column -- gutter -- column -- gutter -- column 20 | // 21 | // $fg-column: 60px; // Column Width 22 | // $fg-gutter: 25px; // Gutter Width 23 | // $fg-max-columns: 12; // Total Columns For Main Container 24 | // 25 | // div { 26 | // width: flex-grid(4); // returns (315px / 995px) = 31.65829%; 27 | // margin-left: flex-gutter(); // returns (25px / 995px) = 2.51256%; 28 | // 29 | // p { 30 | // width: flex-grid(2, 4); // returns (145px / 315px) = 46.031746%; 31 | // float: left; 32 | // margin: flex-gutter(4); // returns (25px / 315px) = 7.936508%; 33 | // } 34 | // 35 | // blockquote { 36 | // float: left; 37 | // width: flex-grid(2, 4); // returns (145px / 315px) = 46.031746%; 38 | // } 39 | // } -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/functions/_modular-scale.scss: -------------------------------------------------------------------------------- 1 | // Scaling Variables 2 | $golden: 1.618; 3 | $minor-second: 1.067; 4 | $major-second: 1.125; 5 | $minor-third: 1.2; 6 | $major-third: 1.25; 7 | $perfect-fourth: 1.333; 8 | $augmented-fourth: 1.414; 9 | $perfect-fifth: 1.5; 10 | $minor-sixth: 1.6; 11 | $major-sixth: 1.667; 12 | $minor-seventh: 1.778; 13 | $major-seventh: 1.875; 14 | $octave: 2; 15 | $major-tenth: 2.5; 16 | $major-eleventh: 2.667; 17 | $major-twelfth: 3; 18 | $double-octave: 4; 19 | 20 | @function modular-scale($value, $increment, $ratio) { 21 | $v1: nth($value, 1); 22 | $v2: nth($value, length($value)); 23 | $value: $v1; 24 | 25 | // scale $v2 to just above $v1 26 | @while $v2 > $v1 { 27 | $v2: ($v2 / $ratio); // will be off-by-1 28 | } 29 | @while $v2 < $v1 { 30 | $v2: ($v2 * $ratio); // will fix off-by-1 31 | } 32 | 33 | // check AFTER scaling $v2 to prevent double-counting corner-case 34 | $double-stranded: $v2 > $v1; 35 | 36 | @if $increment > 0 { 37 | @for $i from 1 through $increment { 38 | @if $double-stranded and ($v1 * $ratio) > $v2 { 39 | $value: $v2; 40 | $v2: ($v2 * $ratio); 41 | } @else { 42 | $v1: ($v1 * $ratio); 43 | $value: $v1; 44 | } 45 | } 46 | } 47 | 48 | @if $increment < 0 { 49 | // adjust $v2 to just below $v1 50 | @if $double-stranded { 51 | $v2: ($v2 / $ratio); 52 | } 53 | 54 | @for $i from $increment through -1 { 55 | @if $double-stranded and ($v1 / $ratio) < $v2 { 56 | $value: $v2; 57 | $v2: ($v2 / $ratio); 58 | } @else { 59 | $v1: ($v1 / $ratio); 60 | $value: $v1; 61 | } 62 | } 63 | } 64 | 65 | @return $value; 66 | } 67 | -------------------------------------------------------------------------------- /lib/thesis/models/page.rb: -------------------------------------------------------------------------------- 1 | module Thesis 2 | class Page < ActiveRecord::Base 3 | attr_accessor :editable 4 | 5 | self.table_name = "pages" 6 | 7 | belongs_to :parent, class_name: "Page" 8 | has_many :subpages, -> { order(:sort_order) }, class_name: "Page", foreign_key: "parent_id" 9 | has_many :page_contents, dependent: :destroy 10 | 11 | after_save :update_subpage_slugs 12 | 13 | validates :slug, 14 | uniqueness: { message: "There's already a page at that location." }, 15 | presence: true, 16 | allow_blank: false, 17 | allow_null: false 18 | 19 | def update_slug 20 | self.slug = "/" << self.name.to_s.parameterize 21 | self.slug = "#{parent.slug.to_s}#{self.slug.to_s}" if parent 22 | end 23 | 24 | def update_subpage_slugs 25 | subpages.each(&:save) if slug_changed? 26 | end 27 | 28 | def content(name, content_type = :html, opts = {}) 29 | find_or_create_page_content(name, content_type, opts).render(editable: self.editable) 30 | end 31 | 32 | def path 33 | self.slug 34 | end 35 | 36 | protected 37 | 38 | def find_or_create_page_content(name, content_type, opts = {}) 39 | page_content = self.page_contents.where(name: name).first_or_create do |pc| 40 | pc.content = opts[:default] || "

Edit This HTML Area

" if content_type == :html 41 | pc.content = opts[:default] || "Edit This Text Area" if content_type == :text 42 | width = opts[:width] || 350 43 | height = opts[:height] || 150 44 | pc.content = opts[:default] || "http://placehold.it/#{width}x#{height}" if content_type == :image 45 | end 46 | page_content.content_type = content_type 47 | page_content.save if page_content.changed? 48 | page_content 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/css3/_animation.scss: -------------------------------------------------------------------------------- 1 | // http://www.w3.org/TR/css3-animations/#the-animation-name-property- 2 | // Each of these mixins support comma separated lists of values, which allows different transitions for individual properties to be described in a single style rule. Each value in the list corresponds to the value at that same position in the other properties. 3 | 4 | // Official animation shorthand property. 5 | @mixin animation ($animations...) { 6 | @include prefixer(animation, $animations, webkit moz spec); 7 | } 8 | 9 | // Individual Animation Properties 10 | @mixin animation-name ($names...) { 11 | @include prefixer(animation-name, $names, webkit moz spec); 12 | } 13 | 14 | 15 | @mixin animation-duration ($times...) { 16 | @include prefixer(animation-duration, $times, webkit moz spec); 17 | } 18 | 19 | 20 | @mixin animation-timing-function ($motions...) { 21 | // ease | linear | ease-in | ease-out | ease-in-out 22 | @include prefixer(animation-timing-function, $motions, webkit moz spec); 23 | } 24 | 25 | 26 | @mixin animation-iteration-count ($values...) { 27 | // infinite | 28 | @include prefixer(animation-iteration-count, $values, webkit moz spec); 29 | } 30 | 31 | 32 | @mixin animation-direction ($directions...) { 33 | // normal | alternate 34 | @include prefixer(animation-direction, $directions, webkit moz spec); 35 | } 36 | 37 | 38 | @mixin animation-play-state ($states...) { 39 | // running | paused 40 | @include prefixer(animation-play-state, $states, webkit moz spec); 41 | } 42 | 43 | 44 | @mixin animation-delay ($times...) { 45 | @include prefixer(animation-delay, $times, webkit moz spec); 46 | } 47 | 48 | 49 | @mixin animation-fill-mode ($modes...) { 50 | // none | forwards | backwards | both 51 | @include prefixer(animation-fill-mode, $modes, webkit moz spec); 52 | } 53 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/addons/_timing-functions.scss: -------------------------------------------------------------------------------- 1 | // CSS cubic-bezier timing functions. Timing functions courtesy of jquery.easie (github.com/jaukia/easie) 2 | // Timing functions are the same as demo'ed here: http://jqueryui.com/resources/demos/effect/easing.html 3 | 4 | // EASE IN 5 | $ease-in-quad: cubic-bezier(0.550, 0.085, 0.680, 0.530); 6 | $ease-in-cubic: cubic-bezier(0.550, 0.055, 0.675, 0.190); 7 | $ease-in-quart: cubic-bezier(0.895, 0.030, 0.685, 0.220); 8 | $ease-in-quint: cubic-bezier(0.755, 0.050, 0.855, 0.060); 9 | $ease-in-sine: cubic-bezier(0.470, 0.000, 0.745, 0.715); 10 | $ease-in-expo: cubic-bezier(0.950, 0.050, 0.795, 0.035); 11 | $ease-in-circ: cubic-bezier(0.600, 0.040, 0.980, 0.335); 12 | $ease-in-back: cubic-bezier(0.600, -0.280, 0.735, 0.045); 13 | 14 | // EASE OUT 15 | $ease-out-quad: cubic-bezier(0.250, 0.460, 0.450, 0.940); 16 | $ease-out-cubic: cubic-bezier(0.215, 0.610, 0.355, 1.000); 17 | $ease-out-quart: cubic-bezier(0.165, 0.840, 0.440, 1.000); 18 | $ease-out-quint: cubic-bezier(0.230, 1.000, 0.320, 1.000); 19 | $ease-out-sine: cubic-bezier(0.390, 0.575, 0.565, 1.000); 20 | $ease-out-expo: cubic-bezier(0.190, 1.000, 0.220, 1.000); 21 | $ease-out-circ: cubic-bezier(0.075, 0.820, 0.165, 1.000); 22 | $ease-out-back: cubic-bezier(0.175, 0.885, 0.320, 1.275); 23 | 24 | // EASE IN OUT 25 | $ease-in-out-quad: cubic-bezier(0.455, 0.030, 0.515, 0.955); 26 | $ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1.000); 27 | $ease-in-out-quart: cubic-bezier(0.770, 0.000, 0.175, 1.000); 28 | $ease-in-out-quint: cubic-bezier(0.860, 0.000, 0.070, 1.000); 29 | $ease-in-out-sine: cubic-bezier(0.445, 0.050, 0.550, 0.950); 30 | $ease-in-out-expo: cubic-bezier(1.000, 0.000, 0.000, 1.000); 31 | $ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.150, 0.860); 32 | $ease-in-out-back: cubic-bezier(0.680, -0.550, 0.265, 1.550); 33 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/css3/_background.scss: -------------------------------------------------------------------------------- 1 | //************************************************************************// 2 | // Background property for adding multiple backgrounds using shorthand 3 | // notation. 4 | //************************************************************************// 5 | 6 | @mixin background($backgrounds...) { 7 | $webkit-backgrounds: (); 8 | $spec-backgrounds: (); 9 | 10 | @each $background in $backgrounds { 11 | $webkit-background: (); 12 | $spec-background: (); 13 | $background-type: type-of($background); 14 | 15 | @if $background-type == string or list { 16 | $background-str: if($background-type == list, nth($background, 1), $background); 17 | 18 | $url-str: str-slice($background-str, 0, 3); 19 | $gradient-type: str-slice($background-str, 0, 6); 20 | 21 | @if $url-str == "url" { 22 | $webkit-background: $background; 23 | $spec-background: $background; 24 | } 25 | 26 | @else if $gradient-type == "linear" { 27 | $gradients: _linear-gradient-parser("#{$background}"); 28 | $webkit-background: map-get($gradients, webkit-image); 29 | $spec-background: map-get($gradients, spec-image); 30 | } 31 | 32 | @else if $gradient-type == "radial" { 33 | $gradients: _radial-gradient-parser("#{$background}"); 34 | $webkit-background: map-get($gradients, webkit-image); 35 | $spec-background: map-get($gradients, spec-image); 36 | } 37 | 38 | @else { 39 | $webkit-background: $background; 40 | $spec-background: $background; 41 | } 42 | } 43 | 44 | @else { 45 | $webkit-background: $background; 46 | $spec-background: $background; 47 | } 48 | 49 | $webkit-backgrounds: append($webkit-backgrounds, $webkit-background, comma); 50 | $spec-backgrounds: append($spec-backgrounds, $spec-background, comma); 51 | } 52 | 53 | background: $webkit-backgrounds; 54 | background: $spec-backgrounds; 55 | } 56 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/css3/_border-image.scss: -------------------------------------------------------------------------------- 1 | @mixin border-image($borders...) { 2 | $webkit-borders: (); 3 | $spec-borders: (); 4 | 5 | @each $border in $borders { 6 | $webkit-border: (); 7 | $spec-border: (); 8 | $border-type: type-of($border); 9 | 10 | @if $border-type == string or list { 11 | $border-str: if($border-type == list, nth($border, 1), $border); 12 | 13 | $url-str: str-slice($border-str, 0, 3); 14 | $gradient-type: str-slice($border-str, 0, 6); 15 | 16 | @if $url-str == "url" { 17 | $webkit-border: $border; 18 | $spec-border: $border; 19 | } 20 | 21 | @else if $gradient-type == "linear" { 22 | $gradients: _linear-gradient-parser("#{$border}"); 23 | $webkit-border: map-get($gradients, webkit-image); 24 | $spec-border: map-get($gradients, spec-image); 25 | } 26 | 27 | @else if $gradient-type == "radial" { 28 | $gradients: _radial-gradient-parser("#{$border}"); 29 | $webkit-border: map-get($gradients, webkit-image); 30 | $spec-border: map-get($gradients, spec-image); 31 | } 32 | 33 | @else { 34 | $webkit-border: $border; 35 | $spec-border: $border; 36 | } 37 | } 38 | 39 | @else { 40 | $webkit-border: $border; 41 | $spec-border: $border; 42 | } 43 | 44 | $webkit-borders: append($webkit-borders, $webkit-border, comma); 45 | $spec-borders: append($spec-borders, $spec-border, comma); 46 | } 47 | 48 | -webkit-border-image: $webkit-borders; 49 | border-image: $spec-borders; 50 | border-style: solid; 51 | } 52 | 53 | //Examples: 54 | // @include border-image(url("image.png")); 55 | // @include border-image(url("image.png") 20 stretch); 56 | // @include border-image(linear-gradient(45deg, orange, yellow)); 57 | // @include border-image(linear-gradient(45deg, orange, yellow) stretch); 58 | // @include border-image(linear-gradient(45deg, orange, yellow) 20 30 40 50 stretch round); 59 | // @include border-image(radial-gradient(top, cover, orange, yellow, orange)); 60 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/helpers/_radial-arg-parser.scss: -------------------------------------------------------------------------------- 1 | @function _radial-arg-parser($G1, $G2, $pos, $shape-size) { 2 | @each $value in $G1, $G2 { 3 | $first-val: nth($value, 1); 4 | $pos-type: type-of($first-val); 5 | $spec-at-index: null; 6 | 7 | // Determine if spec was passed to mixin 8 | @if type-of($value) == list { 9 | $spec-at-index: if(index($value, at), index($value, at), false); 10 | } 11 | @if $spec-at-index { 12 | @if $spec-at-index > 1 { 13 | @for $i from 1 through ($spec-at-index - 1) { 14 | $shape-size: $shape-size nth($value, $i); 15 | } 16 | @for $i from ($spec-at-index + 1) through length($value) { 17 | $pos: $pos nth($value, $i); 18 | } 19 | } 20 | @else if $spec-at-index == 1 { 21 | @for $i from ($spec-at-index + 1) through length($value) { 22 | $pos: $pos nth($value, $i); 23 | } 24 | } 25 | $G1: null; 26 | } 27 | 28 | // If not spec calculate correct values 29 | @else { 30 | @if ($pos-type != color) or ($first-val != "transparent") { 31 | @if ($pos-type == number) 32 | or ($first-val == "center") 33 | or ($first-val == "top") 34 | or ($first-val == "right") 35 | or ($first-val == "bottom") 36 | or ($first-val == "left") { 37 | 38 | $pos: $value; 39 | 40 | @if $pos == $G1 { 41 | $G1: null; 42 | } 43 | } 44 | 45 | @else if 46 | ($first-val == "ellipse") 47 | or ($first-val == "circle") 48 | or ($first-val == "closest-side") 49 | or ($first-val == "closest-corner") 50 | or ($first-val == "farthest-side") 51 | or ($first-val == "farthest-corner") 52 | or ($first-val == "contain") 53 | or ($first-val == "cover") { 54 | 55 | $shape-size: $value; 56 | 57 | @if $value == $G1 { 58 | $G1: null; 59 | } 60 | 61 | @else if $value == $G2 { 62 | $G2: null; 63 | } 64 | } 65 | } 66 | } 67 | } 68 | @return $G1, $G2, $pos, $shape-size; 69 | } 70 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/helpers/_linear-positions-parser.scss: -------------------------------------------------------------------------------- 1 | @function _linear-positions-parser($pos) { 2 | $type: type-of(nth($pos, 1)); 3 | $spec: null; 4 | $degree: null; 5 | $side: null; 6 | $corner: null; 7 | $length: length($pos); 8 | // Parse Side and corner positions 9 | @if ($length > 1) { 10 | @if nth($pos, 1) == "to" { // Newer syntax 11 | $side: nth($pos, 2); 12 | 13 | @if $length == 2 { // eg. to top 14 | // Swap for backwards compatability 15 | $degree: _position-flipper(nth($pos, 2)); 16 | } 17 | @else if $length == 3 { // eg. to top left 18 | $corner: nth($pos, 3); 19 | } 20 | } 21 | @else if $length == 2 { // Older syntax ("top left") 22 | $side: _position-flipper(nth($pos, 1)); 23 | $corner: _position-flipper(nth($pos, 2)); 24 | } 25 | 26 | @if ("#{$side} #{$corner}" == "left top") or ("#{$side} #{$corner}" == "top left") { 27 | $degree: _position-flipper(#{$side}) _position-flipper(#{$corner}); 28 | } 29 | @else if ("#{$side} #{$corner}" == "right top") or ("#{$side} #{$corner}" == "top right") { 30 | $degree: _position-flipper(#{$side}) _position-flipper(#{$corner}); 31 | } 32 | @else if ("#{$side} #{$corner}" == "right bottom") or ("#{$side} #{$corner}" == "bottom right") { 33 | $degree: _position-flipper(#{$side}) _position-flipper(#{$corner}); 34 | } 35 | @else if ("#{$side} #{$corner}" == "left bottom") or ("#{$side} #{$corner}" == "bottom left") { 36 | $degree: _position-flipper(#{$side}) _position-flipper(#{$corner}); 37 | } 38 | $spec: to $side $corner; 39 | } 40 | @else if $length == 1 { 41 | // Swap for backwards compatability 42 | @if $type == string { 43 | $degree: $pos; 44 | $spec: to _position-flipper($pos); 45 | } 46 | @else { 47 | $degree: -270 - $pos; //rotate the gradient opposite from spec 48 | $spec: $pos; 49 | } 50 | } 51 | $degree: unquote($degree + ","); 52 | $spec: unquote($spec + ","); 53 | @return $degree $spec; 54 | } 55 | 56 | @function _position-flipper($pos) { 57 | @return if($pos == left, right, null) 58 | if($pos == right, left, null) 59 | if($pos == top, bottom, null) 60 | if($pos == bottom, top, null); 61 | } 62 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler/setup' 3 | require 'factory_girl' 4 | require 'rspec/autorun' 5 | require 'database_cleaner' 6 | require 'rails/all' 7 | 8 | # Add a fake ApplicationController for testing. 9 | class ApplicationController < ActionController::Base 10 | def page_is_editable?(page) 11 | true 12 | end 13 | end 14 | 15 | # Require thesis 16 | require "thesis" 17 | 18 | # Manually run the Rails initializer. Thanks to @JoshReedSchramm for the code. 19 | initializer = Thesis::Engine.initializers.select { |i| i.name == "thesis.action_controller" }.first 20 | initializer.run 21 | # Dir[File.join('.', '/lib/thesis/**/*.rb')].each {|file| require file } 22 | 23 | # Load Factories 24 | FactoryGirl.find_definitions 25 | 26 | # Configure ActiveRecord Connection 27 | # Use memory store since we don't care about persistent data here. 28 | ActiveRecord::Base.establish_connection( 29 | :adapter => "sqlite3", 30 | :database => ":memory:" 31 | ) 32 | 33 | # Configure ActiveRecord Testing Schema 34 | ActiveRecord::Schema.define do 35 | self.verbose = false 36 | 37 | create_table :pages do |t| 38 | t.integer :parent_id 39 | t.string :name 40 | t.string :slug 41 | t.string :title 42 | t.string :description 43 | t.integer :sort_order, default: 0, null: false 44 | t.string :template, default: "default", null: false 45 | t.timestamps 46 | end 47 | 48 | create_table :page_contents do |t| 49 | t.integer :page_id, null: false 50 | t.string :name, null: false 51 | t.text :content, default: "Edit This Content Area" 52 | t.string :content_type, default: :html 53 | t.timestamps 54 | end 55 | end 56 | 57 | RSpec.configure do |config| 58 | # Pretty FactoryGirl syntax. For more details, visit 59 | # https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md#using-factories 60 | config.include FactoryGirl::Syntax::Methods 61 | 62 | DatabaseCleaner.strategy = :transaction 63 | 64 | # Configure DatabaseCleaner to set up a new 65 | # transaction at the beginning of each test. 66 | config.before do 67 | DatabaseCleaner.start 68 | end 69 | 70 | # Reload FactoryGirl definitions and clean 71 | # the database after every test. 72 | config.after do 73 | FactoryGirl.reload 74 | DatabaseCleaner.clean 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/_bourbon.scss: -------------------------------------------------------------------------------- 1 | // Settings 2 | @import "settings/prefixer"; 3 | @import "settings/px-to-em"; 4 | @import "settings/asset-pipeline"; 5 | 6 | // Custom Helpers 7 | @import "helpers/convert-units"; 8 | @import "helpers/gradient-positions-parser"; 9 | @import "helpers/is-num"; 10 | @import "helpers/linear-angle-parser"; 11 | @import "helpers/linear-gradient-parser"; 12 | @import "helpers/linear-positions-parser"; 13 | @import "helpers/linear-side-corner-parser"; 14 | @import "helpers/radial-arg-parser"; 15 | @import "helpers/radial-positions-parser"; 16 | @import "helpers/radial-gradient-parser"; 17 | @import "helpers/render-gradients"; 18 | @import "helpers/shape-size-stripper"; 19 | @import "helpers/str-to-num"; 20 | 21 | // Custom Functions 22 | @import "functions/assign"; 23 | @import "functions/color-lightness"; 24 | @import "functions/flex-grid"; 25 | @import "functions/golden-ratio"; 26 | @import "functions/grid-width"; 27 | @import "functions/modular-scale"; 28 | @import "functions/px-to-em"; 29 | @import "functions/px-to-rem"; 30 | @import "functions/strip-units"; 31 | @import "functions/tint-shade"; 32 | @import "functions/transition-property-name"; 33 | @import "functions/unpack"; 34 | 35 | // CSS3 Mixins 36 | @import "css3/animation"; 37 | @import "css3/appearance"; 38 | @import "css3/backface-visibility"; 39 | @import "css3/background"; 40 | @import "css3/background-image"; 41 | @import "css3/border-image"; 42 | @import "css3/border-radius"; 43 | @import "css3/box-sizing"; 44 | @import "css3/calc"; 45 | @import "css3/columns"; 46 | @import "css3/filter"; 47 | @import "css3/flex-box"; 48 | @import "css3/font-face"; 49 | @import "css3/font-feature-settings"; 50 | @import "css3/hyphens"; 51 | @import "css3/hidpi-media-query"; 52 | @import "css3/image-rendering"; 53 | @import "css3/keyframes"; 54 | @import "css3/linear-gradient"; 55 | @import "css3/perspective"; 56 | @import "css3/radial-gradient"; 57 | @import "css3/transform"; 58 | @import "css3/transition"; 59 | @import "css3/user-select"; 60 | @import "css3/placeholder"; 61 | 62 | // Addons & other mixins 63 | @import "addons/button"; 64 | @import "addons/clearfix"; 65 | @import "addons/directional-values"; 66 | @import "addons/ellipsis"; 67 | @import "addons/font-family"; 68 | @import "addons/hide-text"; 69 | @import "addons/html5-input-types"; 70 | @import "addons/position"; 71 | @import "addons/prefixer"; 72 | @import "addons/retina-image"; 73 | @import "addons/size"; 74 | @import "addons/timing-functions"; 75 | @import "addons/triangle"; 76 | @import "addons/word-wrap"; 77 | 78 | // Soon to be deprecated Mixins 79 | @import "bourbon-deprecated-upcoming"; 80 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/css3/_transition.scss: -------------------------------------------------------------------------------- 1 | // Shorthand mixin. Supports multiple parentheses-deliminated values for each variable. 2 | // Example: @include transition (all 2s ease-in-out); 3 | // @include transition (opacity 1s ease-in 2s, width 2s ease-out); 4 | // @include transition-property (transform, opacity); 5 | 6 | @mixin transition ($properties...) { 7 | // Fix for vendor-prefix transform property 8 | $needs-prefixes: false; 9 | $webkit: (); 10 | $moz: (); 11 | $spec: (); 12 | 13 | // Create lists for vendor-prefixed transform 14 | @each $list in $properties { 15 | @if nth($list, 1) == "transform" { 16 | $needs-prefixes: true; 17 | $list1: -webkit-transform; 18 | $list2: -moz-transform; 19 | $list3: (); 20 | 21 | @each $var in $list { 22 | $list3: join($list3, $var); 23 | 24 | @if $var != "transform" { 25 | $list1: join($list1, $var); 26 | $list2: join($list2, $var); 27 | } 28 | } 29 | 30 | $webkit: append($webkit, $list1); 31 | $moz: append($moz, $list2); 32 | $spec: append($spec, $list3); 33 | } 34 | 35 | // Create lists for non-prefixed transition properties 36 | @else { 37 | $webkit: append($webkit, $list, comma); 38 | $moz: append($moz, $list, comma); 39 | $spec: append($spec, $list, comma); 40 | } 41 | } 42 | 43 | @if $needs-prefixes { 44 | -webkit-transition: $webkit; 45 | -moz-transition: $moz; 46 | transition: $spec; 47 | } 48 | @else { 49 | @if length($properties) >= 1 { 50 | @include prefixer(transition, $properties, webkit moz spec); 51 | } 52 | 53 | @else { 54 | $properties: all 0.15s ease-out 0s; 55 | @include prefixer(transition, $properties, webkit moz spec); 56 | } 57 | } 58 | } 59 | 60 | @mixin transition-property ($properties...) { 61 | -webkit-transition-property: transition-property-names($properties, 'webkit'); 62 | -moz-transition-property: transition-property-names($properties, 'moz'); 63 | transition-property: transition-property-names($properties, false); 64 | } 65 | 66 | @mixin transition-duration ($times...) { 67 | @include prefixer(transition-duration, $times, webkit moz spec); 68 | } 69 | 70 | @mixin transition-timing-function ($motions...) { 71 | // ease | linear | ease-in | ease-out | ease-in-out | cubic-bezier() 72 | @include prefixer(transition-timing-function, $motions, webkit moz spec); 73 | } 74 | 75 | @mixin transition-delay ($times...) { 76 | @include prefixer(transition-delay, $times, webkit moz spec); 77 | } 78 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/addons/_triangle.scss: -------------------------------------------------------------------------------- 1 | @mixin triangle ($size, $color, $direction) { 2 | height: 0; 3 | width: 0; 4 | 5 | $width: nth($size, 1); 6 | $height: nth($size, length($size)); 7 | 8 | $foreground-color: nth($color, 1); 9 | $background-color: if(length($color) == 2, nth($color, 2), transparent); 10 | 11 | @if ($direction == up) or ($direction == down) or ($direction == right) or ($direction == left) { 12 | 13 | $width: $width / 2; 14 | $height: if(length($size) > 1, $height, $height/2); 15 | 16 | @if $direction == up { 17 | border-left: $width solid $background-color; 18 | border-right: $width solid $background-color; 19 | border-bottom: $height solid $foreground-color; 20 | 21 | } @else if $direction == right { 22 | border-top: $width solid $background-color; 23 | border-bottom: $width solid $background-color; 24 | border-left: $height solid $foreground-color; 25 | 26 | } @else if $direction == down { 27 | border-left: $width solid $background-color; 28 | border-right: $width solid $background-color; 29 | border-top: $height solid $foreground-color; 30 | 31 | } @else if $direction == left { 32 | border-top: $width solid $background-color; 33 | border-bottom: $width solid $background-color; 34 | border-right: $height solid $foreground-color; 35 | } 36 | } 37 | 38 | @else if ($direction == up-right) or ($direction == up-left) { 39 | border-top: $height solid $foreground-color; 40 | 41 | @if $direction == up-right { 42 | border-left: $width solid $background-color; 43 | 44 | } @else if $direction == up-left { 45 | border-right: $width solid $background-color; 46 | } 47 | } 48 | 49 | @else if ($direction == down-right) or ($direction == down-left) { 50 | border-bottom: $height solid $foreground-color; 51 | 52 | @if $direction == down-right { 53 | border-left: $width solid $background-color; 54 | 55 | } @else if $direction == down-left { 56 | border-right: $width solid $background-color; 57 | } 58 | } 59 | 60 | @else if ($direction == inset-up) { 61 | border-width: $height $width; 62 | border-style: solid; 63 | border-color: $background-color $background-color $foreground-color; 64 | } 65 | 66 | @else if ($direction == inset-down) { 67 | border-width: $height $width; 68 | border-style: solid; 69 | border-color: $foreground-color $background-color $background-color; 70 | } 71 | 72 | @else if ($direction == inset-right) { 73 | border-width: $width $height; 74 | border-style: solid; 75 | border-color: $background-color $background-color $background-color $foreground-color; 76 | } 77 | 78 | @else if ($direction == inset-left) { 79 | border-width: $width $height; 80 | border-style: solid; 81 | border-color: $background-color $foreground-color $background-color $background-color; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/addons/_directional-values.scss: -------------------------------------------------------------------------------- 1 | // directional-property mixins are shorthands 2 | // for writing properties like the following 3 | // 4 | // @include margin(null 0 10px); 5 | // ------ 6 | // margin-right: 0; 7 | // margin-bottom: 10px; 8 | // margin-left: 0; 9 | // 10 | // - or - 11 | // 12 | // @include border-style(dotted null); 13 | // ------ 14 | // border-top-style: dotted; 15 | // border-bottom-style: dotted; 16 | // 17 | // ------ 18 | // 19 | // Note: You can also use false instead of null 20 | 21 | @function collapse-directionals($vals) { 22 | $output: null; 23 | 24 | $A: nth( $vals, 1 ); 25 | $B: if( length($vals) < 2, $A, nth($vals, 2)); 26 | $C: if( length($vals) < 3, $A, nth($vals, 3)); 27 | $D: if( length($vals) < 2, $A, nth($vals, if( length($vals) < 4, 2, 4) )); 28 | 29 | @if $A == 0 { $A: 0 } 30 | @if $B == 0 { $B: 0 } 31 | @if $C == 0 { $C: 0 } 32 | @if $D == 0 { $D: 0 } 33 | 34 | @if $A == $B and $A == $C and $A == $D { $output: $A } 35 | @else if $A == $C and $B == $D { $output: $A $B } 36 | @else if $B == $D { $output: $A $B $C } 37 | @else { $output: $A $B $C $D } 38 | 39 | @return $output; 40 | } 41 | 42 | @function contains-falsy($list) { 43 | @each $item in $list { 44 | @if not $item { 45 | @return true; 46 | } 47 | } 48 | 49 | @return false; 50 | } 51 | 52 | @mixin directional-property($pre, $suf, $vals) { 53 | // Property Names 54 | $top: $pre + "-top" + if($suf, "-#{$suf}", ""); 55 | $bottom: $pre + "-bottom" + if($suf, "-#{$suf}", ""); 56 | $left: $pre + "-left" + if($suf, "-#{$suf}", ""); 57 | $right: $pre + "-right" + if($suf, "-#{$suf}", ""); 58 | $all: $pre + if($suf, "-#{$suf}", ""); 59 | 60 | $vals: collapse-directionals($vals); 61 | 62 | @if contains-falsy($vals) { 63 | @if nth($vals, 1) { #{$top}: nth($vals, 1); } 64 | 65 | @if length($vals) == 1 { 66 | @if nth($vals, 1) { #{$right}: nth($vals, 1); } 67 | } @else { 68 | @if nth($vals, 2) { #{$right}: nth($vals, 2); } 69 | } 70 | 71 | // prop: top/bottom right/left 72 | @if length($vals) == 2 { 73 | @if nth($vals, 1) { #{$bottom}: nth($vals, 1); } 74 | @if nth($vals, 2) { #{$left}: nth($vals, 2); } 75 | 76 | // prop: top right/left bottom 77 | } @else if length($vals) == 3 { 78 | @if nth($vals, 3) { #{$bottom}: nth($vals, 3); } 79 | @if nth($vals, 2) { #{$left}: nth($vals, 2); } 80 | 81 | // prop: top right bottom left 82 | } @else if length($vals) == 4 { 83 | @if nth($vals, 3) { #{$bottom}: nth($vals, 3); } 84 | @if nth($vals, 4) { #{$left}: nth($vals, 4); } 85 | } 86 | 87 | // prop: top/right/bottom/left 88 | } @else { 89 | #{$all}: $vals; 90 | } 91 | } 92 | 93 | @mixin margin($vals...) { 94 | @include directional-property(margin, false, $vals...); 95 | } 96 | 97 | @mixin padding($vals...) { 98 | @include directional-property(padding, false, $vals...); 99 | } 100 | 101 | @mixin border-style($vals...) { 102 | @include directional-property(border, style, $vals...); 103 | } 104 | 105 | @mixin border-color($vals...) { 106 | @include directional-property(border, color, $vals...); 107 | } 108 | 109 | @mixin border-width($vals...) { 110 | @include directional-property(border, width, $vals...); 111 | } 112 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/addons/_html5-input-types.scss: -------------------------------------------------------------------------------- 1 | //************************************************************************// 2 | // Generate a variable ($all-text-inputs) with a list of all html5 3 | // input types that have a text-based input, excluding textarea. 4 | // http://diveintohtml5.org/forms.html 5 | //************************************************************************// 6 | $inputs-list: 'input[type="email"]', 7 | 'input[type="number"]', 8 | 'input[type="password"]', 9 | 'input[type="search"]', 10 | 'input[type="tel"]', 11 | 'input[type="text"]', 12 | 'input[type="url"]', 13 | 14 | // Webkit & Gecko may change the display of these in the future 15 | 'input[type="color"]', 16 | 'input[type="date"]', 17 | 'input[type="datetime"]', 18 | 'input[type="datetime-local"]', 19 | 'input[type="month"]', 20 | 'input[type="time"]', 21 | 'input[type="week"]'; 22 | 23 | // Bare inputs 24 | //************************************************************************// 25 | $all-text-inputs: assign-inputs($inputs-list); 26 | 27 | // Hover Pseudo-class 28 | //************************************************************************// 29 | $all-text-inputs-hover: assign-inputs($inputs-list, hover); 30 | 31 | // Focus Pseudo-class 32 | //************************************************************************// 33 | $all-text-inputs-focus: assign-inputs($inputs-list, focus); 34 | 35 | 36 | 37 | // You must use interpolation on the variable: 38 | // #{$all-text-inputs} 39 | // #{$all-text-inputs-hover} 40 | // #{$all-text-inputs-focus} 41 | 42 | // Example 43 | //************************************************************************// 44 | // #{$all-text-inputs}, textarea { 45 | // border: 1px solid red; 46 | // } 47 | 48 | 49 | 50 | //************************************************************************// 51 | // Generate a variable ($all-button-inputs) with a list of all html5 52 | // input types that have a button-based input, excluding button. 53 | //************************************************************************// 54 | $inputs-button-list: 'input[type="button"]', 55 | 'input[type="reset"]', 56 | 'input[type="submit"]'; 57 | 58 | // Bare inputs 59 | //************************************************************************// 60 | $all-button-inputs: assign-inputs($inputs-button-list); 61 | 62 | // Hover Pseudo-class 63 | //************************************************************************// 64 | $all-button-inputs-hover: assign-inputs($inputs-button-list, hover); 65 | 66 | // Focus Pseudo-class 67 | //************************************************************************// 68 | $all-button-inputs-focus: assign-inputs($inputs-button-list, focus); 69 | 70 | // Active Pseudo-class 71 | //************************************************************************// 72 | $all-button-inputs-active: assign-inputs($inputs-button-list, active); 73 | 74 | 75 | 76 | // You must use interpolation on the variable: 77 | // #{$all-button-inputs} 78 | // #{$all-button-inputs-hover} 79 | // #{$all-button-inputs-focus} 80 | // #{$all-button-inputs-active} 81 | 82 | // Example 83 | //************************************************************************// 84 | // #{$all-button-inputs}, button { 85 | // border: 1px solid red; 86 | // } 87 | -------------------------------------------------------------------------------- /lib/thesis/controllers/thesis_controller.rb: -------------------------------------------------------------------------------- 1 | module Thesis 2 | class ThesisController < ::ApplicationController 3 | include Thesis::ControllerHelpers 4 | 5 | def show 6 | raise ActionController::RoutingError.new('Not Found') unless current_page 7 | 8 | if current_page.template && template_exists?("page_templates/#{current_page.template}") 9 | render "page_templates/#{current_page.template}", layout: false 10 | elsif template_exists?("page_templates/default") 11 | render "page_templates/default" 12 | else 13 | raise PageRequiresTemplate.new("No default template found in page_templates. Create page_templates/default.html.(erb|haml|slim).") 14 | end 15 | end 16 | 17 | def create_page 18 | page = Page.new 19 | return head :forbidden unless page_is_editable?(page) 20 | 21 | update_page_attributes page 22 | if params[:parent_slug].present? 23 | parent_slug = params[:parent_slug].to_s.sub(/(\/)+$/,'') 24 | parent = Page.where(slug: parent_slug).first 25 | page.parent = parent 26 | end 27 | 28 | resp = { page: page } 29 | 30 | page.name = page.slug.to_s.split("/").last.to_s.humanize 31 | page.update_slug 32 | if page.save 33 | resp[:page] = page 34 | else 35 | resp[:message] = page.errors.messages.first 36 | end 37 | 38 | render json: resp, status: page.valid? ? :ok : :not_acceptable 39 | end 40 | 41 | def delete_page 42 | slug = params[:slug].to_s.sub(/(\/)+$/,'') 43 | page = Page.where(slug: slug).first 44 | return head :forbidden unless page && page_is_editable?(page) 45 | 46 | head page.destroy ? :ok : :not_acceptable 47 | end 48 | 49 | def update_page 50 | page = current_page 51 | return head :forbidden unless page_is_editable?(page) 52 | 53 | update_page_attributes page 54 | page.update_slug 55 | 56 | head page.save ? :ok : :not_acceptable 57 | end 58 | 59 | def page_attributes 60 | [ :name, :title, :description, :parent_id ] 61 | end 62 | 63 | def update_page_attributes(page) 64 | page_attributes.each { |a| page.send("#{a}=", params[a].to_s) if params[a] } 65 | page 66 | end 67 | 68 | def update_page_content 69 | errors = false 70 | error_message = "Unknown error." 71 | 72 | page_contents = PageContent.where(id: params.keys).includes(:page).all 73 | if page_contents.length.zero? 74 | error_message = "That page doesn't exist anymore." 75 | errors = true 76 | else 77 | page_contents.each do |pc| 78 | if page_is_editable? pc.page 79 | pc.content = params[pc.id.to_s].to_s.presence || " ".html_safe 80 | pc.save 81 | else 82 | errors = true 83 | error_message = "You don't have permission to update this page." 84 | end 85 | end 86 | end 87 | 88 | resp = {} 89 | resp[:message] = error_message if errors 90 | 91 | render json: resp, status: errors ? :not_acceptable : :ok 92 | end 93 | 94 | # The ApplicationController should implement this. 95 | def page_is_editable?(page) 96 | raise RequiredMethodNotImplemented.new("Add a `page_is_editable?(page)` method to your controller that returns true or false.") unless defined?(super) 97 | super 98 | end 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /spec/thesis/controllers/thesis_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Thesis::ThesisController, type: :controller do 4 | let(:page) { create :page, template: "default" } 5 | let(:page_content) { create :page_content, name: "Main", page: page } 6 | 7 | before do 8 | described_class.any_instance.stub(:render).and_return(page.title) 9 | described_class.any_instance.stub(:template_exists?).and_return(true) 10 | described_class.any_instance.stub(:page_is_editable?).and_return(true) 11 | end 12 | 13 | describe "#show" do 14 | it "displays a page when it can be found" do 15 | make_request :show, path: page.slug 16 | expect(response).to_not be_nil 17 | end 18 | 19 | it "raises PageRequiresTemplate when template can't be found" do 20 | described_class.any_instance.stub(:template_exists?).and_return(false) 21 | expect { make_request :show, path: page.slug }.to raise_error Thesis::PageRequiresTemplate 22 | end 23 | end 24 | 25 | describe "#create_page" do 26 | context "when the page can be edited" do 27 | it "creates a page" do 28 | make_request :create_page, name: "New Page" 29 | page = Thesis::Page.last 30 | expect(page.name).to eq "New Page" 31 | end 32 | end 33 | 34 | context "when the page can't be edited" do 35 | before { described_class.any_instance.stub(:page_is_editable?).and_return(false) } 36 | 37 | it "returns a status of 403 'Forbidden'" do 38 | make_request :create_page, name: "New Page" 39 | expect(@response.status).to eq 403 40 | end 41 | end 42 | end 43 | 44 | describe "#update_page" do 45 | context "when the page can be edited" do 46 | it "updates the attributes of the page" do 47 | make_request :update_page, path: page.slug, name: "New Name" 48 | expect(response.status).to eq 200 49 | page.reload 50 | expect(page.name).to eq "New Name" 51 | expect(page.slug).to eq "/new-name" 52 | end 53 | end 54 | end 55 | 56 | describe "#update_page_content" do 57 | context "when the page can be edited" do 58 | it "updates the content on the page" do 59 | page_content.content = "I'm the content" 60 | page_content.save 61 | 62 | make_request :update_page_content, { method: :put, page_content.id => "New content" } 63 | 64 | expect(page_content.reload.content).to eq "New content" 65 | end 66 | end 67 | 68 | context "when the page can't be edited" do 69 | before { described_class.any_instance.stub(:page_is_editable?).and_return(false) } 70 | 71 | it "doesn't update the page_content" do 72 | page_content.content = "I'm the content" 73 | page_content.save 74 | 75 | make_request :update_page_content, { method: :post, page_content.id => "New content" } 76 | 77 | expect(page_content.reload.content).to eq "I'm the content" 78 | end 79 | end 80 | end 81 | end 82 | 83 | # Mock a request to the controller without 84 | # Using Rails routing. Makes the response available 85 | # in the @response instance variable. 86 | def make_request(action, params = {}) 87 | path = params[:path] || "/" 88 | method = params[:method] || "post" 89 | env = Rack::MockRequest.env_for(path, params: params.except(:path).except(:method), method: method) 90 | status, headers, body = described_class.action(action).call(env) 91 | @response = ActionDispatch::TestResponse.new(status, headers, body) 92 | @controller = body.instance_variable_get(:@response).request.env['action_controller.instance'] 93 | end 94 | 95 | def response 96 | @response 97 | end 98 | 99 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/_forms.scss: -------------------------------------------------------------------------------- 1 | #thesis-modal { 2 | form { 3 | margin : 0; 4 | padding : 0 5 | } 6 | 7 | input, 8 | label, 9 | select { 10 | display : block; 11 | font-family : $raleway; 12 | font-size : 12px; 13 | } 14 | 15 | label { 16 | font-weight : normal; 17 | margin-bottom : 8px; 18 | 19 | &.required:after { 20 | content: "*"; 21 | } 22 | 23 | abbr { 24 | display: none; 25 | } 26 | } 27 | 28 | textarea, 29 | input, 30 | button, 31 | select[multiple=multiple] { 32 | @include box-sizing(border-box); 33 | @include transition(border-color); 34 | background-color : white; 35 | border : none; 36 | box-shadow : none; 37 | font-family : $raleway; 38 | font-size : 17px; 39 | margin-bottom : 0px; 40 | padding : 5px 0px; 41 | width : 100%; 42 | color : darken($gray, 20%); 43 | font-weight : bold; 44 | 45 | @include placeholder { 46 | color: #A7AAAB; 47 | } 48 | } 49 | 50 | textarea { 51 | resize : vertical; 52 | height : 60px; 53 | } 54 | 55 | input[type="search"] { 56 | @include appearance(none); 57 | } 58 | 59 | input[type="checkbox"], input[type="radio"] { 60 | display : inline; 61 | margin-right : 15px; 62 | } 63 | 64 | input[type="file"] { 65 | margin-bottom : 8px; 66 | width : 100%; 67 | } 68 | 69 | select { 70 | width : auto; 71 | max-width : 100%; 72 | margin-bottom : 8px; 73 | } 74 | 75 | label.checkbox { 76 | margin-bottom : 10px; 77 | // @extend %noselect; 78 | 79 | input { 80 | display : none; 81 | } 82 | 83 | input:checked { 84 | + span:before { 85 | background : image-url('ico.checkbox-selected.png') no-repeat center center; 86 | } 87 | } 88 | 89 | span { 90 | font-size : 15px; 91 | color : #8C9092; 92 | font-weight : normal; 93 | line-height : 20px; 94 | 95 | &:before { 96 | display : inline-block; 97 | margin-right : 8px; 98 | background : image-url('ico.checkbox.png') no-repeat center center; 99 | width : 20px; 100 | height : 20px; 101 | content : ""; 102 | vertical-align : middle; 103 | } 104 | } 105 | } 106 | 107 | .modal-field { 108 | width : 100%; 109 | border-bottom : 1px solid $gray; 110 | clear : both; 111 | @include clearfix; 112 | 113 | > span { 114 | width : 33%; 115 | float : left; 116 | display : block; 117 | padding : 12px 0px 5px; 118 | } 119 | 120 | > input { 121 | width: 66%; 122 | float: left; 123 | } 124 | 125 | &.submit, &.cancel { 126 | border-bottom : 0px; 127 | margin-bottom : 0px; 128 | margin-top : 15px; 129 | float : left; 130 | width : 50%; 131 | clear : none; 132 | 133 | button { 134 | height : 60px; 135 | width : 100%; 136 | color : $green; 137 | 138 | i, span { 139 | display : inline-block; 140 | vertical-align : middle; 141 | margin : 0px 5px; 142 | } 143 | 144 | &:hover { 145 | background-color : rgba($green,20%); 146 | box-shadow : 0px 0px 35px 26px white inset; 147 | @include transition(background-color .2s ease-in-out); 148 | } 149 | } 150 | } 151 | 152 | &.cancel { 153 | button { 154 | color: $pink; 155 | @include animation(none); 156 | 157 | &:hover { 158 | background-color : rgba($pink,20%); 159 | box-shadow : 0px 0px 35px 26px white inset; 160 | @include transition(background-color .2s ease-in-out); 161 | } 162 | } 163 | } 164 | } 165 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/_keyframes.sass: -------------------------------------------------------------------------------- 1 | @-webkit-keyframes barber_pole 2 | 0% 3 | background-position: 0px 0px 4 | 100% 5 | background-position: 60px 0px 6 | 7 | @-moz-keyframes barber_pole 8 | 0% 9 | background-position: 0px 0px 10 | 100% 11 | background-position: 60px 0px 12 | 13 | @-ms-keyframes barber_pole 14 | 0% 15 | background-position: 0px 0px 16 | 100% 17 | background-position: 60px 0px 18 | 19 | @-o-keyframes barber_pole 20 | 0% 21 | background-position: 0px 0px 22 | 100% 23 | background-position: 60px 0px 24 | 25 | @keyframes barber_pole 26 | 0% 27 | background-position: 0px 0px 28 | 100% 29 | background-position: 60px 0px 30 | 31 | 32 | 33 | @-webkit-keyframes thesis_fader 34 | 0% 35 | opacity: 0 36 | 100% 37 | opacity: 1 38 | 39 | @-moz-keyframes thesis_fader 40 | 0% 41 | opacity: 0 42 | 100% 43 | opacity: 1 44 | 45 | @-ms-keyframes thesis_fader 46 | 0% 47 | opacity: 0 48 | 100% 49 | opacity: 1 50 | 51 | @-o-keyframes thesis_fader 52 | 0% 53 | opacity: 0 54 | 100% 55 | opacity: 1 56 | 57 | @keyframes thesis_fader 58 | 0% 59 | opacity: 0 60 | 100% 61 | opacity: 1 62 | 63 | 64 | 65 | @-webkit-keyframes modal_border 66 | 0% 67 | border-color: $blue 68 | 20% 69 | border-color: $green 70 | 40% 71 | border-color: $yellow 72 | 60% 73 | border-color: $orange 74 | 80% 75 | border-color: $pink 76 | 100% 77 | border-color: $blue 78 | 79 | @-moz-keyframes modal_border 80 | 0% 81 | border-color: $blue 82 | 20% 83 | border-color: $green 84 | 40% 85 | border-color: $yellow 86 | 60% 87 | border-color: $orange 88 | 80% 89 | border-color: $pink 90 | 100% 91 | border-color: $blue 92 | 93 | @-ms-keyframes modal_border 94 | 0% 95 | border-color: $blue 96 | 20% 97 | border-color: $green 98 | 40% 99 | border-color: $yellow 100 | 60% 101 | border-color: $orange 102 | 80% 103 | border-color: $pink 104 | 100% 105 | border-color: $blue 106 | 107 | @-o-keyframes modal_border 108 | 0% 109 | border-color: $blue 110 | 20% 111 | border-color: $green 112 | 40% 113 | border-color: $yellow 114 | 60% 115 | border-color: $orange 116 | 80% 117 | border-color: $pink 118 | 100% 119 | border-color: $blue 120 | 121 | @keyframes modal_border 122 | 0% 123 | border-color: $blue 124 | 20% 125 | border-color: $green 126 | 40% 127 | border-color: $yellow 128 | 60% 129 | border-color: $orange 130 | 80% 131 | border-color: $pink 132 | 100% 133 | border-color: $blue 134 | 135 | 136 | 137 | @-webkit-keyframes modal_icon_color 138 | 0% 139 | color: darken($blue, 10%) 140 | 20% 141 | color: darken($green, 10%) 142 | 40% 143 | color: darken($yellow, 10%) 144 | 60% 145 | color: darken($orange, 10%) 146 | 80% 147 | color: darken($pink, 10%) 148 | 100% 149 | color: darken($blue, 10%) 150 | 151 | @-moz-keyframes modal_icon_color 152 | 0% 153 | color: darken($blue, 10%) 154 | 20% 155 | color: darken($green, 10%) 156 | 40% 157 | color: darken($yellow, 10%) 158 | 60% 159 | color: darken($orange, 10%) 160 | 80% 161 | color: darken($pink, 10%) 162 | 100% 163 | color: darken($blue, 10%) 164 | 165 | @-ms-keyframes modal_icon_color 166 | 0% 167 | color: darken($blue, 10%) 168 | 20% 169 | color: darken($green, 10%) 170 | 40% 171 | color: darken($yellow, 10%) 172 | 60% 173 | color: darken($orange, 10%) 174 | 80% 175 | color: darken($pink, 10%) 176 | 100% 177 | color: darken($blue, 10%) 178 | 179 | @-o-keyframes modal_icon_color 180 | 0% 181 | color: darken($blue, 10%) 182 | 20% 183 | color: darken($green, 10%) 184 | 40% 185 | color: darken($yellow, 10%) 186 | 60% 187 | color: darken($orange, 10%) 188 | 80% 189 | color: darken($pink, 10%) 190 | 100% 191 | color: darken($blue, 10%) 192 | 193 | @keyframes modal_icon_color 194 | 0% 195 | color: darken($blue, 10%) 196 | 20% 197 | color: darken($green, 10%) 198 | 40% 199 | color: darken($yellow, 10%) 200 | 60% 201 | color: darken($orange, 10%) 202 | 80% 203 | color: darken($pink, 10%) 204 | 100% 205 | color: darken($blue, 10%) 206 | -------------------------------------------------------------------------------- /lib/generators/thesis/install/install_generator.rb: -------------------------------------------------------------------------------- 1 | require 'rails/generators/migration' 2 | 3 | module Thesis 4 | module Generators 5 | class InstallGenerator < ::Rails::Generators::Base 6 | include Rails::Generators::Migration 7 | 8 | source_root File.expand_path('../templates', __FILE__) 9 | 10 | desc "install or upgrade Thesis" 11 | 12 | def self.next_migration_number(path) 13 | unless @prev_migration_nr 14 | @prev_migration_nr = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i 15 | else 16 | @prev_migration_nr += 1 17 | end 18 | @prev_migration_nr.to_s 19 | end 20 | 21 | def copy_migrations 22 | copy_migration "thesis_create_page" 23 | copy_migration "thesis_create_page_content" 24 | end 25 | 26 | def create_folders 27 | return copy_file "page_templates/default.html.slim", "app/views/page_templates/default.html.slim" if defined? Slim 28 | return copy_file "page_templates/default.html.haml", "app/views/page_templates/default.html.haml" if defined? Haml 29 | copy_file "page_templates/default.html.erb", "app/views/page_templates/default.html.erb" 30 | end 31 | 32 | def install_js 33 | filename = "app/assets/javascripts/application.js" 34 | existing = File.binread("#{filename}").include?("require thesis") 35 | 36 | if existing && generating? 37 | say_status("skipped", "insert into #{filename}", :yellow) 38 | else 39 | insert_into_file "#{filename}", after: %r{//= require +['"]?jquery_ujs['"]?} do 40 | "\n//= require jquery-ui" + 41 | "\n//= require thesis" 42 | end 43 | end 44 | end 45 | 46 | def install_css 47 | filename = "app/assets/stylesheets/application.css" 48 | filename = filename << ".scss" unless File.exists? filename 49 | if File.exists? filename 50 | existing = File.binread("#{filename}").include?("require thesis") 51 | 52 | if existing && generating? 53 | say_status("skipped", "insert into #{filename}", :yellow) 54 | else 55 | insert_into_file "#{filename}", after: %r{ *= require_self} do 56 | "\n *= require thesis" 57 | end 58 | end 59 | else 60 | say_status("skipped", "Couldn't insert into #{filename} -- doesn't exist") 61 | end 62 | end 63 | 64 | def install_page_is_editable 65 | filename = "app/controllers/application_controller.rb" 66 | existing = File.binread("#{filename}").include?("def page_is_editable?") 67 | 68 | if existing && generating? 69 | say_status("skipped", "insert into #{filename}", :yellow) 70 | else 71 | insert_into_file "#{filename}", after: %r{ protect_from_forgery with: :exception} do 72 | "\n" + 73 | "\n # Thesis authentication" + 74 | "\n def page_is_editable?(page)" + 75 | "\n # Add your own criteria here for editing privileges. Examples:" + 76 | "\n # current_user.admin? # Basic admin" + 77 | "\n # can? :update, page # CanCan" + 78 | "\n true # EVERYONE has access right now." + 79 | "\n end" 80 | end 81 | end 82 | end 83 | 84 | def complete_message 85 | require "thesis/colorizer" 86 | 87 | if generating? 88 | puts " " 89 | puts " Thesis installed.".green 90 | puts " Now run `rake db:migrate` to set up your database.".pink 91 | else 92 | puts " " 93 | puts " Thesis uninstalled.".red 94 | puts " You will need to remove the database tables manually if you've already run `rake db:migrate`.".pink 95 | end 96 | end 97 | 98 | protected 99 | 100 | def generating? 101 | :invoke == behavior 102 | end 103 | 104 | def destroying? 105 | :revoke == behavior 106 | end 107 | 108 | def copy_migration(filename) 109 | if generating? && self.class.migration_exists?("db/migrate", "#{filename}") 110 | say_status("skipped", "Migration #{filename}.rb already exists") 111 | else 112 | migration_template "migrations/#{filename}.rb", "db/migrate/#{filename}.rb" 113 | end 114 | end 115 | end 116 | end 117 | end 118 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/_thesis.sass: -------------------------------------------------------------------------------- 1 | body 2 | @include transition(all .3s ease-in-out) 3 | 4 | #thesis-editor 5 | display : block 6 | position : absolute 7 | right : 15px 8 | bottom : 15px 9 | z-index : 1040 10 | position : fixed 11 | z-index : 1040 12 | color : white 13 | line-height : 0 14 | font-size : 0 15 | text-decoration : none 16 | 17 | .thesis-button 18 | @include editor_icons(("cancel": $orange, "delete": $pink, "add": $blue, "save": $green, "settings": $yellow )) 19 | @include transition(opacity .75s ease-in-out, right .75s ease-in-out, transform .75s ease-in-out, padding .001s linear .75s, height .001s linear .75s) 20 | right : 0 21 | opacity : 0 22 | display : block 23 | float : right 24 | clear : both 25 | text-align : right 26 | position : relative 27 | cursor : pointer 28 | padding : 0px 0px 29 | height : 0px 30 | width : auto 31 | 32 | &.edit 33 | opacity : 1 34 | height : 70px 35 | padding-bottom : 0px !important 36 | 37 | &:hover 38 | min-width : 320px 39 | 40 | .tooltip 41 | position : relative 42 | right : 0px 43 | opacity : 1 44 | @include transition(all .2s ease-out) 45 | 46 | i 47 | position : relative 48 | z-index : 2 49 | width : 60px 50 | height : 60px 51 | display : inline-block 52 | background-color : $gray 53 | border : 3px solid white 54 | font-size : 30px 55 | line-height : 55px 56 | text-align : center 57 | vertical-align : middle 58 | @include transition(background-color .3s ease-in-out) 59 | 60 | &:hover 61 | background-color: darken($gray, 8%) 62 | @include transition(background-color .3s ease-in-out) 63 | 64 | .tooltip 65 | z-index : 1 66 | position : absolute 67 | right : -30px 68 | font-size : 13px 69 | color : white 70 | background : rgba(black, .70) 71 | padding : 5px 15px 4px 72 | line-height : 1 73 | opacity : 0 74 | text-transform : uppercase 75 | letter-spacing : 1px 76 | vertical-align : middle 77 | display : inline-block 78 | font-weight : normal 79 | margin-right : 15px 80 | border : 2px solid white 81 | 82 | &.error 83 | background : rgba(darken(red, 20%), .75) 84 | 85 | 86 | body.thesis-editing 87 | 88 | .thesis-content 89 | position : relative 90 | z-index : 1039 91 | padding : 5px 92 | min-width : 40px 93 | display : block 94 | outline : 2px solid lighten($yellow,15%) !important 95 | cursor : crosshair 96 | min-height : 1em 97 | 98 | &:before, &:after 99 | @include box-sizing(content-box) 100 | @include barber_pole(lighten($orange,15%), .5, 1.5s) 101 | position : absolute 102 | padding : 2px 103 | top : -2px 104 | left : -2px 105 | width : 100% 106 | height : 100% 107 | content : "" 108 | z-index : -2 109 | opacity : 0 110 | 111 | &:after 112 | @include transition(opacity .7s ease-in-out) 113 | padding : 0 114 | background : white 115 | z-index : -1 116 | top : 0 117 | left : 0 118 | opacity : .8 119 | 120 | &.isModified 121 | outline : 2px solid lighten($green,15%) !important 122 | 123 | &.inEditMode 124 | outline : none !important 125 | cursor : text 126 | 127 | &:before 128 | opacity: 1 129 | 130 | &:after 131 | @include transition(none) 132 | opacity: 1 133 | 134 | #thesis-editor 135 | .thesis-button 136 | &.edit 137 | i 138 | @include barber_pole 139 | 140 | &.active 141 | .thesis-button 142 | @include curve_buttons(6) 143 | @include transition(opacity .75s ease-in-out, right .75s ease-in-out, transform .75s ease-in-out) 144 | padding : 10px 0px 145 | opacity : 1 146 | height : 80px 147 | 148 | .hallotoolbar 149 | z-index: 1040 150 | 151 | .thesis-fader 152 | top : 0px 153 | left : 0px 154 | width : 100% 155 | height : 100% 156 | position : fixed 157 | background-color : rgba(29, 49, 58, .15) 158 | cursor : not-allowed 159 | z-index : 1038 160 | @include animation(thesis_fader .7s ease-in-out) 161 | 162 | #thesis-modal 163 | display : none 164 | position : fixed 165 | width : 600px 166 | height : auto 167 | background : white 168 | left : 50% 169 | top : 50% 170 | margin-left : -300px 171 | margin-top : -100px 172 | border-radius : 50px 173 | z-index : 1039 174 | padding : 25px 175 | border : 3px solid $blue 176 | box-shadow : 0px 0px 0px 4px white 177 | color : darken($gray, 20%) 178 | font-family : $raleway 179 | @include animation(modal_border 10s ease-in-out infinite) 180 | 181 | &.settings 182 | margin-top : -252px 183 | 184 | .modal-icon 185 | text-align : center 186 | font-size : 24px 187 | font-weight : 300 188 | color : $blue 189 | margin-bottom : 5px 190 | @include animation(modal_icon_color 10s ease-in-out infinite) 191 | 192 | .modal-title 193 | text-align : center 194 | font-weight : 300 195 | font-size : 28px 196 | margin-bottom : 15px 197 | 198 | &.windows 199 | .modal-field.cancel 200 | float: right 201 | 202 | .modal-field.save 203 | float: left 204 | 205 | body.thesis-blur #wrapper 206 | @include transition(all .3s ease-in-out) 207 | @include filter(blur(12px)) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Thesis Rails 2 | 3 | **NOTE: Thesis Rails has been deprecated. If you're interested in maintaining it, please email hello@infinite.red.** 4 | 5 | See also [Thesis Phoenix](https://github.com/infinitered/thesis-phoenix) for an Elixir take on Thesis. 6 | 7 | ### Thesis is a Rails CMS gem that integrates as seamlessly as possible into your current Rails website. 8 | 9 | Most Rails content management systems make you conform to their system from the start, 10 | making it difficult to just "drop in" the gem and make it work with your CMS. Thesis 11 | tries to be a drop-in CMS that doesn't hijack your development workflow and stays out 12 | of the way. 13 | 14 | Thesis is a research project of [Infinite Red](http://infinite.red), a web and mobile development company based in Portland, OR and San Francisco, CA. 15 | 16 | ### Requirements 17 | 18 | * Rails 4.0.x (or higher) 19 | * ActiveRecord on MySQL, PostgresQL, or SQLite3 20 | * Ruby 2.0.0 (or higher) 21 | * jQuery 22 | * File hosting or Amazon S3 (for image upload) 23 | 24 | ## Getting Started 25 | 26 | ### Installation 27 | 28 | In your Gemfile: 29 | 30 | gem 'thesis', '~> 0.1' 31 | 32 | Then run these from your command line: 33 | 34 | bundle install 35 | rails g thesis:install 36 | rake db:migrate 37 | 38 | This will install thesis and add the database tables. 39 | 40 | [API Reference](#API) 41 | 42 | ## Using the CMS 43 | 44 | ### Adding a Page 45 | 46 | TODO 47 | 48 | ### Editing a Page 49 | 50 | TODO 51 | 52 | ### Deleting a Page 53 | 54 | TODO 55 | 56 | ### Rearranging Pages 57 | 58 | TODO 59 | 60 | ## API 61 | 62 | ### Authentication 63 | 64 | **Thesis does not force you to use a particular user or authentication strategy.** 65 | 66 | Instead, it adds a method into your application_controller.rb file that 67 | allows you to hook up your own authentication logic. 68 | 69 | * If you return `false` from this method, nothing will show up client-side nor will the page be editable. 70 | * If you return `true` from this method, the Thesis editor will appear and the page will be editable. 71 | 72 | #### Thesis authentication examples 73 | 74 | ```ruby 75 | def page_is_editable?(page) 76 | logged_in? && current_user.admin? # Devise + admin boolean 77 | end 78 | 79 | def page_is_editable?(page) 80 | can? :update, page # CanCanCan 81 | end 82 | 83 | def page_is_editable?(page) 84 | true # Just let everyone edit everything 85 | end 86 | 87 | def page_is_editable?(page) 88 | if current_user.admin? 89 | true 90 | elsif page.parent == Thesis::Page.find_by(name: "Blog") 91 | true 92 | else 93 | false 94 | end 95 | end 96 | ``` 97 | 98 | ### Models 99 | 100 | Thesis creates two straightforward ActiveRecord tables: pages and page_contents. Since they're normal ActiveRecord models, you're free to use them in your code however you want. 101 | 102 | #### Thesis::Page 103 | 104 | t.integer :parent_id 105 | t.string :name 106 | t.string :slug 107 | t.string :title 108 | t.string :description 109 | t.integer :sort_order, default: 0, null: false 110 | t.string :template, default: "default", null: false 111 | t.timestamps 112 | 113 | #### Thesis::PageContent 114 | 115 | t.integer :page_id, null: false 116 | t.string :name, null: false 117 | t.text :content, default: "Edit This Content Area" 118 | t.string :content_type, default: :html 119 | t.timestamps 120 | 121 | ### Page Templates 122 | 123 | Thesis's installer will drop a `page_templates` folder into your `app/views` folder. 124 | This is where you put different styles of pages for use in the CMS. 125 | Thesis will install an ERB, [HAML](http://haml.info), or [Slim](http://slim-lang.com) version, depending on your configuration. 126 | 127 | ### Meta information 128 | 129 | Pages come with a few built-in fields for use in SEO meta tags. 130 | 131 | ```slim 132 | head 133 | title = current_page.title || "Default Title" 134 | meta content="#{current_page.description}" type="description" 135 | ``` 136 | 137 | ### Thesis Editor 138 | 139 | Place this right after your opening `body` tag to embed the Thesis editor. It will only show 140 | up if your `page_is_editable?` method returns `true`. 141 | 142 | ```slim 143 | body 144 | = thesis_editor 145 | ``` 146 | 147 | ### Primary Navigation 148 | 149 | Use `root_pages` to get a list of pages at the root level. You can use the 150 | page's `name` and `path` accessors in your links. 151 | 152 | ```slim 153 | nav 154 | ul 155 | li = link_to "Home", root_path # You can mix and match dynamic and static pages 156 | - root_pages.each do |p| 157 | li = link_to p.name, p.path 158 | li = link_to "Static Page", static_page_path 159 | ``` 160 | 161 | ### Page content 162 | 163 | Content areas are accessible from any page using the `content` method. This method 164 | takes two arguments: name and type. Type defaults to `:html`. The only other type 165 | is `:text` (for now) which is plain text, no HTML accepted. `:image` will be added soon. 166 | 167 | Referencing a content area in a page template will create one if it doesn't exist already, using the `default:` value. 168 | 169 | ```slim 170 | article 171 | = current_page.content("Main Content", :html, default: "

This is my default HTML content.

") 172 | = current_page.content("Splash Image", :image, default: "/assets/default-image.jpg") # coming soon 173 | aside 174 | = current_page.content("Sidebar Content", :html) 175 | footer 176 | p = current_page.content("Footer Content", :text, default: "Copyright Me") 177 | ``` 178 | 179 | ### Routing 180 | 181 | Thesis will automatically handle routes for pages you create with Thesis. Your 182 | routes will take precedence over Thesis-created pages, so if you create a page 183 | with Thesis called "About" and you already have a route for 184 | `get "about" => "something#else"` Thesis won't show the page. 185 | 186 | ## What Thesis Isn't 187 | 188 | You can't have it all. Thesis isn't the same as other -bloated- full-functioned 189 | content management systems out there. This is a list of what it's not now and 190 | not likely to be in the future. 191 | 192 | *We reserve the right to change our mind, however, especially with well planned and written 193 | pull requests to help prod us in the right direction. :-)* 194 | 195 | 1. A WordPress Replacement 196 | 2. A full featured CMS 197 | 3. A full featured WYSIWYG editor 198 | 4. An authentication or permission system 199 | 5. A gem that works well with Sinatra or non-ActiveRecord ORMs 200 | 6. Anything other than a basic editor for pages and page content 201 | 202 | ## Contributing 203 | 204 | 1. Fork it 205 | 2. Create your feature branch (`git checkout -b my-new-feature`) 206 | 3. Commit your changes (`git commit -am 'Add some feature'`) 207 | 4. Write tests for your new feature 208 | 5. Run `rake spec` in the root directory to ensure that all tests pass. 209 | 6. Push to the branch (`git push origin my-new-feature`) 210 | 7. Create new Pull Request 211 | 212 | ### Key Contributors 213 | 214 | * Jamon Holmgren [@jamonholmgren](https://twitter.com/jamonholmgren) 215 | * Daniel Berkompas [@dberkom](https://twitter.com/dberkom) 216 | * The Infinite Red team 217 | 218 | ## Premium Support 219 | 220 | [Thesis Rails](https://github.com/infinitered/thesis-rails), as an open source project, is free to use and always will be. [Infinite Red](https://infinite.red/) offers premium Thesis Rails support and general mobile app design/development services. Email us at [hello@infinite.red](mailto:hello@infinite.red) to get in touch with us for more details. 221 | -------------------------------------------------------------------------------- /app/assets/javascripts/thesis/thesis.coffee: -------------------------------------------------------------------------------- 1 | Thesis = 2 | setup: -> 3 | @edit_mode = false 4 | if this.requirements() && this.page_is_editable() 5 | this.draw_editor() 6 | this.set_up_bindings() 7 | 8 | requirements: -> 9 | if jQuery.ui 10 | true 11 | else 12 | alert "jQuery UI not included. Thesis will not work properly without it." 13 | false 14 | 15 | edit_mode: -> 16 | @edit_mode 17 | 18 | thesis: -> 19 | @thesis = $("#thesis-editor") 20 | 21 | # ATTRIBUTES 22 | page_is_editable: -> 23 | this.thesis().length > 0 24 | 25 | page_in_edit_mode: -> 26 | $("body").hasClass("thesis-editing") 27 | 28 | page_has_unsaved_content: -> 29 | $("body .thesis-content.isModified").length 30 | 31 | modal: (blur=false, type="settings")-> 32 | this.close_modal() 33 | $("body").addClass "thesis-blur" if blur 34 | $("body").append this["#{type}_modal_markup"]() 35 | $("#thesis-modal").fadeIn 300 36 | 37 | $("#thesis-modal").on "click", ".modal-field.cancel", (e)-> 38 | e.preventDefault() 39 | Thesis.close_modal() 40 | 41 | $("#thesis-modal").on "submit", "form", (e) -> 42 | alert "To Do: submit form" 43 | e.preventDefault() #temp 44 | return false #temp 45 | 46 | close_modal: -> 47 | if $("#thesis-modal").length 48 | $("body").removeClass "thesis-blur" 49 | $("#thesis-modal").remove() 50 | 51 | settings_modal_markup: -> 52 | is_win = navigator.appVersion.indexOf("Win")!=-1 53 | """ 54 |

55 | 56 | 57 |
58 | 59 | 60 | 61 | 62 | 63 | 64 |
65 |
66 | """ 67 | 68 | # ACTIONS 69 | set_up_bindings: -> 70 | t = this 71 | 72 | t.thesis.on "mouseenter", -> 73 | clearTimeout @hide_editor_delay 74 | $(this).addClass "active" 75 | .on "mouseleave", -> 76 | @hide_editor_delay = Utilities.delay 2000, ()=> 77 | $(this).removeClass "active" 78 | 79 | @edit_page_button.on "mouseenter", -> 80 | t.change_edit_tooltip_status() 81 | 82 | @edit_page_button.on "click", (e) -> 83 | e.preventDefault() 84 | t.edit_button_click_actions() 85 | 86 | edit_button_click_actions: -> 87 | if this.page_in_edit_mode() 88 | if this.page_has_unsaved_content() 89 | this.change_edit_tooltip_status("Save Page before Exiting", "error") 90 | else 91 | this.end_editing() 92 | else 93 | this.start_editing() 94 | 95 | change_edit_tooltip_status: (edit_text=null, classes=null) -> 96 | $tooltip = @edit_page_button.find(".tooltip") 97 | $tooltip.removeClass().addClass("tooltip") 98 | $tooltip.addClass "#{classes}" if classes 99 | unless edit_text 100 | if this.page_in_edit_mode() 101 | if this.page_has_unsaved_content() 102 | edit_text = "Editing Page" 103 | else 104 | edit_text = "Exit Edit Mode" 105 | else 106 | edit_text = "Edit Page" 107 | $tooltip.text edit_text 108 | 109 | start_editing: -> 110 | $("body").append($("
").addClass("thesis-fader")) 111 | $("body").addClass("thesis-editing") 112 | $(".thesis-content-html").hallo this.hallo_html_options() 113 | $(".thesis-content-text").hallo this.hallo_text_options() 114 | this.change_edit_tooltip_status() 115 | 116 | end_editing: -> 117 | $(".thesis-fader").remove() 118 | $("body").removeClass("thesis-editing") 119 | $(".thesis-content-html").hallo editable: false 120 | $(".thesis-content-text").hallo editable: false 121 | this.change_edit_tooltip_status() 122 | 123 | save_content: -> 124 | # Gather the content for posting 125 | payload = {} 126 | $(".thesis-content-html, .thesis-content-text").each ()-> 127 | content_area = $(this) 128 | content_id = content_area.data("thesis-content-id") 129 | payload[content_id] = content_area.html() 130 | 131 | $.ajax 132 | url: "/thesis/update_page_content" 133 | data: payload 134 | type: "put" 135 | dataType: "json" 136 | success: -> 137 | alert "Page saved." 138 | error: -> 139 | alert "Sorry, couldn't save this page." 140 | 141 | add_page: -> 142 | page_name = prompt "What is the name of the new page?" 143 | if page_name 144 | parent_slug = null 145 | if confirm("Make this a subpage of the current page?") 146 | parent_slug = window.location.pathname 147 | $.ajax 148 | url: "/thesis/create_page" 149 | data: 150 | name: page_name 151 | parent_slug: parent_slug 152 | type: "post" 153 | dataType: "json" 154 | success: (resp, status, xhr)-> 155 | if resp && resp.page 156 | window.location = resp.page.slug 157 | else 158 | alert "Unknown error" 159 | error: -> 160 | alert "Sorry, couldn't save this page." 161 | 162 | delete_page: -> 163 | really_sure = confirm "Are you sure you want to delete this page? There is no undo!" 164 | if really_sure 165 | $.ajax 166 | url: "/thesis/delete_page" 167 | data: 168 | slug: window.location.pathname 169 | type: "delete" 170 | # dataType: "json" 171 | success: -> 172 | alert "Page was deleted." 173 | error: -> 174 | alert "Sorry, couldn't delete this page." 175 | 176 | 177 | # OPTIONS 178 | hallo_text_options: -> 179 | options = 180 | editable: true 181 | 182 | hallo_html_options: -> 183 | options = 184 | editable: true 185 | plugins: 186 | 'halloformat': {} 187 | 'halloheadings': {} 188 | 'hallojustify': {} 189 | 'hallolists': {} 190 | 'halloreundo': {} 191 | 'hallolink': {} 192 | # 'halloimage': {} # Someday? 193 | 194 | 195 | # EDITOR 196 | draw_editor: -> 197 | @thesis.append(this.draw_add_icon()) 198 | @thesis.append(this.draw_delete_icon()) 199 | @thesis.append(this.draw_settings_icon()) 200 | @thesis.append(this.draw_cancel_icon()) 201 | @thesis.append(this.draw_save_icon()) 202 | @thesis.append(this.draw_edit_icon()) 203 | @edit_page_button = @thesis.find(".thesis-button.edit") 204 | 205 | draw_edit_icon: -> 206 | $icon = $("").addClass "fa fa-edit fa-2x" 207 | $tooltip = $("
").addClass("tooltip").text "Edit Page" 208 | $("
").addClass("thesis-button edit").append $tooltip, $icon 209 | 210 | draw_save_icon: -> 211 | $icon = $("").addClass "fa fa-save fa-2x" 212 | $tooltip = $("
").addClass("tooltip").text "Save Changes" 213 | $button = $("
").addClass("thesis-button save").append $tooltip, $icon 214 | $button.on "click", -> 215 | Thesis.save_content() 216 | Thesis.end_editing() 217 | 218 | draw_add_icon: -> 219 | $icon = $("").addClass "fa fa-plus fa-2x" 220 | $tooltip = $("
").addClass("tooltip").text "Add New Page" 221 | $button = $("
").addClass("thesis-button add").append $tooltip, $icon 222 | $button.on "click", -> 223 | Thesis.add_page() 224 | 225 | draw_settings_icon: -> 226 | $icon = $("").addClass "fa fa-wrench fa-2x" 227 | $tooltip = $("
").addClass("tooltip").text "Page Settings" 228 | $button = $("
").addClass("thesis-button settings").append $tooltip, $icon 229 | $button.on "click", -> 230 | Thesis.modal(true, "settings") 231 | 232 | draw_delete_icon: -> 233 | $icon = $("").addClass "fa fa-trash fa-2x" 234 | $tooltip = $("
").addClass("tooltip").text "Delete Page" 235 | $button = $("
").addClass("thesis-button delete").append $tooltip, $icon 236 | $button.on "click", -> 237 | Thesis.delete_page() 238 | 239 | draw_cancel_icon: -> 240 | $icon = $("").addClass "fa fa-remove fa-2x" 241 | $tooltip = $("
").addClass("tooltip").text "Discard Changes" 242 | $button = $("
").addClass("thesis-button cancel").append $tooltip, $icon 243 | $button.on "click", -> 244 | Thesis.end_editing() 245 | location.reload() 246 | 247 | jQuery ($)-> 248 | Thesis.setup() 249 | 250 | # window.Thesis = Thesis # Enable if necessary -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/css3/_flex-box.scss: -------------------------------------------------------------------------------- 1 | // CSS3 Flexible Box Model and property defaults 2 | 3 | // Custom shorthand notation for flexbox 4 | @mixin box($orient: inline-axis, $pack: start, $align: stretch) { 5 | @include display-box; 6 | @include box-orient($orient); 7 | @include box-pack($pack); 8 | @include box-align($align); 9 | } 10 | 11 | @mixin display-box { 12 | display: -webkit-box; 13 | display: -moz-box; 14 | display: -ms-flexbox; // IE 10 15 | display: box; 16 | } 17 | 18 | @mixin box-orient($orient: inline-axis) { 19 | // horizontal|vertical|inline-axis|block-axis|inherit 20 | @include prefixer(box-orient, $orient, webkit moz spec); 21 | } 22 | 23 | @mixin box-pack($pack: start) { 24 | // start|end|center|justify 25 | @include prefixer(box-pack, $pack, webkit moz spec); 26 | -ms-flex-pack: $pack; // IE 10 27 | } 28 | 29 | @mixin box-align($align: stretch) { 30 | // start|end|center|baseline|stretch 31 | @include prefixer(box-align, $align, webkit moz spec); 32 | -ms-flex-align: $align; // IE 10 33 | } 34 | 35 | @mixin box-direction($direction: normal) { 36 | // normal|reverse|inherit 37 | @include prefixer(box-direction, $direction, webkit moz spec); 38 | -ms-flex-direction: $direction; // IE 10 39 | } 40 | 41 | @mixin box-lines($lines: single) { 42 | // single|multiple 43 | @include prefixer(box-lines, $lines, webkit moz spec); 44 | } 45 | 46 | @mixin box-ordinal-group($int: 1) { 47 | @include prefixer(box-ordinal-group, $int, webkit moz spec); 48 | -ms-flex-order: $int; // IE 10 49 | } 50 | 51 | @mixin box-flex($value: 0.0) { 52 | @include prefixer(box-flex, $value, webkit moz spec); 53 | -ms-flex: $value; // IE 10 54 | } 55 | 56 | @mixin box-flex-group($int: 1) { 57 | @include prefixer(box-flex-group, $int, webkit moz spec); 58 | } 59 | 60 | // CSS3 Flexible Box Model and property defaults 61 | // Unified attributes for 2009, 2011, and 2012 flavours. 62 | 63 | // 2009 - display (box | inline-box) 64 | // 2011 - display (flexbox | inline-flexbox) 65 | // 2012 - display (flex | inline-flex) 66 | @mixin display($value) { 67 | // flex | inline-flex 68 | @if $value == "flex" { 69 | // 2009 70 | display: -webkit-box; 71 | display: -moz-box; 72 | display: box; 73 | 74 | // 2012 75 | display: -webkit-flex; 76 | display: -moz-flex; 77 | display: -ms-flexbox; // 2011 (IE 10) 78 | display: flex; 79 | } 80 | 81 | @elseif $value == "inline-flex" { 82 | display: -webkit-inline-box; 83 | display: -moz-inline-box; 84 | display: inline-box; 85 | 86 | display: -webkit-inline-flex; 87 | display: -moz-inline-flex; 88 | display: -ms-inline-flexbox; 89 | display: inline-flex; 90 | } 91 | 92 | @else { 93 | display: $value; 94 | } 95 | } 96 | 97 | // 2009 - box-flex (integer) 98 | // 2011 - flex (decimal | width decimal) 99 | // 2012 - flex (integer integer width) 100 | @mixin flex($value) { 101 | 102 | // Grab flex-grow for older browsers. 103 | $flex-grow: nth($value, 1); 104 | 105 | // 2009 106 | @include prefixer(box-flex, $flex-grow, webkit moz spec); 107 | 108 | // 2011 (IE 10), 2012 109 | @include prefixer(flex, $value, webkit moz ms spec); 110 | } 111 | 112 | // 2009 - box-orient ( horizontal | vertical | inline-axis | block-axis) 113 | // - box-direction (normal | reverse) 114 | // 2011 - flex-direction (row | row-reverse | column | column-reverse) 115 | // 2012 - flex-direction (row | row-reverse | column | column-reverse) 116 | @mixin flex-direction($value: row) { 117 | 118 | // Alt values. 119 | $value-2009: $value; 120 | $value-2011: $value; 121 | $direction: "normal"; 122 | 123 | @if $value == row { 124 | $value-2009: horizontal; 125 | } 126 | 127 | @elseif $value == "row-reverse" { 128 | $value-2009: horizontal; 129 | $direction: reverse; 130 | } 131 | 132 | @elseif $value == column { 133 | $value-2009: vertical; 134 | } 135 | 136 | @elseif $value == "column-reverse" { 137 | $value-2009: vertical; 138 | $direction: reverse; 139 | } 140 | 141 | // 2009 142 | @include prefixer(box-orient, $value-2009, webkit moz spec); 143 | @if $direction == "reverse" { 144 | @include prefixer(box-direction, $direction, webkit moz spec); 145 | } 146 | 147 | // 2012 148 | @include prefixer(flex-direction, $value, webkit moz spec); 149 | 150 | // 2011 (IE 10) 151 | -ms-flex-direction: $value; 152 | } 153 | 154 | // 2009 - box-lines (single | multiple) 155 | // 2011 - flex-wrap (nowrap | wrap | wrap-reverse) 156 | // 2012 - flex-wrap (nowrap | wrap | wrap-reverse) 157 | @mixin flex-wrap($value: nowrap) { 158 | 159 | // Alt values. 160 | $alt-value: $value; 161 | @if $value == nowrap { 162 | $alt-value: single; 163 | } 164 | 165 | @elseif $value == wrap { 166 | $alt-value: multiple; 167 | } 168 | 169 | @elseif $value == "wrap-reverse" { 170 | $alt-value: multiple; 171 | } 172 | 173 | @include prefixer(box-lines, $alt-value, webkit moz spec); 174 | @include prefixer(flex-wrap, $value, webkit moz ms spec); 175 | } 176 | 177 | // 2009 - TODO: parse values into flex-direction/flex-wrap 178 | // 2011 - TODO: parse values into flex-direction/flex-wrap 179 | // 2012 - flex-flow (flex-direction || flex-wrap) 180 | @mixin flex-flow($value) { 181 | @include prefixer(flex-flow, $value, webkit moz spec); 182 | } 183 | 184 | // 2009 - box-ordinal-group (integer) 185 | // 2011 - flex-order (integer) 186 | // 2012 - order (integer) 187 | @mixin order($int: 0) { 188 | // 2009 189 | @include prefixer(box-ordinal-group, $int, webkit moz spec); 190 | 191 | // 2012 192 | @include prefixer(order, $int, webkit moz spec); 193 | 194 | // 2011 (IE 10) 195 | -ms-flex-order: $int; 196 | } 197 | 198 | // 2012 - flex-grow (number) 199 | @mixin flex-grow($number: 0) { 200 | @include prefixer(flex-grow, $number, webkit moz spec); 201 | -ms-flex-positive: $number; 202 | } 203 | 204 | // 2012 - flex-shrink (number) 205 | @mixin flex-shrink($number: 1) { 206 | @include prefixer(flex-shrink, $number, webkit moz spec); 207 | -ms-flex-negative: $number; 208 | } 209 | 210 | // 2012 - flex-basis (number) 211 | @mixin flex-basis($width: auto) { 212 | @include prefixer(flex-basis, $width, webkit moz spec); 213 | -ms-flex-preferred-size: $width; 214 | } 215 | 216 | // 2009 - box-pack (start | end | center | justify) 217 | // 2011 - flex-pack (start | end | center | justify) 218 | // 2012 - justify-content (flex-start | flex-end | center | space-between | space-around) 219 | @mixin justify-content ($value: flex-start) { 220 | 221 | // Alt values. 222 | $alt-value: $value; 223 | @if $value == "flex-start" { 224 | $alt-value: start; 225 | } 226 | 227 | @elseif $value == "flex-end" { 228 | $alt-value: end; 229 | } 230 | 231 | @elseif $value == "space-between" { 232 | $alt-value: justify; 233 | } 234 | 235 | @elseif $value == "space-around" { 236 | $alt-value: center; 237 | } 238 | 239 | // 2009 240 | @include prefixer(box-pack, $alt-value, webkit moz spec); 241 | 242 | // 2012 243 | @include prefixer(justify-content, $value, webkit moz ms o spec); 244 | 245 | // 2011 (IE 10) 246 | -ms-flex-pack: $alt-value; 247 | } 248 | 249 | // 2009 - box-align (start | end | center | baseline | stretch) 250 | // 2011 - flex-align (start | end | center | baseline | stretch) 251 | // 2012 - align-items (flex-start | flex-end | center | baseline | stretch) 252 | @mixin align-items($value: stretch) { 253 | 254 | $alt-value: $value; 255 | 256 | @if $value == "flex-start" { 257 | $alt-value: start; 258 | } 259 | 260 | @elseif $value == "flex-end" { 261 | $alt-value: end; 262 | } 263 | 264 | // 2009 265 | @include prefixer(box-align, $alt-value, webkit moz spec); 266 | 267 | // 2012 268 | @include prefixer(align-items, $value, webkit moz ms o spec); 269 | 270 | // 2011 (IE 10) 271 | -ms-flex-align: $alt-value; 272 | } 273 | 274 | // 2011 - flex-item-align (auto | start | end | center | baseline | stretch) 275 | // 2012 - align-self (auto | flex-start | flex-end | center | baseline | stretch) 276 | @mixin align-self($value: auto) { 277 | 278 | $value-2011: $value; 279 | @if $value == "flex-start" { 280 | $value-2011: start; 281 | } 282 | 283 | @elseif $value == "flex-end" { 284 | $value-2011: end; 285 | } 286 | 287 | // 2012 288 | @include prefixer(align-self, $value, webkit moz spec); 289 | 290 | // 2011 (IE 10) 291 | -ms-flex-item-align: $value-2011; 292 | } 293 | 294 | // 2011 - flex-line-pack (start | end | center | justify | distribute | stretch) 295 | // 2012 - align-content (flex-start | flex-end | center | space-between | space-around | stretch) 296 | @mixin align-content($value: stretch) { 297 | 298 | $value-2011: $value; 299 | @if $value == "flex-start" { 300 | $value-2011: start; 301 | } 302 | 303 | @elseif $value == "flex-end" { 304 | $value-2011: end; 305 | } 306 | 307 | @elseif $value == "space-between" { 308 | $value-2011: justify; 309 | } 310 | 311 | @elseif $value == "space-around" { 312 | $value-2011: distribute; 313 | } 314 | 315 | // 2012 316 | @include prefixer(align-content, $value, webkit moz spec); 317 | 318 | // 2011 (IE 10) 319 | -ms-flex-line-pack: $value-2011; 320 | } 321 | 322 | -------------------------------------------------------------------------------- /app/assets/stylesheets/thesis/base/bourbon/addons/_button.scss: -------------------------------------------------------------------------------- 1 | @mixin button ($style: simple, $base-color: #4294f0, $text-size: inherit, $padding: 7px 18px) { 2 | 3 | @if type-of($style) == string and type-of($base-color) == color { 4 | @include buttonstyle($style, $base-color, $text-size, $padding); 5 | } 6 | 7 | @if type-of($style) == string and type-of($base-color) == number { 8 | $padding: $text-size; 9 | $text-size: $base-color; 10 | $base-color: #4294f0; 11 | 12 | @if $padding == inherit { 13 | $padding: 7px 18px; 14 | } 15 | 16 | @include buttonstyle($style, $base-color, $text-size, $padding); 17 | } 18 | 19 | @if type-of($style) == color and type-of($base-color) == color { 20 | $base-color: $style; 21 | $style: simple; 22 | @include buttonstyle($style, $base-color, $text-size, $padding); 23 | } 24 | 25 | @if type-of($style) == color and type-of($base-color) == number { 26 | $padding: $text-size; 27 | $text-size: $base-color; 28 | $base-color: $style; 29 | $style: simple; 30 | 31 | @if $padding == inherit { 32 | $padding: 7px 18px; 33 | } 34 | 35 | @include buttonstyle($style, $base-color, $text-size, $padding); 36 | } 37 | 38 | @if type-of($style) == number { 39 | $padding: $base-color; 40 | $text-size: $style; 41 | $base-color: #4294f0; 42 | $style: simple; 43 | 44 | @if $padding == #4294f0 { 45 | $padding: 7px 18px; 46 | } 47 | 48 | @include buttonstyle($style, $base-color, $text-size, $padding); 49 | } 50 | 51 | &:disabled { 52 | opacity: 0.5; 53 | cursor: not-allowed; 54 | } 55 | } 56 | 57 | 58 | // Selector Style Button 59 | //************************************************************************// 60 | @mixin buttonstyle($type, $b-color, $t-size, $pad) { 61 | // Grayscale button 62 | @if $type == simple and $b-color == grayscale($b-color) { 63 | @include simple($b-color, true, $t-size, $pad); 64 | } 65 | 66 | @if $type == shiny and $b-color == grayscale($b-color) { 67 | @include shiny($b-color, true, $t-size, $pad); 68 | } 69 | 70 | @if $type == pill and $b-color == grayscale($b-color) { 71 | @include pill($b-color, true, $t-size, $pad); 72 | } 73 | 74 | @if $type == flat and $b-color == grayscale($b-color) { 75 | @include flat($b-color, true, $t-size, $pad); 76 | } 77 | 78 | // Colored button 79 | @if $type == simple { 80 | @include simple($b-color, false, $t-size, $pad); 81 | } 82 | 83 | @else if $type == shiny { 84 | @include shiny($b-color, false, $t-size, $pad); 85 | } 86 | 87 | @else if $type == pill { 88 | @include pill($b-color, false, $t-size, $pad); 89 | } 90 | 91 | @else if $type == flat { 92 | @include flat($b-color, false, $t-size, $pad); 93 | } 94 | } 95 | 96 | 97 | // Simple Button 98 | //************************************************************************// 99 | @mixin simple($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) { 100 | $color: hsl(0, 0, 100%); 101 | $border: adjust-color($base-color, $saturation: 9%, $lightness: -14%); 102 | $inset-shadow: adjust-color($base-color, $saturation: -8%, $lightness: 15%); 103 | $stop-gradient: adjust-color($base-color, $saturation: 9%, $lightness: -11%); 104 | $text-shadow: adjust-color($base-color, $saturation: 15%, $lightness: -18%); 105 | 106 | @if is-light($base-color) { 107 | $color: hsl(0, 0, 20%); 108 | $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%); 109 | } 110 | 111 | @if $grayscale == true { 112 | $border: grayscale($border); 113 | $inset-shadow: grayscale($inset-shadow); 114 | $stop-gradient: grayscale($stop-gradient); 115 | $text-shadow: grayscale($text-shadow); 116 | } 117 | 118 | border: 1px solid $border; 119 | border-radius: 3px; 120 | box-shadow: inset 0 1px 0 0 $inset-shadow; 121 | color: $color; 122 | display: inline-block; 123 | font-size: $textsize; 124 | font-weight: bold; 125 | @include linear-gradient ($base-color, $stop-gradient); 126 | padding: $padding; 127 | text-decoration: none; 128 | text-shadow: 0 1px 0 $text-shadow; 129 | background-clip: padding-box; 130 | 131 | &:hover:not(:disabled) { 132 | $base-color-hover: adjust-color($base-color, $saturation: -4%, $lightness: -5%); 133 | $inset-shadow-hover: adjust-color($base-color, $saturation: -7%, $lightness: 5%); 134 | $stop-gradient-hover: adjust-color($base-color, $saturation: 8%, $lightness: -14%); 135 | 136 | @if $grayscale == true { 137 | $base-color-hover: grayscale($base-color-hover); 138 | $inset-shadow-hover: grayscale($inset-shadow-hover); 139 | $stop-gradient-hover: grayscale($stop-gradient-hover); 140 | } 141 | 142 | box-shadow: inset 0 1px 0 0 $inset-shadow-hover; 143 | cursor: pointer; 144 | @include linear-gradient ($base-color-hover, $stop-gradient-hover); 145 | } 146 | 147 | &:active:not(:disabled), 148 | &:focus:not(:disabled) { 149 | $border-active: adjust-color($base-color, $saturation: 9%, $lightness: -14%); 150 | $inset-shadow-active: adjust-color($base-color, $saturation: 7%, $lightness: -17%); 151 | 152 | @if $grayscale == true { 153 | $border-active: grayscale($border-active); 154 | $inset-shadow-active: grayscale($inset-shadow-active); 155 | } 156 | 157 | border: 1px solid $border-active; 158 | box-shadow: inset 0 0 8px 4px $inset-shadow-active, inset 0 0 8px 4px $inset-shadow-active; 159 | } 160 | } 161 | 162 | 163 | // Shiny Button 164 | //************************************************************************// 165 | @mixin shiny($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) { 166 | $color: hsl(0, 0, 100%); 167 | $border: adjust-color($base-color, $red: -117, $green: -111, $blue: -81); 168 | $border-bottom: adjust-color($base-color, $red: -126, $green: -127, $blue: -122); 169 | $fourth-stop: adjust-color($base-color, $red: -79, $green: -70, $blue: -46); 170 | $inset-shadow: adjust-color($base-color, $red: 37, $green: 29, $blue: 12); 171 | $second-stop: adjust-color($base-color, $red: -56, $green: -50, $blue: -33); 172 | $text-shadow: adjust-color($base-color, $red: -140, $green: -141, $blue: -114); 173 | $third-stop: adjust-color($base-color, $red: -86, $green: -75, $blue: -48); 174 | 175 | @if is-light($base-color) { 176 | $color: hsl(0, 0, 20%); 177 | $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%); 178 | } 179 | 180 | @if $grayscale == true { 181 | $border: grayscale($border); 182 | $border-bottom: grayscale($border-bottom); 183 | $fourth-stop: grayscale($fourth-stop); 184 | $inset-shadow: grayscale($inset-shadow); 185 | $second-stop: grayscale($second-stop); 186 | $text-shadow: grayscale($text-shadow); 187 | $third-stop: grayscale($third-stop); 188 | } 189 | 190 | border: 1px solid $border; 191 | border-bottom: 1px solid $border-bottom; 192 | border-radius: 5px; 193 | box-shadow: inset 0 1px 0 0 $inset-shadow; 194 | color: $color; 195 | display: inline-block; 196 | font-size: $textsize; 197 | font-weight: bold; 198 | @include linear-gradient(top, $base-color 0%, $second-stop 50%, $third-stop 50%, $fourth-stop 100%); 199 | padding: $padding; 200 | text-align: center; 201 | text-decoration: none; 202 | text-shadow: 0 -1px 1px $text-shadow; 203 | 204 | &:hover:not(:disabled) { 205 | $first-stop-hover: adjust-color($base-color, $red: -13, $green: -15, $blue: -18); 206 | $second-stop-hover: adjust-color($base-color, $red: -66, $green: -62, $blue: -51); 207 | $third-stop-hover: adjust-color($base-color, $red: -93, $green: -85, $blue: -66); 208 | $fourth-stop-hover: adjust-color($base-color, $red: -86, $green: -80, $blue: -63); 209 | 210 | @if $grayscale == true { 211 | $first-stop-hover: grayscale($first-stop-hover); 212 | $second-stop-hover: grayscale($second-stop-hover); 213 | $third-stop-hover: grayscale($third-stop-hover); 214 | $fourth-stop-hover: grayscale($fourth-stop-hover); 215 | } 216 | 217 | cursor: pointer; 218 | @include linear-gradient(top, $first-stop-hover 0%, 219 | $second-stop-hover 50%, 220 | $third-stop-hover 50%, 221 | $fourth-stop-hover 100%); 222 | } 223 | 224 | &:active:not(:disabled), 225 | &:focus:not(:disabled) { 226 | $inset-shadow-active: adjust-color($base-color, $red: -111, $green: -116, $blue: -122); 227 | 228 | @if $grayscale == true { 229 | $inset-shadow-active: grayscale($inset-shadow-active); 230 | } 231 | 232 | box-shadow: inset 0 0 20px 0 $inset-shadow-active; 233 | } 234 | } 235 | 236 | 237 | // Pill Button 238 | //************************************************************************// 239 | @mixin pill($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) { 240 | $color: hsl(0, 0, 100%); 241 | $border-bottom: adjust-color($base-color, $hue: 8, $saturation: -11%, $lightness: -26%); 242 | $border-sides: adjust-color($base-color, $hue: 4, $saturation: -21%, $lightness: -21%); 243 | $border-top: adjust-color($base-color, $hue: -1, $saturation: -30%, $lightness: -15%); 244 | $inset-shadow: adjust-color($base-color, $hue: -1, $saturation: -1%, $lightness: 7%); 245 | $stop-gradient: adjust-color($base-color, $hue: 8, $saturation: 14%, $lightness: -10%); 246 | $text-shadow: adjust-color($base-color, $hue: 5, $saturation: -19%, $lightness: -15%); 247 | 248 | @if is-light($base-color) { 249 | $color: hsl(0, 0, 20%); 250 | $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%); 251 | } 252 | 253 | @if $grayscale == true { 254 | $border-bottom: grayscale($border-bottom); 255 | $border-sides: grayscale($border-sides); 256 | $border-top: grayscale($border-top); 257 | $inset-shadow: grayscale($inset-shadow); 258 | $stop-gradient: grayscale($stop-gradient); 259 | $text-shadow: grayscale($text-shadow); 260 | } 261 | 262 | border: 1px solid $border-top; 263 | border-color: $border-top $border-sides $border-bottom; 264 | border-radius: 16px; 265 | box-shadow: inset 0 1px 0 0 $inset-shadow; 266 | color: $color; 267 | display: inline-block; 268 | font-size: $textsize; 269 | font-weight: normal; 270 | line-height: 1; 271 | @include linear-gradient ($base-color, $stop-gradient); 272 | padding: $padding; 273 | text-align: center; 274 | text-decoration: none; 275 | text-shadow: 0 -1px 1px $text-shadow; 276 | background-clip: padding-box; 277 | 278 | &:hover:not(:disabled) { 279 | $base-color-hover: adjust-color($base-color, $lightness: -4.5%); 280 | $border-bottom: adjust-color($base-color, $hue: 8, $saturation: 13.5%, $lightness: -32%); 281 | $border-sides: adjust-color($base-color, $hue: 4, $saturation: -2%, $lightness: -27%); 282 | $border-top: adjust-color($base-color, $hue: -1, $saturation: -17%, $lightness: -21%); 283 | $inset-shadow-hover: adjust-color($base-color, $saturation: -1%, $lightness: 3%); 284 | $stop-gradient-hover: adjust-color($base-color, $hue: 8, $saturation: -4%, $lightness: -15.5%); 285 | $text-shadow-hover: adjust-color($base-color, $hue: 5, $saturation: -5%, $lightness: -22%); 286 | 287 | @if $grayscale == true { 288 | $base-color-hover: grayscale($base-color-hover); 289 | $border-bottom: grayscale($border-bottom); 290 | $border-sides: grayscale($border-sides); 291 | $border-top: grayscale($border-top); 292 | $inset-shadow-hover: grayscale($inset-shadow-hover); 293 | $stop-gradient-hover: grayscale($stop-gradient-hover); 294 | $text-shadow-hover: grayscale($text-shadow-hover); 295 | } 296 | 297 | border: 1px solid $border-top; 298 | border-color: $border-top $border-sides $border-bottom; 299 | box-shadow: inset 0 1px 0 0 $inset-shadow-hover; 300 | cursor: pointer; 301 | @include linear-gradient ($base-color-hover, $stop-gradient-hover); 302 | text-shadow: 0 -1px 1px $text-shadow-hover; 303 | background-clip: padding-box; 304 | } 305 | 306 | &:active:not(:disabled), 307 | &:focus:not(:disabled) { 308 | $active-color: adjust-color($base-color, $hue: 4, $saturation: -12%, $lightness: -10%); 309 | $border-active: adjust-color($base-color, $hue: 6, $saturation: -2.5%, $lightness: -30%); 310 | $border-bottom-active: adjust-color($base-color, $hue: 11, $saturation: 6%, $lightness: -31%); 311 | $inset-shadow-active: adjust-color($base-color, $hue: 9, $saturation: 2%, $lightness: -21.5%); 312 | $text-shadow-active: adjust-color($base-color, $hue: 5, $saturation: -12%, $lightness: -21.5%); 313 | 314 | @if $grayscale == true { 315 | $active-color: grayscale($active-color); 316 | $border-active: grayscale($border-active); 317 | $border-bottom-active: grayscale($border-bottom-active); 318 | $inset-shadow-active: grayscale($inset-shadow-active); 319 | $text-shadow-active: grayscale($text-shadow-active); 320 | } 321 | 322 | background: $active-color; 323 | border: 1px solid $border-active; 324 | border-bottom: 1px solid $border-bottom-active; 325 | box-shadow: inset 0 0 6px 3px $inset-shadow-active; 326 | text-shadow: 0 -1px 1px $text-shadow-active; 327 | } 328 | } 329 | 330 | 331 | 332 | // Flat Button 333 | //************************************************************************// 334 | @mixin flat($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) { 335 | $color: hsl(0, 0, 100%); 336 | 337 | @if is-light($base-color) { 338 | $color: hsl(0, 0, 20%); 339 | } 340 | 341 | background-color: $base-color; 342 | border-radius: 3px; 343 | border: none; 344 | color: $color; 345 | display: inline-block; 346 | font-size: inherit; 347 | font-weight: bold; 348 | padding: 7px 18px; 349 | text-decoration: none; 350 | background-clip: padding-box; 351 | 352 | &:hover:not(:disabled){ 353 | $base-color-hover: adjust-color($base-color, $saturation: 4%, $lightness: 5%); 354 | 355 | @if $grayscale == true { 356 | $base-color-hover: grayscale($base-color-hover); 357 | } 358 | 359 | background-color: $base-color-hover; 360 | cursor: pointer; 361 | } 362 | 363 | &:active:not(:disabled), 364 | &:focus:not(:disabled) { 365 | $base-color-active: adjust-color($base-color, $saturation: -4%, $lightness: -5%); 366 | 367 | @if $grayscale == true { 368 | $base-color-active: grayscale($base-color-active); 369 | } 370 | 371 | background-color: $base-color-active; 372 | cursor: pointer; 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /app/assets/javascripts/thesis/rangy-core.js: -------------------------------------------------------------------------------- 1 | /* 2 | Rangy, a cross-browser JavaScript range and selection library 3 | http://code.google.com/p/rangy/ 4 | 5 | Copyright 2012, Tim Down 6 | Licensed under the MIT license. 7 | Version: 1.2.3 8 | Build date: 26 February 2012 9 | */ 10 | window.rangy=function(){function l(p,u){var w=typeof p[u];return w=="function"||!!(w=="object"&&p[u])||w=="unknown"}function K(p,u){return!!(typeof p[u]=="object"&&p[u])}function H(p,u){return typeof p[u]!="undefined"}function I(p){return function(u,w){for(var B=w.length;B--;)if(!p(u,w[B]))return false;return true}}function z(p){return p&&A(p,x)&&v(p,t)}function C(p){window.alert("Rangy not supported in your browser. Reason: "+p);c.initialized=true;c.supported=false}function N(){if(!c.initialized){var p, 11 | u=false,w=false;if(l(document,"createRange")){p=document.createRange();if(A(p,n)&&v(p,i))u=true;p.detach()}if((p=K(document,"body")?document.body:document.getElementsByTagName("body")[0])&&l(p,"createTextRange")){p=p.createTextRange();if(z(p))w=true}!u&&!w&&C("Neither Range nor TextRange are implemented");c.initialized=true;c.features={implementsDomRange:u,implementsTextRange:w};u=k.concat(f);w=0;for(p=u.length;w["+c.childNodes.length+"]":c.nodeName}function n(c){this._next=this.root=c}function t(c,f){this.node=c;this.offset=f}function x(c){this.code=this[c]; 20 | this.codeName=c;this.message="DOMException: "+this.codeName}var A=l.util;A.areHostMethods(document,["createDocumentFragment","createElement","createTextNode"])||K.fail("document missing a Node creation method");A.isHostMethod(document,"getElementsByTagName")||K.fail("document missing getElementsByTagName method");var q=document.createElement("div");A.areHostMethods(q,["insertBefore","appendChild","cloneNode"])||K.fail("Incomplete Element implementation");A.isHostProperty(q,"innerHTML")||K.fail("Element is missing innerHTML property"); 21 | q=document.createTextNode("test");A.areHostMethods(q,["splitText","deleteData","insertData","appendData","cloneNode"])||K.fail("Incomplete Text Node implementation");var v=function(c,f){for(var k=c.length;k--;)if(c[k]===f)return true;return false};n.prototype={_current:null,hasNext:function(){return!!this._next},next:function(){var c=this._current=this._next,f;if(this._current)if(f=c.firstChild)this._next=f;else{for(f=null;c!==this.root&&!(f=c.nextSibling);)c=c.parentNode;this._next=f}return this._current}, 22 | detach:function(){this._current=this._next=this.root=null}};t.prototype={equals:function(c){return this.node===c.node&this.offset==c.offset},inspect:function(){return"[DomPosition("+i(this.node)+":"+this.offset+")]"}};x.prototype={INDEX_SIZE_ERR:1,HIERARCHY_REQUEST_ERR:3,WRONG_DOCUMENT_ERR:4,NO_MODIFICATION_ALLOWED_ERR:7,NOT_FOUND_ERR:8,NOT_SUPPORTED_ERR:9,INVALID_STATE_ERR:11};x.prototype.toString=function(){return this.message};l.dom={arrayContains:v,isHtmlNamespace:function(c){var f;return typeof c.namespaceURI== 23 | "undefined"||(f=c.namespaceURI)===null||f=="http://www.w3.org/1999/xhtml"},parentElement:function(c){c=c.parentNode;return c.nodeType==1?c:null},getNodeIndex:H,getNodeLength:function(c){var f;return C(c)?c.length:(f=c.childNodes)?f.length:0},getCommonAncestor:I,isAncestorOf:function(c,f,k){for(f=k?f:f.parentNode;f;)if(f===c)return true;else f=f.parentNode;return false},getClosestAncestorIn:z,isCharacterDataNode:C,insertAfter:N,splitDataNode:function(c,f){var k=c.cloneNode(false);k.deleteData(0,f); 24 | c.deleteData(f,c.length-f);N(k,c);return k},getDocument:O,getWindow:function(c){c=O(c);if(typeof c.defaultView!="undefined")return c.defaultView;else if(typeof c.parentWindow!="undefined")return c.parentWindow;else throw Error("Cannot get a window object for node");},getIframeWindow:function(c){if(typeof c.contentWindow!="undefined")return c.contentWindow;else if(typeof c.contentDocument!="undefined")return c.contentDocument.defaultView;else throw Error("getIframeWindow: No Window object found for iframe element"); 25 | },getIframeDocument:function(c){if(typeof c.contentDocument!="undefined")return c.contentDocument;else if(typeof c.contentWindow!="undefined")return c.contentWindow.document;else throw Error("getIframeWindow: No Document object found for iframe element");},getBody:function(c){return A.isHostObject(c,"body")?c.body:c.getElementsByTagName("body")[0]},getRootContainer:function(c){for(var f;f=c.parentNode;)c=f;return c},comparePoints:function(c,f,k,r){var L;if(c==k)return f===r?0:f=e.childNodes.length?e.appendChild(a):e.insertBefore(a,e.childNodes[j]);return o}function O(a){for(var e,j,o=H(a.range).createDocumentFragment();j=a.next();){e=a.isPartiallySelectedSubtree();j=j.cloneNode(!e);if(e){e=a.getSubtreeIterator();j.appendChild(O(e));e.detach(true)}if(j.nodeType==10)throw new S("HIERARCHY_REQUEST_ERR");o.appendChild(j)}return o}function i(a,e,j){var o,E;for(j=j||{stop:false};o=a.next();)if(a.isPartiallySelectedSubtree())if(e(o)=== 30 | false){j.stop=true;return}else{o=a.getSubtreeIterator();i(o,e,j);o.detach(true);if(j.stop)return}else for(o=g.createIterator(o);E=o.next();)if(e(E)===false){j.stop=true;return}}function n(a){for(var e;a.next();)if(a.isPartiallySelectedSubtree()){e=a.getSubtreeIterator();n(e);e.detach(true)}else a.remove()}function t(a){for(var e,j=H(a.range).createDocumentFragment(),o;e=a.next();){if(a.isPartiallySelectedSubtree()){e=e.cloneNode(false);o=a.getSubtreeIterator();e.appendChild(t(o));o.detach(true)}else a.remove(); 31 | if(e.nodeType==10)throw new S("HIERARCHY_REQUEST_ERR");j.appendChild(e)}return j}function x(a,e,j){var o=!!(e&&e.length),E,T=!!j;if(o)E=RegExp("^("+e.join("|")+")$");var m=[];i(new q(a,false),function(s){if((!o||E.test(s.nodeType))&&(!T||j(s)))m.push(s)});return m}function A(a){return"["+(typeof a.getName=="undefined"?"Range":a.getName())+"("+g.inspectNode(a.startContainer)+":"+a.startOffset+", "+g.inspectNode(a.endContainer)+":"+a.endOffset+")]"}function q(a,e){this.range=a;this.clonePartiallySelectedTextNodes= 32 | e;if(!a.collapsed){this.sc=a.startContainer;this.so=a.startOffset;this.ec=a.endContainer;this.eo=a.endOffset;var j=a.commonAncestorContainer;if(this.sc===this.ec&&g.isCharacterDataNode(this.sc)){this.isSingleCharacterDataNode=true;this._first=this._last=this._next=this.sc}else{this._first=this._next=this.sc===j&&!g.isCharacterDataNode(this.sc)?this.sc.childNodes[this.so]:g.getClosestAncestorIn(this.sc,j,true);this._last=this.ec===j&&!g.isCharacterDataNode(this.ec)?this.ec.childNodes[this.eo-1]:g.getClosestAncestorIn(this.ec, 33 | j,true)}}}function v(a){this.code=this[a];this.codeName=a;this.message="RangeException: "+this.codeName}function c(a,e,j){this.nodes=x(a,e,j);this._next=this.nodes[0];this._position=0}function f(a){return function(e,j){for(var o,E=j?e:e.parentNode;E;){o=E.nodeType;if(g.arrayContains(a,o))return E;E=E.parentNode}return null}}function k(a,e){if(G(a,e))throw new v("INVALID_NODE_TYPE_ERR");}function r(a){if(!a.startContainer)throw new S("INVALID_STATE_ERR");}function L(a,e){if(!g.arrayContains(e,a.nodeType))throw new v("INVALID_NODE_TYPE_ERR"); 34 | }function p(a,e){if(e<0||e>(g.isCharacterDataNode(a)?a.length:a.childNodes.length))throw new S("INDEX_SIZE_ERR");}function u(a,e){if(h(a,true)!==h(e,true))throw new S("WRONG_DOCUMENT_ERR");}function w(a){if(D(a,true))throw new S("NO_MODIFICATION_ALLOWED_ERR");}function B(a,e){if(!a)throw new S(e);}function V(a){return!!a.startContainer&&!!a.endContainer&&!(!g.arrayContains(ba,a.startContainer.nodeType)&&!h(a.startContainer,true))&&!(!g.arrayContains(ba,a.endContainer.nodeType)&&!h(a.endContainer, 35 | true))&&a.startOffset<=(g.isCharacterDataNode(a.startContainer)?a.startContainer.length:a.startContainer.childNodes.length)&&a.endOffset<=(g.isCharacterDataNode(a.endContainer)?a.endContainer.length:a.endContainer.childNodes.length)}function J(a){r(a);if(!V(a))throw Error("Range error: Range is no longer valid after DOM mutation ("+a.inspect()+")");}function ca(){}function Y(a){a.START_TO_START=ia;a.START_TO_END=la;a.END_TO_END=ra;a.END_TO_START=ma;a.NODE_BEFORE=na;a.NODE_AFTER=oa;a.NODE_BEFORE_AND_AFTER= 36 | pa;a.NODE_INSIDE=ja}function W(a){Y(a);Y(a.prototype)}function da(a,e){return function(){J(this);var j=this.startContainer,o=this.startOffset,E=this.commonAncestorContainer,T=new q(this,true);if(j!==E){j=g.getClosestAncestorIn(j,E,true);o=C(j);j=o.node;o=o.offset}i(T,w);T.reset();E=a(T);T.detach();e(this,j,o,j,o);return E}}function fa(a,e,j){function o(m,s){return function(y){r(this);L(y,$);L(d(y),ba);y=(m?z:C)(y);(s?E:T)(this,y.node,y.offset)}}function E(m,s,y){var F=m.endContainer,Q=m.endOffset; 37 | if(s!==m.startContainer||y!==m.startOffset){if(d(s)!=d(F)||g.comparePoints(s,y,F,Q)==1){F=s;Q=y}e(m,s,y,F,Q)}}function T(m,s,y){var F=m.startContainer,Q=m.startOffset;if(s!==m.endContainer||y!==m.endOffset){if(d(s)!=d(F)||g.comparePoints(s,y,F,Q)==-1){F=s;Q=y}e(m,F,Q,s,y)}}a.prototype=new ca;l.util.extend(a.prototype,{setStart:function(m,s){r(this);k(m,true);p(m,s);E(this,m,s)},setEnd:function(m,s){r(this);k(m,true);p(m,s);T(this,m,s)},setStartBefore:o(true,true),setStartAfter:o(false,true),setEndBefore:o(true, 38 | false),setEndAfter:o(false,false),collapse:function(m){J(this);m?e(this,this.startContainer,this.startOffset,this.startContainer,this.startOffset):e(this,this.endContainer,this.endOffset,this.endContainer,this.endOffset)},selectNodeContents:function(m){r(this);k(m,true);e(this,m,0,m,g.getNodeLength(m))},selectNode:function(m){r(this);k(m,false);L(m,$);var s=z(m);m=C(m);e(this,s.node,s.offset,m.node,m.offset)},extractContents:da(t,e),deleteContents:da(n,e),canSurroundContents:function(){J(this);w(this.startContainer); 39 | w(this.endContainer);var m=new q(this,true),s=m._first&&K(m._first,this)||m._last&&K(m._last,this);m.detach();return!s},detach:function(){j(this)},splitBoundaries:function(){J(this);var m=this.startContainer,s=this.startOffset,y=this.endContainer,F=this.endOffset,Q=m===y;g.isCharacterDataNode(y)&&F>0&&F0&&s=g.getNodeIndex(m)&&F++;s=0}e(this,m,s,y,F)},normalizeBoundaries:function(){J(this); 40 | var m=this.startContainer,s=this.startOffset,y=this.endContainer,F=this.endOffset,Q=function(U){var R=U.nextSibling;if(R&&R.nodeType==U.nodeType){y=U;F=U.length;U.appendData(R.data);R.parentNode.removeChild(R)}},qa=function(U){var R=U.previousSibling;if(R&&R.nodeType==U.nodeType){m=U;var sa=U.length;s=R.length;U.insertData(0,R.data);R.parentNode.removeChild(R);if(m==y){F+=s;y=m}else if(y==U.parentNode){R=g.getNodeIndex(U);if(F==R){y=U;F=sa}else F>R&&F--}}},ga=true;if(g.isCharacterDataNode(y))y.length== 41 | F&&Q(y);else{if(F>0)(ga=y.childNodes[F-1])&&g.isCharacterDataNode(ga)&&Q(ga);ga=!this.collapsed}if(ga)if(g.isCharacterDataNode(m))s==0&&qa(m);else{if(sx";X=P.firstChild.nodeType==3}catch(ta){}l.features.htmlParsingConforms=X;var ka=["startContainer","startOffset","endContainer","endOffset", 47 | "collapsed","commonAncestorContainer"],ia=0,la=1,ra=2,ma=3,na=0,oa=1,pa=2,ja=3;ca.prototype={attachListener:function(a,e){this._listeners[a].push(e)},compareBoundaryPoints:function(a,e){J(this);u(this.startContainer,e.startContainer);var j=a==ma||a==ia?"start":"end",o=a==la||a==ia?"start":"end";return g.comparePoints(this[j+"Container"],this[j+"Offset"],e[o+"Container"],e[o+"Offset"])},insertNode:function(a){J(this);L(a,aa);w(this.startContainer);if(g.isAncestorOf(a,this.startContainer,true))throw new S("HIERARCHY_REQUEST_ERR"); 48 | this.setStartBefore(N(a,this.startContainer,this.startOffset))},cloneContents:function(){J(this);var a,e;if(this.collapsed)return H(this).createDocumentFragment();else{if(this.startContainer===this.endContainer&&g.isCharacterDataNode(this.startContainer)){a=this.startContainer.cloneNode(true);a.data=a.data.slice(this.startOffset,this.endOffset);e=H(this).createDocumentFragment();e.appendChild(a);return e}else{e=new q(this,true);a=O(e);e.detach()}return a}},canSurroundContents:function(){J(this);w(this.startContainer); 49 | w(this.endContainer);var a=new q(this,true),e=a._first&&K(a._first,this)||a._last&&K(a._last,this);a.detach();return!e},surroundContents:function(a){L(a,b);if(!this.canSurroundContents())throw new v("BAD_BOUNDARYPOINTS_ERR");var e=this.extractContents();if(a.hasChildNodes())for(;a.lastChild;)a.removeChild(a.lastChild);N(a,this.startContainer,this.startOffset);a.appendChild(e);this.selectNode(a)},cloneRange:function(){J(this);for(var a=new M(H(this)),e=ka.length,j;e--;){j=ka[e];a[j]=this[j]}return a}, 50 | toString:function(){J(this);var a=this.startContainer;if(a===this.endContainer&&g.isCharacterDataNode(a))return a.nodeType==3||a.nodeType==4?a.data.slice(this.startOffset,this.endOffset):"";else{var e=[];a=new q(this,true);i(a,function(j){if(j.nodeType==3||j.nodeType==4)e.push(j.data)});a.detach();return e.join("")}},compareNode:function(a){J(this);var e=a.parentNode,j=g.getNodeIndex(a);if(!e)throw new S("NOT_FOUND_ERR");a=this.comparePoint(e,j);e=this.comparePoint(e,j+1);return a<0?e>0?pa:na:e>0? 51 | oa:ja},comparePoint:function(a,e){J(this);B(a,"HIERARCHY_REQUEST_ERR");u(a,this.startContainer);if(g.comparePoints(a,e,this.startContainer,this.startOffset)<0)return-1;else if(g.comparePoints(a,e,this.endContainer,this.endOffset)>0)return 1;return 0},createContextualFragment:X?function(a){var e=this.startContainer,j=g.getDocument(e);if(!e)throw new S("INVALID_STATE_ERR");var o=null;if(e.nodeType==1)o=e;else if(g.isCharacterDataNode(e))o=g.parentElement(e);o=o===null||o.nodeName=="HTML"&&g.isHtmlNamespace(g.getDocument(o).documentElement)&& 52 | g.isHtmlNamespace(o)?j.createElement("body"):o.cloneNode(false);o.innerHTML=a;return g.fragmentFromNodeChildren(o)}:function(a){r(this);var e=H(this).createElement("body");e.innerHTML=a;return g.fragmentFromNodeChildren(e)},toHtml:function(){J(this);var a=H(this).createElement("div");a.appendChild(this.cloneContents());return a.innerHTML},intersectsNode:function(a,e){J(this);B(a,"NOT_FOUND_ERR");if(g.getDocument(a)!==H(this))return false;var j=a.parentNode,o=g.getNodeIndex(a);B(j,"NOT_FOUND_ERR"); 53 | var E=g.comparePoints(j,o,this.endContainer,this.endOffset);j=g.comparePoints(j,o+1,this.startContainer,this.startOffset);return e?E<=0&&j>=0:E<0&&j>0},isPointInRange:function(a,e){J(this);B(a,"HIERARCHY_REQUEST_ERR");u(a,this.startContainer);return g.comparePoints(a,e,this.startContainer,this.startOffset)>=0&&g.comparePoints(a,e,this.endContainer,this.endOffset)<=0},intersectsRange:function(a,e){J(this);if(H(a)!=H(this))throw new S("WRONG_DOCUMENT_ERR");var j=g.comparePoints(this.startContainer, 54 | this.startOffset,a.endContainer,a.endOffset),o=g.comparePoints(this.endContainer,this.endOffset,a.startContainer,a.startOffset);return e?j<=0&&o>=0:j<0&&o>0},intersection:function(a){if(this.intersectsRange(a)){var e=g.comparePoints(this.startContainer,this.startOffset,a.startContainer,a.startOffset),j=g.comparePoints(this.endContainer,this.endOffset,a.endContainer,a.endOffset),o=this.cloneRange();e==-1&&o.setStart(a.startContainer,a.startOffset);j==1&&o.setEnd(a.endContainer,a.endOffset);return o}return null}, 55 | union:function(a){if(this.intersectsRange(a,true)){var e=this.cloneRange();g.comparePoints(a.startContainer,a.startOffset,this.startContainer,this.startOffset)==-1&&e.setStart(a.startContainer,a.startOffset);g.comparePoints(a.endContainer,a.endOffset,this.endContainer,this.endOffset)==1&&e.setEnd(a.endContainer,a.endOffset);return e}else throw new v("Ranges do not intersect");},containsNode:function(a,e){return e?this.intersectsNode(a,false):this.compareNode(a)==ja},containsNodeContents:function(a){return this.comparePoint(a, 56 | 0)>=0&&this.comparePoint(a,g.getNodeLength(a))<=0},containsRange:function(a){return this.intersection(a).equals(a)},containsNodeText:function(a){var e=this.cloneRange();e.selectNode(a);var j=e.getNodes([3]);if(j.length>0){e.setStart(j[0],0);a=j.pop();e.setEnd(a,a.length);a=this.containsRange(e);e.detach();return a}else return this.containsNodeContents(a)},createNodeIterator:function(a,e){J(this);return new c(this,a,e)},getNodes:function(a,e){J(this);return x(this,a,e)},getDocument:function(){return H(this)}, 57 | collapseBefore:function(a){r(this);this.setEndBefore(a);this.collapse(false)},collapseAfter:function(a){r(this);this.setStartAfter(a);this.collapse(true)},getName:function(){return"DomRange"},equals:function(a){return M.rangesEqual(this,a)},isValid:function(){return V(this)},inspect:function(){return A(this)}};fa(M,ha,function(a){r(a);a.startContainer=a.startOffset=a.endContainer=a.endOffset=null;a.collapsed=a.commonAncestorContainer=null;I(a,"detach",null);a._listeners=null});l.rangePrototype=ca.prototype; 58 | M.rangeProperties=ka;M.RangeIterator=q;M.copyComparisonConstants=W;M.createPrototypeRange=fa;M.inspect=A;M.getRangeDocument=H;M.rangesEqual=function(a,e){return a.startContainer===e.startContainer&&a.startOffset===e.startOffset&&a.endContainer===e.endContainer&&a.endOffset===e.endOffset};l.DomRange=M;l.RangeException=v}); 59 | rangy.createModule("WrappedRange",function(l){function K(i,n,t,x){var A=i.duplicate();A.collapse(t);var q=A.parentElement();z.isAncestorOf(n,q,true)||(q=n);if(!q.canHaveHTML)return new C(q.parentNode,z.getNodeIndex(q));n=z.getDocument(q).createElement("span");var v,c=t?"StartToStart":"StartToEnd";do{q.insertBefore(n,n.previousSibling);A.moveToElementText(n)}while((v=A.compareEndPoints(c,i))>0&&n.previousSibling);c=n.nextSibling;if(v==-1&&c&&z.isCharacterDataNode(c)){A.setEndPoint(t?"EndToStart":"EndToEnd", 60 | i);if(/[\r\n]/.test(c.data)){q=A.duplicate();t=q.text.replace(/\r\n/g,"\r").length;for(t=q.moveStart("character",t);q.compareEndPoints("StartToEnd",q)==-1;){t++;q.moveStart("character",1)}}else t=A.text.length;q=new C(c,t)}else{c=(x||!t)&&n.previousSibling;q=(t=(x||t)&&n.nextSibling)&&z.isCharacterDataNode(t)?new C(t,0):c&&z.isCharacterDataNode(c)?new C(c,c.length):new C(q,z.getNodeIndex(n))}n.parentNode.removeChild(n);return q}function H(i,n){var t,x,A=i.offset,q=z.getDocument(i.node),v=q.body.createTextRange(), 61 | c=z.isCharacterDataNode(i.node);if(c){t=i.node;x=t.parentNode}else{t=i.node.childNodes;t=A12");d.close();var h=c.getIframeWindow(b).getSelection(),D=d.documentElement.lastChild.firstChild;d=d.createRange();d.setStart(D,1);d.collapse(true);h.addRange(d);ha=h.rangeCount==1;h.removeAllRanges();var G=d.cloneRange();d.setStart(D,0);G.setEnd(D,2);h.addRange(d);h.addRange(G);ea=h.rangeCount==2;d.detach();G.detach();Y.removeChild(b)}();l.features.selectionSupportsMultipleRanges=ea; 81 | l.features.collapsedNonEditableSelectionsSupported=ha;var M=false,g;if(Y&&f.isHostMethod(Y,"createControlRange")){g=Y.createControlRange();if(f.areHostProperties(g,["item","add"]))M=true}l.features.implementsControlRange=M;w=W?function(b){return b.anchorNode===b.focusNode&&b.anchorOffset===b.focusOffset}:function(b){return b.rangeCount?b.getRangeAt(b.rangeCount-1).collapsed:false};var Z;if(f.isHostMethod(B,"getRangeAt"))Z=function(b,d){try{return b.getRangeAt(d)}catch(h){return null}};else if(W)Z= 82 | function(b){var d=c.getDocument(b.anchorNode);d=l.createRange(d);d.setStart(b.anchorNode,b.anchorOffset);d.setEnd(b.focusNode,b.focusOffset);if(d.collapsed!==this.isCollapsed){d.setStart(b.focusNode,b.focusOffset);d.setEnd(b.anchorNode,b.anchorOffset)}return d};l.getSelection=function(b){b=b||window;var d=b._rangySelection,h=u(b),D=V?I(b):null;if(d){d.nativeSelection=h;d.docSelection=D;d.refresh(b)}else{d=new x(h,D,b);b._rangySelection=d}return d};l.getIframeSelection=function(b){return l.getSelection(c.getIframeWindow(b))}; 83 | g=x.prototype;if(!J&&W&&f.areHostMethods(B,["removeAllRanges","addRange"])){g.removeAllRanges=function(){this.nativeSelection.removeAllRanges();C(this)};var S=function(b,d){var h=k.getRangeDocument(d);h=l.createRange(h);h.collapseToPoint(d.endContainer,d.endOffset);b.nativeSelection.addRange(N(h));b.nativeSelection.extend(d.startContainer,d.startOffset);b.refresh()};g.addRange=fa?function(b,d){if(M&&V&&this.docSelection.type=="Control")t(this,b);else if(d&&da)S(this,b);else{var h;if(ea)h=this.rangeCount; 84 | else{this.removeAllRanges();h=0}this.nativeSelection.addRange(N(b));this.rangeCount=this.nativeSelection.rangeCount;if(this.rangeCount==h+1){if(l.config.checkSelectionRanges)if((h=Z(this.nativeSelection,this.rangeCount-1))&&!k.rangesEqual(h,b))b=new r(h);this._ranges[this.rangeCount-1]=b;z(this,b,aa(this.nativeSelection));this.isCollapsed=w(this)}else this.refresh()}}:function(b,d){if(d&&da)S(this,b);else{this.nativeSelection.addRange(N(b));this.refresh()}};g.setRanges=function(b){if(M&&b.length> 85 | 1)A(this,b);else{this.removeAllRanges();for(var d=0,h=b.length;d1)A(this,b);else d&&this.addRange(b[0])}}else{K.fail("No means of selecting a Range or TextRange was found");return false}g.getRangeAt=function(b){if(b<0||b>=this.rangeCount)throw new L("INDEX_SIZE_ERR");else return this._ranges[b]}; 87 | var $;if(J)$=function(b){var d;if(l.isSelectionValid(b.win))d=b.docSelection.createRange();else{d=c.getBody(b.win.document).createTextRange();d.collapse(true)}if(b.docSelection.type=="Control")n(b);else d&&typeof d.text!="undefined"?i(b,d):C(b)};else if(f.isHostMethod(B,"getRangeAt")&&typeof B.rangeCount=="number")$=function(b){if(M&&V&&b.docSelection.type=="Control")n(b);else{b._ranges.length=b.rangeCount=b.nativeSelection.rangeCount;if(b.rangeCount){for(var d=0,h=b.rangeCount;d