@ -1,2 +1 @@
|
||||
public/icons
|
||||
test
|
||||
test
|
||||
|
@ -0,0 +1 @@
|
||||
sprites/**/*
|
Before Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 119 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 48 KiB |
@ -1,42 +0,0 @@
|
||||
class app.AppCache
|
||||
$.extend @prototype, Events
|
||||
|
||||
@isEnabled: ->
|
||||
try
|
||||
applicationCache and applicationCache.status isnt applicationCache.UNCACHED
|
||||
catch
|
||||
|
||||
constructor: ->
|
||||
@cache = applicationCache
|
||||
@notifyUpdate = true
|
||||
@onUpdateReady() if @cache.status is @cache.UPDATEREADY
|
||||
|
||||
$.on @cache, 'progress', @onProgress
|
||||
$.on @cache, 'updateready', @onUpdateReady
|
||||
|
||||
update: ->
|
||||
@notifyUpdate = true
|
||||
@notifyProgress = true
|
||||
try @cache.update() catch
|
||||
return
|
||||
|
||||
updateInBackground: ->
|
||||
@notifyUpdate = false
|
||||
@notifyProgress = false
|
||||
try @cache.update() catch
|
||||
return
|
||||
|
||||
reload: ->
|
||||
$.on @cache, 'updateready noupdate error', -> app.reboot()
|
||||
@notifyUpdate = false
|
||||
@notifyProgress = true
|
||||
try @cache.update() catch
|
||||
return
|
||||
|
||||
onProgress: (event) =>
|
||||
@trigger 'progress', event if @notifyProgress
|
||||
return
|
||||
|
||||
onUpdateReady: =>
|
||||
@trigger 'updateready' if @notifyUpdate
|
||||
return
|
@ -0,0 +1,49 @@
|
||||
class app.ServiceWorker
|
||||
$.extend @prototype, Events
|
||||
|
||||
@isEnabled: ->
|
||||
!!navigator.serviceWorker and app.config.service_worker_enabled
|
||||
|
||||
constructor: ->
|
||||
@registration = null
|
||||
@notifyUpdate = true
|
||||
|
||||
navigator.serviceWorker.register(app.config.service_worker_path, {scope: '/'})
|
||||
.then(
|
||||
(registration) => @updateRegistration(registration),
|
||||
(error) -> console.error('Could not register service worker:', error)
|
||||
)
|
||||
|
||||
update: ->
|
||||
return unless @registration
|
||||
@notifyUpdate = true
|
||||
return @registration.update().catch(->)
|
||||
|
||||
updateInBackground: ->
|
||||
return unless @registration
|
||||
@notifyUpdate = false
|
||||
return @registration.update().catch(->)
|
||||
|
||||
reload: ->
|
||||
return @updateInBackground().then(() -> app.reboot())
|
||||
|
||||
updateRegistration: (registration) ->
|
||||
@registration = registration
|
||||
$.on @registration, 'updatefound', @onUpdateFound
|
||||
return
|
||||
|
||||
onUpdateFound: =>
|
||||
$.off @installingRegistration, 'statechange', @onStateChange() if @installingRegistration
|
||||
@installingRegistration = @registration.installing
|
||||
$.on @installingRegistration, 'statechange', @onStateChange
|
||||
return
|
||||
|
||||
onStateChange: =>
|
||||
if @installingRegistration and @installingRegistration.state == 'installed' and navigator.serviceWorker.controller
|
||||
@installingRegistration = null
|
||||
@onUpdateReady()
|
||||
return
|
||||
|
||||
onUpdateReady: ->
|
||||
@trigger 'updateready' if @notifyUpdate
|
||||
return
|
@ -1,28 +1,32 @@
|
||||
try {
|
||||
if (app.config.env == 'production') {
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
ga('create', 'UA-5544833-12', 'devdocs.io');
|
||||
page.track(function() {
|
||||
ga('send', 'pageview', {
|
||||
page: location.pathname + location.search + location.hash,
|
||||
dimension1: app.router.context && app.router.context.doc && app.router.context.doc.slug_without_version
|
||||
if (app.config.env === 'production') {
|
||||
if (Cookies.get('analyticsConsent') === '1') {
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
ga('create', 'UA-5544833-12', 'devdocs.io');
|
||||
page.track(function() {
|
||||
ga('send', 'pageview', {
|
||||
page: location.pathname + location.search + location.hash,
|
||||
dimension1: app.router.context && app.router.context.doc && app.router.context.doc.slug_without_version
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
page.track(function() {
|
||||
if (window._gauges)
|
||||
_gauges.push(['track']);
|
||||
else
|
||||
(function() {
|
||||
var _gauges=_gauges||[];!function(){var a=document.createElement("script");
|
||||
a.type="text/javascript",a.async=!0,a.id="gauges-tracker",
|
||||
a.setAttribute("data-site-id","51c15f82613f5d7819000067"),
|
||||
a.src="https://secure.gaug.es/track.js";var b=document.getElementsByTagName("script")[0];
|
||||
b.parentNode.insertBefore(a,b)}();
|
||||
})();
|
||||
});
|
||||
page.track(function() {
|
||||
if (window._gauges)
|
||||
_gauges.push(['track']);
|
||||
else
|
||||
(function() {
|
||||
var _gauges=_gauges||[];!function(){var a=document.createElement("script");
|
||||
a.type="text/javascript",a.async=!0,a.id="gauges-tracker",
|
||||
a.setAttribute("data-site-id","51c15f82613f5d7819000067"),
|
||||
a.src="https://secure.gaug.es/track.js";var b=document.getElementsByTagName("script")[0];
|
||||
b.parentNode.insertBefore(a,b)}();
|
||||
})();
|
||||
});
|
||||
} else {
|
||||
resetAnalytics();
|
||||
}
|
||||
}
|
||||
} catch(e) { }
|
||||
|
@ -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; }
|
@ -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('')
|
||||
%>
|
@ -0,0 +1,211 @@
|
||||
class SpritesCLI < Thor
|
||||
def self.to_s
|
||||
'Sprites'
|
||||
end
|
||||
|
||||
def initialize(*args)
|
||||
require 'docs'
|
||||
require 'chunky_png'
|
||||
require 'fileutils'
|
||||
super
|
||||
end
|
||||
|
||||
desc 'generate [--remove-public-icons] [--verbose]', 'Generate the documentation icon spritesheets'
|
||||
option :remove_public_icons, type: :boolean, desc: 'Remove public/icons after generating the 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')
|
||||
|
||||
if options[:remove_public_icons]
|
||||
logger.info('Removing public/icons')
|
||||
FileUtils.rm_rf('public/icons')
|
||||
end
|
||||
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
|
@ -1 +0,0 @@
|
||||
[]
|
Before Width: | Height: | Size: 1018 B After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 31 KiB |
@ -1,14 +0,0 @@
|
||||
CACHE MANIFEST
|
||||
# <%= app_theme %> <%= app_size %> <%= app_layout %>
|
||||
|
||||
CACHE:
|
||||
/
|
||||
<%= manifest_asset_urls.join "\n" %>
|
||||
<%= doc_index_urls.join "\n" %>
|
||||
|
||||
NETWORK:
|
||||
/s/
|
||||
*
|
||||
|
||||
FALLBACK:
|
||||
/ /
|
@ -0,0 +1,49 @@
|
||||
<%# The name of the cache to store responses in %>
|
||||
<%# If the cache name changes DevDocs is assumed to be updated %>
|
||||
const cacheName = '<%= service_worker_cache_name %>';
|
||||
|
||||
<%# Url's to cache when the service worker is installed %>
|
||||
const urlsToCache = [
|
||||
'/',
|
||||
'/favicon.ico',
|
||||
'/manifest.json',
|
||||
'<%= service_worker_asset_urls.join "',\n '" %>',
|
||||
'<%= doc_index_urls.join "',\n '" %>',
|
||||
];
|
||||
|
||||
<%# Set-up the cache %>
|
||||
self.addEventListener('install', event => {
|
||||
self.skipWaiting();
|
||||
|
||||
event.waitUntil(
|
||||
caches.open(cacheName).then(cache => cache.addAll(urlsToCache)),
|
||||
);
|
||||
});
|
||||
|
||||
<%# Remove old caches %>
|
||||
self.addEventListener('activate', event => {
|
||||
event.waitUntil((async () => {
|
||||
const keys = await caches.keys();
|
||||
const jobs = keys.map(key => key !== cacheName ? caches.delete(key) : Promise.resolve());
|
||||
return Promise.all(jobs);
|
||||
})());
|
||||
});
|
||||
|
||||
<%# Handle HTTP requests %>
|
||||
self.addEventListener('fetch', event => {
|
||||
event.respondWith((async () => {
|
||||
const cachedResponse = await caches.match(event.request);
|
||||
if (cachedResponse) return cachedResponse;
|
||||
|
||||
const url = new URL(event.request.url);
|
||||
|
||||
<%# Attempt to return the index page from the cache if the user is visiting a url like devdocs.io/offline or devdocs.io/javascript/global_objects/array/find %>
|
||||
<%# The index page will handle the routing %>
|
||||
if (url.origin === location.origin && !url.pathname.includes('.')) {
|
||||
const cachedIndex = await caches.match('/');
|
||||
if (cachedIndex) return cachedIndex;
|
||||
}
|
||||
|
||||
return fetch(event.request);
|
||||
})());
|
||||
});
|