33 Commits
v64 ... v97

Author SHA1 Message Date
138a3c3342 improve update-time detection 2015-11-08 11:49:18 +13:00
fc28e8c5c8 improve newline handling, remove redundant newlines 2015-11-08 11:35:43 +13:00
d67c7fc926 style: use bullets for unordered lists 2015-11-08 11:35:29 +13:00
8a9b36d3db style: multiline code blocks 2015-11-08 11:35:07 +13:00
deea763923 style: mobile css 2015-11-08 11:34:58 +13:00
8f34df0cbd patch halfw width 2015-11-07 18:41:08 +13:00
87f6da2957 fix viewport width value+syntax 2015-11-07 18:38:53 +13:00
766fee12d5 switch background pattern 2015-11-07 18:37:33 +13:00
6488bed03c more github-like CSS theme 2015-11-07 18:33:58 +13:00
ea7092ebef remove redundant html tags 2015-11-07 18:26:46 +13:00
ac79051db4 definitively shunt shields to top 2015-11-07 18:25:48 +13:00
1194223ddd shunt shields (almost) to the top 2015-11-07 18:22:10 +13:00
ad76f6fb7a config flag "shields_prefix" to add a "build:success" shield to everything 2015-11-07 18:20:17 +13:00
f39a05ba59 replace 'written in' with a shields.io SVG 2015-11-07 18:15:01 +13:00
67f20f4379 fix extra space near project titles 2015-11-01 13:50:25 +13:00
2c857250c0 update paths for now using cygwin php 2015-10-11 15:25:36 +13:00
931b6e0208 validate only load directories from /data/ directories 2015-09-06 11:59:08 +12:00
8ea9ca2b2f hgignore drafts for the storytime.ivysaur.me site 2015-09-06 11:56:18 +12:00
9def31abeb track static elements for the storytime.ivysaur.me site 2015-09-06 11:55:57 +12:00
1878d023bf add css class to 'more...' links to allow customising in stylesheet 2015-09-06 11:49:09 +12:00
fef327ec61 replace underscores with spaces in titles 2015-09-06 11:48:54 +12:00
5ddd86e4aa support [b], [i], [spoiler], and [entry] bbcode tags in formatting 2015-09-06 11:48:40 +12:00
87dd96adbd optional 'article_header={string}' directive in site's config.ini file 2015-09-06 11:48:26 +12:00
9de0095c8d optional 'blurbs=off' directive in site's config.ini file 2015-09-06 11:01:13 +12:00
7480da7568 Added tag release-r72 for changeset d4733a95c342 2015-04-05 16:59:38 +12:00
81c65b3cce changelog pre release 2015-04-05 16:59:13 +12:00
441c05f096 if no projects have any images, don't generate spritesheet 2015-04-05 16:51:52 +12:00
671573bc2e sha1 hashes for files, to prevent overwriting srv with same name (e.g. 'screenshot.jpg') 2015-04-05 16:49:17 +12:00
17b0bea213 TODO: switchable CSS 2015-04-05 16:36:31 +12:00
0ad96058d5 TODO: update completed items 2015-04-05 16:36:13 +12:00
092efca34d support redirects 2015-04-05 16:35:54 +12:00
0303019641 only count filemtime, not filectime 2015-04-05 16:29:28 +12:00
8efff11ac2 Added tag release-r64 for changeset 0f89ae041c2e 2015-04-05 16:25:06 +12:00
22 changed files with 401 additions and 38 deletions

View File

@@ -2,3 +2,5 @@ mode:regex
^_dist/
^sites/[^/]+/data/
^sites/[^/]+/wwwroot/
^shields_cache/

View File

@@ -1 +1,3 @@
42a17645b5b21d7fe395767de7fa3e26ee999014 release-r54
0f89ae041c2ee60cc1ea308d047fce816b19c490 release-r64
d4733a95c3428db8722ce0d0350d17bbbabc8720 release-r72

View File

@@ -1,16 +1,8 @@
- Support project redirection (e.g. `code.ivysaur.me` was renamed to `codesite`, breaking links)
- If no projects have any images, don't generate spritesheet
- Move tag javascript and no-image image out of per-site configuration
- Merge "written in" and "tags"
- Per-project directories, to prevent overwriting files (e.g. 'screenshot.jpg')
- Cache recent changes somehow
- RSS for recent changes
- RSS for all projects
- Switchable CSS (reddit theme, 4chan theme, HN theme)

View File

@@ -14,6 +14,12 @@ Written in PHP, Bash
=CHANGELOG=
2015-04-05: r72
- 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: r64
- Feature: Support sorting projects

