Merge branch 'roadmap'

* roadmap:
  spacing and formatting
  break out curl caching into separate class
  roadmap fixes
  roadmap gtg?
  nocache flag
  roadmap ready?
  light progress
  better roadmap styling?
  yep, I store images in Git, come at me @lyoshenka
  roadmap v1
  working asana task loading + curl caching
  barest of bones
This commit is contained in:
Alex Grintsvayg 2016-10-12 11:37:23 -04:00
commit 67cc0a2086
28 changed files with 3147 additions and 226 deletions

View file

@ -85,6 +85,7 @@ class Controller
$router->get(['/osx', 'get-osx'], 'DownloadActions::executeGet');
$router->get(['/android', 'get-android'], 'DownloadActions::executeGet');
$router->get(['/ios', 'get-ios'], 'DownloadActions::executeGet');
$router->get('/roadmap', 'ContentActions::executeRoadmap');
$router->get(['/press-kit.zip', 'press-kit'], 'ContentActions::executePressKit');
@ -114,9 +115,9 @@ class Controller
'/deck.pdf' => 'https://www.dropbox.com/s/0xj4vgucsbi8rtv/lbry-deck.pdf?dl=1',
'/pln.pdf' => 'https://www.dropbox.com/s/uevjrwnyr672clj/lbry-pln.pdf?dl=1',
'/plan.pdf' => 'https://www.dropbox.com/s/uevjrwnyr672clj/lbry-pln.pdf?dl=1',
'/get/lbry.dmg' => DownloadActions::getDownloadUrl(DownloadActions::OS_OSX) ?: '/get',
'/get/lbry.deb' => DownloadActions::getDownloadUrl(DownloadActions::OS_LINUX) ?: '/get',
'/get/lbry.msi' => DownloadActions::getDownloadUrl(DownloadActions::OS_WINDOWS) ?: '/get',
'/get/lbry.dmg' => GitHub::getDownloadUrl(OS::OS_OSX) ?: '/get',
'/get/lbry.deb' => GitHub::getDownloadUrl(OS::OS_LINUX) ?: '/get',
'/get/lbry.msi' => GitHub::getDownloadUrl(OS::OS_WINDOWS) ?: '/get',
];
foreach ([302 => $tempRedirects, 301 => $permanentRedirects] as $code => $redirects)

View file

