Added resume.js, updated gruntfile

This commit is contained in:
R. Eric Wheeler 2016-07-11 14:56:40 -07:00
parent 3fb36fdfb1
commit 15627da05c
17 changed files with 1512 additions and 171 deletions

View File

@ -22,11 +22,11 @@ $finder = Symfony\CS\Finder\DefaultFinder::create()
->in(__DIR__)
;
return Symfony\CS\Config\Config::create()
->level(Symfony\CS\FixerInterface::PSR2_LEVEL)
->fixers(
[
'symfony',
'header_comment',
'ordered_use',
'php_unit_construct',
@ -38,9 +38,8 @@ return Symfony\CS\Config\Config::create()
'phpdoc_order',
'short_array_syntax',
'short_echo_tag',
'-multiple_use'
]
)
->finder($finder)
;
;

View File

@ -1,12 +1,14 @@
module.exports = function (grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
bower: {
js: {
dest: 'web/js/vendor',
options: {
checkExistence: false,
debugging: true,
cleanTargetDir: true,
checkExistence: true,
debugging: false,
paths: {
bowerDirectory: 'vendor/bower',
bowerrc: '.bowerrc',
@ -46,19 +48,42 @@ module.exports = function (grunt) {
}
},
watch: {
configFiles: {
files: ['Gruntfile.js'],
options: {
reload: true
}
},
dev: {
files: ['src/Sikofitt/less/*', 'src/Sikofitt/js/*'],
tasks: ['dev']
}
},
uglify: {
options: {
mangle: true,
compress: {
drop_console: true
},
banner: '/*! Resume.PHP - v<%= pkg.version %> - ' +
'<%= grunt.template.today("dddd, mmmm dS, yyyy, h:MM:ss tt") %> */',
nameCache: '.tmp/grunt-uglify-cache.json'
},
dist: {
files: {'web/js/resume.min.js': ['src/Sikofitt/js/resume.js']}
}
}
});
grunt.loadNpmTasks('grunt-contrib-less');
grunt.loadNpmTasks('main-bower-files');
grunt.loadNpmTasks('grunt-phpunit');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.registerTask('dist', ['bower', 'less']);
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.registerTask('dist', ['bower', 'less', 'uglify', 'copy']);
grunt.registerTask('dev', ['less', 'uglify'])
grunt.registerTask('default', 'dist');
};

View File

@ -14,7 +14,7 @@ use Sikofitt\Json\JsonFileTrait;
use Sikofitt\Json\JsonTrait;
use Silex\Application;
require '../vendor/autoload.php';
require __DIR__ . '/../vendor/autoload.php';
/**
* Class App
@ -31,6 +31,7 @@ class App extends Application
use Application\TranslationTrait;
use Application\UrlGeneratorTrait;
private $debug;
/**
* Returns the application directory.
*
@ -108,6 +109,11 @@ class App extends Application
$this['env'] = null !== $this->config('app.environment') ? $this->config('app.environment') : 'dev';
}
}
public function getDebug()
{
return $this['debug'];
}
public function registerExtenders()
{
if (!$this['debug']) {

View File

@ -5,6 +5,9 @@ app:
email: eric@rewiv.com
phone: 510-646-2135
schema: https://raw.githubusercontent.com/jsonresume/resume-schema/v1.0.0/schema.json
captcha: true
captcha_sitekey: 6LcvmSQTAAAAAMmf9w6mhCbpdLvknuD9SGVHT0q-
captcha_secret: 6LcvmSQTAAAAAITkvYJjgLar1LqGGLz-ic0ZMiXo
twig:
paths:
- views

View File

@ -9,51 +9,72 @@
* file that was distributed with this source code.
*/
use Silex\Provider\CsrfServiceProvider;
use Silex\Provider\LocaleServiceProvider;
use Silex\Provider\TranslationServiceProvider;
use Silex\Provider\ValidatorServiceProvider;
use Knp\Provider\ConsoleServiceProvider;
use Sikofitt\Config\ConfigServiceProvider;
use Sikofitt\Json\JsonServiceProvider;
use Silex\Provider\{
AssetServiceProvider,
CsrfServiceProvider,
FormServiceProvider,
HttpFragmentServiceProvider,
HttpKernelServiceProvider,
LocaleServiceProvider,
MonologServiceProvider,
RoutingServiceProvider,
ServiceControllerServiceProvider,
SessionServiceProvider,
TranslationServiceProvider,
TwigServiceProvider,
ValidatorServiceProvider,
VarDumperServiceProvider,
WebProfilerServiceProvider
};
use Symfony\Bridge\Monolog\Logger;
use WhoopsPimple\WhoopsServiceProvider;
$app->register(new \Sikofitt\Config\ConfigServiceProvider(), [
$app->register(new ConfigServiceProvider(), [
'config.path' => $app->getConfDirectory(),
]);
$app->setDebug();
$app
->register(new TwigServiceProvider(), [
'twig.path' => [
$app->getRootDirectory() . '/app/views',
],
])
->register(new JsonServiceProvider())
->register(new AssetServiceProvider())
->register(new MonologServiceProvider())
->register(new SessionServiceProvider())
->register(new HttpKernelServiceProvider())
->register(new FormServiceProvider())
->register(new LocaleServiceProvider())
->register(new TranslationServiceProvider())
->register(new ValidatorServiceProvider())
->register(new CsrfServiceProvider())
->register(new MonologServiceProvider(), [
'monolog.logfile' => sprintf('%s/%s.log', $app->getLogDirectory(), $app['env']),
'monolog.name' => 'Resume.PHP',
'monolog.level' => $app->getDebug() ? Logger::DEBUG : Logger::INFO,
])
->register(new RoutingServiceProvider())
->register(new ServiceControllerServiceProvider())
->register(new HttpFragmentServiceProvider());
if ($app['debug'] || 0 === strcasecmp($app['env'], 'dev')) {
$app->register(new WebProfilerServiceProvider(), [
'profiler.cache_dir' => $app->getDataDirectory() . '/cache/profiler',
])
->register(new WhoopsServiceProvider())
->register(new VarDumperServiceProvider())
->register(new ConsoleServiceProvider(), [
'console.name' => 'Resume.PHP',
'console.version' => '0.0.1',
'console.project_directory' => $app->getAppDirectory(),
]);
}
if (null === $app->config('app.schema')) {
$app->config('app.schema', 'https://raw.githubusercontent.com/jsonresume/resume-schema/v1.0.0/schema.json');
}
$app->register(new \Silex\Provider\TwigServiceProvider(), [
'twig.path' => [
$app->getRootDirectory() . '/app/views',
],
]);
$app->register(new \Sikofitt\Json\JsonServiceProvider());
$app->register(new \Silex\Provider\AssetServiceProvider());
$app->register(new \Silex\Provider\MonologServiceProvider());
$app->register(new \Silex\Provider\SessionServiceProvider());
$app->register(new \Silex\Provider\HttpKernelServiceProvider());
$app->register(new \Silex\Provider\FormServiceProvider());
$app->register(new LocaleServiceProvider());
$app->register(new TranslationServiceProvider());
$app->register(new ValidatorServiceProvider());
$app->register(new CsrfServiceProvider());
$app->register(new \Silex\Provider\MonologServiceProvider(),
[
'monolog.logfile' => sprintf('%s/%s.log', $app->getLogDirectory(), $app['env']),
]
);
$app->register(new \Silex\Provider\RoutingServiceProvider());
$app->register(new \Silex\Provider\ServiceControllerServiceProvider());
$app->register(new \Silex\Provider\HttpFragmentServiceProvider());
if ($app['debug'] || 0 === strcasecmp($app['env'], 'dev')) {
$app->register(new \Silex\Provider\WebProfilerServiceProvider(), [
'profiler.cache_dir' => $app->getDataDirectory() . '/cache/profiler',
]);
$app->register(new \WhoopsSilex\WhoopsServiceProvider());
$app->register(new \Silex\Provider\VarDumperServiceProvider());
}

View File

@ -263,40 +263,5 @@
{% endblock %}
{% block javascripts_foot %}
<script src='https://www.google.com/recaptcha/api.js'></script>
{% endblock %}
{% block inline_js_foot %}
<script type="text/javascript">
jQuery(document).ready(function ($) {
jQuery('form#recaptcha').on('submit', function (event) {
event.stopImmediatePropagation();
event.stopPropagation();
jQuery.post(jQuery(this).attr('action'), jQuery(this).serialize(), function (response) {
data = JSON.parse(response);
if (false === data.valid) {
data.message.every(function (d) {
UIkit.notify('<i class="uk-icon-medium uk-icon-frown-o uk-icon-justify"></i> ' + d, {
pos: 'bottom-center',
status: 'danger'
});
})
} else if (true === data.valid) {
var divRoot = jQuery('<div />');
var h1 = jQuery('<h1 />');
var href = jQuery('<a />');
href
.attr('href', 'tel:' + data.message.phone)
.text(data.message.phone);
h1.append(href);
phone = jQuery('.hidden-phone');
phone.attr('href', 'tel:' + data.message.phone);
phone.text(data.message.phone);
divRoot.append(h1);
jQuery('#recaptcha-wrapper').replaceWith(divRoot);
}
});
});
});
</script>
{% endblock %}

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en-gb" dir="ltr">
<html lang="en-us">
<head>
<meta charset="utf-8">
@ -7,18 +7,18 @@
{% block meta %}{% endblock %}
<title>{% block title %}Resume{% endblock %}</title>
{% block shortcut_icon %}
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon">
{# <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon"> #}
{% endblock %}
{% block apple_meta %}
<link rel="apple-touch-icon-precomposed" href="images/apple-touch-icon.png">
{# <link rel="apple-touch-icon-precomposed" href="images/apple-touch-icon.png"> #}
{% endblock %}
<link rel="stylesheet" href="css/resume.min.css">
<link rel="stylesheet" href="{{ asset('css/resume.min.css') }}">
{% block stylesheets %}{% endblock %}
{% block inline_styles %}{% endblock %}
<script src="js/vendor/jquery.min.js"></script>
<script src="js/vendor/uikit.min.js"></script>
<script src="js/vendor/sticky.min.js"></script>
<script src="{{ asset('js/vendor/notify.min.js') }}"></script>
<script src="{{ asset('js/vendor/jquery.min.js') }}"></script>
<script src="{{ asset('js/vendor/uikit.min.js') }}"></script>
<script src="{{ asset('js/vendor/sticky.min.js') }}"></script>
<script src="{{ asset('js/vendor/notify.min.js') }}"></script>
{% block javascripts_head %}{% endblock %}
{% block inline_js_head %}{% endblock %}
</head>
@ -29,6 +29,7 @@
{% block body %}
{% endblock %}
</div>
<script src="{{ asset('js/resume.min.js') }}"></script>
{% block javascripts_foot %}{% endblock %}
{% block inline_js_foot %}{% endblock %}
</body>

View File

@ -2,15 +2,13 @@
<?php
use Sikofitt\Command\SchemaValidationCommand;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Command\HelpCommand;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Debug\Debug;
use Symfony\Component\{
Console\Input\ArgvInput,
Console\Input\InputOption,
Debug\Debug
};
// if you don't want to setup permissions the proper way, just uncomment the following PHP line
// read http://symfony.com/doc/current/book/installation.html#configuration-and-setup for more information
//umask(0000);
set_time_limit(0);
@ -21,14 +19,22 @@ set_time_limit(0);
require_once __DIR__. '/../vendor/autoload.php';
$input = new ArgvInput();
$env = $input->getParameterOption(['--env', '-e'], getenv('SYMFONY_ENV') ?: 'dev');
$debug = getenv('SYMFONY_DEBUG') !== '0' && !$input->hasParameterOption(['--no-debug', '']) && $env !== 'prod';
if ($debug) {
Debug::enable();
$app = new App();
require_once $app->getAppDirectory() . '/providers.php';
if($app->getDebug()) {
Debug::enable();
}
//$kernel = new AppKernel($env, $debug);
$application = new Application();
$application = $app['console'];
$application->getDefinition()->addOption(new InputOption('--env', '-e', InputOption::VALUE_REQUIRED, 'The Environment name.', 'dev'));
$application->setDispatcher($app['dispatcher']);
$twigDebugCommand = new Symfony\Bridge\Twig\Command\DebugCommand('twig:debug');
$twigDebugCommand->setTwigEnvironment($app['twig']);
$twigLintCommand = new \Symfony\Bridge\Twig\Command\LintCommand('twig:lint');
$twigLintCommand->setTwigEnvironment($app['twig']);
$application->add(new SchemaValidationCommand());
$application->add($twigDebugCommand);
$application->add($twigLintCommand);
$application->add(new \Symfony\CS\Console\Command\FixCommand());
$application->run($input);

View File

@ -41,7 +41,8 @@
"silex/web-profiler": "^2.0",
"twig/extensions": "^1.3",
"symfony/config": "^3.1",
"symfony/security-csrf": "^3.1"
"symfony/security-csrf": "^3.1",
"knplabs/console-service-provider": "^2.0"
},
"require-dev": {
"symfony/debug": "~2.8|^3.0",
@ -53,7 +54,8 @@
"texthtml/whoops-silex": "^1.0",
"symfony/debug-bundle": "^3.1",
"friendsofphp/php-cs-fixer": "^1.11",
"heroku/heroku-buildpack-php": "^108.0"
"heroku/heroku-buildpack-php": "^108.0",
"phpunit/phpunit": "^5.4"
},
"autoload": {
"psr-4": {

1256
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
{
"name": "resume.php",
"version": "0.0.1",
"author": "R. Eric Wheeler <sikofitt@gmail.com>",
"private": true,
"homepage": "https://code.reric.me",

View File

@ -14,22 +14,84 @@ namespace Sikofitt\Config;
use Noodlehaus\Config;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
use Silex\Api\BootableProviderInterface;
use Silex\Application;
use Symfony\Component\Validator\Constraints\Collection;
use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\NotNull;
use Symfony\Component\Validator\Constraints\Regex;
/**
* Class ConfigServiceProvider
*
* @package Sikofitt\Config
*/
class ConfigServiceProvider implements ServiceProviderInterface
class ConfigServiceProvider implements ServiceProviderInterface, BootableProviderInterface
{
/**
* @param Container $app
*/
public function register(Container $app)
* @param Container $app
*/
public function register(Container $app)
{
$app['config'] = function ($app) {
$config = Config::load($app['config.path']);
return $config;
};
}
public function boot(Application $app)
{
$app['config'] = function ($app) {
$config = Config::load($app['config.path']);
return $config;
};
$configItems = [
'email' => $app->config('app.email'),
'phone' => $app->config('app.phone'),
];
$constraints = [
'email' => [
new NotNull(['message' => 'Email value in app config is not present.']),
new NotBlank(['message' => 'Email should cannot be blank in config.']),
new Email(['message' => sprintf('Invalid email address in config. (%s)', $configItems['email'])]),
],
'phone' => [
new NotNull(['message' => 'Phone number value in app config is not present.']),
new NotBlank(['message' => 'Phone number cannot be blank in config.']),
new Length([
'min' => 10,
'minMessage' => sprintf('Invalid phone number, it should have at least 10 characters. %s given.', count($configItems['phone'])),
]),
],
];
$captcha = $app->config('app.captcha');
if (isset($captcha) && $captcha) {
$configItems['captcha_sitekey'] = $app->config('app.captcha_sitekey');
$configItems['captcha_secret'] = $app->config('app.captcha_secret');
$constraints['captcha_sitekey'] = [
new NotNull(['message' => 'ReCaptcha sitekey is a required value to use the captcha, this check can be disabled by removing or setting the captcha config item to false.']),
new NotBlank(),
new Length(['min' => 40]),
];
$constraints['captcha_secret'] = [
new NotNull(),
new NotBlank(),
new Length(['min' => 40]),
];
}
$errors = $app['validator']->validate($configItems, new Collection($constraints));
$validationErrors = [];
foreach ($errors->getIterator() as $error) {
$validationErrors[] = $error->getMessage();
}
if (count($validationErrors) > 0) {
throw new MissingConfigurationItemException($validationErrors[0]);
}
}
}

View File

@ -0,0 +1,16 @@
<?php
/*
* This file is part of Resume.PHP.
*
* (copyleft) R. Eric Wheeler <sikofitt@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sikofitt\Config;
class MissingConfigurationItemException extends \InvalidArgumentException
{
}

View File

@ -27,10 +27,10 @@ namespace Sikofitt\Controller;
use ReCaptcha\ReCaptcha;
use Silex\Api\ControllerProviderInterface;
use Silex\Application;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class ApiControllerProvider implements ControllerProviderInterface
{
/**
@ -46,13 +46,13 @@ class ApiControllerProvider implements ControllerProviderInterface
$controllers->get('/v1/schema', function () use($app) {
$controllers->get('/v1/schema', function () use ($app) {
$response = new Response(file_get_contents($app->getDataDirectory() . '/schema/schema.v1.json'), Response::HTTP_OK);
$response->headers->set('Content-Type', 'application/schema+json');
return $response;
});
$controllers->match('/v1/message', function(Request $request) use ($app) {
$controllers->match('/v1/message', function (Request $request) use ($app) {
/*$app->mail(\Swift_Message::newInstance()
->setSubject('[YourSite] Feedback')
->setFrom(array('noreply@yoursite.com'))

View File

@ -1,21 +1,29 @@
<?php
/*
* This file is part of Resume.PHP.
*
* (copyleft) R. Eric Wheeler <sikofitt@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sikofitt\Form\Type;
use Symfony\Component\Form\{
FormBuilderInterface, AbstractType, Extension\Core\Type\SubmitType, Extension\Core\Type\EmailType, Extension\Core\Type\TextareaType, Extension\Core\Type\TextType, FormConfigInterface, FormInterface
};
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Validator\Constraints\{
Email,
Length,
NotBlank
};
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormConfigInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
/**
* Class ContactType
@ -29,7 +37,7 @@ class ContactType extends AbstractType
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm (FormBuilderInterface $builder, array $options)
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class, [
@ -96,7 +104,7 @@ class ContactType extends AbstractType
*
* @param OptionsResolver $resolver
*/
public function configureOptions (OptionsResolver $resolver)
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'attr' => [
@ -104,4 +112,4 @@ class ContactType extends AbstractType
],
]);
}
}
}

33
src/Sikofitt/js/resume.js Normal file
View File

@ -0,0 +1,33 @@
jQuery(document).ready(function ($) {
$('form#recaptcha').on('submit', function (event) {
event.stopImmediatePropagation();
event.stopPropagation();
jQuery.post(jQuery(this).attr('action'), jQuery(this).serialize(), function (response) {
data = JSON.parse(response);
if (false === data.valid) {
data.message.every(function (d) {
UIkit.notify('<i class="uk-icon-medium uk-icon-frown-o uk-icon-justify"></i> ' + d, {
pos: 'bottom-center',
status: 'danger'
});
})
} else if (true === data.valid) {
var divRoot = jQuery('<div />'),
h1 = jQuery('<h1 />'),
href = jQuery('<a />'),
phone = jQuery('.hidden-phone');
href
.attr('href', 'tel:' + data.message.phone)
.text(data.message.phone);
h1.append(href);
phone.attr('href', 'tel:' + data.message.phone);
phone.text(data.message.phone);
divRoot.append(h1);
jQuery('#recaptcha-wrapper').replaceWith(divRoot);
}
});
});
});

View File

@ -48,4 +48,9 @@ a.profile-link {
margin-bottom: 16px;
border-bottom: 1px solid #ccc;
padding-bottom: 12px;
}
.uk-close {
&:after {
font-size:30px;
}
}