...
 
Commits (8)
---
extends: standard
env:
browser: true
node: false
rules:
semi:
- warn
- always
quotes: warn
space-before-function-paren:
- error
- anonymous: "always"
named: "never"
globals: # match this with webpack define/provide plugins
__PRODUCTION: false
# plugins:
# - foo
......@@ -15,3 +15,12 @@
/lighthouse-results.html
source/calendar.svg
node_modules/
yarn-error.log
# this css file is dynamically generated
source/assets/stylesheets/syntax.css
# webpack output dir
.tmp/
......@@ -8,6 +8,7 @@ gem "builder", "> 3.0"
gem 'middleman-livereload'
gem 'middleman-syntax'
gem 'middleman-minify-html'
gem 'redcarpet'
......
......@@ -39,6 +39,7 @@ GEM
hamster (3.0.0)
concurrent-ruby (~> 1.0)
hashie (3.6.0)
htmlcompressor (0.2.0)
htmlentities (4.3.4)
http_parser.rb (0.6.0)
i18n (0.7.0)
......@@ -89,6 +90,9 @@ GEM
em-websocket (~> 0.5.1)
middleman-core (>= 3.3)
rack-livereload (~> 0.3.15)
middleman-minify-html (3.4.1)
htmlcompressor (~> 0.2.0)
middleman-core (>= 3.2)
middleman-pry (1.0.2)
middleman (>= 3.3, < 5)
pry (~> 0.9)
......@@ -158,6 +162,7 @@ DEPENDENCIES
middleman (~> 4.0)
middleman-blog (~> 4.0)
middleman-livereload
middleman-minify-html
middleman-pry
middleman-syntax
onebox
......
......@@ -75,13 +75,24 @@ task :dirty_git do
end
desc "Build the middleman site"
task :build do
task :build => ['build:syntax_css'] do
cd File.expand_path('..', __FILE__)
sh "bundle exec middleman build --clean --verbose --environment=production"
cp '.tmp/dist/stats.json', 'build/assets/'
end
namespace :build do
task :syntax_css do
puts 'Creating syntax.css from rouge theme.'
require 'rouge'
open('source/assets/stylesheets/syntax.css', 'w') do |css_file|
css_file.puts Rouge::Themes::Github.render(:scope => '.highlight')
end
end
end
desc "Run the middleman preview server"
task :server do
task :server => ['build:syntax_css'] do
cd File.expand_path('..', __FILE__)
sh "bundle exec middleman server"
end
......
......@@ -12,18 +12,20 @@ $color_palette = ['#00c853', '#00e676', '#1b5e20', '#2e7d32', '#33691e',
require_relative 'lib/helpers/resume.rb'
require_relative 'lib/helpers/blog.rb'
require_relative 'lib/helpers/reviews.rb'
require_relative 'lib/helpers/webpack.rb'
require_relative 'lib/tag_cloud.rb'
# Dumb hacks are dumb. Since the blog posts don't include '.html.' in the file
# name (nor do I want them to), Middleman defaults to showing them as plain
# text. Instead, send all pseudo-extensionless files as HTML. This might break
# something down the line.
# name (nor do I want them to), Middleman's development server defaults to
# showing them as plain text. Instead, send all pseudo-extensionless files as
# HTML. This might break something down the line.
::Rack::Mime::MIME_TYPES[''] = 'text/html'
helpers do
include EvaryontsHelpers::Blog
include EvaryontsHelpers::Resume
include EvaryontsHelpers::Reviews
include WebpackStatsLoader
end
activate :blog do |blog|
......@@ -53,11 +55,11 @@ activate :automatic_image_sizes
activate :livereload
set :css_dir, 'assets/stylesheets'
set :js_dir, 'assets/javascripts'
set :js_dir, 'assets/javascript'
set :images_dir, 'assets/images'
# Enable cache buster
activate :asset_hash
#activate :asset_hash
set :layout, 'page'
page '/blog/*', layout: 'post'
......@@ -74,10 +76,19 @@ activate :syntax #, :line_numbers => true
set :markdown_engine, :redcarpet
set :markdown, fenced_code_blocks: true, smartypants: true
# Build-specific configuration
activate :external_pipeline,
name: :webpack,
command: build? ?
'./node_modules/webpack/bin/webpack.js --bail --mode production' :
'./node_modules/webpack/bin/webpack.js --watch -d --mode development --progress --color',
source: ".tmp/dist",
latency: 1
# Ignore everything in javascript or CSS -land, letting WebPack take over entirely
webpack_artifacts = ['index.js', 'index.js.map', 'stats.json', 'styles.css', 'styles.css.map']
configure :build do
if ENV['TARGET'] # Building when we are deploying...
activate :minify_css
activate :minify_javascript
end
ignore { |path| path =~ /^assets\/(stylesheets|javascript)\// && !webpack_artifacts.include?(File.basename(path)) }
end
activate :gzip
#activate :minify_html
require 'json'
module WebpackStatsLoader
def webpack_stats
return @webpack_stats if @webpack_stats
File.open('.tmp/dist/stats.json') do |stats_file|
stats = JSON.load(stats_file)
@webpack_stats = stats
end
end
def link_webpack_css_chunk(chunkname)
output = StringIO.new
[*webpack_stats["assetsByChunkName"][chunkname]].each do |assets|
css_assets = [*assets].select { |filename| filename =~ /.css$/ }
css_assets.each do |filename|
output << "<link rel='stylesheet' href='/#{filename}' integrity='#{webpack_stats["integrity"][chunkname][filename]}' crossorigin='#{webpack_stats['crossOriginLoading']}' />"
end
end
output.string
end
def link_webpack_js_chunk(chunkname)
output = StringIO.new
[*webpack_stats["assetsByChunkName"][chunkname]].each do |assets|
js_assets = [*assets].select { |filename| filename =~ /.js$/ }
js_assets.each do |filename|
output << "<script src='/#{filename}' integrity='#{webpack_stats["integrity"][chunkname][filename]}' crossorigin='#{webpack_stats['crossOriginLoading']}'></script>"
end
end
output.string
end
end
# Originally written by @timurvafin in 2013
# Originally written by @timurvafin in 2013. License unknown.
# https://github.com/timurvafin/timurv.ru/blob/master/lib/tag_cloud.rb
class TagCloud
cattr_accessor :default_options
......
/* Animated text, copied from Thristhart */
// Though I've changed the list of skills to better reflect me.
const skills = ['linux', 'ruby', 'python', 'devops', 'sysadmin', 'debugging', 'security', 'paranoia', 'configuration management'];
const characters = 'abcdefghijklmnopqrstuvwxyz';
let lastFrameTime;
const skill = document.getElementById('skill-slider');
let targetText = 'web';
let currentText = 'web';
const NEW_CHARACTER_RATE = 100; // milliseconds per character
const CHARACTER_CYCLE_RATE = 50;
const TIME_BETWEEN_TEXT_CHANGES = 2000;
let timeSinceLastNewCharacter = 0;
let timeSinceLastCycle = 0;
let timeSinceLastNewText = 1000;
let skillIndex = 0;
function textAnimation(time) {
requestAnimationFrame(textAnimation);
if (!lastFrameTime) {
lastFrameTime = time;
}
let dt = time - lastFrameTime;
if (currentText === targetText) {
timeSinceLastNewText += dt;
if (timeSinceLastNewText > TIME_BETWEEN_TEXT_CHANGES) {
targetText = skills[(skillIndex++) % skills.length];
timeSinceLastNewText = 0;
}
}
if (currentText.length !== targetText.length) {
timeSinceLastNewCharacter += dt;
if (timeSinceLastNewCharacter > NEW_CHARACTER_RATE) {
timeSinceLastNewCharacter = 0;
if (currentText.length > targetText.length) {
currentText = currentText.slice(0, -1);
} else {
currentText += characters[Math.floor(Math.random() * characters.length)];
}
}
}
if (timeSinceLastCycle > CHARACTER_CYCLE_RATE) {
timeSinceLastCycle = 0;
let letters = currentText.split('');
letters.forEach((letter, index) => {
if (letter !== targetText[index]) {
let alphabetIndex = characters.indexOf(letter);
let nextIndex = (alphabetIndex + 1) % characters.length;
letters[index] = characters[nextIndex];
}
});
currentText = letters.join('');
}
timeSinceLastCycle += dt;
lastFrameTime = time;
skill.innerHTML = currentText;
}
/* If there isn't a skill slider element on the page, don't initiate the animation */
if (skill != null) {
requestAnimationFrame(textAnimation);
}
import './ani-text.js';
function log() {
if (!__PRODUCTION) {
console.log(...arguments);
}
}
log('Only going to show in the development local server!');
......@@ -204,3 +204,24 @@ aside.onebox:last-child, aside.onebox header.source {
float: right;
}
}
.highlight table pre {
padding: 0px;
}
pre.highlight, .highlight table pre {
word-break: none;
word-wrap: pre;
white-space: pre;
overflow: auto;
}
/* I want to expand the code blocks when they're hovered, but the layout
doesn't work with that. Investigate later?
pre.highlight:hover, .highlight table pre:hover {
width: 150%;
transition: width .5s ease;
-webkit-transition: width .5s ease;
}
*/
@import "normalize.css";
@import "open-sans.css";
@import "raleway.css";
@import "font-awesome.css.scss";
@import "syntax.css";
@import "all.css.scss";
//
// ___
// /\_ \
// _____ ___ ___\//\ \ __
// /\ '__`\ / __`\ / __`\\ \ \ /'__`\
// \ \ \_\ \/\ \_\ \/\ \_\ \\_\ \_/\ __/
// \ \ ,__/\ \____/\ \____//\____\ \____\
// \ \ \/ \/___/ \/___/ \/____/\/____/
// \ \_\
// \/_/
//
// Designed, built, and released under MIT license by @mdo. Learn more at
// https://github.com/poole/poole.
@import "poole/variables";
@import "poole/base";
@import "poole/type";
@import "poole/syntax";
@import "poole/code";
@import "poole/layout";
@import "poole/masthead";
@import "poole/posts";
@import "poole/pagination";
@import "poole/message";
......@@ -16,7 +16,7 @@ title: Automated by soda and scripts
<% end %>
<img role='banner' class='avatar' alt='My avatar' title='My avatar' src="https://secure.gravatar.com/avatar/efb9a1ec42adeec9615f062e55bf5c3e?size=256" />
<span class='nav' role='navigation'>
<div class='nav' role='navigation'>
<ul>
<% data.elsewhere.sites.each do |site| %>
<li>
......@@ -25,14 +25,14 @@ title: Automated by soda and scripts
<% end %>
</ul>
<%= link_to "More &raquo;", 'more.html', class: 'more-sites' %>
</span>
</div>
<!--
IndieAuth requires the root page link to every site, but they don't
necessarily need to be visible to end-users. This list is visible in the
'more' page anyways.
-->
<div class='hidden' hidden aria-hidden="true">
<div class='hidden' hidden>
<% data.elsewhere.sites.each do |site| %>
<a href='<%= site.url %>' rel='me'><%= site.name %></a>
<% end %>
......
......@@ -2,16 +2,11 @@
<html lang="<%= data.site.lang %>">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta http-equiv='X-UA-Compatible' content='IE=edge;chrome=1' />
<meta http-equiv='X-UA-Compatible' content='IE=edge' />
<title><%= data.site.title %> - <%= current_resource.data.title %></title>
<%= feed_tag :atom, "#{blog.options.prefix.to_s}/feed.xml", title: "Atom Feed" %>
<link rel="alternate" title="Json Feed" type="application/json" href="<%= URI.join(data.site.url, "/feed.json") %>" />
<%= stylesheet_link_tag "normalize" %>
<%= stylesheet_link_tag "open-sans" %>
<%= stylesheet_link_tag "raleway" %>
<%= stylesheet_link_tag "font-awesome" %>
<%= stylesheet_link_tag "syntax" %>
<%= stylesheet_link_tag "all" %>
<%= link_webpack_css_chunk 'styles' %>
<!-- Begin favicons -->
<link rel="apple-touch-icon" sizes="57x57" href="/icons/apple-touch-icon-57x57.png">
......@@ -68,5 +63,7 @@
<noscript><p><img src="//<%= data.site.piwik.host %>/piwik.php?idsite=<%= data.site.piwik.id %>" style="border:0;" alt="" /></p></noscript>
<% end %>
<!-- End Matomo Code -->
<%= link_webpack_js_chunk 'index' %>
</body>
</html>
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const StatsWriterPlugin = require("webpack-stats-plugin").StatsWriterPlugin;;
const SriPlugin = require('webpack-subresource-integrity');
const webpack = require('webpack');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = (env, options) => {
const isProduction = options.mode === 'production';
return {
entry: {
index: "./source/assets/javascript/index.js",
styles: "./source/assets/stylesheets/entry.scss",
},
output: {
"path": __dirname + '/.tmp/dist/',
"filename": "assets/javascript/[name].js",
"crossOriginLoading": "anonymous"
},
devtool: "source-map",
module: {
rules: [
{
"enforce": "pre",
"test": /\.(js|jsx)$/,
"exclude": /node_modules\//,
"use": "eslint-loader"
},
{
"test": /\.js$/,
"exclude": /node_modules\//,
"use": {
"loader": "babel-loader",
"options": {
"presets": [
"@babel/env"
]
}
}
},
{
"test": /\.s?css$/,
"use": [
MiniCssExtractPlugin.loader,
"css-loader",
"sass-loader"
]
}
]
},
plugins: [
new webpack.DefinePlugin({
__PRODUCTION: isProduction
}),
new MiniCssExtractPlugin({filename: "assets/stylesheets/[name].css"}),
new OptimizeCssAssetsPlugin({
cssProcessorOptions: {
map: {
inline: false,
annotation: true,
}
}
}),
new SriPlugin({
hashFuncNames: ['sha256', 'sha384'],
enabled: isProduction,
}),
new StatsWriterPlugin({
filename: 'stats.json',
fields: null,
transform: (data, comp) => {
stats = comp.compiler.getStats()
var output = {
concerns: {
warnings: stats.hasWarnings(),
errors: stats.hasErrors()
},
crossOriginLoading: comp.compiler.outputOptions.crossOriginLoading
};
output.assetsByChunkName = data.assetsByChunkName;
output.integrity = {}
for (let chunk of Object.keys(output.assetsByChunkName)) {
integrity_list = {}
for (let asset_name of output.assetsByChunkName[chunk]) {
integrity_list[asset_name] = stats.compilation.assets[asset_name] && stats.compilation.assets[asset_name].integrity;
}
output.integrity[chunk] = integrity_list
}
return JSON.stringify(output, null, 4);
}
})
]
}
}
This diff is collapsed.