View File

@@ -3,6 +3,21 @@
// Code-hosting website
// ````````````````````
function mkshield($left_str, $right_str, $color_str) {
$filename = rawurlencode(str_replace('-', '--', $left_str)).'-'.rawurlencode(str_replace('-', '--', $right_str)).'-'.rawurlencode($color_str).'.svg';
$cache_path = __DIR__.'/../../shields_cache/'.$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.
@@ -116,11 +131,20 @@ function text2html($sz) {
$base = preg_replace('~^=+(.+)=+~m', '<strong>\\1</strong>', $base);
$base = preg_replace('~(https?://[^ \\r\\n\\t]+)~i', '<a href="\\1">\\1</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('~\\[entry=([^\\]]+?)\\](.+?)\\[/entry\\]~m', '<a href="\\1.html">\\2</a>', $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) {
$btparts[$i] = '<span class="code">'.$btparts[$i].'</span>';
$class = 'code';
if (strpos($btparts[$i], "\n") !== false) {
$class .= ' code-multiline';
}
$btparts[$i] = '<span class="'.$class.'">'.$btparts[$i].'</span>';
}
}
@@ -153,8 +177,10 @@ class CProject {
public $subtag = '';
public $lastupdate = 0;
private $longdesc = '';
private $prefix_html = '';
private $images = array();
private $downloads = array();
private $downloads_hashes = array();
public $tags = array();
public $homeimage = null;
@@ -166,13 +192,23 @@ class CProject {
// 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->lastupdate = max($this->lastupdate, filectime($this->dir.$file)); // don't count README updates
// Guess 'last update' time
$matches = [];
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;
}
$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], ' .');
@@ -185,14 +221,36 @@ class CProject {
$parts = explode("\n", $this->longdesc);
$this->shortdesc = array_shift($parts);
$this->shortdesc[0] = strtolower($this->shortdesc[0]); // cosmetic lowercase
// Filter longdesc
$this->longdesc = str_replace("\r", "", $this->longdesc); // filter windows CR
$prefix_html = '';
$this->longdesc = preg_replace_callback('~\r?\nWritten in ([^\\n]+)~ms', function($matches) use (&$prefix_html) {
$prefix_html .= (
(SHIELDS_PREFIX ? mkshield('build', 'success', 'brightgreen').'&nbsp;' : '').
mkshield('written in', $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");
$this->prefix_html = $prefix_html;
continue;
}
if (! $found_real_lastupdate) {
$this->lastupdate = max(
$this->lastupdate,
filemtime($this->dir.$file),
filectime($this->dir.$file)
// 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;
@@ -203,6 +261,12 @@ class CProject {
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() {
@@ -231,7 +295,21 @@ class CProject {
// Copy downloads to wwwroot
foreach($this->downloads as $idx => $filename) {
copy($this->dir.$filename, BASEDIR.'wwwroot/srv/'.$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
@@ -252,13 +330,17 @@ class CProject {
public function index() {
?>
<h2><?=hesc($this->projname)?></h2>
<h2><?=hesc(str_replace('_', ' ', $this->projname))?></h2>
<div class="projinfo">
<div class="projbody projbody_<?=(count($this->images) ? 'half' : 'full')?>w">
<strong>ABOUT</strong>
<?php if (strlen($this->prefix_html)) { ?>
<p style="margin-top:0;"><?=$this->prefix_html?></p>
<?php } ?>
<strong><?=hesc(strtoupper(ARTICLE_HEADER))?></strong>
<p><?=text2html($this->longdesc)?></p>
@@ -269,11 +351,11 @@ class CProject {
<strong>DOWNLOAD</strong>
<ul>
<?php foreach($this->downloads as $filename) { ?>
<?php foreach($this->downloads as $idx => $filename) { ?>
<li>
<a href="srv/<?=hesc(rawurlencode($filename))?>"><?=hesc($filename)?></a>
<a href="srv/<?=hesc($this->downloads_hashes[$idx])?>/<?=hesc(rawurlencode($filename))?>"><?=hesc($filename)?></a>
<small>
<?=hesc(fbytes(filesize(BASEDIR.'wwwroot/srv/'.$filename)))?>
<?=hesc(fbytes(filesize(BASEDIR.'wwwroot/srv/'.$this->downloads_hashes[$idx].'/'.$filename)))?>
</small>
</li>
<?php } ?>
@@ -307,7 +389,7 @@ function template($title, $content) {
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" >
<meta name="viewport" content="width=768px" >
<meta name="viewport" content="width=960" >
<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>
@@ -334,6 +416,7 @@ function listprojects() {
$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)) {
@@ -400,8 +483,10 @@ function buildcommon() {
// Build homepage spritesheet
if (count($handles)) {
mkspritesheet($handles, BASEDIR.'wwwroot/logos.jpg', INDEX_THUMB_W, INDEX_THUMB_H);
array_map('imagedestroy', $handles); // free
}
// Build index page
@@ -425,9 +510,10 @@ function buildcommon() {
<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>,
<strong><?=hesc(str_replace('_', ' ', $pr->projname))?></strong><?php if (SHOW_BLURBS) { ?>,
<?=hesc($pr->shortdesc)?>
<a href="<?=hesc(urlencode($pr->projname))?>.html">more...</a>
<?php } ?>
<a href="<?=hesc(urlencode($pr->projname))?>.html" class="article-read-more">more...</a>
<?php if (strlen($pr->subtag) || count($pr->tags)) { ?>
<br>
<small>
@@ -452,6 +538,18 @@ function buildcommon() {
// Done
}
function buildredirects($redirects) {
foreach($redirects as $oldname => $newname) {
ob_start();
?>
<meta http-equiv="refresh" content="0; url=<?=hesc($newname)?>.html">
<a href="<?=hesc($newname)?>.html">Moved &raquo;</a>
<?php
$page = ob_get_clean();
file_put_contents(BASEDIR.'wwwroot/'.$oldname.'.html', $page);
}
}
function main($args) {
$basedir = './';
$total = $args[0];
@@ -475,11 +573,17 @@ function main($args) {
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));
}
@@ -489,6 +593,7 @@ function main($args) {
//
ini_set('display_errors', 'On');
date_default_timezone_set('Etc/UTC');
error_reporting(E_ALL);
main(array_slice($_SERVER['argv'], 1));

View File

@@ -1,7 +1,6 @@
#!/bin/bash
set -eu
PHP=/cygdrive/c/bin/php/php.exe
THREADS=4
buildsite() {
@@ -40,7 +39,7 @@ buildsite() {
echo "Building pages..."
for i in $(seq 0 "$THREADS") ; do
$PHP "$rebuild" "$THREADS" "$i" &
php "$rebuild" "$THREADS" "$i" &
done
wait

View File

@@ -5,4 +5,10 @@ page_thumb_h=60
index_thumb_w=90
index_thumb_h=32
shields_prefix=true
; n.b. Recommend a multiple of the JPEG iDCT block size for index_thumb_h
[redirect]
; old project name = new project name
code.ivysaur.me=codesite

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@@ -7,14 +7,15 @@ img {
border:0;
}
a {
color:black;
text-decoration:underline;
color:#4078c0;
text-decoration:none;
}
a:hover {
color:blue;
cursor:pointer;
text-decoration:underline;
}
h1 a {
color:black;
text-decoration:none;
}
h1 a:hover {
@@ -28,6 +29,11 @@ h1,h2,h3 {
font-family:Consolas,monospace;
white-space:pre;
}
.code-multiline {
display:inline-block;
padding:8px;
border-radius:8px;
}
/* */
@@ -41,14 +47,15 @@ html, body {
/* cosmetic */
font-family:"Helvetica Neue","Segoe UI",Arial,sans-serif;
font-size:12px;
background:#DDD url('pixel_weave.png'); /* thanks subtlepatterns.com ! */
font-size:13px;
line-height:1.4;
background:#DDD url('greyzz.png'); /* thanks subtlepatterns.com ! */
color:#333;
}
#container {
margin:0 auto;
width:768px;
width:960px;
position:relative;
height:auto !important;
@@ -119,7 +126,7 @@ html, body {
}
.projbody_halfw {
float:left;
width: 678px; /* 740px full - 60px rhs column - 2px border */
width: 860px; /* 740px full - 60px rhs column - 2px border */
}
.projbody_fullw {
@@ -131,6 +138,24 @@ html, body {
/* */
@media screen and (max-width:960px) {
#container {
width:100%;
}
.projimg {
float:clear;
width:100%;
}
.projbody_halfw {
float:clear;
width:100%;
}
}
/* */
#ivylogo {
background:transparent url('ivysaur24.png') no-repeat 0 0;
width:24px;

View File

@@ -6,3 +6,8 @@ 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

@@ -0,0 +1,11 @@
[codesite]
title=storytime.ivysaur.me
page_thumb_w=60
page_thumb_h=60
index_thumb_w=90
index_thumb_h=32
blurbs=off
article_header=ARTICLE
; n.b. Recommend a multiple of the JPEG iDCT block size for index_thumb_h

View File

@@ -0,0 +1,10 @@
<p>
<strong>CONTACT</strong>
</p>
<p>
<a
href="http://www.google.com/recaptcha/mailhide/d?k=01GuAWzMc9JjSdooo-2KCMQA==&amp;c=kgR3dBrP39yhPIy8FvLFbuBLmWqorQBDc_Zjbw6NAmU="
onclick="window.open('http://www.google.com/recaptcha/mailhide/d?k\07501GuAWzMc9JjSdooo-2KCMQA\75\75\46c\75kgR3dBrP39yhPIy8FvLFbuBLmWqorQBDc_Zjbw6NAmU\075', '', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=500,height=300'); return false;"
title="Reveal this e-mail address"
>Click here</a> to email me.
</p>

View File

@@ -0,0 +1 @@
<h1><a href="index.html"><div id="ivylogo"></div>storytime.ivysaur.me</a></h1>

View File

@@ -0,0 +1,12 @@
<p>
The stories and information posted here are artistic works of fiction and falsehood. Only a fool would take anything posted here as fact.
</p>
<p>
<strong>LICENSE</strong>
</p>
<p>
Please consider all articles on this page to be under the <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">CC-BY-SA 4.0 International license</a> unless otherwise specified.
</p>
<p>
<strong>ARTICLES</strong>
</p>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 B

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

@@ -0,0 +1,187 @@
/* style.css */
html {
overflow-y:scroll; /* always display scrollbar to prevent horizontal lurch */
}
img {
border:0;
}
a {
color:black;
text-decoration:underline;
}
a:hover {
color:blue;
cursor:pointer;
}
h1 a {
text-decoration:none;
}
h1 a:hover {
color:black;
}
h1,h2,h3 {
margin-top:0;
}
.code {
background: #F8F8F8;
font-family:Consolas,monospace;
white-space:pre;
}
/* */
html, body {
/* structural */
height:100%;
min-height:100%;
margin:0;
border:0;
padding:0;
/* cosmetic */
font-family:"Helvetica Neue","Segoe UI",Arial,sans-serif;
font-size:12px;
background:#DDD url('pixel_weave.png'); /* thanks subtlepatterns.com ! */
color:#333;
}
#container {
margin:0 auto;
width:768px;
position:relative;
height:auto !important;
height:100%; /* oldIE */
min-height:100%;
/* cosmetic */
background:white;
}
#content {
padding:14px;
background:white;
}
/* */
.tag::before {
content:"";
display:inline-block;
width:7px;
height:7px;
margin-right:2px;
background:transparent url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAZUlEQVQI12P4//8/Awyrqqp6KSkp/QdhFRWV7cgS+kDBN0B8RVlZuRikACahB+T8ger6BMTXwJJAiTwg4xvMOCj+ART3ZwCa3YYm8QcopgsyEWasDVT3W5AVMHcgO6gViNORXQ8A84NToxbSsJcAAAAASUVORK5CYII=') no-repeat 0 0;
}
.tag-filter-warn {
position:fixed;
top:0;
right:0;
padding:4px;
background:lightyellow;
border-bottom: 1px solid #888;
border-left:1px solid #888;
}
/* */
.projtable {
border-collapse: collapse;
width:100%;
}
.projtable tr {
transition:0.2s linear;
}
.projtable tr:hover {
background:#F8F8F8;
}
.projtable td {
padding: 2px 4px;
}
.projtable small {
color:grey;
font-style:italic;
}
.projtable tr td:first-child {
width:95px;
}
.projinfo {
}
.projbody {
}
.projbody_halfw {
float:left;
width: 678px; /* 740px full - 60px rhs column - 2px border */
}
.projbody_fullw {
}
.projimg {
float:right;
width:62px; /* 60px + 2px border */
}
/* */
#ivylogo {
background:transparent url('ivysaur24.png') no-repeat 0 0;
width:24px;
height:24px;
display:inline-block;
*display:block;
*zoom:1;
margin-right:4px;
position:relative;
top:4px;
}
/* */
.homeimage {
width:90px;
height:32px;
}
.homeimage-sprite {
background: white url('logos.jpg') no-repeat 0 0;
}
.thumbimage {
width:60px;
height:60px;
opacity: 0.8;
transition:0.2s opacity;
border:1px solid lightgrey;
}
.thumbimage:hover {
opacity:1.0;
}
.no-image {
width:90px;
height:32px;
display:block;
background: white url('no_image.png') no-repeat 0 0;
}
/* */
.spoiler {
color:black;
background:black;
}
.spoiler:hover {
color:white;
}
.article-read-more {
display:none;
}