@ -69,7 +69,6 @@ class ContentActions extends Actions
]];
}
public static function executeFaq(string $slug = null): array
{
Response::enableHttpCache();
@ -190,6 +189,33 @@ class ContentActions extends Actions
]];
}
public static function executeRoadmap()
{
$cache = !Request::getParam('nocache');
$githubItems = Github::listRoadmapChangesets($cache);
$projectMaxVersions = [];
$closedGroups = [];
foreach($githubItems as $group => $items)
{
if ($items)
{
$lastItem = end($items);
$project = $lastItem['project'];
if (!isset($projectMaxVersions[$project]) || $lastItem['version'] > $projectMaxVersions[$project])
{
$projectMaxVersions[$project] = $lastItem['version'];
}
}
}
$items = array_merge($githubItems, Asana::listRoadmapTasks($cache));
return ['content/roadmap', [
'projectMaxVersions' => $projectMaxVersions,
'items' => $items
]];
}
public static function executePressKit(): array
{
$zipFileName = 'lbry-press-kit-' . date('Y-m-d') . '.zip';

View file

@ -2,23 +2,6 @@
class DownloadActions extends Actions
{
const OS_ANDROID = 'android',
OS_IOS = 'ios',
OS_LINUX = 'linux',
OS_OSX = 'osx',
OS_WINDOWS = 'windows';
public static function getOses()
{
return [
static::OS_WINDOWS => ['/windows', 'Windows', 'icon-windows', '_windows'],
static::OS_OSX => ['/osx', 'OS X', 'icon-apple', '_osx'],
static::OS_LINUX => ['/linux', 'Linux', 'icon-linux', '_linux'],
static::OS_ANDROID => ['/android', 'Android', 'icon-android', '_android'],
static::OS_IOS => ['/ios', 'iOS', 'icon-mobile', '_ios']
];
}
public static function executeGet()
{
$email = Request::getParam('e');
@ -52,7 +35,7 @@ class DownloadActions extends Actions
return ['download/get'];
}
$osChoices = static::getOses();
$osChoices = OS::getAll();
$os = static::guessOs();
if ($os && isset($osChoices[$os]))
@ -64,7 +47,7 @@ class DownloadActions extends Actions
'osIcon' => $osIcon,
'prefineryUser' => $user ?: [],
'downloadHtml' => View::exists('download/' . $partial) ?
View::render('download/' . $partial, ['downloadUrl' => static::getDownloadUrl($os)]) :
View::render('download/' . $partial, ['downloadUrl' => Github::getDownloadUrl($os)]) :
false
]];
}
@ -122,8 +105,8 @@ class DownloadActions extends Actions
public static function prepareListPartial(array $vars)
{
return $vars + ['osChoices' => isset($vars['excludeOs']) ?
array_diff_key(static::getOses(), [$vars['excludeOs'] => null]) :
static::getOses()
array_diff_key(OS::getAll(), [$vars['excludeOs'] => null]) :
OS::getAll()
];
}
@ -164,7 +147,7 @@ class DownloadActions extends Actions
{
//if exact OS is requested, use that
$uri = Request::getRelativeUri();
foreach (static::getOses() as $os => $osChoice)
foreach (OS::getAll() as $os => $osChoice)
{
if ($osChoice[0] == $uri)
{
@ -181,68 +164,15 @@ class DownloadActions extends Actions
$ua = Request::getUserAgent();
if (stripos($ua, 'OS X') !== false)
{
return strpos($ua, 'iPhone') !== false || stripos($ua, 'iPad') !== false ? static::OS_IOS : static::OS_OSX;
return strpos($ua, 'iPhone') !== false || stripos($ua, 'iPad') !== false ? OS::OS_IOS : OS::OS_OSX;
}
if (stripos($ua, 'Linux') !== false || strpos($ua, 'X11') !== false)
{
return strpos($ua, 'Android') !== false ? static::OS_ANDROID : static::OS_LINUX;
return strpos($ua, 'Android') !== false ? OS::OS_ANDROID : OS::OS_LINUX;
}
if (stripos($ua, 'Windows') !== false)
{
return static::OS_WINDOWS;
return OS::OS_WINDOWS;
}
return null;
}
public static function getDownloadUrl($os, $useCache = true)
{
if (!in_array($os, array_keys(static::getOses())))
{
throw new DomainException('Unknown OS');
}
$apc = $useCache && extension_loaded('apc') && ini_get('apc.enabled');
$key = 'lbry_release_data2';
$releaseData = null;
if ($apc)
{
$releaseData = apc_fetch($key);
}
if (!$releaseData)
{
try
{
$releaseData =
json_decode(Curl::get('https://api.github.com/repos/lbryio/lbry/releases/latest', [], ['user_agent' => 'LBRY']), true);
if ($apc)
{
apc_store($key, $releaseData, 600); // cache for 10 min
}
}
catch (Exception $e)
{
}
}
if (!$releaseData)
{
return null;
}
foreach ($releaseData['assets'] as $asset)
{
if (
($os == static::OS_LINUX && substr($asset['name'], -4) == '.deb') ||
($os == static::OS_OSX && substr($asset['name'], -4) == '.dmg') ||
($os == static::OS_WINDOWS && substr($asset['name'], -4) == '.msi')
)
{
return $asset['browser_download_url'];
}
}
return null;
}
}

View file

@ -248,3 +248,6 @@ title:
publish: Publish
refer: Share and Earn
what: "Art in the Internet Age: An Introduction to LBRY"
roadmap:
title: LBRY Roadmap
description: See past and future progress on development of LBRY, the world's first user-controlled digital marketplace.

15
lib/cache/Apc.class.php vendored Normal file
View file

@ -0,0 +1,15 @@
<?php
/**
* Created by PhpStorm.
* User: kauffj
* Date: 8/13/16
* Time: 2:42 PM
*/
class Apc
{
public static function isEnabled()
{
return extension_loaded('apc') && ini_get('apc.enabled');
}
}

85
lib/thirdparty/Asana.class.php vendored Normal file
View file

@ -0,0 +1,85 @@
<?php
class Asana
{
protected static $curlOptions = ['json_response' => true, 'cache' => true, 'timeout' => 10];
public static function listRoadmapTasks($cache = true)
{
// Use print_r(static::get('/projects')) to get project IDs
$projects = [
158602294500138 => ['LBRY Browser', 'https://github.com/lbryio/lbry-web-ui'],
158602294500137 => ['LBRY Data Network', 'https://github.com/lbryio/lbry'],
161514803479899 => ['Blockchain and Wallets', 'https://github.com/lbryio/lbrycrd'],
136290697597644 => ['Integration and Building', null],
158602294500249 => ['Documentation', null],
];
$tasks = [];
$tags = [
192699565737944 => 'Ongoing',
192699565737946 => 'Upcoming',
192699565737948 => 'Future'
];
foreach ($tags as $tagId => $tagLabel)
{
$taggedTasks = static::get('/tags/' . $tagId . '/tasks', ['completed_since' => 'now'], $cache);
foreach ($taggedTasks as $task)
{
$fullTask = static::get('/tasks/' . $task['id']);
$projectId = $fullTask['memberships'][0]['project']['id'] ?? null;
if ($fullTask['name'] && $projectId && isset($projects[$projectId]))
{
list($projectName, $projectUrl) = $projects[$projectId];
$tasks[$tagLabel][] = array_intersect_key($fullTask, ['name' => null]) + [
'project_label' => $projectName,
'badge' => $projectName,
'date' => $fullTask['due_on'] ?? null,
'body' => $fullTask['notes'],
'url' => $projectUrl,
'group' => $tagLabel,
'assignee' => $fullTask['assignee'] ? ucwords($fullTask['assignee']['name']) : ''
];
}
}
}
foreach ($tasks as &$groupTasks)
{
usort($groupTasks, function ($tA, $tB)
{
if ($tA['group'] != $tB['group'])
{
return $tA['group'] <= $tB['group'] ? -1 : 1;
}
if ($tA['date'] xor $tB['date'])
{
return $tA['date'] ? -1 : 1;
}
return $tA['date'] < $tB['date'] ? -1 : 1;
});
}
return $tasks;
}
protected static function get($endpoint, array $data = [], $cache = true)
{
$apiKey = Config::get('asana_key');
$options = [
'headers' => ['Authorization: Bearer ' . $apiKey],
'cache' => $cache
] + static::$curlOptions;
$responseData = CurlWithCache::get('https://app.asana.com/api/1.0' . $endpoint, $data, $options);
return $responseData['data'] ?? [];
}
}
class AsanaException extends Exception
{
}

127
lib/thirdparty/Github.class.php vendored Normal file
View file

@ -0,0 +1,127 @@
<?php
class Github
{
public static function getDownloadUrl($os, $cache = true)
{
if (!in_array($os, array_keys(OS::getAll())))
{
throw new DomainException('Unknown OS');
}
try
{
$releaseData = static::get('/repos/lbryio/lbry/releases/latest', $cache);
foreach ($releaseData['assets'] as $asset)
{
if (
($os == OS::OS_LINUX && in_array($asset['content_type'], ['application/x-debian-package', 'application/x-deb'])) ||
($os == OS::OS_OSX && in_array($asset['content_type'], ['application/x-diskcopy', 'application/x-apple-diskimage'])) ||
($os == OS::OS_WINDOWS && substr($asset['name'], -4) == '.msi')
)
{
return $asset['browser_download_url'];
}
}
}
catch (Exception $e)
{
}
return null;
}
public static function get($endpoint, $cache = true)
{
return CurlWithCache::get('https://api.github.com' . $endpoint, [],
['user_agent' => 'LBRY', 'json_response' => true, 'cache' => $cache]);
}
public static function listRoadmapChangesets($cache = true)
{
$sets = [];
$allReleases = [];
$projects = [
'lbry' => ''
];
foreach ($projects as $project => $label)
{
$page = 1;
do
{
$releases = static::get('/repos/lbryio/' . $project . '/releases?page=' . $page, $cache);
$page++;
$allReleases = array_merge($allReleases, array_map(function ($release) use ($label, $project)
{
return $release + ['project_label' => $label, 'project' => $project];
}, array_filter($releases, function ($release)
{
return isset($release['tag_name']) && isset($release['published_at']) && $release['published_at'] &&
$release['tag_name'] != 'v0.4.0';
})));
} while (count($releases) >= 30);
}
foreach ($allReleases as $release)
{
$group = null;
$matches = null;
if (isset($release['tag_name']) && preg_match('/v(\d+)\.(\d+).?(\d+)?/', $release['tag_name'], $matches))
{
$group = $release['project_label'] . ' v' . $matches[1] . '.' . $matches[2];
}
if ($group)
{
$sets[$group][] = array_intersect_key($release, [
'prerelease' => null, 'tag_name' => null, 'published_at' => null, 'project' => null, 'project_label' => null
]) + [
'date' => date('Y-m-d', strtotime($release['created_at'])),
//I thought published_at, but GitHub displays created_at and published_at is out of sync sometimes (0.3.2, 0.3.3)
'name' => $release['name'] ?: $release['tag_name'],
'github_url' => $release['url'],
'major_version' => $matches[1],
'minor_version' => $matches[2],
'patch_version' => isset($matches[3]) ? $matches[3] : null,
'version' => $matches[1] . '.' . $matches[2] . '.' . (isset($matches[3]) ? $matches[3] : ''),
'body' => ParsedownExtra::instance()->text($release['body'])
];
}
}
uasort($sets, function ($sA, $sB)
{
if ($sA[0]['project'] != $sB[0]['project'])
{
return $sA[0]['project'] < $sB[0]['project'] ? -1 : 1;
}
if ($sA[0]['major_version'] != $sB[0]['major_version'])
{
return $sA[0]['major_version'] < $sB[0]['major_version'] ? -1 : 1;
}
if ($sA[0]['minor_version'] != $sB[0]['minor_version'])
{
return $sA[0]['minor_version'] < $sB[0]['minor_version'] ? -1 : 1;
}
return $sA[0]['patch_version'] < $sB[0]['patch_version'] ? -1 : 1;
});
foreach ($sets as $group => &$groupSet)
{
usort($groupSet, function ($rA, $rB) {
if ($rA['major_version'] != $rB['major_version'])
{
return $rA['major_version'] < $rB['major_version'] ? -1 : 1;
}
if ($rA['minor_version'] != $rB['minor_version'])
{
return $rA['minor_version'] < $rB['minor_version'] ? -1 : 1;
}
return $rA['patch_version'] < $rB['patch_version'] ? -1 : 1;
});
}
return $sets;
}
}

View file

@ -17,13 +17,13 @@ class Prefinery
'Accept: application/json',
'Content-type: application/json'
],
'json_data' => true
'json_data' => true,
'json_response' => true
];
public static function findUser($emailOrId, $useApc = true)
{
$apcEnabled = extension_loaded('apc') && ini_get('apc.enabled');
$apcEnabled = Apc::isEnabled();
if ($useApc && $apcEnabled)
{
$cached = apc_fetch('prefinery-user-' . $emailOrId, $success);
@ -113,8 +113,7 @@ class Prefinery
{
throw new PrefineryException('Update tester must be called with a tester id');
}
$apcEnabled = extension_loaded('apc') && ini_get('apc.enabled');
if ($apcEnabled)
if (Apc::isEnabled())
{
apc_delete('prefinery-user-' . $testerData['id']);
}

View file

@ -2,9 +2,12 @@
class Curl
{
const GET = 'GET',
POST = 'POST',
PUT = 'PUT';
const
GET = 'GET',
POST = 'POST',
PUT = 'PUT',
DELETE = 'DELETE';
public static function get($url, $params = [], $options = [])
{
@ -24,6 +27,12 @@ class Curl
return $body;
}
public static function delete($url, $params = [], $options = [])
{
list ($status, $headers, $body) = static::doCurl(static::DELETE, $url, $params, $options);
return $body;
}
public static function doCurl($method, $url, $params = [], $options = [])
{
$defaults = [
@ -36,6 +45,7 @@ class Curl
'password' => null,
'cookie' => null,
'json_data' => false,
'json_response' => false,
'retry' => false,
];
@ -45,13 +55,13 @@ class Curl
throw new DomainException('Invalid curl options: ' . join(', ', array_keys($invalid)));
}
$options = array_merge($defaults, $options);
if (!in_array($method, [static::GET, static::POST, static::PUT]))
{
throw new DomainException('Invalid method: ' . $method);
}
$options = array_merge($defaults, $options);
if ($options['headers'] && $options['headers'] !== array_values($options['headers'])) // associative array
{
throw new DomainException('Headers must not be an associative array. Its a simple array with values of the form "Header: value"');
@ -137,7 +147,16 @@ class Curl
return strlen($h);
});
$responseContent = curl_exec($ch);
$rawResponse = curl_exec($ch);
if ($options['json_response'])
{
$responseContent = $rawResponse ? json_decode($rawResponse, true) : [];
}
else
{
$responseContent = $rawResponse;
}
$statusCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);

View file

@ -0,0 +1,32 @@
<?php
class CurlWithCache extends Curl
{
const DEFAULT_CACHE = 600000;
public static function doCurl($method, $url, $params = [], $options = [])
{
$useCache = ($options['cache'] ?? true) && Apc::isEnabled();
$cacheTimeout = is_numeric($options['cache'] ?? null) ? $options['cache'] : static::DEFAULT_CACHE;
unset($options['cache']);
if ($useCache)
{
$cacheKey = md5('z' . $url . $method . serialize($options) . serialize($params));
$cachedData = apc_fetch($cacheKey);
if ($cachedData)
{
return $cachedData;
}
}
$response = parent::doCurl($method, $url, $params, $options);
if ($useCache)
{
apc_store($cacheKey, $response, $cacheTimeout);
}
return $response;
}
}

23
lib/tools/OS.class.php Normal file
View file

@ -0,0 +1,23 @@
<?php
class OS
{
const OS_ANDROID = 'android',
OS_IOS = 'ios',
OS_LINUX = 'linux',
OS_OSX = 'osx',
OS_WINDOWS = 'windows';
public static function getAll()
{
//url, English name, icon class, partial name
//yes, this is probably a bad pattern
return [
OS::OS_WINDOWS => ['/windows', 'Windows', 'icon-windows', '_windows'],
OS::OS_OSX => ['/osx', 'OS X', 'icon-apple', '_osx'],
OS::OS_LINUX => ['/linux', 'Linux', 'icon-linux', '_linux'],
OS::OS_ANDROID => ['/android', 'Android', 'icon-android', '_android'],
OS::OS_IOS => ['/ios', 'iOS', 'icon-mobile', '_ios']
];
}
}

2485
lib/vendor/MCAPI.class.php vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -71,7 +71,7 @@
<?php endforeach ?>
</div>
<?php else: ?>
<p><em>{{bounty.list.noresults}}</em></p>
<p><em class="no-results">{{bounty.list.noresults}}</em></p>
<?php endif ?>
</section>
</main>

View file

@ -0,0 +1,68 @@
<?php Response::setMetaDescription(__('roadmap.description')) ?>
<?php Response::addJsAsset('/js/roadmap.js') ?>
<?php NavActions::setNavUri('/learn') ?>
<?php echo View::render('nav/_header', ['isDark' => false]) ?>
<?php js_start() ?>
lbry.roadmap('#project-roadmap');
<?php js_end() ?>
<main>
<div class="hero hero-quote hero-img hero-img-short spacer1" title="Here Be Dragons" style="background-image: url(/img/here-be-dragons.jpg)">
<div class="hero-content-wrapper">
<div class="hero-content text-center">
<h1 class="cover-title">{{roadmap.title}}</h1>
<h2 class="cover-subtitle">Past successes and future plans for the journey into the land of dragons.</h2>
</div>
</div>
</div>
<div style="max-width: 800px; margin: 0 auto">
<div class="roadmap-container" id="project-roadmap">
<div class="text-center"><a href="javascript:;" class="link-primary show-all-roadmap-groups">Show Earlier Releases</a></div>
<?php foreach($items as $group => $groupItems): ?>
<?php $lastItem = end($groupItems) ?>
<?php $isOpen = !isset($lastItem['project']) || !isset($lastItem['version']) || $lastItem['version'] === $projectMaxVersions[$lastItem['project']] ?>
<h2 class="roadmap-group-title" <?php echo !$isOpen ? 'style="display: none"' : '' ?>">
<span class="roadmap-group-title-label">
<?php echo $group ?> <?php echo isset($lastItem['version']) && $lastItem['version'] === $projectMaxVersions[$lastItem['project']] ? '(latest )' : '' ?>
</span>
</h2>
<div class="roadmap-group <?php echo !$isOpen ? 'roadmap-group-closed' : '' ?>">
<?php $lastItem = end($groupItems) ?>
<?php $maxItems = isset($lastItem['version']) ? 3 : count($groupItems) ?>
<?php $index = 0 ?>
<?php if (count($groupItems) > $maxItems): ?>
<div class="text-center spacer1"><a href="javascript:;" class="link-primary show-all-roadmap-group-items">Show All Items for <?php echo $group ?></a></div>
<?php endif ?>
<?php foreach($groupItems as $item): ?>
<?php ++$index ?>
<div class="roadmap-item" <?php echo $index <= count($groupItems) - $maxItems ? 'style="display: none"' : '' ?>>
<?php if (isset($item['badge']) || isset($item['assignee'])): ?>
<div>
<?php if (isset($item['assignee'])): ?>
<span class="roadmap-item-assignee"><?php echo $item['assignee'] ?></span>
<?php endif ?>
<?php if (isset($item['badge'])): ?>
<span class="badge"><?php echo $item['badge'] ?></span><br/>
<?php endif ?>
</div>
<?php endif ?>
<h3 class="roadmap-item-title">
<?php echo $item['name'] ?>
</h3>
<div class="roadmap-item-date">
<?php echo $item['date'] ? date('m-d-Y', strtotime($item['date'])) : '' ?>
</div>
<div class="roadmap-item-content">
<?php echo $item['body'] ?: '<em class="no-results">No description</em>' ?>
<?php if (isset($item['github_url'])): ?>
<?php endif ?>
</div>
</div>
<?php endforeach ?>
</div>
<?php endforeach ?>
</div>
</div>
</main>
<?php echo View::render('nav/_footer') ?>

View file

@ -10,7 +10,6 @@
<h1 class="cover-title">{{global.tagline}}</h1>
<h2 class="cover-subtitle" style="max-width: 600px; margin-left: auto; margin-right: auto">{{global.sentence}}</h2>
</div>
<div class="control-group spacer2 text-center">
<div class="control-item">
<a href="/get" class="btn-primary">{{global.get}}</a>

BIN
web/img/facebook-cover.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 788 KiB

BIN
web/img/here-be-dragons.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 KiB

BIN
web/img/twitter-cover.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 829 KiB

View file

@ -19,8 +19,7 @@ jQuery.fn.extend({
});
$(document).ready(function() {
var body = $('body'),
labelCycles = body.find('.label-cycle'); //should use better pattern but we have so little JS right now
var body = $('body');
body.on('click', 'a', onAnchorClick);
@ -32,22 +31,6 @@ $(document).ready(function() {
window.fbAsyncInit = function()
{
window.FB.Event.subscribe('edge.create', onFacebookLike);
};
//$(window).scroll(onBodyScroll);
if (labelCycles.length)
{
setInterval(refreshLabelCycles,5000);
labelCycles.each(function() {
var labelCycle = $(this),
maxHeight = Math.max.apply(Math, labelCycles.find('> *').map(function(){ return $(this).height(); }).get());
if (maxHeight)
{
labelCycle.height(maxHeight);
}
labelCycle.addClass('label-cycle-init');
});
}
function onAnchorClick()
@ -80,24 +63,6 @@ $(document).ready(function() {
}
}
function onBodyScroll()
{
var header = $('.header');
if (header.hasClass('header-scroll'))
{
if (window.scrollY <= 1)
{
header.removeClass('header-light');
header.addClass('header-dark');
}
else
{
header.removeClass('header-dark');
header.addClass('header-light');
}
}
}
function resizeVideo(iframe)
{
var maxWidth = Math.min(iframe.offsetParent().width(), iframe.data('maxWidth')),
@ -149,21 +114,4 @@ $(document).ready(function() {
resizeVideo($(this));
})
});
function refreshLabelCycles()
{
labelCycles.each(function() {
var labelCycle = $(this),
activeLabel = labelCycle.find(':first-child');
activeLabel.fadeOut(function() {
labelCycle.append(activeLabel);
labelCycle.find(':first-child').fadeIn();
});
});
}
$('#language-dropdown').on('change', function() {
$(this).closest('form').submit();
});
});

22
web/js/roadmap.js Normal file
View file

@ -0,0 +1,22 @@
lbry.roadmap = function(selector)
{
var roadmap = $(selector);
roadmap.on('click', '.show-all-roadmap-groups', function() {
roadmap.find('.roadmap-group-title').show();
$(this).remove();
});
roadmap.on('click', '.show-all-roadmap-group-items', function() {
$(this).closest('.roadmap-group').find('.roadmap-item').show();
$(this).remove();
});
roadmap.on('click', '.roadmap-group-title', function() {
$(this).next('.roadmap-group')
.toggleClass('roadmap-group-closed').toggleClass('roadmap-group-open');
// .find('.roadmap-item')
// .toggleClass('roadmap-item-open', $(this).hasClass('roadmap-group-open'))
// .toggleClass('roadmap-item-closed', $(this).hasClass('roadmap-group-closed'))
});
// roadmap.on('click', '.roadmap-item-header', function() {
// $(this).closest('.roadmap-item').toggleClass('roadmap-item-open').toggleClass('roadmap-item-closed');
// });
}

View file

@ -18,4 +18,8 @@ $badge-height: $spacing-vertical * 3/4;
{
background-color: $color-primary;
}
&.badge-info
{
background-color: $color-info-bg;
}
}

View file

@ -42,10 +42,10 @@ h1, h2, h3, h4
margin-bottom: $spacing-vertical / 2;
margin-top: $spacing-vertical / 2;
}
h1 { font-size: 2.4em; }
h2 { font-size: 2.0em; }
h3 { font-size: 1.75em; }
h4 { font-size: 1.4em; }
h1 { font-size: $font-size-h1; }
h2 { font-size: $font-size-h2; }
h3 { font-size: $font-size-h3; }
h4 { font-size: $font-size-h4; }
nav
{
font-size: 1.1em;

View file

@ -108,74 +108,83 @@
{
margin-top: $spacing-vertical;
}
table
}
.post-content table, table.content
{
margin-bottom: $spacing-vertical;
word-wrap: break-word;
max-width: 100%;
th, td
{
margin-bottom: $spacing-vertical;
word-wrap: break-word;
max-width: 100%;
th, td
padding: $spacing-vertical/2 8px;
}
th
{
font-weight: bold;
font-size: 0.9em;
}
td
{
vertical-align: top;
}
thead th, > tr:first-child th
{
vertical-align: bottom;
font-weight: bold;
font-size: 0.9em;
padding: $spacing-vertical/4+1 8px $spacing-vertical/4-2;
text-align: left;
border-bottom: 1px solid #e2e2e2;
img
{
padding: $spacing-vertical/2 8px;
vertical-align: text-bottom;
}
th
}
td.center
{
vertical-align: middle;
}
tr.thead:not(:first-child) th
{
border-top: 1px solid #e2e2e2;
}
tfoot td
{
padding: $spacing-vertical / 2 8px;
font-size: .85em;
}
tbody
{
tr
{
font-weight: bold;
font-size: 0.9em;
}
td
{
vertical-align: top;
}
thead th, > tr:first-child th
{
vertical-align: bottom;
font-weight: bold;
font-size: 0.9em;
padding: $spacing-vertical/4+1 8px $spacing-vertical/4-2;
text-align: left;
border-bottom: 1px solid #e2e2e2;
img
&:nth-child(even):not(.odd)
{
vertical-align: text-bottom;
}
}
tr.thead:not(:first-child) th
{
border-top: 1px solid #e2e2e2;
}
tfoot td
{
padding: $spacing-vertical / 2 8px;
font-size: .85em;
}
tbody
{
tr
{
&:nth-child(even):not(.odd)
{
background-color: #f4f4f4;
}
&:nth-child(odd):not(.even)
{
background-color: white;
}
&.thead
{
background: none;
}
td
{
border: 0 none;
}
background-color: #f4f4f4;
}
&:nth-child(odd):not(.even)
{
background-color: white;
}
&.thead
{
background: none;
}
td
{
border: 0 none;
}
}
}
&:last-child
{
margin-bottom: 0;
}
&:last-child
{
margin-bottom: 0;
}
&.full-table
{
width: 100%;
}
}

