33 Commits
v39 ... v72

Author SHA1 Message Date
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
4a6f8d90d8 tweaks pre release 2015-04-05 16:25:01 +12:00
b572d957fc update changelog 2015-04-05 16:24:23 +12:00
0f70fc7bf0 support sorting 2015-04-05 16:22:46 +12:00
a38b0dbea5 drop dead code 2015-04-05 16:07:50 +12:00
42f4bca8ba drop dead code 2015-04-05 15:50:03 +12:00
2f254fd355 move common static files to /static_global/ 2015-04-05 15:44:32 +12:00
f54c388b1a TODO: update 2015-04-04 19:53:53 +13:00
ee66a10fc3 TODO.txt 2015-04-04 19:47:38 +13:00
d52f58ecae commit version into readme 2015-04-04 19:47:32 +13:00
376745b825 Added tag release-r54 for changeset 42a17645b5b2 2015-04-04 19:45:07 +13:00
1c432afdbf readme: update for r54 2015-04-04 19:44:53 +13:00
df731a8171 readme: track current/original readme file 2015-04-04 19:42:18 +13:00
503bdbd527 mkdist: check in tarballer script 2015-04-04 19:41:09 +13:00
8b153119ea deploy.sh fixes for new file locations 2015-04-04 19:37:12 +13:00
de3e1f1210 hgignore 2015-04-04 19:37:05 +13:00
e693abc079 work on sample site 2015-04-04 19:34:19 +13:00
72bf8d9410 merge example site into main branch 2015-04-04 19:30:20 +13:00
8736cc97b4 move to multisite-model 2015-04-04 19:29:33 +13:00
2eac27a439 rebuild.sh: convert to cygwin 2015-04-04 19:03:14 +13:00
bbbfba2e8f deployscript assert permissions 2015-02-08 16:19:18 +13:00
97c06514eb hgignore dist 2015-02-06 17:11:49 +13:00
4c3e285547 patches for new windows install 2015-02-06 17:11:38 +13:00
40a86b41c7 fix code white-space 2014-08-23 22:22:34 +12:00
301d81a637 fix page overflow 2014-08-23 22:19:02 +12:00
dc260a27d6 fix corrupting urls with multiple params 2014-07-03 22:31:13 +12:00
29 changed files with 540 additions and 79 deletions

View File

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

2
.hgtags Normal file
View File

@@ -0,0 +1,2 @@
42a17645b5b21d7fe395767de7fa3e26ee999014 release-r54
0f89ae041c2ee60cc1ea308d047fce816b19c490 release-r64

8
TODO.txt Normal file
View File

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

46
_dist/README.txt Normal file
View File

@@ -0,0 +1,46 @@
A static site generator for a portfolio website.
The scripts used 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=
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
2015-04-04: r54
- 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: r39
- 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: r13
- Initial public source code release
2013-09-21: r3
- Initial deployment

View File

@@ -1,3 +0,0 @@
@echo off
start C:\bin\cygwin\bin\mintty.exe /bin/bash -l -c "rsync --delete -avz -e ""ssh -i /cygdrive/c/www/ms1_deploy_key -p 2222"" --progress /cygdrive/c/www/m6/code/wwwroot www-data@ms1.ivysaur.me:~/code.ivysaur.me/"

16
deploy.sh Normal file
View File

@@ -0,0 +1,16 @@
#!/bin/bash
set -eu
LOCALDIR=/cygdrive/c/www/m6/code/sites/code.ivysaur.me/wwwroot
chmod -R 644 "$LOCALDIR"
chmod -R a+X "$LOCALDIR"
rsync --delete -avz -e "ssh -i /cygdrive/c/www/ms1_deploy_key -p 2222" --progress \
"$LOCALDIR" \
www-data@ms1.ivysaur.me:~/code.ivysaur.me/
# n.b. set chmod -R a+rX remote side?
read -p "Press any key to continue..."

9
mkdist.sh Normal file
View File

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

View File

@@ -1,20 +0,0 @@
@echo off
set PHP=C:\bin\php54\php.exe
echo Cleaning target directory...
echo.
rmdir /s /q wwwroot
mkdir wwwroot
mkdir wwwroot\srv
copy static\* wwwroot
echo Building pages...
echo.
start /b %PHP% rebuild.php 4 0
start /b %PHP% rebuild.php 4 1
start /b %PHP% rebuild.php 4 2
start /b %PHP% rebuild.php 4 3
start /b %PHP% rebuild.php 4 4
pause

