5 Commits
v126 ... v39

Author SHA1 Message Date
f08081a129 remove some more ivysaur.me references 2
--HG--
branch : sanitise-paths
2014-07-03 22:01:04 +12:00
8c4754ae8d remove some more ivysaur.me references
--HG--
branch : sanitise-paths
2014-07-03 21:59:42 +12:00
fccaa6a326 Merge with default
--HG--
branch : sanitise-paths
2014-07-03 21:58:05 +12:00
3073231d38 remove homepage blurb for publication
--HG--
branch : sanitise-paths
2013-09-28 13:35:43 +12:00
0d522e1cd1 remove all branding for publication
--HG--
branch : sanitise-paths
2013-09-28 13:33:47 +12:00
25 changed files with 751 additions and 1319 deletions

View File

@@ -1,6 +1,3 @@
mode:regex wwwroot/*
^_dist/ nbproject/*
^sites/[^/]+/data/ data/*
^sites/[^/]+/wwwroot/
^shields_cache/

View File

@@ -1,5 +0,0 @@
42a17645b5b21d7fe395767de7fa3e26ee999014 release-r54
0f89ae041c2ee60cc1ea308d047fce816b19c490 release-r64
d4733a95c3428db8722ce0d0350d17bbbabc8720 release-r72
7c92f9e2e4818d74eded59ad516d7d58b4072f8d release-r97
b1426986ff5f265f79d6412c0e81ccc7cae652ff release-r118

View File

@@ -1,8 +0,0 @@
- Merge "written in" and "tags"
- RSS for recent changes
- RSS for all projects
- Switchable CSS (reddit theme, 4chan theme, HN theme)

View File

@@ -1,69 +0,0 @@
A static site generator for a portfolio website.
This script is currently in use to generate the code.ivysaur.me website.
Written in PHP, Bash
=FEATURES=
- Generates static website, minimising server load and decreasing attack surface compared to dynamic server-side PHP
- Automatic thumbnailing and sprite sheet generation
- Download file attachments per-project
- One-click rebuild, one-click deploy
- Parallel generation
=CHANGELOG=
2017-04-23: v126
- Breaking: BASEURL is now a mandatory field
- Feature: Support `[go-get]` tags
- Feature: Canonical paths for SEO
- Feature: Use extension-less paths by default (old .html paths still supported via redirect)
- Feature: Support `[url]` tags
- Enhancement: Cache-busting parameters on homepage thumbnails
2016-04-19: v118
- Feature: Classify download artefacts by matching CHANGELOG entry
- Feature: Allow sorting by lifespan, artefacts, release entries
- Feature: Separate site generation code from site data repositories
- Enhancement: Update supplied CSS normalize script
- Enhancement: Read parallelism from number of CPUs
2015-11-08: v97
- Feature: Support BBCode b/i/spoiler/entry tags
- Feature: Add more CSS styles to allow per-site customistaion
- Feature: Use http://shields.io images where appropriate
- Feature: Optional blurbs=off, article_header={string}, shields_prefix=true configuration directives
- Fix an issue with project update-time detection
- Fix a cosmetic issue with whitespace
2015-04-05: v72
- Feature: Support redirecting old project names
- Feature: Add file hash in download URLs to prevent filename collisions
- Fix an issue generating spritesheets even if no project images are present
- Don't include `ctime` when estimating project update time
2015-04-05: v64
- Feature: Support sorting projects
2015-04-04: v54
- Feature: Support multiple code sites
- Fix an issue with parallel builds on some versions of windows
- Fix an issue corrupting URL links with multiple parameters
- Fix a cosmetic issue with page overflow
- Fix a cosmetic issue with whitespace on code elements
2014-07-02: v39
- Feature: Tags
- Feature: Generate pages in parallel
- Enhancement: Support raw HTML sections in page content
- Fix an issue with URLs containing spaces
- Fix a cosmetic issue with image thumbnail backgrounds
- Fix a cosmetic issue with download sort order
- Fix a cosmetic issue with page layout
2013-09-28: v13
- Initial public source code release
2013-09-21: v3
- Initial deployment

3
deploy.cmd Normal file
View File

@@ -0,0 +1,3 @@
@echo off
start C:\path\to\cygwin\bin\mintty.exe /bin/bash -l -c "rsync -avz -e ""ssh -i /cygdrive/c/path/to/deploy_key"" --progress /cygdrive/c/path/to/code/wwwroot www-data@your-web-server:/var/www/path/to/code-site/"

8
homepage_blurb.htm Normal file
View File

@@ -0,0 +1,8 @@
<p>
Homepage blurb goes here.
</p>
<p>
<strong>PROJECTS</strong>
</p>

View File

@@ -1,381 +0,0 @@
<?php
class CProject {
private $dir;
public $projname;
public $shortdesc = '(no description)';
public $subtag = '';
public $lastupdate = 0;
public $numreleases = 0;
private $longdesc = '';
private $prefix_html = '';
private $images = array();
private $downloads = array();
private $downloads_hashes = array();
public $downloads_section_was_replaced = false;
public $lifespan = 0;
public $tags = array();
public $homeimage = null;
protected $go_get_target = '';
public function __construct($dirname, $projname) {
$this->dir = BASEDIR.'data/'.$dirname.'/';
$this->projname = $projname;
$matches = [];
// Identify resources in folder
$ls = scandir($this->dir);
$found_real_lastupdate = false;
foreach($ls as $file) {
if ($file[0] == '.') {
continue;
}
if ($file == 'README.txt') {
$this->longdesc = file_get_contents($this->dir.'README.txt');
$this->longdesc = str_replace("\r", "", $this->longdesc); // filter windows CR
$this->longdesc = preg_replace("~[\s\t]*$~s", "", $this->longdesc); // filter trailing spaces at line endings
// Guess 'last update' time
if (preg_match('~\n(\d\d\d\d-\d\d-\d\d)~', $this->longdesc, $matches)) {
// Use first date entry (assumed to be a CHANGELOG)
$this->lastupdate = strtotime($matches[1]);
$found_real_lastupdate = true;
}
// Find number of releases
preg_match_all('~\n(\d\d\d\d-\d\d-\d\d)~', $this->longdesc, $matches, PREG_SET_ORDER);
$this->numreleases = count($matches);
// Find support lifespan (newest minus youngest)
$eldest = time();
$newest = 0;
foreach($matches as $match) {
$stamp = strtotime($match[1]);
$eldest = min($stamp, $eldest);
$newest = max($stamp, $newest);
}
$this->lifespan = floor(max(0, $newest - $eldest) / 3600); // could divide by 86400 but it doesn't matter
// Find 'written in'
if (preg_match('~Written in ([^\\n]+)~', $this->longdesc, $matches)) {
$this->subtag = rtrim($matches[1], ' .');
}
// Find `go-get` tags
$this->longdesc = preg_replace_callback('~\[go-get\](.+)\[/go-get\]~', function($matches) {
$this->go_get_target = $matches[1];
return '';
}, $this->longdesc);
// Find tags
if (preg_match('~Tags: ([^\\n]+)~', $this->longdesc, $matches)) {
$this->tags = array_map('trim', explode(',', $matches[1]));
}
// Find `entry` tags
$this->longdesc = preg_replace(
'~\\[entry=([^\\]]+?)\\](.+?)\\[/entry\\]~m',
'[url='.BASEURL.'\\1/]\\2[/url]', // hesc still hasn't happened, transform bbcode->bbcode
$this->longdesc
);
// Extract short description
$parts = explode("\n", $this->longdesc);
$this->shortdesc = array_shift($parts);
$this->shortdesc[0] = strtolower($this->shortdesc[0]); // cosmetic lowercase
// Filters for longdesc
$prefix_html = '';
$this->longdesc = preg_replace_callback('~\nWritten in ([^\\n]+)~ms', function($matches) use (&$prefix_html) {
$prefix_html .= (
(SHIELDS_PREFIX ? mkshield('build', 'success', 'brightgreen').'&nbsp;' : '').
mkshield('written in', rtrim($matches[1], '.'), 'blue')
);
return '';
}, $this->longdesc);
while(strpos($this->longdesc, "\n\n\n") !== false) {
$this->longdesc = str_replace("\n\n\n", "\n\n", $this->longdesc);
}
$this->longdesc = rtrim($this->longdesc, "\n")."\n";
$this->prefix_html = $prefix_html;
continue;
}
if (! $found_real_lastupdate) {
$this->lastupdate = max(
$this->lastupdate,
// filectime($this->dir.$file),
($file == 'README.txt' ? filectime($this->dir.$file) : filemtime($this->dir.$file)) // Don't count README updates
);
}
if (is_image($file)) {
$this->images[] = $file;
} else {
$this->downloads[] = $file;
}
}
natcasesort($this->downloads);
$this->downloads = array_reverse($this->downloads);
for($i = 0, $e = count($this->downloads); $i !== $e; ++$i) {
$this->downloads_hashes[] = (
sha1_file($this->dir.$this->downloads[$i])
);
}
}
public function genHomeImage() {
if (count($this->images)) {
$this->homeimage = mkthumbnail(
$this->dir.$this->images[0],
null, // raw handle
INDEX_THUMB_W, INDEX_THUMB_H
);
}
}
protected function filterLongDescArea() {
// If *all* downloads can be assigned to a changelog release entry,
// then move the files
// A release entry is marked by any string following the date field.
// Add one more NL than we really want, for regex reasons
$this->longdesc .= "\n";
preg_match_all('~^(\d\d\d\d-\d\d-\d\d)\s?:? (.+?)\n\n~ms', $this->longdesc, $matches, PREG_SET_ORDER);
do {
// Ensure changelog exists
if (! count($matches)) {
break;
}
// Find all tags
$known_tags = [];
foreach($matches as $i => $match) {
$tag = trim(explode("\n", $match[2])[0]);
$known_tags[$tag] = $i;
}
// Ensure all release entries have tags
if (count($matches) != count($known_tags)) {
error_log("[".$this->projname."] not all release entries have tags\n");
break;
}
// Ensure all downloads can be assigned to tags.
// In the event of a download matching multiple tags, it'll
// be assigned to the newest (topmost) entry
$found_idx = [];
$render_per_tag = [];
foreach(array_keys($known_tags) as $tagname) {
$render_per_tag[$tagname] = [];
}
foreach($this->downloads as $idx => $filename) {
foreach(array_keys($known_tags) as $tagname) {
if (stripos($filename, $tagname) !== false) {
$found_idx[$idx] = $tagname;
$render_per_tag[$tagname][$idx] = $filename;
break; // next file
}
}
}
if (count($found_idx) != count($this->downloads)) {
error_log("[".$this->projname."] not all downloads have matching tags");
break;
}
// Make HTML modifications
foreach($known_tags as $tag_name => $tag_idx) {
$find = rtrim($matches[$tag_idx][0]);
$this->longdesc = str_replace(
$find,
$find.'${{TAG_'.$tag_idx.'}}',
$this->longdesc
);
}
$this->longdesc = substr($this->longdesc, 0, strlen($this->longdesc)-1); // Strip the extra NL we added
$this->longdesc = text2html($this->longdesc);
foreach($known_tags as $tag_name => $tag_idx) {
$this->longdesc = str_replace(
'${{TAG_'.$tag_idx.'}}',
$this->renderDownloadsBlock($render_per_tag[$tag_name], false),
$this->longdesc
);
}
$this->longdesc = str_replace("</ul>\n<br />", "</ul>", $this->longdesc);
// Skip displaying the global downloads area
// This flag also indicates that the content has been pre-HTMLified
$this->downloads_section_was_replaced = true;
// Successful upgrade
} while(false);
if (! $this->downloads_section_was_replaced) {
$this->longdesc = substr($this->longdesc, 0, strlen($this->longdesc)-1); // Strip the extra NL we added
}
}
public function numDownloads() {
return count($this->downloads);
}
public function write() {
// Generate image thumbnails
foreach($this->images as $idx => $image) {
$outfile = BASEDIR.'wwwroot/img/'.$this->projname.'_'.$idx;
copy($this->dir.$image, $outfile.'.'.str_ext($image));
mkthumbnail($outfile.'.'.str_ext($image), $outfile.'_thumb.jpg', PAGE_THUMB_W, PAGE_THUMB_H);
}
// Copy downloads to wwwroot
foreach($this->downloads as $idx => $filename) {
$cmkdir = @mkdir( BASEDIR.'wwwroot/srv/'.$this->downloads_hashes[$idx] );
if (! $cmkdir) {
fputs(
STDOUT,
"WARNING: Couldn't create directory ".$this->downloads_hashes[$idx].
" for file '${filename}'".
" in project '".$this->projname."'!\n"
);
}
copy(
$this->dir.$filename,
BASEDIR.'wwwroot/srv/'.$this->downloads_hashes[$idx].'/'.$filename
);
}
// Generate index page
ob_start();
$this->index();
$extra_head_items = [];
$extra_head_items[] = '<link rel="canonical" href="'.hesc(BASEURL.$this->projname).'">'; // TODO include golang `go get` meta if necessary
if (strlen($this->go_get_target) > 0) {
$extra_head_items[] = '<meta name="go-import" content="'.hesc($this->go_get_target).'">'; // TODO include golang `go get` meta if necessary
}
$idxfile = template($this->projname.' | '.SITE_TITLE, ob_get_clean(), implode("\n", $extra_head_items));
mkdir(BASEDIR.'wwwroot/'.$this->projname);
file_put_contents(BASEDIR.'wwwroot/'.$this->projname.'/index.html', $idxfile); // new URL format
file_put_contents(BASEDIR.'wwwroot/'.$this->projname.'.html', redirecthtml(BASEURL.$this->projname)); // old URL format
}
public function getClassAttr() {
if (count($this->tags)) {
return 'taggedWith-'.implode(' taggedWith-', $this->tags);
} else {
return '';
}
}
public function renderDownloadsBlock($render_downloads, $include_header=false) {
if (! count($render_downloads)) {
return;
}
ob_start();
?>
<?php if ($include_header) { ?>
<strong>DOWNLOAD</strong>
<?php } ?>
<ul class="<?=$include_header ? 'downloads-large' : 'downloads-small' ?>">
<?php foreach($render_downloads as $idx => $filename) { ?>
<li>
<a href="<?=BASEURL?>srv/<?=hesc($this->downloads_hashes[$idx])?>/<?=hesc(rawurlencode($filename))?>"><?=hesc($filename)?></a>
<small>
<?=hesc(fbytes(filesize(BASEDIR.'wwwroot/srv/'.$this->downloads_hashes[$idx].'/'.$filename)))?>
</small>
</li>
<?php } ?>
</ul>
<?php
return ob_get_clean();
}
public function index() {
$this->filterLongDescArea();
$longdesc_html = $this->downloads_section_was_replaced ? $this->longdesc : text2html($this->longdesc);
?>
<h2><?=hesc(str_replace('_', ' ', $this->projname))?></h2>
<div class="projinfo">
<div class="projbody projbody_<?=(count($this->images) ? 'half' : 'full')?>w">
<?php if (strlen($this->prefix_html)) { ?>
<p style="margin-top:0;"><?=$this->prefix_html?></p>
<?php } ?>
<strong><?=hesc(strtoupper(ARTICLE_HEADER))?></strong>
<div class="content-paragraph">
<?=$longdesc_html?>
</div>
<?=file_get_contents(BASEDIR.'/footer.htm')?>
<?php if (! $this->downloads_section_was_replaced) { ?>
<?=$this->renderDownloadsBlock($this->downloads, true)?>
<?php } ?>
</div>
<?php if (count($this->images)) { ?>
<div class="projimg">
<?php foreach($this->images as $idx => $origname) { ?>
<a href="<?=BASEURL?>img/<?=hesc(urlencode($this->projname))?>_<?=$idx?>.<?=str_ext($origname)?>"><img src="<?=BASEURL?>img/<?=hesc(urlencode($this->projname))?>_<?=$idx?>_thumb.jpg" class="thumbimage"></a>
<?php } ?>
</div>
<div style="clear:both;"></div>
<?php } ?>
</div>
<?php
}
}

View File

@@ -1,54 +0,0 @@
#!/usr/bin/php
<?php
ini_set('display_errors', 'On');
date_default_timezone_set('Etc/UTC');
error_reporting(E_ALL);
require __DIR__.'/util.php';
require __DIR__.'/template.php';
require __DIR__.'/CProject.php';
define('SHIELDS_CACHE_DIR', __DIR__.'/../shields_cache/');
function main($args) {
$basedir = './';
$total = $args[0];
$pos = $args[1];
// Parse configuration
$config = @parse_ini_file(
$basedir . 'config.ini',
true,
INI_SCANNER_RAW
);
if ($config === false) {
die("[FATAL] Couldn't load '${basedir}/config.ini'!\n");
}
define('BASEDIR', $basedir);
define('BASEURL', trim($config['codesite']['baseurl']));
define('SITE_TITLE', trim($config['codesite']['title']));
define('PAGE_THUMB_W', intval($config['codesite']['page_thumb_w']));
define('PAGE_THUMB_H', intval($config['codesite']['page_thumb_h']));
define('INDEX_THUMB_W', intval($config['codesite']['index_thumb_w']));
define('INDEX_THUMB_H', intval($config['codesite']['index_thumb_h']));
define('SHOW_BLURBS', !(isset($config['codesite']['blurbs']) && $config['codesite']['blurbs'] === 'off') );
define('ARTICLE_HEADER', (isset($config['codesite']['article_header']) ? $config['codesite']['article_header'] : 'ABOUT') );
define('SHIELDS_PREFIX', isset($config['codesite']['shields_prefix']));
// Perform build tasks
if ($pos == 0) {
buildcommon();
if (array_key_exists('redirect', $config)) {
buildredirects( $config['redirect'] );
}
} else {
buildprojects($pos, array_decimate(listprojects(), $total, $pos));
}
}
main(array_slice($_SERVER['argv'], 1));

View File

@@ -1,189 +0,0 @@
<?php
function template($title, $content, $extra_head='') {
ob_start();
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" >
<meta name="viewport" content="width=960" >
<?=$extra_head?>
<link type="text/css" rel="stylesheet" href="<?=BASEURL?>static/normalize.css">
<link type="text/css" rel="stylesheet" href="<?=BASEURL?>static/style.css">
<script type="text/javascript" src="<?=BASEURL?>static/site.js"></script>
<title><?=hesc($title)?></title>
</head>
<body>
<div id="container">
<div id="content">
<?=file_get_contents(BASEDIR.'/header.htm')?>
<?=$content?>
</div>
</div>
</body>
</html>
<?php
return ob_get_clean();
}
function listprojects() {
// List projects
$ls = scandir(BASEDIR.'data');
rsort($ls);
$projects = array();
foreach($ls as $dirname) {
if ($dirname[0] == '.') continue;
if (! is_dir(BASEDIR.'data/'.$dirname)) continue;
$matches = array();
if (preg_match('~(?:\d+-)?(.+)~', $dirname, $matches)) {
$projects[$dirname] = $matches[1];
}
}
return $projects;
}
function buildprojects($id, $projects) {
$count = 0;
foreach($projects as $dirname => $projectname) {
echo sprintf("@%1d [%3d/%3d] ".$projectname."...\n", $id, ++$count, count($projects));
$pr = new CProject($dirname, $projectname);
$pr->write();
}
}
function buildcommon() {
echo "@0 [ 0/ ?] Common files...\n";
$projects = listprojects();
// Build all projects
$plist = array();
$handles = array();
$handle_lookup = array();
$alphasort = [];
foreach($projects as $dirname => $projectname) {
$pr = new CProject($dirname, $projectname);
$pr->genHomeImage(); // thumbnail
$plist[] = $pr;
if (is_null($pr->homeimage)) {
$handle_lookup[$projectname] = null;
} else {
$handle_lookup[$projectname] = count($handles);
$handles[] = $pr->homeimage;
}
$alphasort[] = [$pr->projname, count($plist)-1];
}
usort($alphasort, function($a, $b) {
return strcasecmp($a[0], $b[0]);
});
$alphaidx = [];
foreach($alphasort as $a) {
$alphaidx[ $a[1] ] = count($alphaidx);
}
// Build homepage spritesheet
if (count($handles)) {
mkspritesheet($handles, BASEDIR.'wwwroot/logos.jpg', INDEX_THUMB_W, INDEX_THUMB_H);
array_map('imagedestroy', $handles); // free
}
// Cache-busting stylesheet
$style = '.homeimage-sprite { background-image: url("logos.jpg?'.md5_file(BASEDIR.'wwwroot/logos.jpg').'"); }';
// Build index page
ob_start();
?>
<?php if (file_exists(BASEDIR.'homepage_blurb.htm')) { ?>
<!-- homepage blurb {{ -->
<?=file_get_contents(BASEDIR.'homepage_blurb.htm')?>
<!-- }} -->
<?php } ?>
<style type="text/css">
<?php echo $style; ?>
</style>
<table id="projtable-main" class="projtable">
<?php foreach ($plist as $i => $pr) { ?>
<tr class="<?=$pr->getClassAttr()?>"
data-sort-mt="-<?=$pr->lastupdate?>"
data-sort-ct="<?=$i?>"
data-sort-al="<?=$alphaidx[$i]?>"
data-sort-nr="-<?=$pr->numreleases?>"
data-sort-nf="-<?=$pr->numDownloads()?>"
data-sort-ls="-<?=$pr->lifespan?>"
>
<td>
<a href="<?=hesc(BASEURL.urlencode($pr->projname))?>/"><?=(is_null($handle_lookup[$pr->projname]) ? '<div class="no-image"></div>' : '<div class="homeimage homeimage-sprite" style="background-position:0 -'.($handle_lookup[$pr->projname]*INDEX_THUMB_H).'px"></div>')?></a>
</td>
<td>
<strong><?=hesc(str_replace('_', ' ', $pr->projname))?></strong><?php if (SHOW_BLURBS) { ?>,
<?=hesc($pr->shortdesc)?>
<?php } ?>
<a href="<?=hesc(BASEURL.urlencode($pr->projname))?>/" class="article-read-more">more...</a>
<?php if (strlen($pr->subtag) || count($pr->tags)) { ?>
<br>
<small>
<?=hesc($pr->subtag)?>
<?php if (strlen($pr->subtag) && count($pr->tags)) { ?>
::
<?php } ?>
<?php foreach($pr->tags as $tag) { ?>
<a class="tag tag-link" data-tag="<?=hesc($tag)?>"><?=hesc($tag)?></a>
<?php } ?>
</small>
<?php } ?>
</td>
</tr>
<?php } ?>
</table>
<?php
$extra_head = '<link rel="canonical" href="'.hesc(BASEURL).'">';
$index = template(SITE_TITLE, ob_get_clean(), $extra_head);
file_put_contents(BASEDIR.'wwwroot/index.html', $index);
// Done
}
function redirecthtml($target) {
ob_start();
?>
<meta http-equiv="refresh" content="0; url=<?=hesc($target)?>">
<a href="<?=hesc($target)?>">Moved &raquo;</a>
<?php
return ob_get_clean();
}
function buildredirects($redirects) {
foreach($redirects as $oldname => $newname) {
$page = redirecthtml($newname.'.html');
file_put_contents(BASEDIR.'wwwroot/'.$oldname.'.html', $page);
}
}

View File

@@ -1,170 +0,0 @@
<?php
function mkshield($left_str, $right_str, $color_str) {
$filename = sprintf(
"%s-%s-%s.svg",
rawurlencode(str_replace('-', '--', $left_str)),
rawurlencode(str_replace('-', '--', $right_str)),
rawurlencode($color_str)
);
$cache_path = SHIELDS_CACHE_DIR.$filename;
if (file_exists($cache_path)) {
return file_get_contents($cache_path);
} else {
$retn = file_get_contents('https://img.shields.io/badge/'.$filename);
file_put_contents($cache_path, $retn);
return $retn;
}
}
/**
* Create a thumbnail of an image. It overscales, centers, and crops to fit the
* target dimensions.
*
* @param string $src_file
* @param string $dest_file Null to return an image handle
* @param int $width
* @param int $height
* @return boolean
*/
function mkthumbnail($src_file, $dest_file, $width, $height) {
list($src_width, $src_height) = getimagesize($src_file);
$im = imagecreatefromstring(file_get_contents($src_file));
$dest = imagecreatetruecolor($width, $height);
imagefilledrectangle($dest, 0, 0, $width, $height, imagecolorallocate($dest, 0xFF, 0xFF, 0xFF));
$scale = max( $width/$src_width, $height/$src_height ); // overscale + crop
$box_w = $width/$scale;
$box_h = $height/$scale;
$box_xoff = floor(($src_width - $box_w)/2);
$box_yoff = floor(($src_height - $box_h)/2);
imagecopyresampled(
$dest, $im,
0, 0,
$box_xoff, $box_yoff,
$width, $height, $box_w, $box_h
);
imagedestroy($im);
if (is_null($dest_file)) {
return $dest;
} else {
return imagejpeg($dest, $dest_file);
}
}
function mkspritesheet(array $handles, $dest_file, $width, $height) {
$im = imagecreatetruecolor($width, $height * count($handles));
for($i = 0, $e = count($handles); $i != $e; ++$i) {
imagecopy($im, $handles[$i], 0, $i * $height, 0, 0, $width, $height);
}
if (is_null($dest_file)) {
return $dest_file;
} else {
return imagejpeg($im, $dest_file);
}
}
function fbytes($size, $suffixes='B|KiB|MiB|GiB|TiB') {
$sxlist = explode('|', $suffixes);
if ($size < 1024) {
return $size.$sxlist[0];
}
while ($size > 1024 && count($sxlist) >= 2) {
array_shift($sxlist);
$size /= 1024;
}
return number_format($size, 2).array_shift($sxlist);
}
function str_ext($sz) {
$dpos = strrpos($sz, '.');
return substr($sz, $dpos+1);
}
function is_image($sz) {
return in_array(strtolower(str_ext($sz)), ['jpg', 'png', 'jpeg']);
}
function hesc($sz) {
return @htmlentities($sz, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
function text2html($sz) {
$identity = function($sz) {
return $sz;
};
$splitInside = function($begin, $end, $sz) {
$parts = explode($begin, $sz);
if (count($parts) == 1) return [$sz];
$ret = [$parts[0]];
for($i = 1, $e = count($parts); $i !== $e; ++$i) {
$inner = explode($end, $parts[$i], 2);
$ret = array_merge($ret, $inner);
}
return $ret;
};
$oddEven = function(array $parts, $odd, $even, $join='') {
$ret = [];
for($i = 0, $e = count($parts); $i != $e; ++$i) {
$ret[] = ($i % 2) ? $odd($parts[$i]) : $even($parts[$i]);
}
return implode($join, $ret);
};
$sectionFmt = function($sz) use($oddEven, $identity) {
$base = hesc($sz);
$base = preg_replace('~^=+(.+)=+~m', '<strong>\\1</strong>', $base);
$base = preg_replace('~\\[url=([^\\]]+?)\\](.+?)\\[/url\\]~m', '<a href="\\1">\\2</a>', $base);
$base = preg_replace('~([^="])(https?://[^ \\r\\n\\t]+)~i', '\\1<a href="\\2">\\2</a>', $base);
$base = preg_replace('~\\[b\\](.+?)\\[/b\\]~m', '<strong>\\1</strong>', $base);
$base = preg_replace('~\\[i\\](.+?)\\[/i\\]~m', '<i>\\1</i>', $base);
$base = preg_replace('~\\[spoiler\\](.+?)\\[/spoiler\\]~m', '<span class="spoiler">\\1</span>', $base);
$base = preg_replace('~\n- ~ms', "\n&bull; ", $base);
$btparts = explode('`', $base);
if (count($btparts) > 1 && (count($btparts) % 2)) {
for ($i = 1, $e = count($btparts); $i < $e; $i += 2) {
$class = 'code';
if (strpos($btparts[$i], "\n") !== false) {
$class .= ' code-multiline';
}
$btparts[$i] = '<span class="'.$class.'">'.$btparts[$i].'</span>';
}
}
return $oddEven($btparts, $identity, 'nl2br');
};
$htmlSections = $splitInside('<html>', '</html>', $sz);
return $oddEven($htmlSections, $identity, $sectionFmt);
}
function array_decimate($array, $total, $partno) {
$ct = 0;
$ret = [];
foreach($array as $k => $v) {
if (++$ct % $total == ($partno - 1)) {
$ret[$k] = $v;
}
}
return $ret;
}

View File

@@ -1,14 +0,0 @@
#!/bin/bash
set -eu
tar cJvf "codesite-$(date +%s).tar.xz" \
--exclude='sites/codesite.example.com/wwwroot' \
rebuild.sh \
sites/codesite.example.com \
static \
lib \
--owner=0 --group=0
read -p "Press any key to continue..."

3
rebuild.cmd Normal file
View File

@@ -0,0 +1,3 @@
@echo off
C:\path\to\php_54\php.exe rebuild.php
pause

453
rebuild.php Normal file
View File

@@ -0,0 +1,453 @@
<?php
// Code-hosting website
// ````````````````````
// CONFIGURATION
// `````````````
define('BASEDIR', __DIR__.'\\');
define('SITE_TITLE', 'codesite');
define('PAGE_THUMB_W', 60);
define('PAGE_THUMB_H', 60);
define('INDEX_THUMB_W', 90);
define('INDEX_THUMB_H', 32); // recommend a multiple of the jpg iDCT block size
/**
* Create a thumbnail of an image. It overscales, centers, and crops to fit the
* target dimensions.
*
* @param string $src_file
* @param string $dest_file Null to return an image handle
* @param int $width
* @param int $height
* @return boolean
*/
function mkthumbnail($src_file, $dest_file, $width, $height) {
list($src_width, $src_height) = getimagesize($src_file);
$im = imagecreatefromstring(file_get_contents($src_file));
$dest = imagecreatetruecolor($width, $height);
imagefilledrectangle($dest, 0, 0, $width, $height, imagecolorallocate($dest, 0xFF, 0xFF, 0xFF));
$scale = max( $width/$src_width, $height/$src_height ); // overscale + crop
$box_w = $width/$scale;
$box_h = $height/$scale;
$box_xoff = floor(($src_width - $box_w)/2);
$box_yoff = floor(($src_height - $box_h)/2);
imagecopyresampled(
$dest, $im,
0, 0,
$box_xoff, $box_yoff,
$width, $height, $box_w, $box_h
);
imagedestroy($im);
if (is_null($dest_file)) {
return $dest;
} else {
return imagejpeg($dest, $dest_file);
}
}
function mkspritesheet(array $handles, $dest_file, $width, $height) {
$im = imagecreatetruecolor($width, $height * count($handles));
for($i = 0, $e = count($handles); $i != $e; ++$i) {
imagecopy($im, $handles[$i], 0, $i * $height, 0, 0, $width, $height);
}
if (is_null($dest_file)) {
return $dest_file;
} else {
return imagejpeg($im, $dest_file);
}
}
function fbytes($size, $suffixes='B|KiB|MiB|GiB|TiB') {
$sxlist = explode('|', $suffixes);
if ($size < 1024) {
return $size.$sxlist[0];
}
while ($size > 1024 && count($sxlist) >= 2) {
array_shift($sxlist);
$size /= 1024;
}
return number_format($size, 2).array_shift($sxlist);
}
function str_ext($sz) {
$dpos = strrpos($sz, '.');
return substr($sz, $dpos+1);
}
function is_image($sz) {
return in_array(strtolower(str_ext($sz)), ['jpg', 'png', 'jpeg']);
}
function hesc($sz) {
return @htmlentities($sz, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
function text2html($sz) {
$sectionFmt = function($sz) {
$base = hesc($sz);
$base = preg_replace('~=+(.+)=+~', '<strong>\\1</strong>', $base);
$base = preg_replace('~(https?://[^ \\r\\n\\t]+)~i', '<a href="\\1">\\1</a>', $base);
$btparts = explode('`', $base);
if (count($btparts) > 1 && (count($btparts) % 2)) {
for ($i = 1, $e = count($btparts); $i < $e; $i += 2) {
$btparts[$i] = '<span class="code">'.$btparts[$i].'</span>';
}
$base = implode('', $btparts);
}
return nl2br($base);
};
$identity = function($sz) {
return $sz;
};
$splitInside = function($begin, $end, $sz) {
$parts = explode($begin, $sz);
if (count($parts) == 1) return [$sz];
$ret = [$parts[0]];
for($i = 1, $e = count($parts); $i !== $e; ++$i) {
$inner = explode($end, $parts[$i], 2);
$ret = array_merge($ret, $inner);
}
return $ret;
};
$oddEven = function(array $parts, $odd, $even, $join='') {
$ret = [];
for($i = 0, $e = count($parts); $i != $e; ++$i) {
$ret[] = ($i % 2) ? $odd($parts[$i]) : $even($parts[$i]);
}
return implode($join, $ret);
};
$htmlSections = $splitInside('<html>', '</html>', $sz);
return $oddEven($htmlSections, $identity, $sectionFmt);
}
function array_decimate($array, $total, $partno) {
$ct = 0;
$ret = [];
foreach($array as $k => $v) {
if (++$ct % $total == ($partno - 1)) {
$ret[$k] = $v;
}
}
return $ret;
}
/**
*
*/
class CProject {
private $dir;
public $projname;
public $shortdesc = '(no description)';
public $subtag = '';
private $longdesc = '';
private $images = array();
private $downloads = array();
public $tags = array();
public $homeimage = null;
public function __construct($dirname, $projname) {
$this->dir = BASEDIR.'data/'.$dirname.'/';
$this->projname = $projname;
// Identify resources in folder
$ls = scandir($this->dir);
foreach($ls as $file) {
if ($file[0] == '.') continue;
if ($file == 'README.txt') {
$this->longdesc = file_get_contents($this->dir.'README.txt');
$matches = array();
if (preg_match('~Written in ([^\\r\\n]+)~', $this->longdesc, $matches)) {
$this->subtag = rtrim($matches[1], ' .');
}
if (preg_match('~Tags: ([^\\r\\n]+)~', $this->longdesc, $matches)) {
$this->tags = array_map('trim', explode(',', $matches[1]));
}
$parts = explode("\n", $this->longdesc);
$this->shortdesc = array_shift($parts);
$this->shortdesc[0] = strtolower($this->shortdesc[0]); // cosmetic lowercase
continue;
}
if (is_image($file)) {
$this->images[] = $file;
} else {
$this->downloads[] = $file;
}
}
natcasesort($this->downloads);
$this->downloads = array_reverse($this->downloads);
}
public function genHomeImage() {
if (count($this->images)) {
$this->homeimage = mkthumbnail(
$this->dir.$this->images[0], //BASEDIR.'wwwroot/srv/'.$this->projname.'_0.'.str_ext($this->images[0]),
null, // raw handle
INDEX_THUMB_W, INDEX_THUMB_H
);
}
}
public function write() {
// Generate image thumbnails
foreach($this->images as $idx => $image) {
$outfile = BASEDIR.'wwwroot/srv/'.$this->projname.'_'.$idx;
copy($this->dir.$image, $outfile.'.'.str_ext($image));
mkthumbnail($outfile.'.'.str_ext($image), $outfile.'_thumb.jpg', PAGE_THUMB_W, PAGE_THUMB_H);
}
// Copy downloads to wwwroot
foreach($this->downloads as $idx => $filename) {
copy($this->dir.$filename, BASEDIR.'wwwroot/srv/'.$filename);
}
// Generate index page
ob_start();
$this->index();
$idxfile = template($this->projname.' | '.SITE_TITLE, ob_get_clean());
file_put_contents(BASEDIR.'wwwroot/'.$this->projname.'.html', $idxfile);
}
public function getClassAttr() {
if (count($this->tags)) {
return 'taggedWith-'.implode(' taggedWith-', $this->tags);
} else {
return '';
}
}
public function index() {
?>
<h2><?=hesc($this->projname)?></h2>
<div class="projinfo">
<div class="projbody projbody_<?=(count($this->images) ? 'half' : 'full')?>w">
<strong>ABOUT</strong>
<p><?=text2html($this->longdesc)?></p>
<?=file_get_contents(__DIR__.'/footer.htm')?>
<?php if (count($this->downloads)) { ?>
<strong>DOWNLOAD</strong>
<ul>
<?php foreach($this->downloads as $filename) { ?>
<li>
<a href="srv/<?=hesc(rawurlencode($filename))?>"><?=hesc($filename)?></a>
<small>
<?=hesc(fbytes(filesize(BASEDIR.'wwwroot/srv/'.$filename)))?>
</small>
</li>
<?php } ?>
</ul>
<?php } ?>
</div>
<?php if (count($this->images)) { ?>
<div class="projimg">
<?php foreach($this->images as $idx => $origname) { ?>
<a href="srv/<?=hesc(urlencode($this->projname))?>_<?=$idx?>.<?=str_ext($origname)?>"><img src="srv/<?=hesc(urlencode($this->projname))?>_<?=$idx?>_thumb.jpg" class="thumbimage"></a>
<?php } ?>
</div>
<?php } ?>
</div>
<?php
}
}
function template($title, $content) {
ob_start();
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" >
<meta name="viewport" content="width=768px" >
<link type="text/css" rel="stylesheet" href="normalize.css">
<link type="text/css" rel="stylesheet" href="style.css">
<script type="text/javascript" src="site.js"></script>
<title><?=hesc($title)?></title>
</head>
<body>
<div id="container">
<div id="content">
<?=file_get_contents(__DIR__.'/header.htm')?>
<?=$content?>
</div>
</div>
</body>
</html>
<?php
return ob_get_clean();
}
function listprojects() {
// List projects
$ls = scandir(BASEDIR.'data');
rsort($ls);
$projects = array();
foreach($ls as $dirname) {
if ($dirname[0] == '.') continue;
$matches = array();
if (preg_match('~(?:\d+-)?(.+)~', $dirname, $matches)) {
$projects[$dirname] = $matches[1];
}
}
return $projects;
}
function buildprojects($id, $projects) {
$count = 0;
foreach($projects as $dirname => $projectname) {
echo sprintf("@%1d [%3d/%3d] ".$projectname."...\n", $id, ++$count, count($projects));
$pr = new CProject($dirname, $projectname);
$pr->write();
}
}
function buildcommon() {
echo "@0 [ 0/ ?] Common files...\n";
$projects = listprojects();
// Build all projects
$plist = array();
$handles = array();
$handle_lookup = array();
foreach($projects as $dirname => $projectname) {
$pr = new CProject($dirname, $projectname);
$pr->genHomeImage(); // thumbnail
$plist[] = $pr;
if (is_null($pr->homeimage)) {
$handle_lookup[$projectname] = null;
} else {
$handle_lookup[$projectname] = count($handles);
$handles[] = $pr->homeimage;
}
}
// Build homepage spritesheet
mkspritesheet($handles, BASEDIR.'wwwroot/logos.jpg', INDEX_THUMB_W, INDEX_THUMB_H);
array_map('imagedestroy', $handles); // free
// Build index page
ob_start();
?>
<?php if (file_exists(BASEDIR.'homepage_blurb.htm')) { ?>
<!-- homepage blurb {{ -->
<?=file_get_contents(BASEDIR.'homepage_blurb.htm')?>
<!-- }} -->
<?php } ?>
<table class="projtable">
<?php foreach ($plist as $pr) { ?>
<tr class="<?=$pr->getClassAttr()?>">
<td>
<a href="<?=hesc(urlencode($pr->projname))?>.html"><?=(is_null($handle_lookup[$pr->projname]) ? '<div class="no-image"></div>' : '<div class="homeimage homeimage-sprite" style="background-position:0 -'.($handle_lookup[$pr->projname]*INDEX_THUMB_H).'px"></div>')?></a>
</td>
<td>
<strong><?=hesc($pr->projname)?></strong>,
<?=hesc($pr->shortdesc)?>
<a href="<?=hesc(urlencode($pr->projname))?>.html">more...</a>
<?php if (strlen($pr->subtag) || count($pr->tags)) { ?>
<br>
<small>
<?=hesc($pr->subtag)?>
<?php if (strlen($pr->subtag) && count($pr->tags)) { ?>
::
<?php } ?>
<?php foreach($pr->tags as $tag) { ?>
<a class="tag tag-link" data-tag="<?=hesc($tag)?>"><?=hesc($tag)?></a>
<?php } ?>
</small>
<?php } ?>
</td>
</tr>
<?php } ?>
</table>
<?php
$index = template(SITE_TITLE, ob_get_clean());
file_put_contents(BASEDIR.'wwwroot/index.html', $index);
// Done
}
function main($args) {
$total = $args[0];
$pos = $args[1];
if ($pos == 0) {
buildcommon();
} else {
buildprojects($pos, array_decimate(listprojects(), $total, $pos));
}
}
// Entry point
//
ini_set('display_errors', 'On');
error_reporting(E_ALL);
main(array_slice($_SERVER['argv'], 1));

View File

@@ -1,65 +0,0 @@
#!/bin/bash
set -eu
APP_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
numcpus() {
cat /proc/cpuinfo | grep '^processor' | wc -l
}
buildsite() {
echo "Site: ${1}"
pushd "$1" >/dev/null
echo "Cleaning wwwroot directory..."
if [[ -d wwwroot ]] ; then
rm -r wwwroot
fi
mkdir -p wwwroot/{img,srv,static}
echo "Copying static resources..."
if [[ ! -d static ]] ; then
mkdir static
fi
cp "${APP_DIR}/static/"* wwwroot/static || true
cp static/* wwwroot/static || true
for htm in footer header homepage_blurb ; do
if [[ ! -f "${htm}.htm" ]] ; then
touch "${htm}.htm"
fi
done
echo "Building pages..."
local threadcount=$(numcpus)
for i in $(seq 0 "$threadcount") ; do
"${APP_DIR}/lib/bootstrap.php" "$threadcount" "$i" &
done
wait
echo "Site: ${1} finished."
echo ""
popd >/dev/null
}
usage() {
echo "USAGE: ./rebuild.sh path-to-siteroot"
exit 1
}
main() {
if [[ $# -ne 1 ]] ; then
usage
fi
buildsite "$1"
}
main "$@"

View File

@@ -1,13 +0,0 @@
[codesite]
title=codesite.example.com
page_thumb_w=60
page_thumb_h=60
index_thumb_w=90
index_thumb_h=32
; n.b. Recommend a multiple of the JPEG iDCT block size for index_thumb_h
[redirect]
; old project name = new project name
old-project-name=example-project

View File

@@ -1,14 +0,0 @@
<p>
Homepage blurb goes here.
</p>
<p>
<select id="sortorder" style="float:right;">
<option value="a">Youngest project first</option>
<option value="b">Recent updates first</option>
<option value="c">Alphabetical</option>
</select>
<strong>PROJECTS</strong>
</p>

BIN
static/header.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 436 B

After

Width:  |  Height:  |  Size: 436 B

571
static/normalize.css vendored
View File

@@ -1,419 +1,406 @@
/*! normalize.css v4.1.1 | MIT License | github.com/necolas/normalize.css */ /*! normalize.css v2.1.3 | MIT License | git.io/normalize */
/** /* ==========================================================================
* 1. Change the default font family in all browsers (opinionated). HTML5 display definitions
* 2. Prevent adjustments of font size after orientation changes in IE and iOS.
*/
html {
font-family: sans-serif; /* 1 */
-ms-text-size-adjust: 100%; /* 2 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/**
* Remove the margin in all browsers (opinionated).
*/
body {
margin: 0;
}
/* HTML5 display definitions
========================================================================== */ ========================================================================== */
/** /**
* Add the correct display in IE 9-. * Correct `block` display not defined in IE 8/9.
* 1. Add the correct display in Edge, IE, and Firefox.
* 2. Add the correct display in IE.
*/ */
article, article,
aside, aside,
details, /* 1 */ details,
figcaption, figcaption,
figure, figure,
footer, footer,
header, header,
main, /* 2 */ hgroup,
menu, main,
nav, nav,
section, section,
summary { /* 1 */ summary {
display: block; display: block;
} }
/** /**
* Add the correct display in IE 9-. * Correct `inline-block` display not defined in IE 8/9.
*/ */
audio, audio,
canvas, canvas,
progress,
video { video {
display: inline-block; display: inline-block;
} }
/** /**
* Add the correct display in iOS 4-7. * Prevent modern browsers from displaying `audio` without controls.
* Remove excess height in iOS 5 devices.
*/ */
audio:not([controls]) { audio:not([controls]) {
display: none; display: none;
height: 0; height: 0;
} }
/** /**
* Add the correct vertical alignment in Chrome, Firefox, and Opera. * Address `[hidden]` styling not present in IE 8/9.
* Hide the `template` element in IE, Safari, and Firefox < 22.
*/ */
progress { [hidden],
vertical-align: baseline; template {
display: none;
} }
/** /* ==========================================================================
* Add the correct display in IE 10-. Base
* 1. Add the correct display in IE.
*/
template, /* 1 */
[hidden] {
display: none;
}
/* Links
========================================================================== */ ========================================================================== */
/** /**
* 1. Remove the gray background on active links in IE 10. * 1. Set default font family to sans-serif.
* 2. Remove gaps in links underline in iOS 8+ and Safari 8+. * 2. Prevent iOS text size adjust after orientation change, without disabling
* user zoom.
*/ */
a { html {
background-color: transparent; /* 1 */ font-family: sans-serif; /* 1 */
-webkit-text-decoration-skip: objects; /* 2 */ -ms-text-size-adjust: 100%; /* 2 */
-webkit-text-size-adjust: 100%; /* 2 */
} }
/** /**
* Remove the outline on focused links when they are also active or hovered * Remove default margin.
* in all browsers (opinionated). */
body {
margin: 0;
}
/* ==========================================================================
Links
========================================================================== */
/**
* Remove the gray background color from active links in IE 10.
*/
a {
background: transparent;
}
/**
* Address `outline` inconsistency between Chrome and other browsers.
*/
a:focus {
outline: thin dotted;
}
/**
* Improve readability when focused and also mouse hovered in all browsers.
*/ */
a:active, a:active,
a:hover { a:hover {
outline-width: 0; outline: 0;
} }
/* Text-level semantics /* ==========================================================================
Typography
========================================================================== */ ========================================================================== */
/** /**
* 1. Remove the bottom border in Firefox 39-. * Address variable `h1` font-size and margin within `section` and `article`
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. * contexts in Firefox 4+, Safari 5, and Chrome.
*/
abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
text-decoration: underline dotted; /* 2 */
}
/**
* Prevent the duplicate application of `bolder` by the next rule in Safari 6.
*/
b,
strong {
font-weight: inherit;
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* Add the correct font style in Android 4.3-.
*/
dfn {
font-style: italic;
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
*/ */
h1 { h1 {
font-size: 2em; font-size: 2em;
margin: 0.67em 0; margin: 0.67em 0;
} }
/** /**
* Add the correct background and color in IE 9-. * Address styling not present in IE 8/9, Safari 5, and Chrome.
*/
abbr[title] {
border-bottom: 1px dotted;
}
/**
* Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome.
*/
b,
strong {
font-weight: bold;
}
/**
* Address styling not present in Safari 5 and Chrome.
*/
dfn {
font-style: italic;
}
/**
* Address differences between Firefox and other browsers.
*/
hr {
-moz-box-sizing: content-box;
box-sizing: content-box;
height: 0;
}
/**
* Address styling not present in IE 8/9.
*/ */
mark { mark {
background-color: #ff0; background: #ff0;
color: #000; color: #000;
} }
/** /**
* Add the correct font size in all browsers. * Correct font family set oddly in Safari 5 and Chrome.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Embedded content
========================================================================== */
/**
* Remove the border on images inside links in IE 10-.
*/
img {
border-style: none;
}
/**
* Hide the overflow in IE.
*/
svg:not(:root) {
overflow: hidden;
}
/* Grouping content
========================================================================== */
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/ */
code, code,
kbd, kbd,
pre, pre,
samp { samp {
font-family: monospace, monospace; /* 1 */ font-family: monospace, serif;
font-size: 1em; /* 2 */ font-size: 1em;
} }
/** /**
* Add the correct margin in IE 8. * Improve readability of pre-formatted text in all browsers.
*/ */
figure { pre {
margin: 1em 40px; white-space: pre-wrap;
} }
/** /**
* 1. Add the correct box sizing in Firefox. * Set consistent quote types.
* 2. Show the overflow in Edge and IE.
*/ */
hr { q {
box-sizing: content-box; /* 1 */ quotes: "\201C" "\201D" "\2018" "\2019";
height: 0; /* 1 */
overflow: visible; /* 2 */
} }
/* Forms /**
* Address inconsistent and variable font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sup {
top: -0.5em;
}
sub {
bottom: -0.25em;
}
/* ==========================================================================
Embedded content
========================================================================== */ ========================================================================== */
/** /**
* 1. Change font properties to `inherit` in all browsers (opinionated). * Remove border when inside `a` element in IE 8/9.
* 2. Remove the margin in Firefox and Safari. */
img {
border: 0;
}
/**
* Correct overflow displayed oddly in IE 9.
*/
svg:not(:root) {
overflow: hidden;
}
/* ==========================================================================
Figures
========================================================================== */
/**
* Address margin not present in IE 8/9 and Safari 5.
*/
figure {
margin: 0;
}
/* ==========================================================================
Forms
========================================================================== */
/**
* Define consistent border, margin, and padding.
*/
fieldset {
border: 1px solid #c0c0c0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
/**
* 1. Correct `color` not being inherited in IE 8/9.
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
*/
legend {
border: 0; /* 1 */
padding: 0; /* 2 */
}
/**
* 1. Correct font family not being inherited in all browsers.
* 2. Correct font size not being inherited in all browsers.
* 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome.
*/ */
button, button,
input, input,
select, select,
textarea { textarea {
font: inherit; /* 1 */ font-family: inherit; /* 1 */
margin: 0; /* 2 */ font-size: 100%; /* 2 */
margin: 0; /* 3 */
} }
/** /**
* Restore the font weight unset by the previous rule. * Address Firefox 4+ setting `line-height` on `input` using `!important` in
*/ * the UA stylesheet.
optgroup {
font-weight: bold;
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/ */
button, button,
input { /* 1 */ input {
overflow: visible; line-height: normal;
} }
/** /**
* Remove the inheritance of text transform in Edge, Firefox, and IE. * Address inconsistent `text-transform` inheritance for `button` and `select`.
* 1. Remove the inheritance of text transform in Firefox. * All other form control elements do not inherit `text-transform` values.
* Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+.
* Correct `select` style inheritance in Firefox 4+ and Opera.
*/ */
button, button,
select { /* 1 */ select {
text-transform: none; text-transform: none;
} }
/** /**
* 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
* controls in Android 4. * and `video` controls.
* 2. Correct the inability to style clickable types in iOS and Safari. * 2. Correct inability to style clickable `input` types in iOS.
* 3. Improve usability and consistency of cursor style between image-type
* `input` and others.
*/ */
button, button,
html [type="button"], /* 1 */ html input[type="button"], /* 1 */
[type="reset"], input[type="reset"],
[type="submit"] { input[type="submit"] {
-webkit-appearance: button; /* 2 */ -webkit-appearance: button; /* 2 */
cursor: pointer; /* 3 */
} }
/** /**
* Remove the inner border and padding in Firefox. * Re-set default cursor for disabled elements.
*/
button[disabled],
html input[disabled] {
cursor: default;
}
/**
* 1. Address box sizing set to `content-box` in IE 8/9/10.
* 2. Remove excess padding in IE 8/9/10.
*/
input[type="checkbox"],
input[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
* 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
* (include `-moz` to future-proof).
*/
input[type="search"] {
-webkit-appearance: textfield; /* 1 */
-moz-box-sizing: content-box;
-webkit-box-sizing: content-box; /* 2 */
box-sizing: content-box;
}
/**
* Remove inner padding and search cancel button in Safari 5 and Chrome
* on OS X.
*/
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* Remove inner padding and border in Firefox 4+.
*/ */
button::-moz-focus-inner, button::-moz-focus-inner,
[type="button"]::-moz-focus-inner, input::-moz-focus-inner {
[type="reset"]::-moz-focus-inner, border: 0;
[type="submit"]::-moz-focus-inner { padding: 0;
border-style: none;
padding: 0;
} }
/** /**
* Restore the focus styles unset by the previous rule. * 1. Remove default vertical scrollbar in IE 8/9.
*/ * 2. Improve readability and alignment in all browsers.
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Change the border, margin, and padding in all browsers (opinionated).
*/
fieldset {
border: 1px solid #c0c0c0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
legend {
box-sizing: border-box; /* 1 */
color: inherit; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
}
/**
* Remove the default vertical scrollbar in IE.
*/ */
textarea { textarea {
overflow: auto; overflow: auto; /* 1 */
vertical-align: top; /* 2 */
} }
/* ==========================================================================
Tables
========================================================================== */
/** /**
* 1. Add the correct box sizing in IE 10-. * Remove most spacing between table cells.
* 2. Remove the padding in IE 10-.
*/ */
[type="checkbox"], table {
[type="radio"] { border-collapse: collapse;
box-sizing: border-box; /* 1 */ border-spacing: 0;
padding: 0; /* 2 */
} }
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[type="search"] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/**
* Remove the inner padding and cancel buttons in Chrome and Safari on OS X.
*/
[type="search"]::-webkit-search-cancel-button,
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* Correct the text style of placeholders in Chrome, Edge, and Safari.
*/
::-webkit-input-placeholder {
color: inherit;
opacity: 0.54;
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}

View File

Before

Width:  |  Height:  |  Size: 233 B

After

Width:  |  Height:  |  Size: 233 B

View File

Before

Width:  |  Height:  |  Size: 264 B

After

Width:  |  Height:  |  Size: 264 B

View File

@@ -1,10 +1,6 @@
(function() { (function() {
"use strict"; "use strict";
//
// Tag support
//
var show_all = function() { var show_all = function() {
var tr = document.querySelectorAll(".projtable tr"); var tr = document.querySelectorAll(".projtable tr");
for (var i = 0, e = tr.length; i !== e; ++i) { for (var i = 0, e = tr.length; i !== e; ++i) {
@@ -37,10 +33,7 @@
}; };
var get_show_tag = function(tag) { var get_show_tag = function(tag) {
return function() { return function() { show_tag(tag); return false; };
show_tag(tag);
return false;
};
}; };
window.addEventListener('load', function() { window.addEventListener('load', function() {
@@ -50,34 +43,4 @@
taglinks[i].addEventListener('click', get_show_tag(tag)); taglinks[i].addEventListener('click', get_show_tag(tag));
} }
}); });
})();
//
// Sort support (theme opt-in)
//
var sort_rows = function(cb) {
var tr = document.querySelectorAll(".projtable tr");
var items = [];
for (var i = 0, e = tr.length; i !== e; ++i) {
items.push([i, cb(tr[i])]);
}
items.sort(function(a, b) {
return (a[1] - b[1]);
});
for (var i = 0, e = items.length; i !== e; ++i) {
var el = tr[items[i][0]];
var parent = el.parentElement;
parent.removeChild(el);
parent.appendChild(el);
}
};
var sort_update = function(sort_by) {
sort_rows(function(el) {
return el.getAttribute(sort_by);
});
};
window.sortUpdate = sort_update;
})();