diff --git a/Gemfile b/Gemfile index 31b57064..a06f57d7 100644 --- a/Gemfile +++ b/Gemfile @@ -22,6 +22,8 @@ group :app do gem 'browser' gem 'sass' gem 'coffee-script' + gem 'chunky_png' + gem 'sprockets-sass' end group :production do diff --git a/Gemfile.lock b/Gemfile.lock index d968a27b..4ae8c270 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -12,6 +12,7 @@ GEM erubi (>= 1.0.0) rack (>= 0.9.0) browser (2.5.3) + chunky_png (1.3.10) coderay (1.1.2) coffee-script (2.4.1) coffee-script-source @@ -96,6 +97,8 @@ GEM rack (> 1, < 3) sprockets-helpers (1.2.1) sprockets (>= 2.2) + sprockets-sass (2.0.0.beta2) + sprockets (>= 2.0, < 4.0) strings (0.1.4) strings-ansi (~> 0.1.0) unicode-display_width (~> 1.4.0) @@ -132,6 +135,7 @@ DEPENDENCIES activesupport (~> 5.2) better_errors browser + chunky_png coffee-script erubi html-pipeline @@ -153,6 +157,7 @@ DEPENDENCIES sinatra-contrib sprockets sprockets-helpers + sprockets-sass thin thor tty-pager diff --git a/assets/images/.gitignore b/assets/images/.gitignore new file mode 100644 index 00000000..4a72ec74 --- /dev/null +++ b/assets/images/.gitignore @@ -0,0 +1 @@ +sprites/**/* diff --git a/assets/images/docs-1.png b/assets/images/docs-1.png deleted file mode 100644 index c5bfb6b8..00000000 Binary files a/assets/images/docs-1.png and /dev/null differ diff --git a/assets/images/docs-1@2x.png b/assets/images/docs-1@2x.png deleted file mode 100644 index 1f81ecd9..00000000 Binary files a/assets/images/docs-1@2x.png and /dev/null differ diff --git a/assets/images/docs-2.png b/assets/images/docs-2.png deleted file mode 100644 index bd9d44a8..00000000 Binary files a/assets/images/docs-2.png and /dev/null differ diff --git a/assets/images/docs-2@2x.png b/assets/images/docs-2@2x.png deleted file mode 100644 index 512ea7a5..00000000 Binary files a/assets/images/docs-2@2x.png and /dev/null differ diff --git a/assets/stylesheets/application.css.scss b/assets/stylesheets/application.css.scss index 2a64e5c9..85d1134f 100644 --- a/assets/stylesheets/application.css.scss +++ b/assets/stylesheets/application.css.scss @@ -1,7 +1,6 @@ -//= depend_on docs-1.png -//= depend_on docs-1@2x.png -//= depend_on docs-2.png -//= depend_on docs-2@2x.png +//= depend_on sprites/docs.png +//= depend_on sprites/docs@2x.png +//= depend_on sprites/docs.json /*! * Copyright 2013-2019 Thibaut Courouble and other contributors diff --git a/assets/stylesheets/global/_icons.scss b/assets/stylesheets/global/_icons.scss deleted file mode 100644 index e7a805f4..00000000 --- a/assets/stylesheets/global/_icons.scss +++ /dev/null @@ -1,182 +0,0 @@ -%svg-icon { - display: inline-block; - vertical-align: top; - width: 1rem; - height: 1rem; - pointer-events: none; - fill: currentColor; -} - -%doc-icon { - content: ''; - display: block; - width: 1rem; - height: 1rem; - background-image: image-url('docs-1.png'); - background-size: 10rem 10rem; -} - -%doc-icon-2 { background-image: image-url('docs-2.png') !important; } - -@media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 144dpi) { - %doc-icon { background-image: image-url('docs-1@2x.png'); } - %doc-icon-2 { background-image: image-url('docs-2@2x.png') !important; } -} - -html._theme-dark { - %darkIconFix { - filter: invert(100%) grayscale(100%); - -webkit-filter: invert(100%) grayscale(100%); - } -} - -._icon-jest:before { background-position: 0 0; } -._icon-liquid:before { background-position: -1rem 0; } -._icon-openjdk:before { background-position: -2rem 0; } -._icon-codeceptjs:before { background-position: -3rem 0; } -._icon-codeception:before { background-position: -4rem 0; } -._icon-sqlite:before { background-position: -5rem 0; @extend %darkIconFix !optional; } -._icon-async:before { background-position: -6rem 0; @extend %darkIconFix !optional; } -._icon-http:before { background-position: -7rem 0; @extend %darkIconFix !optional; } -._icon-jquery:before { background-position: -8rem 0; @extend %darkIconFix !optional; } -._icon-underscore:before { background-position: -9rem 0; @extend %darkIconFix !optional; } -._icon-html:before { background-position: 0 -1rem; } -._icon-css:before { background-position: -1rem -1rem; } -._icon-dom:before { background-position: -2rem -1rem; } -._icon-dom_events:before { background-position: -3rem -1rem; } -._icon-javascript:before { background-position: -4rem -1rem; } -._icon-backbone:before { background-position: -5rem -1rem; @extend %darkIconFix !optional; } -._icon-node:before, -._icon-node_lts:before { background-position: -6rem -1rem; } -._icon-sass:before { background-position: -7rem -1rem; } -._icon-less:before { background-position: -8rem -1rem; } -._icon-angularjs:before { background-position: -9rem -1rem; } -._icon-coffeescript:before { background-position: 0 -2rem; @extend %darkIconFix !optional; } -._icon-ember:before { background-position: -1rem -2rem; } -._icon-yarn:before { background-position: -2rem -2rem; } -._icon-immutable:before { background-position: -3rem -2rem; @extend %darkIconFix !optional; } -._icon-jqueryui:before { background-position: -4rem -2rem; } -._icon-jquerymobile:before { background-position: -5rem -2rem; } -._icon-lodash:before { background-position: -6rem -2rem; } -._icon-php:before { background-position: -7rem -2rem; } -._icon-ruby:before, -._icon-minitest:before { background-position: -8rem -2rem; } -._icon-rails:before { background-position: -9rem -2rem; } -._icon-python:before, -._icon-python2:before { background-position: 0 -3rem; } -._icon-git:before { background-position: -1rem -3rem; } -._icon-redis:before { background-position: -2rem -3rem; } -._icon-postgresql:before { background-position: -3rem -3rem; } -._icon-d3:before { background-position: -4rem -3rem; } -._icon-knockout:before { background-position: -5rem -3rem; } -._icon-moment:before { background-position: -6rem -3rem; @extend %darkIconFix !optional; } -._icon-c:before { background-position: -7rem -3rem; } -._icon-statsmodels:before { background-position: -8rem -3rem; } -._icon-yii:before, -._icon-yii1:before { background-position: -9rem -3rem; } -._icon-cpp:before { background-position: 0 -4rem; } -._icon-go:before { background-position: -1rem -4rem; } -._icon-express:before { background-position: -2rem -4rem; } -._icon-grunt:before { background-position: -3rem -4rem; } -._icon-rust:before { background-position: -4rem -4rem; @extend %darkIconFix !optional; } -._icon-laravel:before { background-position: -5rem -4rem; } -._icon-haskell:before { background-position: -6rem -4rem; } -._icon-requirejs:before { background-position: -7rem -4rem; } -._icon-chai:before { background-position: -8rem -4rem; } -._icon-sinon:before { background-position: -9rem -4rem; } -._icon-cordova:before { background-position: 0 -5rem; } -._icon-markdown:before { background-position: -1rem -5rem; @extend %darkIconFix !optional; } -._icon-django:before { background-position: -2rem -5rem; } -._icon-xslt_xpath:before { background-position: -3rem -5rem; } -._icon-nginx:before, -._icon-nginx_lua_module:before { background-position: -4rem -5rem; } -._icon-svg:before { background-position: -5rem -5rem; } -._icon-marionette:before { background-position: -6rem -5rem; } -._icon-jsdoc:before, -._icon-koa:before, -._icon-graphite:before, -._icon-mongoose:before { background-position: -7rem -5rem; } -._icon-phpunit:before { background-position: -8rem -5rem; } -._icon-nokogiri:before { background-position: -9rem -5rem; @extend %darkIconFix !optional; } -._icon-rethinkdb:before { background-position: 0 -6rem; } -._icon-react:before { background-position: -1rem -6rem; } -._icon-socketio:before { background-position: -2rem -6rem; } -._icon-modernizr:before { background-position: -3rem -6rem; } -._icon-bower:before { background-position: -4rem -6rem; } -._icon-fish:before { background-position: -5rem -6rem; @extend %darkIconFix !optional; } -._icon-scikit_image:before { background-position: -6rem -6rem; } -._icon-twig:before { background-position: -7rem -6rem; } -._icon-pandas:before { background-position: -8rem -6rem; } -._icon-scikit_learn:before { background-position: -9rem -6rem; } -._icon-bottle:before { background-position: 0 -7rem; } -._icon-docker:before { background-position: -1rem -7rem; } -._icon-cakephp:before { background-position: -2rem -7rem; } -._icon-lua:before { background-position: -3rem -7rem; @extend %darkIconFix !optional; } -._icon-clojure:before { background-position: -4rem -7rem; } -._icon-symfony:before { background-position: -5rem -7rem; } -._icon-mocha:before { background-position: -6rem -7rem; } -._icon-meteor:before { background-position: -7rem -7rem; @extend %darkIconFix !optional; } -._icon-npm:before { background-position: -8rem -7rem; } -._icon-apache_http_server:before { background-position: -9rem -7rem; } -._icon-drupal:before { background-position: 0 -8rem; } -._icon-webpack:before { background-position: -1rem -8rem; } -._icon-phaser:before { background-position: -2rem -8rem; } -._icon-vue:before { background-position: -3rem -8rem; } -._icon-opentsdb:before { background-position: -4rem -8rem; } -._icon-q:before { background-position: -5rem -8rem; } -._icon-crystal:before { background-position: -6rem -8rem; @extend %darkIconFix !optional; } -._icon-julia:before { background-position: -7rem -8rem; @extend %darkIconFix !optional; } -._icon-redux:before { background-position: -8rem -8rem; @extend %darkIconFix !optional; } -._icon-bootstrap:before { background-position: -9rem -8rem; } -._icon-react_native:before { background-position: 0 -9rem; } -._icon-phalcon:before { background-position: -1rem -9rem; } -._icon-matplotlib:before { background-position: -2rem -9rem; } -._icon-cmake:before { background-position: -3rem -9rem; } -._icon-elixir:before { background-position: -4rem -9rem; @extend %darkIconFix !optional; } -._icon-vagrant:before { background-position: -5rem -9rem; } -._icon-dojo:before { background-position: -6rem -9rem; } -._icon-flow:before { background-position: -7rem -9rem; } -._icon-relay:before { background-position: -8rem -9rem; } -._icon-phoenix:before { background-position: -9rem -9rem; } - -._icon-tcl_tk:before { background-position: 0 0; @extend %doc-icon-2; } -._icon-erlang:before { background-position: -1rem 0; @extend %doc-icon-2; } -._icon-chef:before { background-position: -2rem 0; @extend %doc-icon-2; } -._icon-ramda:before { background-position: -3rem 0; @extend %doc-icon-2; @extend %darkIconFix !optional; } -._icon-codeigniter:before { background-position: -4rem 0; @extend %doc-icon-2; @extend %darkIconFix !optional; } -._icon-influxdata:before { background-position: -5rem 0; @extend %doc-icon-2; @extend %darkIconFix !optional; } -._icon-tensorflow:before { background-position: -6rem 0; @extend %doc-icon-2; } -._icon-haxe:before { background-position: -7rem 0; @extend %doc-icon-2; } -._icon-ansible:before { background-position: -8rem 0; @extend %doc-icon-2; @extend %darkIconFix !optional; } -._icon-typescript:before { background-position: -9rem 0; @extend %doc-icon-2; } -._icon-browser_support_tables:before { background-position: 0rem -1rem; @extend %doc-icon-2; } -._icon-gnu_fortran:before { background-position: -1rem -1rem; @extend %doc-icon-2; } -._icon-gcc:before { background-position: -2rem -1rem; @extend %doc-icon-2; } -._icon-perl:before { background-position: -3rem -1rem; @extend %doc-icon-2; } -._icon-apache_pig:before { background-position: -4rem -1rem; @extend %doc-icon-2; } -._icon-numpy:before { background-position: -5rem -1rem; @extend %doc-icon-2; } -._icon-kotlin:before { background-position: -6rem -1rem; @extend %doc-icon-2; } -._icon-padrino:before { background-position: -7rem -1rem; @extend %doc-icon-2; } -._icon-angular:before { background-position: -8rem -1rem; @extend %doc-icon-2; } -._icon-love:before { background-position: -9rem -1rem; @extend %doc-icon-2; } -._icon-jasmine:before { background-position: 0 -2rem; @extend %doc-icon-2; } -._icon-pug:before { background-position: -1rem -2rem; @extend %doc-icon-2; } -._icon-electron:before { background-position: -2rem -2rem; @extend %doc-icon-2; } -._icon-falcon:before { background-position: -3rem -2rem; @extend %doc-icon-2; } -._icon-godot:before { background-position: -4rem -2rem; @extend %doc-icon-2; } -._icon-nim:before { background-position: -5rem -2rem; @extend %doc-icon-2; @extend %darkIconFix !optional; } -._icon-vulkan:before { background-position: -6rem -2rem; @extend %doc-icon-2; @extend %darkIconFix !optional; } -._icon-d:before { background-position: -7rem -2rem; @extend %doc-icon-2; } -._icon-bluebird:before { background-position: -8rem -2rem; @extend %doc-icon-2; } -._icon-eslint:before { background-position: -9rem -2rem; @extend %doc-icon-2; } -._icon-homebrew:before { background-position: 0 -3rem; @extend %doc-icon-2; } -._icon-jekyll:before { background-position: -1rem -3rem; @extend %doc-icon-2; } -._icon-babel:before { background-position: -2rem -3rem; @extend %doc-icon-2; } -._icon-leaflet:before { background-position: -3rem -3rem; @extend %doc-icon-2; } -._icon-terraform:before { background-position: -4rem -3rem; @extend %doc-icon-2; } -._icon-pygame:before { background-position: -5rem -3rem; @extend %doc-icon-2; } -._icon-bash:before { background-position: -6rem -3rem; @extend %doc-icon-2; } -._icon-dart:before { background-position: -7rem -3rem; @extend %doc-icon-2; } -._icon-qt:before { background-position: -8rem -3rem; @extend %doc-icon-2; } -._icon-puppeteer:before { background-position: -9rem -3rem; @extend %doc-icon-2; } -._icon-handlebars:before { background-position: 0 -4rem; @extend %doc-icon-2; @extend %darkIconFix !optional; } diff --git a/assets/stylesheets/global/_icons.scss.erb b/assets/stylesheets/global/_icons.scss.erb new file mode 100644 index 00000000..b2b22c22 --- /dev/null +++ b/assets/stylesheets/global/_icons.scss.erb @@ -0,0 +1,43 @@ +<% manifest = JSON.parse(File.read('assets/images/sprites/docs.json')) %> + +%svg-icon { + display: inline-block; + vertical-align: top; + width: 1rem; + height: 1rem; + pointer-events: none; + fill: currentColor; +} + +%doc-icon { + content: ''; + display: block; + width: 1rem; + height: 1rem; + background-image: image-url('sprites/docs.png'); + background-size: <%= manifest['icons_per_row'] %>rem <%= manifest['icons_per_row'] %>rem; +} + +@media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 144dpi) { + %doc-icon { background-image: image-url('sprites/docs@2x.png'); } +} + +html._theme-dark { + %darkIconFix { + filter: invert(100%) grayscale(100%); + -webkit-filter: invert(100%) grayscale(100%); + } +} + +<%= + items = [] + + manifest['items'].each do |item| + rules = [] + rules << "background-position: -#{item['col']}rem -#{item['row']}rem;" + rules << "@extend %darkIconFix !optional;" if item['dark_icon_fix'] + items << "._icon-#{item['type']}:before { #{rules.join(' ')} }" + end + + items.join('') + %> diff --git a/lib/app.rb b/lib/app.rb index f86ab109..a1ace892 100644 --- a/lib/app.rb +++ b/lib/app.rb @@ -53,6 +53,11 @@ class App < Sinatra::Application end configure :test, :development do + require 'thor' + load 'tasks/sprites.thor' + + SpritesCLI.new.invoke(:generate) + require 'active_support/per_thread_registry' require 'active_support/cache' sprockets.cache = ActiveSupport::Cache.lookup_store :file_store, root.join('tmp', 'cache', 'assets', environment.to_s) @@ -199,10 +204,8 @@ class App < Sinatra::Application @@service_worker_asset_urls ||= [ javascript_path('application', asset_host: false), stylesheet_path('application'), - image_path('docs-1.png'), - image_path('docs-1@2x.png'), - image_path('docs-2.png'), - image_path('docs-2@2x.png'), + image_path('sprites/docs.png'), + image_path('sprites/docs@2x.png'), asset_path('docs.js'), App.production? ? nil : javascript_path('debug'), ].compact diff --git a/lib/tasks/assets.thor b/lib/tasks/assets.thor index c3a4caf5..d49efd7d 100644 --- a/lib/tasks/assets.thor +++ b/lib/tasks/assets.thor @@ -14,6 +14,7 @@ class AssetsCLI < Thor option :keep, type: :numeric, default: 0, desc: 'Number of old assets to keep' option :verbose, type: :boolean def compile + invoke 'sprites:generate', [], :verbose => options[:verbose] manifest.compile App.assets_compile manifest.clean(options[:keep]) if options[:clean] end diff --git a/lib/tasks/sprites.thor b/lib/tasks/sprites.thor new file mode 100644 index 00000000..b50f0f04 --- /dev/null +++ b/lib/tasks/sprites.thor @@ -0,0 +1,205 @@ +class SpritesCLI < Thor + def self.to_s + 'Sprites' + end + + def initialize(*args) + require 'docs' + require 'chunky_png' + require 'fileutils' + super + end + + desc 'generate [--verbose]', 'Generate the documentation icon spritesheets' + option :verbose, type: :boolean + def generate + items = get_items + items_with_icons = items.select {|item| item[:has_icons]} + items_without_icons = items.select {|item| !item[:has_icons]} + icons_per_row = Math.sqrt(items_with_icons.length).ceil + + bg_color = get_sidebar_background + + items_with_icons.each_with_index do |item, index| + item[:row] = (index / icons_per_row).floor + item[:col] = index - item[:row] * icons_per_row + + item[:icon_16] = get_icon(item[:path_16], 16) + item[:icon_32] = get_icon(item[:path_32], 32) + + item[:dark_icon_fix] = needs_dark_icon_fix(item[:icon_32], bg_color) + end + + log_details(items_with_icons, icons_per_row) + + generate_spritesheet(16, items_with_icons, 'assets/images/sprites/docs.png') {|item| item[:icon_16]} + generate_spritesheet(32, items_with_icons, 'assets/images/sprites/docs@2x.png') {|item| item[:icon_32]} + + # Add Mongoose's icon details to docs without custom icons + default_item = items_with_icons.find {|item| item[:type] == 'mongoose'} + items_without_icons.each do |item| + item[:row] = default_item[:row] + item[:col] = default_item[:col] + item[:dark_icon_fix] = default_item[:dark_icon_fix] + end + + save_manifest(items, icons_per_row, 'assets/images/sprites/docs.json') + end + + private + + def get_items + items = Docs.all.map do |doc| + base_path = "public/icons/docs/#{doc.slug}" + { + :type => doc.slug, + :path_16 => "#{base_path}/16.png", + :path_32 => "#{base_path}/16@2x.png" + } + end + + # Checking paths against an array of possible paths is faster than 200+ File.exist? calls + files = Dir.glob('public/icons/docs/**/*.png') + + items.each do |item| + item[:has_icons] = files.include?(item[:path_16]) && files.include?(item[:path_32]) + end + end + + def get_icon(path, max_size) + icon = ChunkyPNG::Image.from_file(path) + + # Check if the icon is too big + # If it is, resize the image without changing the aspect ratio + if icon.width > max_size || icon.height > max_size + ratio = icon.width.to_f / icon.height + new_width = (icon.width >= icon.height ? max_size : max_size * ratio).floor + new_height = (icon.width >= icon.height ? max_size / ratio : max_size).floor + + logger.warn("Icon #{path} is too big: max size is #{max_size} x #{max_size}, icon is #{icon.width} x #{icon.height}, resizing to #{new_width} x #{new_height}") + + icon.resample_nearest_neighbor!(new_width, new_height) + end + + icon + end + + def get_sidebar_background + # This is a hacky way to get the background color of the sidebar + # Unfortunately, it's not possible to get the value of a SCSS variable from a Thor task + # Because hard-coding the value is even worse, we extract it using some regex + path = 'assets/stylesheets/global/_variables-dark.scss' + regex = /--sidebarBackground:\s+([^;]+);/ + ChunkyPNG::Color.parse(File.read(path)[regex, 1]) + end + + def needs_dark_icon_fix(icon, bg_color) + # Determine whether the icon needs to be grayscaled if the user has enabled the dark theme + # The logic is roughly based on https://www.w3.org/TR/2008/REC-WCAG20-20081211/#visual-audio-contrast + contrast = icon.pixels.select {|pixel| ChunkyPNG::Color.a(pixel) > 0}.map do |pixel| + get_contrast(bg_color, pixel) + end + + avg = contrast.reduce(:+) / contrast.size.to_f + avg < 3.5 + end + + def get_contrast(base, other) + # Calculating the contrast ratio as described in the WCAG 2.0: + # https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef + l1 = get_luminance(base) + 0.05 + l2 = get_luminance(other) + 0.05 + ratio = l1 / l2 + l2 > l1 ? 1 / ratio : ratio + end + + def get_luminance(color) + rgb = [ + ChunkyPNG::Color.r(color).to_f, + ChunkyPNG::Color.g(color).to_f, + ChunkyPNG::Color.b(color).to_f + ] + + # Calculating the relative luminance as described in the WCAG 2.0: + # https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef + + rgb.map! do |value| + value /= 255 + value < 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4 + end + + 0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2] + end + + def generate_spritesheet(size, items_with_icons, output_path, &item_to_icon) + logger.info("Generating spritesheet #{output_path} with icons of size #{size} x #{size}") + + icons_per_row = Math.sqrt(items_with_icons.length).ceil + spritesheet = ChunkyPNG::Image.new(size * icons_per_row, size * icons_per_row) + + items_with_icons.each do |item| + icon = item_to_icon.call(item) + + # Calculate the base coordinates + base_x = item[:col] * size + base_y = item[:row] * size + + # Center the icon if it's not a perfect rectangle + x = base_x + ((size - icon.width) / 2).floor + y = base_y + ((size - icon.height) / 2).floor + + spritesheet.compose!(icon, x, y) + end + + FileUtils.mkdir_p(File.dirname(output_path)) + spritesheet.save(output_path) + end + + def save_manifest(items, icons_per_row, path) + logger.info("Saving spritesheet details to #{path}") + + FileUtils.mkdir_p(File.dirname(path)) + + # Only save the details that the scss file needs + manifest_items = items.map do |item| + { + :type => item[:type], + :row => item[:row], + :col => item[:col], + :dark_icon_fix => item[:dark_icon_fix] + } + end + + manifest = {:icons_per_row => icons_per_row, :items => manifest_items} + + File.open(path, 'w') do |f| + f.write(JSON.generate(manifest)) + end + end + + def log_details(items_with_icons, icons_per_row) + logger.debug("Amount of icons: #{items_with_icons.length}") + logger.debug("Amount of icons needing the dark icon fix: #{items_with_icons.count {|item| item[:dark_icon_fix]}}") + logger.debug("Amount of icons per row: #{icons_per_row}") + + max_type_length = items_with_icons.map {|item| item[:type].length}.max + border = "+#{'-' * (max_type_length + 2)}+#{'-' * 5}+#{'-' * 8}+#{'-' * 15}+" + + logger.debug(border) + logger.debug("| #{'Type'.ljust(max_type_length)} | Row | Column | Dark icon fix |") + logger.debug(border) + + items_with_icons.each do |item| + logger.debug("| #{item[:type].ljust(max_type_length)} | #{item[:row].to_s.ljust(3)} | #{item[:col].to_s.ljust(6)} | #{(item[:dark_icon_fix] ? 'Yes' : 'No').ljust(13)} |") + end + + logger.debug(border) + end + + def logger + @logger ||= Logger.new($stdout).tap do |logger| + logger.level = options[:verbose] ? Logger::DEBUG : Logger::INFO + logger.formatter = proc {|severity, datetime, progname, msg| "#{msg}\n"} + end + end +end diff --git a/public/icons/docs-1.pxm b/public/icons/docs-1.pxm deleted file mode 100644 index d9418ee7..00000000 Binary files a/public/icons/docs-1.pxm and /dev/null differ diff --git a/public/icons/docs-1@2x.pxm b/public/icons/docs-1@2x.pxm deleted file mode 100644 index d2b97c84..00000000 Binary files a/public/icons/docs-1@2x.pxm and /dev/null differ diff --git a/public/icons/docs-2.pxm b/public/icons/docs-2.pxm deleted file mode 100644 index ba5ce1b5..00000000 Binary files a/public/icons/docs-2.pxm and /dev/null differ diff --git a/public/icons/docs-2@2x.pxm b/public/icons/docs-2@2x.pxm deleted file mode 100644 index 3c390953..00000000 Binary files a/public/icons/docs-2@2x.pxm and /dev/null differ diff --git a/public/icons/docs/bluebird/16@2x.png b/public/icons/docs/bluebird/16@2x.png index 9ffae075..64ad5903 100644 Binary files a/public/icons/docs/bluebird/16@2x.png and b/public/icons/docs/bluebird/16@2x.png differ