View File

@@ -3,16 +3,6 @@
// Code-hosting website
// ````````````````````
// CONFIGURATION
// `````````````
define('BASEDIR', __DIR__.'\\');
define('SITE_TITLE', 'code.ivysaur.me');
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.
@@ -97,23 +87,6 @@ function hesc($sz) {
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;
};
@@ -138,6 +111,22 @@ function text2html($sz) {
return implode($join, $ret);
};
$sectionFmt = function($sz) use($oddEven, $identity) {
$base = hesc($sz);
$base = preg_replace('~^=+(.+)=+~m', '<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>';
}
}
return $oddEven($btparts, $identity, 'nl2br');
};
$htmlSections = $splitInside('<html>', '</html>', $sz);
return $oddEven($htmlSections, $identity, $sectionFmt);
}
@@ -162,9 +151,11 @@ class CProject {
public $projname;
public $shortdesc = '(no description)';
public $subtag = '';
public $lastupdate = 0;
private $longdesc = '';
private $images = array();
private $downloads = array();
private $downloads_hashes = array();
public $tags = array();
public $homeimage = null;
@@ -180,6 +171,8 @@ class CProject {
if ($file[0] == '.') continue;
if ($file == 'README.txt') {
$this->lastupdate = max($this->lastupdate, filectime($this->dir.$file)); // don't count README updates
$this->longdesc = file_get_contents($this->dir.'README.txt');
$matches = array();
if (preg_match('~Written in ([^\\r\\n]+)~', $this->longdesc, $matches)) {
@@ -195,7 +188,13 @@ class CProject {
$this->shortdesc[0] = strtolower($this->shortdesc[0]); // cosmetic lowercase
continue;
}
$this->lastupdate = max(
$this->lastupdate,
// filectime($this->dir.$file),
filemtime($this->dir.$file)
);
if (is_image($file)) {
$this->images[] = $file;
} else {
@@ -205,13 +204,19 @@ 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() {
if (count($this->images)) {
$this->homeimage = mkthumbnail(
$this->dir.$this->images[0], //BASEDIR.'wwwroot/srv/'.$this->projname.'_0.'.str_ext($this->images[0]),
$this->dir.$this->images[0],
null, // raw handle
INDEX_THUMB_W, INDEX_THUMB_H
);
@@ -233,7 +238,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
@@ -264,18 +283,18 @@ class CProject {
<p><?=text2html($this->longdesc)?></p>
<?=file_get_contents(__DIR__.'/footer.htm')?>
<?=file_get_contents(BASEDIR.'/footer.htm')?>
<?php if (count($this->downloads)) { ?>
<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 } ?>
@@ -289,6 +308,9 @@ class CProject {
<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>
<div style="clear:both;"></div>
<?php } ?>
</div>
@@ -315,7 +337,7 @@ function template($title, $content) {
<body>
<div id="container">
<div id="content">
<?=file_get_contents(__DIR__.'/header.htm')?>
<?=file_get_contents(BASEDIR.'/header.htm')?>
<?=$content?>
</div>
</div>
@@ -368,6 +390,8 @@ function buildcommon() {
$handles = array();
$handle_lookup = array();
$alphasort = [];
foreach($projects as $dirname => $projectname) {
$pr = new CProject($dirname, $projectname);
@@ -381,12 +405,26 @@ function buildcommon() {
$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
mkspritesheet($handles, BASEDIR.'wwwroot/logos.jpg', INDEX_THUMB_W, INDEX_THUMB_H);
array_map('imagedestroy', $handles); // free
if (count($handles)) {
mkspritesheet($handles, BASEDIR.'wwwroot/logos.jpg', INDEX_THUMB_W, INDEX_THUMB_H);
array_map('imagedestroy', $handles); // free
}
// Build index page
@@ -399,9 +437,13 @@ function buildcommon() {
<!-- }} -->
<?php } ?>
<table class="projtable">
<?php foreach ($plist as $pr) { ?>
<tr class="<?=$pr->getClassAttr()?>">
<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]?>"
>
<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>
@@ -433,12 +475,49 @@ 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) {
$total = $args[0];
$pos = $args[1];
$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('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']));
// Perform build tasks
if ($pos == 0) {
buildcommon();
if (array_key_exists('redirect', $config)) {
buildredirects( $config['redirect'] );
}
} else {
buildprojects($pos, array_decimate(listprojects(), $total, $pos));
}

61
rebuild.sh Normal file
View File

@@ -0,0 +1,61 @@
#!/bin/bash
set -eu
PHP=/cygdrive/c/bin/php/php.exe
THREADS=4
buildsite() {
local basedir="$(realpath .)"
local rebuild="${basedir}/rebuild.php"
if [[ "$(uname -o)" == "Cygwin" ]] ; then
rebuild="$(cygpath -w "$rebuild")"
fi
echo "Site: ${1}"
pushd "$1" >/dev/null
echo "Cleaning target directory..."
if [[ -d wwwroot ]] ; then
rm -r wwwroot
fi
mkdir -p wwwroot/srv
if [[ ! -d static ]] ; then
mkdir static
fi
cp "${basedir}/static_global/"* wwwroot || true
cp static/* wwwroot || true
for htm in footer header homepage_blurb ; do
if [[ ! -f "${htm}.htm" ]] ; then
touch "${htm}.htm"
fi
done
echo "Building pages..."
for i in $(seq 0 "$THREADS") ; do
$PHP "$rebuild" "$THREADS" "$i" &
done
wait
echo "Site: ${1} finished."
echo ""
popd >/dev/null
}
main() {
for site in sites/* ; do
buildsite "$site"
done
}
main "$@"
read -p "Press any key to continue..."

View File

@@ -0,0 +1,12 @@
[codesite]
title=code.ivysaur.me
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
code.ivysaur.me=codesite

View File

@@ -16,5 +16,11 @@
</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>

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

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

@@ -26,6 +26,7 @@ h1,h2,h3 {
.code {
background: #F8F8F8;
font-family:Consolas,monospace;
white-space:pre;
}
/* */
@@ -33,6 +34,7 @@ h1,h2,h3 {
html, body {
/* structural */
height:100%;
min-height:100%;
margin:0;
border:0;
padding:0;
@@ -112,22 +114,18 @@ html, body {
}
.projinfo {
position:relative;
}
.projbody {
position:absolute;
left:0;
.projbody {
}
.projbody_halfw {
right: 74px;
float:left;
width: 678px; /* 740px full - 60px rhs column - 2px border */
}
.projbody_fullw {
right:0;
}
.projimg {
position:absolute;
right:0;
float:right;
width:62px; /* 60px + 2px border */
}

View File

@@ -0,0 +1,13 @@
[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

@@ -0,0 +1,6 @@
<p>
<strong>FOOTER</strong>
</p>
<p>
Content here.
</p>

View File

@@ -0,0 +1 @@
<h1>HEADER</h1>

View File

@@ -0,0 +1,14 @@
<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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 B

View File

@@ -0,0 +1,161 @@
/* 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;
}
/* */
html, body {
/* structural */
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 {
position:relative;
}
.projbody {
position:absolute;
left:0;
}
.projbody_halfw {
right: 74px;
}
.projbody_fullw {
right:0;
}
.projimg {
position:absolute;
right:0;
width:62px; /* 60px + 2px border */
}
/* */
.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;
}

View File

Before

Width:  |  Height:  |  Size: 436 B

After

Width:  |  Height:  |  Size: 436 B

View File

@@ -33,14 +33,65 @@
};
var get_show_tag = function(tag) {
return function() { show_tag(tag); return false; };
return function() {
show_tag(tag);
return false;
};
};
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() {
var cb;
switch(document.getElementById('sortorder').value) {
case 'a':
default: {
cb = function(el) {
return el.getAttribute('data-sort-ct');
};
} break;
case 'b': {
cb = function(el) {
return el.getAttribute('data-sort-mt');
}
} break;
case 'c': {
cb = function(el) {
return el.getAttribute('data-sort-al');
}
} break;
};
sort_rows(cb);
};
window.addEventListener('load', function() {
var taglinks = document.querySelectorAll(".tag-link");
for (var i = 0, e = taglinks.length; i !== e; ++i) {
var tag = taglinks[i].getAttribute("data-tag");
taglinks[i].addEventListener('click', get_show_tag(tag));
}
var so = document.getElementById('sortorder');
if (so) {
so.addEventListener('change', sort_update);
sort_update();
}
});
})();
})();