View file

@ -7,6 +7,8 @@ $color-light-alt: hsl(hue($color-primary), 15, 85);
$color-text-dark: #000;
$color-money: #216C2A;
$color-meta-light: #505050;
$color-meta-lighter: #999999;
$color-info-bg: #3a779d;
$font-size: 16px;
@ -18,6 +20,11 @@ $max-post-content-width: 800px;
$font-header: 'Raleway', sans-serif;
$font-body: 'Raleway', sans-serif;
$font-size-h1: 2.4em;
$font-size-h2: 2.0em;
$font-size-h3: 1.75em;
$font-size-h4: 1.4em;
$content-side-padding: 15px;
@mixin anchor($color)

131
web/scss/_roadmap.scss Normal file
View file

@ -0,0 +1,131 @@
@import "global";
$color-roadmap-border: $color-primary;
$width-roadmap: 4px;
$width-roadmap-item: $width-roadmap - 1;
$width-date: 120px;
$radius-roadmap: 10px;
.roadmap-container
{
border-left: $width-roadmap solid $color-roadmap-border;
margin: $spacing-vertical 0 $spacing-vertical $width-date;
&:before, &:after
{
background: $color-roadmap-border;
content: "";
display: block;
position: relative;
height: $width-roadmap;
left: -2 * ($width-roadmap + 1);
width: $width-roadmap * 4;
}
&:before
{
top: -1 * ($width-roadmap);
}
&:after
{
bottom: -1 * ($width-roadmap);
}
}
.show-all-roadmap-groups
{
display: inline-block;
margin: $spacing-vertical 0 0;
}
.roadmap-group-closed
{
display: none;
}
$title-spur: 8 * $width-roadmap;
.roadmap-group-title
{
&:before
{
border-top: $width-roadmap solid $color-roadmap-border;
content: "";
display: inline-block;
height: $width-roadmap;
margin-right: 10px;
vertical-align: middle;
width: $title-spur;
}
cursor: pointer;
font-weight: 400;
margin: $spacing-vertical * 2 0 $spacing-vertical * 1 0;
padding: 0;
}
.roadmap-group-title-label
{
background: $color-roadmap-border;
@include border-radius($radius-roadmap);
color: rgba(255, 255, 255, 0.85);
display: inline-block;
letter-spacing: 0;
padding: 0 10px;
text-align: center;
width: calc(100% - #{$title-spur} - 20px);
}
.roadmap-item
{
$left-margin: 20px;
$item-padding: $spacing-vertical / 2;
margin-bottom: $spacing-vertical;
position: relative;
margin-left: $left-margin;
position: relative;
padding: $item-padding;
background: #f2f2f2;
@include border-radius($radius-roadmap);
&:before {
border-top: $width-roadmap-item solid $color-roadmap-border;
content: "";
position: absolute;
left: -1 * $left-margin;
top: 0.5 * ($font-size-h3) + $item-padding;
display: inline-block;
margin-top: $width-roadmap-item / 2;
height: $width-roadmap-item;
width: 10px;
}
}
.roadmap-item-date
{
display: inline-block;
position: absolute;
top: 0;
left: -1 * $width-date;
color: $color-meta-light;
line-height: 42px;
}
.roadmap-item-title
{
display: inline-block;
margin: 0;
}
.roadmap-item-content
{
font-size: 0.9em;
margin: 10px;
}
.roadmap-item-assignee
{
float: right;
font-size: 0.8em;
}
/*
.roadmap-item-closed .roadmap-item-content
{
display: none;
}*/

View file

@ -58,19 +58,6 @@
margin: 10px $spacing-vertical / 2 0 $spacing-vertical / 4;
}
}
.label-cycle
{
visibility: hidden;
display: block;
&.label-cycle-init
{
visibility: visible;
:not(:first-child)
{
display: none;
}
}
}
.sale-level
{

View file

@ -13,4 +13,5 @@
@import "sale";
@import "code";
@import "blog";
@import "bounty";
@import "bounty";
@import "roadmap";