commit
eb3fc1c120
22 changed files with 3711 additions and 902 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -7,3 +7,6 @@ src/tmp/github_api_cache/*
|
|||
!src/tmp/github_api_cache/.gitkeep
|
||||
src/vendor
|
||||
src/composer.phar
|
||||
src/config/config.php
|
||||
src/node_modules/
|
||||
src/public/css/style.*
|
||||
|
|
|
@ -14,8 +14,11 @@ Try it, it's easy to setup! No database needed
|
|||
* `cd github-issues-mirror/src`
|
||||
* `curl -s https://getcomposer.org/installer | php`
|
||||
* `php composer.phar install`
|
||||
* `npm install`
|
||||
* `npm run compileCSS`
|
||||
* `cp config/config.example.php config/config.php`
|
||||
|
||||
Make sure to point your vhost to `src/public`. The `src/tmp` directory has to be writable.
|
||||
Make sure to point your vhost to `src/public`. The `src/tmp` and the `src/data` directories have to be writable.
|
||||
|
||||
### Import issues from GitHub
|
||||
|
||||
|
@ -25,7 +28,7 @@ You may want to setup a cronjob to import the issues regularly. The first time y
|
|||
|
||||
## Configuration
|
||||
|
||||
See [src/config/config.php](src/config/config.php)
|
||||
See [src/config/config.php](src/config/config.example.php)
|
||||
|
||||
## Data structure
|
||||
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
{
|
||||
"require": {
|
||||
"slim/slim": "2.*",
|
||||
"twig/twig": "1.15.*",
|
||||
"slim/views": "0.1.*",
|
||||
"slim/extras": "2.*",
|
||||
"knplabs/github-api": "1.2.*",
|
||||
"michelf/php-markdown": "1.4.*",
|
||||
"ezyang/htmlpurifier": "4.6.*",
|
||||
"phpmailer/phpmailer": "5.2.*"
|
||||
"slim/slim": "^3.0",
|
||||
"ezyang/htmlpurifier": "^4.9",
|
||||
"phpmailer/phpmailer": "^6.0",
|
||||
"erusev/parsedown": "^1.6",
|
||||
"slim/twig-view": "^2.2",
|
||||
"knplabs/github-api": "^2.5",
|
||||
"php-http/guzzle6-adapter": "^1.1",
|
||||
"cache/filesystem-adapter": "^1.0",
|
||||
"monolog/monolog": "^1.23"
|
||||
},
|
||||
"autoload":{
|
||||
"psr-0":{
|
||||
|
|
1996
src/composer.lock
generated
1996
src/composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -32,3 +32,15 @@ define('NUMBER_OF_ISSUES_PER_PAGE', 100);
|
|||
* error messages will be displayed if enabled.
|
||||
*/
|
||||
define('DEBUG_ENABLED', false);
|
||||
|
||||
/**
|
||||
* Set list of file extensions that should be disallowed in links
|
||||
* see https://github.com/piwik/github-issues-mirror/issues/5
|
||||
*/
|
||||
define('FORBIDDEN_EXTENSIONS', ['swf', 'js', 'html', 'htm']);
|
||||
|
||||
/**
|
||||
* If you want to enable piwik tracking enter the URL to your piwik instance and the ID of the website here
|
||||
*/
|
||||
define('PIWIK_URL', false);
|
||||
define('PIWIK_ID', false);
|
|
@ -8,29 +8,40 @@
|
|||
|
||||
namespace helpers;
|
||||
|
||||
use Cache\Adapter\Filesystem\FilesystemCachePool;
|
||||
use Github\Client;
|
||||
use Github\HttpClient\CachedHttpClient;
|
||||
use Github\ResultPager;
|
||||
use League\Flysystem\Adapter\Local;
|
||||
use League\Flysystem\Filesystem;
|
||||
use Monolog\Logger;
|
||||
|
||||
class GithubImporter {
|
||||
|
||||
private $client;
|
||||
|
||||
public function __construct(Client $client)
|
||||
private $logger;
|
||||
|
||||
public function __construct(Client $client, Logger $logger)
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
public function import($organization, $repository, $numIssuesPerPage)
|
||||
{
|
||||
$issues = $this->fetchAllIssues($organization, $repository);
|
||||
|
||||
$this->logger->info("saving pages");
|
||||
$issuesList = new Page();
|
||||
$issuesList->save($issues, $numIssuesPerPage);
|
||||
$this->logger->info("saved pages");
|
||||
|
||||
|
||||
$this->logger->info("fetching comments for issues");
|
||||
foreach ($issues as $issue) {
|
||||
$comments = $this->fetchAllComments($organization, $repository, $issue);
|
||||
|
||||
$this->logger->debug("saving comments for #" . $issue["number"]);
|
||||
$instance = new Issue();
|
||||
$instance->save($issue, $comments);
|
||||
}
|
||||
|
@ -38,8 +49,12 @@ class GithubImporter {
|
|||
|
||||
public static function buildClient($clientId, $clientSecret)
|
||||
{
|
||||
$httpClient = new CachedHttpClient(array('cache_dir' => realpath('../tmp/github_api_cache')));
|
||||
$client = new Client($httpClient);
|
||||
$filesystemAdapter = new Local(realpath('../tmp/github_api_cache'));
|
||||
$filesystem = new Filesystem($filesystemAdapter);
|
||||
|
||||
$pool = new FilesystemCachePool($filesystem);
|
||||
$client = new Client();
|
||||
$client->addCache($pool);
|
||||
|
||||
if (!empty($clientId) && !empty($clientSecret)) {
|
||||
$client->authenticate($clientId, $clientSecret, Client::AUTH_URL_CLIENT_ID);
|
||||
|
@ -50,22 +65,25 @@ class GithubImporter {
|
|||
|
||||
private function fetchAllIssues($organization, $repository)
|
||||
{
|
||||
$this->logger->info("fetching all issues");
|
||||
$params = array(
|
||||
$organization,
|
||||
$repository,
|
||||
array('filter' => 'all', 'state' => 'all', 'direction' => 'asc', 'sort' => 'created')
|
||||
array('filter' => 'all', 'state' => 'all', 'direction' => 'desc', 'sort' => 'updated')
|
||||
);
|
||||
|
||||
$paginator = new ResultPager($this->client);
|
||||
$issuesApi = $this->client->api('issue');
|
||||
$issues = $paginator->fetchAll($issuesApi, 'all', $params);
|
||||
|
||||
$this->logger->info("fetched all issues");
|
||||
return $issues;
|
||||
}
|
||||
|
||||
private function fetchAllComments($organization, $repository, $issue)
|
||||
{
|
||||
$this->logger->debug("fetching comments for #" . $issue["number"]);
|
||||
if (empty($issue['comments'])) {
|
||||
$this->logger->debug("no comments for #" . $issue["number"]);
|
||||
return array();
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
namespace helpers;
|
||||
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
|
||||
class Mail {
|
||||
|
||||
public static function sendEmail($subject, $message, $from, $to) {
|
||||
|
@ -15,7 +17,7 @@ class Mail {
|
|||
return;
|
||||
}
|
||||
|
||||
$mail = new \PHPMailer();
|
||||
$mail = new PHPMailer();
|
||||
$mail->From = $from;
|
||||
$mail->FromName = 'Issues Mirror';
|
||||
$mail->AddReplyTo($from);
|
||||
|
|
|
@ -8,16 +8,9 @@
|
|||
|
||||
namespace helpers;
|
||||
|
||||
use \Michelf\MarkdownExtra as MarkdownParser;
|
||||
|
||||
class Markdown extends MarkdownParser {
|
||||
|
||||
protected function doHeaders($text)
|
||||
{
|
||||
// Do not transform headers, for instance because of backtraces which contain #0 #1 ...
|
||||
// They are also not rendered by GitHub issues.
|
||||
return $text;
|
||||
}
|
||||
class Markdown extends \Parsedown
|
||||
{
|
||||
|
||||
/**
|
||||
* Transform markdown to HTML. The HTML will be purified to prevent XSS.
|
||||
|
@ -25,18 +18,44 @@ class Markdown extends MarkdownParser {
|
|||
* @param string $markdown
|
||||
* @return string
|
||||
*/
|
||||
public function transform($markdown)
|
||||
{
|
||||
$html = parent::transform($markdown);
|
||||
public function text($markdown) {
|
||||
$markdown = $this->parseMentions($markdown);
|
||||
$markdown = $this->parseIssueMentions($markdown);
|
||||
$this->setBreaksEnabled(true);
|
||||
$html = parent::text($markdown);
|
||||
|
||||
$html = $this->removeUnsafeFileExtensions($html);
|
||||
return $this->purifyHtml($html);
|
||||
}
|
||||
|
||||
private function purifyHtml($html)
|
||||
{
|
||||
private function parseMentions($markdown) {
|
||||
$regex = '/\@(\w+)/';
|
||||
return preg_replace($regex, "<a class='mention' href='https://github.com/$1'>$0</a>", $markdown);
|
||||
}
|
||||
|
||||
private function parseIssueMentions($markdown) {
|
||||
$regex = '/#(\d+)/';
|
||||
return preg_replace($regex, "<a href='/$1'>$0</a>", $markdown);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <a href="http://issues.piwik.org/attachments/1199/swelen_dateslider.swf">swelen_dateslider.swf</a>
|
||||
* to
|
||||
* <a href="http://issues.piwik.org/">swelen_dateslider.swf</a>
|
||||
* @param $html
|
||||
* @return string html
|
||||
*/
|
||||
private function removeUnsafeFileExtensions($html) {
|
||||
$regex = '/attachments\/(.*?)\.(' . implode("|", FORBIDDEN_EXTENSIONS) . ')/';
|
||||
return preg_replace($regex, "", $html);
|
||||
}
|
||||
|
||||
private function purifyHtml($html) {
|
||||
$config = \HTMLPurifier_Config::createDefault();
|
||||
$config->set('HTML.Doctype', 'XHTML 1.0 Transitional');
|
||||
$config->set('HTML.Allowed', 'p,strong,em,b,a[href],i,span,ul,ol,li,cite,code,pre');
|
||||
$config->set('HTML.Allowed', 'p,strong,em,b,a[href],i,span,ul,ol,li,cite,code,pre,br,blockquote,img');
|
||||
$config->set('HTML.AllowedAttributes', 'src, height, width, alt, href, class');
|
||||
$config->set('URI.AllowedSchemes', array('http' => true, 'https' => true, 'mailto' => true, 'ftp' => true));
|
||||
$config->set('HTML.TargetBlank', true);
|
||||
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
|
||||
namespace helpers;
|
||||
|
||||
class Page {
|
||||
class Page
|
||||
{
|
||||
|
||||
/**
|
||||
* Get all details of the given page number.
|
||||
|
@ -17,8 +18,7 @@ class Page {
|
|||
* @param int $pageNumber
|
||||
* @return bool
|
||||
*/
|
||||
public function getPage($pageNumber)
|
||||
{
|
||||
public function getPage($pageNumber) {
|
||||
$path = $this->getPathToFile($pageNumber);
|
||||
$content = file_get_contents($path);
|
||||
|
||||
|
@ -30,8 +30,7 @@ class Page {
|
|||
* @param int $pageNumber
|
||||
* @return bool
|
||||
*/
|
||||
public function exists($pageNumber)
|
||||
{
|
||||
public function exists($pageNumber) {
|
||||
$path = $this->getPathToFile($pageNumber);
|
||||
|
||||
return file_exists($path);
|
||||
|
@ -43,8 +42,7 @@ class Page {
|
|||
* @param array $issues
|
||||
* @param int $numIssuesPerPage
|
||||
*/
|
||||
public function save($issues, $numIssuesPerPage)
|
||||
{
|
||||
public function save($issues, $numIssuesPerPage) {
|
||||
if (empty($issues)) {
|
||||
return;
|
||||
}
|
||||
|
@ -67,13 +65,11 @@ class Page {
|
|||
}
|
||||
}
|
||||
|
||||
private function getPathToFile($pageNumber)
|
||||
{
|
||||
private function getPathToFile($pageNumber) {
|
||||
return sprintf('%s/../data/pages/%d.json', dirname(__FILE__), $pageNumber);
|
||||
}
|
||||
|
||||
private function formatIssues($issuesInPage)
|
||||
{
|
||||
private function formatIssues($issuesInPage) {
|
||||
$formatted = array();
|
||||
|
||||
foreach ($issuesInPage as $issue) {
|
||||
|
@ -85,10 +81,29 @@ class Page {
|
|||
'login' => $issue['user']['login']
|
||||
),
|
||||
'created_at' => $issue['created_at'],
|
||||
'labels' => $issue['labels']
|
||||
);
|
||||
}
|
||||
|
||||
return $formatted;
|
||||
}
|
||||
|
||||
public function getPaginationArray($numPages, $page, $padding = 2) {
|
||||
$pages = [1];
|
||||
$i = 2;
|
||||
while ($i <= $numPages) {
|
||||
if ($i < ($page - $padding - 1)) {
|
||||
$pages[] = "d";
|
||||
$i = $page - $padding;
|
||||
} elseif (($i > ($page + $padding)) && ($numPages > ($page + $padding + 2))) {
|
||||
# Wenn (
|
||||
$pages[] = "d";
|
||||
$i = $numPages;
|
||||
}
|
||||
$pages[] = $i;
|
||||
$i++;
|
||||
}
|
||||
return $pages;
|
||||
}
|
||||
|
||||
}
|
|
@ -8,44 +8,58 @@
|
|||
|
||||
namespace helpers;
|
||||
|
||||
class Twig {
|
||||
class Twig
|
||||
{
|
||||
|
||||
public static function setDateFormat(\Twig_Environment $environment)
|
||||
{
|
||||
$environment->getExtension('core')->setDateFormat('F jS Y');
|
||||
public static function setDateFormat(\Twig_Environment $environment) {
|
||||
$environment->getExtension("Twig_Extension_Core")->setDateFormat('F jS Y');
|
||||
}
|
||||
|
||||
public static function registerFilter(\Twig_Environment $environment)
|
||||
{
|
||||
public static function registerFilter(\Twig_Environment $environment) {
|
||||
$environment->addFilter(static::getMarkdownFilter());
|
||||
$environment->addFilter(static::getLinkToPageFilter());
|
||||
$environment->addFilter(static::getLinkToIssueFilter());
|
||||
$environment->addFilter(static::getColorFilter());
|
||||
$environment->addFunction(static::getPaginationFunction());
|
||||
}
|
||||
|
||||
private static function getMarkdownFilter()
|
||||
{
|
||||
private static function getMarkdownFilter() {
|
||||
return new \Twig_SimpleFilter('markdown', function ($text) {
|
||||
$parser = new Markdown();
|
||||
return $parser->transform($text);
|
||||
return $parser->text($text);
|
||||
});
|
||||
}
|
||||
|
||||
private static function getLinkToPageFilter()
|
||||
{
|
||||
return new \Twig_SimpleFilter('pageLink', function ($page) {
|
||||
if (1 === (int) $page) {
|
||||
return '/';
|
||||
private static function getColorFilter() {
|
||||
return new \Twig_SimpleFilter(
|
||||
/**
|
||||
* modified from https://24ways.org/2010/calculating-color-contrast/
|
||||
* @param $colorstring "#ffffff"
|
||||
* @return string
|
||||
*/
|
||||
'textcolor', function ($hexcolor) {
|
||||
$r = hexdec(substr($hexcolor, 0, 2));
|
||||
$g = hexdec(substr($hexcolor, 2, 2));
|
||||
$b = hexdec(substr($hexcolor, 4, 2));
|
||||
$yiq = (($r * 299) + ($g * 587) + ($b * 114)) / 1000;
|
||||
return ($yiq >= 128) ? 'black' : 'white';
|
||||
});
|
||||
}
|
||||
|
||||
return '/?page=' . (int) $page;
|
||||
}, array('is_safe' => array('all')));
|
||||
private static function getPaginationFunction() {
|
||||
return new \Twig_Function('paginationFunction', function ($numPages, $page, $padding = 2) {
|
||||
$pages = [1];
|
||||
$i = 2;
|
||||
while ($i <= $numPages) {
|
||||
if ($i < ($page - $padding - 1)) {
|
||||
$pages[] = "d";
|
||||
$i = $page - $padding;
|
||||
} elseif (($i > ($page + $padding)) && ($numPages > ($page + $padding + 2))) {
|
||||
$pages[] = "d";
|
||||
$i = $numPages;
|
||||
}
|
||||
|
||||
private static function getLinkToIssueFilter()
|
||||
{
|
||||
return new \Twig_SimpleFilter('issueLink', function ($number) {
|
||||
|
||||
return '/' . (int) $number;
|
||||
}, array('is_safe' => array('all')));
|
||||
$pages[] = $i;
|
||||
$i++;
|
||||
}
|
||||
return $pages;
|
||||
});
|
||||
}
|
||||
}
|
1533
src/package-lock.json
generated
Normal file
1533
src/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
17
src/package.json
Normal file
17
src/package.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "src",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"bootstrap": "^4.0.0-beta",
|
||||
"node-sass": "^4.5.3"
|
||||
},
|
||||
"devDependencies": {},
|
||||
"scripts": {
|
||||
"compileCSS": "node-sass --recursive --output-style compressed --output public/css --source-map true --source-map-contents scss",
|
||||
"watchSCSS": "node-sass --watch --recursive --output-style compressed --output public/css --source-map true --source-map-contents scss"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
10
src/public/css/bootstrap.min.css
vendored
10
src/public/css/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
|
@ -1,391 +0,0 @@
|
|||
{
|
||||
"vars": {
|
||||
"@gray-darker": "lighten(#000, 13.5%)",
|
||||
"@gray-dark": "lighten(#000, 20%)",
|
||||
"@gray": "lighten(#000, 33.5%)",
|
||||
"@gray-light": "lighten(#000, 46.7%)",
|
||||
"@gray-lighter": "lighten(#000, 93.5%)",
|
||||
"@brand-primary": "#428bca",
|
||||
"@brand-success": "#5cb85c",
|
||||
"@brand-info": "#5bc0de",
|
||||
"@brand-warning": "#f0ad4e",
|
||||
"@brand-danger": "#d9534f",
|
||||
"@body-bg": "#fff",
|
||||
"@text-color": "@gray-dark",
|
||||
"@link-color": "@brand-primary",
|
||||
"@link-hover-color": "darken(@link-color, 15%)",
|
||||
"@font-family-sans-serif": "\"Helvetica Neue\", Helvetica, Arial, sans-serif",
|
||||
"@font-family-serif": "Georgia, \"Times New Roman\", Times, serif",
|
||||
"@font-family-monospace": "Menlo, Monaco, Consolas, \"Courier New\", monospace",
|
||||
"@font-family-base": "@font-family-sans-serif",
|
||||
"@font-size-base": "14px",
|
||||
"@font-size-large": "ceil((@font-size-base * 1.25))",
|
||||
"@font-size-small": "ceil((@font-size-base * 0.85))",
|
||||
"@font-size-h1": "floor((@font-size-base * 2.6))",
|
||||
"@font-size-h2": "floor((@font-size-base * 2.15))",
|
||||
"@font-size-h3": "ceil((@font-size-base * 1.7))",
|
||||
"@font-size-h4": "ceil((@font-size-base * 1.25))",
|
||||
"@font-size-h5": "@font-size-base",
|
||||
"@font-size-h6": "ceil((@font-size-base * 0.85))",
|
||||
"@line-height-base": "1.428571429",
|
||||
"@line-height-computed": "floor((@font-size-base * @line-height-base))",
|
||||
"@headings-font-family": "inherit",
|
||||
"@headings-font-weight": "500",
|
||||
"@headings-line-height": "1.1",
|
||||
"@headings-color": "inherit",
|
||||
"@icon-font-path": "\"../fonts/\"",
|
||||
"@icon-font-name": "\"glyphicons-halflings-regular\"",
|
||||
"@icon-font-svg-id": "\"glyphicons_halflingsregular\"",
|
||||
"@padding-base-vertical": "6px",
|
||||
"@padding-base-horizontal": "12px",
|
||||
"@padding-large-vertical": "10px",
|
||||
"@padding-large-horizontal": "16px",
|
||||
"@padding-small-vertical": "5px",
|
||||
"@padding-small-horizontal": "10px",
|
||||
"@padding-xs-vertical": "1px",
|
||||
"@padding-xs-horizontal": "5px",
|
||||
"@line-height-large": "1.33",
|
||||
"@line-height-small": "1.5",
|
||||
"@border-radius-base": "4px",
|
||||
"@border-radius-large": "6px",
|
||||
"@border-radius-small": "3px",
|
||||
"@component-active-color": "#fff",
|
||||
"@component-active-bg": "@brand-primary",
|
||||
"@caret-width-base": "4px",
|
||||
"@caret-width-large": "5px",
|
||||
"@table-cell-padding": "8px",
|
||||
"@table-condensed-cell-padding": "5px",
|
||||
"@table-bg": "transparent",
|
||||
"@table-bg-accent": "#f9f9f9",
|
||||
"@table-bg-hover": "#f5f5f5",
|
||||
"@table-bg-active": "@table-bg-hover",
|
||||
"@table-border-color": "#ddd",
|
||||
"@btn-font-weight": "normal",
|
||||
"@btn-default-color": "#333",
|
||||
"@btn-default-bg": "#fff",
|
||||
"@btn-default-border": "#ccc",
|
||||
"@btn-primary-color": "#fff",
|
||||
"@btn-primary-bg": "@brand-primary",
|
||||
"@btn-primary-border": "darken(@btn-primary-bg, 5%)",
|
||||
"@btn-success-color": "#fff",
|
||||
"@btn-success-bg": "@brand-success",
|
||||
"@btn-success-border": "darken(@btn-success-bg, 5%)",
|
||||
"@btn-info-color": "#fff",
|
||||
"@btn-info-bg": "@brand-info",
|
||||
"@btn-info-border": "darken(@btn-info-bg, 5%)",
|
||||
"@btn-warning-color": "#fff",
|
||||
"@btn-warning-bg": "@brand-warning",
|
||||
"@btn-warning-border": "darken(@btn-warning-bg, 5%)",
|
||||
"@btn-danger-color": "#fff",
|
||||
"@btn-danger-bg": "@brand-danger",
|
||||
"@btn-danger-border": "darken(@btn-danger-bg, 5%)",
|
||||
"@btn-link-disabled-color": "@gray-light",
|
||||
"@input-bg": "#fff",
|
||||
"@input-bg-disabled": "@gray-lighter",
|
||||
"@input-color": "@gray",
|
||||
"@input-border": "#ccc",
|
||||
"@input-border-radius": "@border-radius-base",
|
||||
"@input-border-focus": "#66afe9",
|
||||
"@input-color-placeholder": "@gray-light",
|
||||
"@input-height-base": "(@line-height-computed + (@padding-base-vertical * 2) + 2)",
|
||||
"@input-height-large": "(ceil(@font-size-large * @line-height-large) + (@padding-large-vertical * 2) + 2)",
|
||||
"@input-height-small": "(floor(@font-size-small * @line-height-small) + (@padding-small-vertical * 2) + 2)",
|
||||
"@legend-color": "@gray-dark",
|
||||
"@legend-border-color": "#e5e5e5",
|
||||
"@input-group-addon-bg": "@gray-lighter",
|
||||
"@input-group-addon-border-color": "@input-border",
|
||||
"@dropdown-bg": "#fff",
|
||||
"@dropdown-border": "rgba(0,0,0,.15)",
|
||||
"@dropdown-fallback-border": "#ccc",
|
||||
"@dropdown-divider-bg": "#e5e5e5",
|
||||
"@dropdown-link-color": "@gray-dark",
|
||||
"@dropdown-link-hover-color": "darken(@gray-dark, 5%)",
|
||||
"@dropdown-link-hover-bg": "#f5f5f5",
|
||||
"@dropdown-link-active-color": "@component-active-color",
|
||||
"@dropdown-link-active-bg": "@component-active-bg",
|
||||
"@dropdown-link-disabled-color": "@gray-light",
|
||||
"@dropdown-header-color": "@gray-light",
|
||||
"@dropdown-caret-color": "#000",
|
||||
"@screen-xs": "480px",
|
||||
"@screen-xs-min": "@screen-xs",
|
||||
"@screen-phone": "@screen-xs-min",
|
||||
"@screen-sm": "768px",
|
||||
"@screen-sm-min": "@screen-sm",
|
||||
"@screen-tablet": "@screen-sm-min",
|
||||
"@screen-md": "992px",
|
||||
"@screen-md-min": "@screen-md",
|
||||
"@screen-desktop": "@screen-md-min",
|
||||
"@screen-lg": "1200px",
|
||||
"@screen-lg-min": "@screen-lg",
|
||||
"@screen-lg-desktop": "@screen-lg-min",
|
||||
"@screen-xs-max": "(@screen-sm-min - 1)",
|
||||
"@screen-sm-max": "(@screen-md-min - 1)",
|
||||
"@screen-md-max": "(@screen-lg-min - 1)",
|
||||
"@grid-columns": "12",
|
||||
"@grid-gutter-width": "30px",
|
||||
"@grid-float-breakpoint": "@screen-sm-min",
|
||||
"@grid-float-breakpoint-max": "(@grid-float-breakpoint - 1)",
|
||||
"@container-tablet": "((720px + @grid-gutter-width))",
|
||||
"@container-sm": "@container-tablet",
|
||||
"@container-desktop": "((940px + @grid-gutter-width))",
|
||||
"@container-md": "@container-desktop",
|
||||
"@container-large-desktop": "((1140px + @grid-gutter-width))",
|
||||
"@container-lg": "@container-large-desktop",
|
||||
"@navbar-height": "50px",
|
||||
"@navbar-margin-bottom": "@line-height-computed",
|
||||
"@navbar-border-radius": "@border-radius-base",
|
||||
"@navbar-padding-horizontal": "floor((@grid-gutter-width / 2))",
|
||||
"@navbar-padding-vertical": "((@navbar-height - @line-height-computed) / 2)",
|
||||
"@navbar-collapse-max-height": "340px",
|
||||
"@navbar-default-color": "#777",
|
||||
"@navbar-default-bg": "#f8f8f8",
|
||||
"@navbar-default-border": "darken(@navbar-default-bg, 6.5%)",
|
||||
"@navbar-default-link-color": "#777",
|
||||
"@navbar-default-link-hover-color": "#333",
|
||||
"@navbar-default-link-hover-bg": "transparent",
|
||||
"@navbar-default-link-active-color": "#555",
|
||||
"@navbar-default-link-active-bg": "darken(@navbar-default-bg, 6.5%)",
|
||||
"@navbar-default-link-disabled-color": "#ccc",
|
||||
"@navbar-default-link-disabled-bg": "transparent",
|
||||
"@navbar-default-brand-color": "@navbar-default-link-color",
|
||||
"@navbar-default-brand-hover-color": "darken(@navbar-default-brand-color, 10%)",
|
||||
"@navbar-default-brand-hover-bg": "transparent",
|
||||
"@navbar-default-toggle-hover-bg": "#ddd",
|
||||
"@navbar-default-toggle-icon-bar-bg": "#888",
|
||||
"@navbar-default-toggle-border-color": "#ddd",
|
||||
"@navbar-inverse-color": "@gray-light",
|
||||
"@navbar-inverse-bg": "#222",
|
||||
"@navbar-inverse-border": "darken(@navbar-inverse-bg, 10%)",
|
||||
"@navbar-inverse-link-color": "@gray-light",
|
||||
"@navbar-inverse-link-hover-color": "#fff",
|
||||
"@navbar-inverse-link-hover-bg": "transparent",
|
||||
"@navbar-inverse-link-active-color": "@navbar-inverse-link-hover-color",
|
||||
"@navbar-inverse-link-active-bg": "darken(@navbar-inverse-bg, 10%)",
|
||||
"@navbar-inverse-link-disabled-color": "#444",
|
||||
"@navbar-inverse-link-disabled-bg": "transparent",
|
||||
"@navbar-inverse-brand-color": "@navbar-inverse-link-color",
|
||||
"@navbar-inverse-brand-hover-color": "#fff",
|
||||
"@navbar-inverse-brand-hover-bg": "transparent",
|
||||
"@navbar-inverse-toggle-hover-bg": "#333",
|
||||
"@navbar-inverse-toggle-icon-bar-bg": "#fff",
|
||||
"@navbar-inverse-toggle-border-color": "#333",
|
||||
"@nav-link-padding": "10px 15px",
|
||||
"@nav-link-hover-bg": "@gray-lighter",
|
||||
"@nav-disabled-link-color": "@gray-light",
|
||||
"@nav-disabled-link-hover-color": "@gray-light",
|
||||
"@nav-open-link-hover-color": "#fff",
|
||||
"@nav-tabs-border-color": "#ddd",
|
||||
"@nav-tabs-link-hover-border-color": "@gray-lighter",
|
||||
"@nav-tabs-active-link-hover-bg": "@body-bg",
|
||||
"@nav-tabs-active-link-hover-color": "@gray",
|
||||
"@nav-tabs-active-link-hover-border-color": "#ddd",
|
||||
"@nav-tabs-justified-link-border-color": "#ddd",
|
||||
"@nav-tabs-justified-active-link-border-color": "@body-bg",
|
||||
"@nav-pills-border-radius": "@border-radius-base",
|
||||
"@nav-pills-active-link-hover-bg": "@component-active-bg",
|
||||
"@nav-pills-active-link-hover-color": "@component-active-color",
|
||||
"@pagination-color": "@link-color",
|
||||
"@pagination-bg": "#fff",
|
||||
"@pagination-border": "#ddd",
|
||||
"@pagination-hover-color": "@link-hover-color",
|
||||
"@pagination-hover-bg": "@gray-lighter",
|
||||
"@pagination-hover-border": "#ddd",
|
||||
"@pagination-active-color": "#fff",
|
||||
"@pagination-active-bg": "@brand-primary",
|
||||
"@pagination-active-border": "@brand-primary",
|
||||
"@pagination-disabled-color": "@gray-light",
|
||||
"@pagination-disabled-bg": "#fff",
|
||||
"@pagination-disabled-border": "#ddd",
|
||||
"@pager-bg": "@pagination-bg",
|
||||
"@pager-border": "@pagination-border",
|
||||
"@pager-border-radius": "15px",
|
||||
"@pager-hover-bg": "@pagination-hover-bg",
|
||||
"@pager-active-bg": "@pagination-active-bg",
|
||||
"@pager-active-color": "@pagination-active-color",
|
||||
"@pager-disabled-color": "@pagination-disabled-color",
|
||||
"@jumbotron-padding": "30px",
|
||||
"@jumbotron-color": "inherit",
|
||||
"@jumbotron-bg": "@gray-lighter",
|
||||
"@jumbotron-heading-color": "inherit",
|
||||
"@jumbotron-font-size": "ceil((@font-size-base * 1.5))",
|
||||
"@state-success-text": "#3c763d",
|
||||
"@state-success-bg": "#dff0d8",
|
||||
"@state-success-border": "darken(spin(@state-success-bg, -10), 5%)",
|
||||
"@state-info-text": "#31708f",
|
||||
"@state-info-bg": "#d9edf7",
|
||||
"@state-info-border": "darken(spin(@state-info-bg, -10), 7%)",
|
||||
"@state-warning-text": "#8a6d3b",
|
||||
"@state-warning-bg": "#fcf8e3",
|
||||
"@state-warning-border": "darken(spin(@state-warning-bg, -10), 5%)",
|
||||
"@state-danger-text": "#a94442",
|
||||
"@state-danger-bg": "#f2dede",
|
||||
"@state-danger-border": "darken(spin(@state-danger-bg, -10), 5%)",
|
||||
"@tooltip-max-width": "200px",
|
||||
"@tooltip-color": "#fff",
|
||||
"@tooltip-bg": "#000",
|
||||
"@tooltip-opacity": ".9",
|
||||
"@tooltip-arrow-width": "5px",
|
||||
"@tooltip-arrow-color": "@tooltip-bg",
|
||||
"@popover-bg": "#fff",
|
||||
"@popover-max-width": "276px",
|
||||
"@popover-border-color": "rgba(0,0,0,.2)",
|
||||
"@popover-fallback-border-color": "#ccc",
|
||||
"@popover-title-bg": "darken(@popover-bg, 3%)",
|
||||
"@popover-arrow-width": "10px",
|
||||
"@popover-arrow-color": "#fff",
|
||||
"@popover-arrow-outer-width": "(@popover-arrow-width + 1)",
|
||||
"@popover-arrow-outer-color": "fadein(@popover-border-color, 5%)",
|
||||
"@popover-arrow-outer-fallback-color": "darken(@popover-fallback-border-color, 20%)",
|
||||
"@label-default-bg": "@gray-light",
|
||||
"@label-primary-bg": "@brand-primary",
|
||||
"@label-success-bg": "@brand-success",
|
||||
"@label-info-bg": "@brand-info",
|
||||
"@label-warning-bg": "@brand-warning",
|
||||
"@label-danger-bg": "@brand-danger",
|
||||
"@label-color": "#fff",
|
||||
"@label-link-hover-color": "#fff",
|
||||
"@modal-inner-padding": "15px",
|
||||
"@modal-title-padding": "15px",
|
||||
"@modal-title-line-height": "@line-height-base",
|
||||
"@modal-content-bg": "#fff",
|
||||
"@modal-content-border-color": "rgba(0,0,0,.2)",
|
||||
"@modal-content-fallback-border-color": "#999",
|
||||
"@modal-backdrop-bg": "#000",
|
||||
"@modal-backdrop-opacity": ".5",
|
||||
"@modal-header-border-color": "#e5e5e5",
|
||||
"@modal-footer-border-color": "@modal-header-border-color",
|
||||
"@modal-lg": "900px",
|
||||
"@modal-md": "600px",
|
||||
"@modal-sm": "300px",
|
||||
"@alert-padding": "15px",
|
||||
"@alert-border-radius": "@border-radius-base",
|
||||
"@alert-link-font-weight": "bold",
|
||||
"@alert-success-bg": "@state-success-bg",
|
||||
"@alert-success-text": "@state-success-text",
|
||||
"@alert-success-border": "@state-success-border",
|
||||
"@alert-info-bg": "@state-info-bg",
|
||||
"@alert-info-text": "@state-info-text",
|
||||
"@alert-info-border": "@state-info-border",
|
||||
"@alert-warning-bg": "@state-warning-bg",
|
||||
"@alert-warning-text": "@state-warning-text",
|
||||
"@alert-warning-border": "@state-warning-border",
|
||||
"@alert-danger-bg": "@state-danger-bg",
|
||||
"@alert-danger-text": "@state-danger-text",
|
||||
"@alert-danger-border": "@state-danger-border",
|
||||
"@progress-bg": "#f5f5f5",
|
||||
"@progress-bar-color": "#fff",
|
||||
"@progress-bar-bg": "@brand-primary",
|
||||
"@progress-bar-success-bg": "@brand-success",
|
||||
"@progress-bar-warning-bg": "@brand-warning",
|
||||
"@progress-bar-danger-bg": "@brand-danger",
|
||||
"@progress-bar-info-bg": "@brand-info",
|
||||
"@list-group-bg": "#fff",
|
||||
"@list-group-border": "#ddd",
|
||||
"@list-group-border-radius": "@border-radius-base",
|
||||
"@list-group-hover-bg": "#f5f5f5",
|
||||
"@list-group-active-color": "@component-active-color",
|
||||
"@list-group-active-bg": "@component-active-bg",
|
||||
"@list-group-active-border": "@list-group-active-bg",
|
||||
"@list-group-active-text-color": "lighten(@list-group-active-bg, 40%)",
|
||||
"@list-group-disabled-color": "@gray-light",
|
||||
"@list-group-disabled-bg": "@gray-lighter",
|
||||
"@list-group-disabled-text-color": "@list-group-disabled-color",
|
||||
"@list-group-link-color": "#555",
|
||||
"@list-group-link-hover-color": "@list-group-link-color",
|
||||
"@list-group-link-heading-color": "#333",
|
||||
"@panel-bg": "#fff",
|
||||
"@panel-body-padding": "15px",
|
||||
"@panel-heading-padding": "10px 15px",
|
||||
"@panel-footer-padding": "@panel-heading-padding",
|
||||
"@panel-border-radius": "@border-radius-base",
|
||||
"@panel-inner-border": "#ddd",
|
||||
"@panel-footer-bg": "#f5f5f5",
|
||||
"@panel-default-text": "@gray-dark",
|
||||
"@panel-default-border": "#ddd",
|
||||
"@panel-default-heading-bg": "#f5f5f5",
|
||||
"@panel-primary-text": "#fff",
|
||||
"@panel-primary-border": "@brand-primary",
|
||||
"@panel-primary-heading-bg": "@brand-primary",
|
||||
"@panel-success-text": "@state-success-text",
|
||||
"@panel-success-border": "@state-success-border",
|
||||
"@panel-success-heading-bg": "@state-success-bg",
|
||||
"@panel-info-text": "@state-info-text",
|
||||
"@panel-info-border": "@state-info-border",
|
||||
"@panel-info-heading-bg": "@state-info-bg",
|
||||
"@panel-warning-text": "@state-warning-text",
|
||||
"@panel-warning-border": "@state-warning-border",
|
||||
"@panel-warning-heading-bg": "@state-warning-bg",
|
||||
"@panel-danger-text": "@state-danger-text",
|
||||
"@panel-danger-border": "@state-danger-border",
|
||||
"@panel-danger-heading-bg": "@state-danger-bg",
|
||||
"@thumbnail-padding": "4px",
|
||||
"@thumbnail-bg": "@body-bg",
|
||||
"@thumbnail-border": "#ddd",
|
||||
"@thumbnail-border-radius": "@border-radius-base",
|
||||
"@thumbnail-caption-color": "@text-color",
|
||||
"@thumbnail-caption-padding": "9px",
|
||||
"@well-bg": "#f5f5f5",
|
||||
"@well-border": "darken(@well-bg, 7%)",
|
||||
"@badge-color": "#fff",
|
||||
"@badge-link-hover-color": "#fff",
|
||||
"@badge-bg": "@gray-light",
|
||||
"@badge-active-color": "@link-color",
|
||||
"@badge-active-bg": "#fff",
|
||||
"@badge-font-weight": "bold",
|
||||
"@badge-border-radius": "10px",
|
||||
"@breadcrumb-padding-vertical": "8px",
|
||||
"@breadcrumb-padding-horizontal": "15px",
|
||||
"@breadcrumb-bg": "#f5f5f5",
|
||||
"@breadcrumb-color": "#ccc",
|
||||
"@breadcrumb-active-color": "@gray-light",
|
||||
"@breadcrumb-separator": "\"/\"",
|
||||
"@carousel-text-shadow": "0 1px 2px rgba(0,0,0,.6)",
|
||||
"@carousel-control-color": "#fff",
|
||||
"@carousel-control-width": "15%",
|
||||
"@carousel-control-opacity": ".5",
|
||||
"@carousel-control-font-size": "20px",
|
||||
"@carousel-indicator-active-bg": "#fff",
|
||||
"@carousel-indicator-border-color": "#fff",
|
||||
"@carousel-caption-color": "#fff",
|
||||
"@close-font-weight": "bold",
|
||||
"@close-color": "#000",
|
||||
"@close-text-shadow": "0 1px 0 #fff",
|
||||
"@code-color": "#c7254e",
|
||||
"@code-bg": "#f9f2f4",
|
||||
"@kbd-color": "#fff",
|
||||
"@kbd-bg": "#333",
|
||||
"@pre-bg": "#f5f5f5",
|
||||
"@pre-color": "@gray-dark",
|
||||
"@pre-border-color": "#ccc",
|
||||
"@pre-scrollable-max-height": "340px",
|
||||
"@component-offset-horizontal": "180px",
|
||||
"@text-muted": "@gray-light",
|
||||
"@abbr-border-color": "@gray-light",
|
||||
"@headings-small-color": "@gray-light",
|
||||
"@blockquote-small-color": "@gray-light",
|
||||
"@blockquote-font-size": "(@font-size-base * 1.25)",
|
||||
"@blockquote-border-color": "@gray-lighter",
|
||||
"@page-header-border-color": "@gray-lighter",
|
||||
"@dl-horizontal-offset": "@component-offset-horizontal",
|
||||
"@hr-border": "@gray-lighter"
|
||||
},
|
||||
"css": [
|
||||
"print.less",
|
||||
"type.less",
|
||||
"code.less",
|
||||
"grid.less",
|
||||
"buttons.less",
|
||||
"responsive-utilities.less",
|
||||
"pagination.less",
|
||||
"pager.less",
|
||||
"labels.less",
|
||||
"badges.less",
|
||||
"jumbotron.less",
|
||||
"list-group.less",
|
||||
"panels.less",
|
||||
"wells.less",
|
||||
"close.less"
|
||||
],
|
||||
"js": [],
|
||||
"customizerUrl": "http://getbootstrap.com/customize/?id=c93b35180144c3316210"
|
||||
}
|
|
@ -10,26 +10,40 @@ require '../vendor/autoload.php';
|
|||
require '../config/config.php';
|
||||
|
||||
date_default_timezone_set('UTC');
|
||||
$config = [
|
||||
'settings' => [
|
||||
'displayErrorDetails' => true,
|
||||
],
|
||||
];
|
||||
|
||||
$app = new \Slim\Slim(array(
|
||||
'view' => new \Slim\Views\Twig(),
|
||||
'debug' => DEBUG_ENABLED
|
||||
));
|
||||
$app = new \Slim\App($config);
|
||||
|
||||
/** @var \Slim\Views\Twig $view */
|
||||
$view = $app->view();
|
||||
$view->parserOptions = array(
|
||||
'charset' => 'utf-8',
|
||||
'debug' => DEBUG_ENABLED,
|
||||
// Get container
|
||||
$container = $app->getContainer();
|
||||
|
||||
// Register component on container
|
||||
$container['view'] = function ($container) {
|
||||
$view = new \Slim\Views\Twig(realpath("../templates/"), [
|
||||
'cache' => realpath('../tmp/templates_cache'),
|
||||
'autoescape' => true
|
||||
);
|
||||
$view->parserExtensions = array(
|
||||
new \Slim\Views\TwigExtension()
|
||||
);
|
||||
$view->setTemplatesDirectory(realpath('../templates'));
|
||||
helpers\Twig::setDateFormat($view->getEnvironment());
|
||||
helpers\Twig::registerFilter($view->getEnvironment());
|
||||
'debug' => DEBUG_ENABLED,
|
||||
]);
|
||||
|
||||
// Instantiate and add Slim specific extension
|
||||
$basePath = rtrim(str_ireplace('index.php', '', $container['request']->getUri()->getBasePath()), '/');
|
||||
$view->addExtension(new Slim\Views\TwigExtension($container['router'], $basePath));
|
||||
$view->addExtension(new \Twig_Extension_Debug());
|
||||
$twig = $view->getEnvironment();
|
||||
helpers\Twig::setDateFormat($twig);
|
||||
helpers\Twig::registerFilter($twig);
|
||||
|
||||
$view->getEnvironment()->addGlobal('projectName', PROJECT_NAME);
|
||||
$view->getEnvironment()->addGlobal('githubOrganization', GITHUB_ORGANIZATION);
|
||||
$view->getEnvironment()->addGlobal('githubRepository', GITHUB_REPOSITORY);
|
||||
$view->getEnvironment()->addGlobal('piwikURL', PIWIK_URL);
|
||||
$view->getEnvironment()->addGlobal('piwikID', PIWIK_ID);
|
||||
|
||||
return $view;
|
||||
};
|
||||
|
||||
require '../routes/page.php';
|
||||
|
||||
|
|
|
@ -6,42 +6,33 @@
|
|||
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
|
||||
*/
|
||||
|
||||
function initView($app)
|
||||
{
|
||||
$app->view->setData('projectName', PROJECT_NAME);
|
||||
$app->view->setData('githubOrganization', GITHUB_ORGANIZATION);
|
||||
$app->view->setData('githubRepository', GITHUB_REPOSITORY);
|
||||
}
|
||||
|
||||
initView($app);
|
||||
$app->get(
|
||||
'/{number:[0-9]+}', function ($request, $response, $args) {
|
||||
|
||||
$app->get('/:number', function ($number) use ($app) {
|
||||
$number = (int) $number;
|
||||
$number = (int)$args["number"];
|
||||
|
||||
$issue = new helpers\Issue();
|
||||
|
||||
if (!$issue->exists($number)) {
|
||||
$app->pass();
|
||||
return;
|
||||
throw new \Slim\Exception\NotFoundException($request, $response);
|
||||
}
|
||||
|
||||
$details = $issue->getIssue($number);
|
||||
return $this->view->render($response, 'issue.twig', $details);
|
||||
})->setName("issue");
|
||||
|
||||
$app->render('issue.twig', $details);
|
||||
|
||||
})->conditions(array('number' => '\d+'));
|
||||
|
||||
$app->get('/', function() use ($app) {
|
||||
$pageNumber = (int) $app->request()->get('page', 1);
|
||||
$app->get('/', function ($request, $response, $args) {
|
||||
/** @var \Slim\Http\Request $request */
|
||||
$pageNumber = (int)$request->getQueryParam('page', 1);
|
||||
|
||||
$page = new helpers\Page();
|
||||
|
||||
if (!$page->exists($pageNumber)) {
|
||||
$app->pass();
|
||||
return;
|
||||
throw new \Slim\Exception\NotFoundException($request, $response);
|
||||
}
|
||||
|
||||
$details = $page->getPage($pageNumber);
|
||||
|
||||
$app->render('page.twig', $details);
|
||||
});
|
||||
return $this->view->render($response, 'page.twig', $details);
|
||||
})->setName("page");
|
102
src/scss/style.scss
Normal file
102
src/scss/style.scss
Normal file
|
@ -0,0 +1,102 @@
|
|||
$grid-gutter-width: 10px;
|
||||
|
||||
@import "../node_modules/bootstrap/scss/bootstrap";
|
||||
|
||||
.card-deck {
|
||||
.row {
|
||||
align-items: stretch;
|
||||
width: 100%;
|
||||
> div {
|
||||
display: flex;
|
||||
margin-bottom: 2*$grid-gutter-width;
|
||||
.card {
|
||||
color: $gray-900;
|
||||
small {
|
||||
color: $gray-700;
|
||||
}
|
||||
text-decoration: none;
|
||||
&:hover, &:focus, &:active {
|
||||
border: 1px solid rgba(0, 0, 0, 0.5);
|
||||
background: $gray-200;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h1 a {
|
||||
color: $gray-700;
|
||||
&:hover, &:focus, &:active {
|
||||
color: $gray-900;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.badge {
|
||||
padding: 6px 10px;
|
||||
white-space: normal;
|
||||
&.text-black {
|
||||
color: $gray-800;
|
||||
}
|
||||
&.text-white {
|
||||
color: $gray-200;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
margin-bottom: 2rem;
|
||||
&.overviewCard {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.avatar {
|
||||
margin-right: 3px;
|
||||
}
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 3px;
|
||||
}
|
||||
.authorAssociation {
|
||||
margin-left: auto;
|
||||
margin-right: 0;
|
||||
display: block;
|
||||
font-size: smaller;
|
||||
color: $gray-700;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pagination {
|
||||
svg {
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.mention {
|
||||
font-weight: bold;
|
||||
color: $gray-900;
|
||||
}
|
||||
|
||||
blockquote { // bootstrap 3 like styling
|
||||
padding: 10px 20px;
|
||||
margin: 0 0 20px;
|
||||
font-size: 17.5px;
|
||||
border-left: 5px solid $gray-300;
|
||||
}
|
||||
|
||||
pre > code {
|
||||
display: block;
|
||||
padding: 9.5px;
|
||||
margin: 0 0 10px;
|
||||
color: $gray-800;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
background-color: $gray-200;
|
||||
border: 1px solid $gray-400;
|
||||
border-radius: $border-radius;
|
||||
overflow-y: auto;
|
||||
}
|
|
@ -13,10 +13,21 @@
|
|||
require '../vendor/autoload.php';
|
||||
require '../config/config.php';
|
||||
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Logger;
|
||||
|
||||
$logger = new Logger('import_log');
|
||||
$logger->pushHandler(new StreamHandler(__DIR__ . '/../tmp/import.log', Logger::DEBUG));
|
||||
if (DEBUG_ENABLED) {
|
||||
$logger->pushHandler(new \Monolog\Handler\ErrorLogHandler());
|
||||
}
|
||||
|
||||
$logger->info("authenticating");
|
||||
$client = helpers\GithubImporter::buildClient(GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET);
|
||||
$importer = new helpers\GithubImporter($client);
|
||||
$importer = new helpers\GithubImporter($client, $logger);
|
||||
|
||||
try {
|
||||
$logger->info("starting import");
|
||||
$importer->import(GITHUB_ORGANIZATION, GITHUB_REPOSITORY, NUMBER_OF_ISSUES_PER_PAGE);
|
||||
} catch (Exception $e) {
|
||||
helpers\Mail::sendEmail('Import error', $e->getMessage(), PROJECT_EMAIL, PROJECT_EMAIL);
|
||||
|
|
|
@ -2,34 +2,48 @@
|
|||
{% import "macros.twig" as macro %}
|
||||
|
||||
{% block title %}
|
||||
<title>{{ projectName }} Issue {{number}} - {{ title }}</title>
|
||||
<title>{{ projectName }} {{ pull_request ? "Pull Request" : "Issue" }} #{{ number }} - {{ title }}</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="page-header">
|
||||
<h1>{{ title }} <small>#{{number}}</small></h1>
|
||||
{% if labels and labels|length %}
|
||||
{% for label in labels %}
|
||||
<span class="label label-default">{{ label.name }}</span>
|
||||
{% endfor %}
|
||||
<h1>{{ title }}
|
||||
<a href="{{ html_url }}">
|
||||
<small>#{{ number }}</small>
|
||||
</a>
|
||||
</h1>
|
||||
{{ macro.labels(labels) }}
|
||||
|
||||
{% if milestone %}
|
||||
<span class="badge badge-secondary" title="{{ milestone.description }}">{{ milestone.title }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div class="container">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">{{ macro.username(user) }} opened this issue on {{ created_at|date }}</div>
|
||||
<div class="panel-body">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
{{ macro.user(user) }} opened this {{ pull_request ? "Pull Request" : "Issue" }} on {{ created_at|date }}
|
||||
{% if author_association != "NONE" %}
|
||||
<span class="authorAssociation">{{ author_association | capitalize }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{ body|markdown|raw }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if comments|length %}
|
||||
{% for comment in comments %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">{{ macro.username(comment.user) }} commented on {{ comment.created_at|date }}</div>
|
||||
<div class="panel-body">
|
||||
<div class="card">
|
||||
<div class="card-header">{{ macro.user(comment.user) }} commented
|
||||
on {{ comment.created_at|date }}
|
||||
{% if comment.author_association != "NONE" %}
|
||||
<span class="authorAssociation">{{ comment.author_association | capitalize }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{{ comment.body|markdown|raw }}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -37,8 +51,10 @@
|
|||
{% endif %}
|
||||
|
||||
{% if state == 'closed' %}
|
||||
<div class="well well-sm">
|
||||
This issue was closed on {{ closed_at|date }}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
This {{ pull_request ? "Pull Request" : "Issue" }} was closed on {{ closed_at|date }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
@ -47,7 +63,7 @@
|
|||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<a href="/">Home</a> |
|
||||
<a onclick="window.history.back();" style="cursor: pointer;">Back</a>
|
||||
<a href="javascript:window.history.back();">Back</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="/css/style.css" rel="stylesheet">
|
||||
|
||||
{% block head %}
|
||||
{% endblock %}
|
||||
|
@ -17,13 +17,35 @@
|
|||
|
||||
{% block content %}{% endblock %}
|
||||
|
||||
<div class="container" style="margin-bottom: 20px;">
|
||||
<div class="container" style="margin-bottom: 20px;">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
Powered by <a target="_blank" href="https://github.com/piwik/github-issues-mirror">GitHub Issue Mirror</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if piwikURL and piwikID %}
|
||||
<!-- Piwik -->
|
||||
<script type="text/javascript">
|
||||
var _paq = _paq || [];
|
||||
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
|
||||
_paq.push(['trackPageView']);
|
||||
_paq.push(['enableLinkTracking']);
|
||||
(function () {
|
||||
var u = "{{ piwikURL }}";
|
||||
_paq.push(['setTrackerUrl', u + 'piwik.php']);
|
||||
_paq.push(['setSiteId', '{{ piwikID }}']);
|
||||
var d = document, g = d.createElement('script'), s = d.getElementsByTagName('script')[0];
|
||||
g.type = 'text/javascript';
|
||||
g.async = true;
|
||||
g.defer = true;
|
||||
g.src = u + 'piwik.js';
|
||||
s.parentNode.insertBefore(g, s);
|
||||
})();
|
||||
</script>
|
||||
<!-- End Piwik Code -->
|
||||
{% endif %}
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,3 +1,71 @@
|
|||
{% macro username(user) %}
|
||||
<a href="{{ user.html_url }}" target="_blank">@{{ user.login }}</a>
|
||||
{% macro user(user) %}
|
||||
<a href="{{ user.html_url }}" target="_blank">
|
||||
<img class="avatar" src="{{ user.avatar_url }}&s=44" role="presentation" aria-hidden="true" width="44"
|
||||
height="44">@{{ user.login }}
|
||||
</a>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro labels(labels) %}
|
||||
{% if labels and labels|length %}
|
||||
{% for label in labels %}
|
||||
<span class="badge text-{{ label.color | textcolor }}"
|
||||
style="background: {{ '#' ~ label.color }}">{{ label.name }}</span>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro pagination(num_pages, page) %}
|
||||
{% import _self as m %}
|
||||
{% set pagearray = paginationFunction(num_pages, page,5) %}
|
||||
<!-- License of svg icons - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) -->
|
||||
<ul class="pagination">
|
||||
{% if page > 1 %}
|
||||
<li class="page-item">
|
||||
<a rel="prev" href="{{ path_for('page', [], { 'page': page - 1 }) }}" class="page-link">
|
||||
<svg viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1203 544q0 13-10 23l-393 393 393 393q10 10 10 23t-10 23l-50 50q-10 10-23 10t-23-10l-466-466q-10-10-10-23t10-23l466-466q10-10 23-10t23 10l50 50q10 10 10 23z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled">
|
||||
<a rel="prev" href="" class="page-link">
|
||||
<svg viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1203 544q0 13-10 23l-393 393 393 393q10 10 10 23t-10 23l-50 50q-10 10-23 10t-23-10l-466-466q-10-10-10-23t10-23l466-466q10-10 23-10t23 10l50 50q10 10 10 23z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% for i in pagearray %}
|
||||
{% if i == "d" %}
|
||||
<li class="page-item disabled">
|
||||
<a href="#" class=" other page-link">…</a>
|
||||
</li>
|
||||
{% else %}
|
||||
{{ m.printpage(i, page) }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<li class="page-item">
|
||||
{% if page < num_pages %}
|
||||
<a rel="next" href="{{ path_for('page', [], { 'page': page + 1 }) }}" class="page-link">
|
||||
<svg viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1171 960q0 13-10 23l-466 466q-10 10-23 10t-23-10l-50-50q-10-10-10-23t10-23l393-393-393-393q-10-10-10-23t10-23l50-50q10-10 23-10t23 10l466 466q10 10 10 23z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
{% else %}
|
||||
<a rel="next" href="" class="disabled page-link">
|
||||
<svg viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1171 960q0 13-10 23l-466 466q-10 10-23 10t-23-10l-50-50q-10-10-10-23t10-23l393-393-393-393q-10-10-10-23t10-23l50-50q10-10 23-10t23 10l466 466q10 10 10 23z"></path>
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro printpage(i,active) %}
|
||||
<li class="page-item {{ i == active ? "active" : "other" }}">
|
||||
<a href="{{ path_for('page', [], { 'page': i }) }}"
|
||||
class="page-link">{{ i }}</a>
|
||||
</li>
|
||||
{% endmacro %}
|
|
@ -1,12 +1,13 @@
|
|||
{% extends "layout.twig" %}
|
||||
{% import "macros.twig" as macro %}
|
||||
|
||||
{% block head %}
|
||||
{% if currentPage > 1 %}
|
||||
<link rel="prev" href="{{ previousPage|pageLink }}" />
|
||||
<link rel="prev" href="{{ path_for('page', [] , { 'page': previousPage }) }}"/>
|
||||
{% endif %}
|
||||
|
||||
{% if currentPage != numPages %}
|
||||
<link rel="next" href="{{ nextPage|pageLink }}" />
|
||||
<link rel="next" href="{{ path_for('page', [], { 'page': nextPage }) }}"/>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
|
@ -14,51 +15,45 @@
|
|||
{% if currentPage == 1 %}
|
||||
<div class="container">
|
||||
<div class="page-header">
|
||||
<h1>Read-only mirror of all <a href="http://github.com/{{ githubOrganization|e('url') }}/{{ githubRepository|e('url') }}/issues" target="_blank">{{ projectName }} issues</a>.</h1>
|
||||
<h1>Read-only mirror of all <a
|
||||
href="https://github.com/{{ githubOrganization|e('url') }}/{{ githubRepository|e('url') }}/issues"
|
||||
target="_blank">{{ projectName }} issues</a>.</h1>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="container">
|
||||
<div class="page-header">
|
||||
<h1>{{ projectName }} Issues <small>Page {{currentPage}}</small></h1>
|
||||
<h1>{{ projectName }} Issues
|
||||
<small>Page {{ currentPage }}</small>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<hr>
|
||||
|
||||
<div class="container">
|
||||
|
||||
{{ macro.pagination(numPages, currentPage) }}
|
||||
<div class="card-deck"><div class="row">
|
||||
{% for issue in issues %}
|
||||
<div class="list-group">
|
||||
<a href="{{ issue.number|issueLink }}" class="list-group-item">
|
||||
<h4 class="list-group-item-heading">{{ issue.title }} <small>#{{ issue.number }}</small></h4>
|
||||
<p class="list-group-item-text">
|
||||
<div class="col-lg-3 col-md-4">
|
||||
<a href="{{ path_for('issue', { 'number': issue.number }) }}" class="card overviewCard">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">{{ issue.title }}
|
||||
<small>#{{ issue.number }}</small>
|
||||
</h4>
|
||||
<small>
|
||||
opened by @{{ issue.user.login }} on {{ issue.created_at|date }}
|
||||
{% if issue.state == 'closed' %}
|
||||
<span class="label label-default">closed</span>
|
||||
<span class="badge badge-secondary">closed</span>
|
||||
{% endif %}
|
||||
{{ macro.labels(issue.labels) }}
|
||||
</small>
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<ul class="pagination">
|
||||
{% if currentPage > 1 %}
|
||||
<li><a href="{{ previousPage|pageLink }}" title="Previous page">«</a></li>
|
||||
{% endif %}
|
||||
|
||||
{% for pageEntry in 1..numPages %}
|
||||
<li {% if pageEntry == currentPage %}class="active"{% endif %}>
|
||||
<a href="{{ pageEntry|pageLink }}" title="Page {{ pageEntry }}">{{ pageEntry }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
||||
{% if currentPage != numPages %}
|
||||
<li><a href="{{ nextPage|pageLink }}" title="Next page">»</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
</div></div>
|
||||
{{ macro.pagination(numPages, currentPage) }}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
Reference in a new issue